747 lines
19 KiB
Markdown
747 lines
19 KiB
Markdown
# UserAgentContext & ProfileSettings v1 设计
|
||
|
||
**Date:** 2026-03-05
|
||
**Status:** Approved
|
||
|
||
---
|
||
|
||
## 目标
|
||
|
||
为 Agent Runtime 提供完整的用户画像上下文,通过 Pydantic 约束 profiles.settings 结构,确保:
|
||
|
||
1. 运行时入口读取 profile(username/bio/settings)
|
||
2. settings 结构类型安全、版本可演进
|
||
3. 关键配置(语言/时区/国家)符合标准格式
|
||
|
||
---
|
||
|
||
## 架构
|
||
|
||
```
|
||
Profile (DB JSONB)
|
||
↓
|
||
ProfileSettings (Pydantic)
|
||
↓
|
||
UserAgentContext (DataClass)
|
||
↓
|
||
build_global_system_prompt(ctx)
|
||
```
|
||
|
||
**设计原则:**
|
||
- 唯一入口:`get_user_agent_context(user_id)` 读取并构造上下文
|
||
- 不可变:UserAgentContext 使用 frozen dataclass
|
||
- 向后兼容:version 字段预留未来演进
|
||
|
||
---
|
||
|
||
## ProfileSettings v1 结构
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"preferences": {
|
||
"interface_language": "zh-CN",
|
||
"ai_language": "zh-CN",
|
||
"timezone": "Asia/Shanghai",
|
||
"country": "CN"
|
||
},
|
||
"privacy": {},
|
||
"notification": {}
|
||
}
|
||
```
|
||
|
||
### 字段说明
|
||
|
||
| 字段 | 类型 | 默认值 | 约束 |
|
||
|------|------|--------|------|
|
||
| `version` | int | 1 | 必须为 1(v1 锁定) |
|
||
| `preferences.interface_language` | str | "zh-CN" | BCP-47 格式 |
|
||
| `preferences.ai_language` | str | "zh-CN" | BCP-47 格式 |
|
||
| `preferences.timezone` | str | "Asia/Shanghai" | IANA 时区 |
|
||
| `preferences.country` | str | "CN" | ISO 3166-1 alpha-2 |
|
||
| `privacy` | dict | {} | 空对象(预留) |
|
||
| `notification` | dict | {} | 空对象(预留) |
|
||
|
||
### 约束规则
|
||
|
||
**1. BCP-47 语言格式**
|
||
|
||
正则:`^[a-z]{2,3}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$`
|
||
|
||
示例:
|
||
- ✅ zh-CN, en-US, zh-TW, ja-JP
|
||
- ❌ zh_CN, EN, chn
|
||
|
||
**2. IANA 时区**
|
||
|
||
使用 `zoneinfo.ZoneInfo` 校验。
|
||
|
||
示例:
|
||
- ✅ Asia/Shanghai, America/New_York, UTC
|
||
- ❌ CST, GMT+8
|
||
|
||
**3. ISO 3166-1 alpha-2 国家代码**
|
||
|
||
使用 `pycountry.countries.get(alpha_2=...)` 校验。
|
||
|
||
示例:
|
||
- ✅ CN, US, JP, GB
|
||
- ❌ CHN, USA, zz
|
||
|
||
---
|
||
|
||
## UserAgentContext 结构
|
||
|
||
```python
|
||
@dataclass(frozen=True)
|
||
class UserAgentContext:
|
||
user_id: UUID
|
||
username: str
|
||
bio: str | None
|
||
settings: ProfileSettings
|
||
```
|
||
|
||
**设计要点:**
|
||
- 不可变(frozen=True):防止运行时修改
|
||
- 完整画像:包含身份(username/bio)和配置(settings)
|
||
- 唯一构造入口:`get_user_agent_context(user_id)`
|
||
|
||
---
|
||
|
||
## Pydantic 模型实现
|
||
|
||
```python
|
||
from pydantic import BaseModel, Field, field_validator
|
||
from dataclasses import dataclass
|
||
from uuid import UUID
|
||
import re
|
||
|
||
class PreferenceSettings(BaseModel):
|
||
interface_language: str = "zh-CN"
|
||
ai_language: str = "zh-CN"
|
||
timezone: str = "Asia/Shanghai"
|
||
country: str = "CN"
|
||
|
||
@field_validator("interface_language", "ai_language")
|
||
@classmethod
|
||
def validate_bcp47(cls, v: str) -> str:
|
||
pattern = r"^[a-z]{2,3}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$"
|
||
if not re.match(pattern, v):
|
||
raise ValueError(f"Invalid BCP-47 language tag: {v}")
|
||
return v
|
||
|
||
@field_validator("timezone")
|
||
@classmethod
|
||
def validate_iana_timezone(cls, v: str) -> str:
|
||
import zoneinfo
|
||
try:
|
||
zoneinfo.ZoneInfo(v)
|
||
except Exception:
|
||
raise ValueError(f"Invalid IANA timezone: {v}")
|
||
return v
|
||
|
||
@field_validator("country")
|
||
@classmethod
|
||
def validate_iso_country(cls, v: str) -> str:
|
||
import pycountry
|
||
if not pycountry.countries.get(alpha_2=v.upper()):
|
||
raise ValueError(f"Invalid ISO 3166-1 alpha-2 country code: {v}")
|
||
return v.upper()
|
||
|
||
class ProfileSettings(BaseModel):
|
||
version: int = Field(default=1, ge=1, le=1)
|
||
preferences: PreferenceSettings = Field(default_factory=PreferenceSettings)
|
||
privacy: dict = Field(default_factory=dict)
|
||
notification: dict = Field(default_factory=dict)
|
||
|
||
@dataclass(frozen=True)
|
||
class UserAgentContext:
|
||
user_id: UUID
|
||
username: str
|
||
bio: str | None
|
||
settings: ProfileSettings
|
||
```
|
||
|
||
---
|
||
|
||
## 依赖项
|
||
|
||
需要添加到 `backend/pyproject.toml`:
|
||
|
||
```toml
|
||
[project.dependencies]
|
||
pycountry = ">=23.0.0"
|
||
```
|
||
|
||
---
|
||
|
||
## 迁移策略
|
||
|
||
**数据库层:**
|
||
- profiles.settings 保持 JSONB,不做 schema 变更
|
||
- 现有数据默认值:`{"version": 1, "preferences": {"country": "CN"}}`
|
||
|
||
**应用层:**
|
||
- 读取时:`ProfileSettings.model_validate(profile.settings or {})`
|
||
- 写入时:`profile.settings = settings.model_dump()`
|
||
|
||
---
|
||
|
||
## 未来演进
|
||
|
||
|
||
**版本迁移:**
|
||
- Pydantic 支持多版本共存
|
||
- 数据库不做破坏性变更
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## AG-UI 事件转发与落库策略
|
||
|
||
### 核心原则
|
||
|
||
**1. 事件转发时机:**
|
||
- 只有 organization 阶段完成后转发 AG-UI 事件
|
||
- AG-UI bridge 已实现底层机制,编排层控制转发时机
|
||
|
||
**2. 落库时机:**
|
||
- 意图识别和任务执行阶段:落库但 seq 取负数(用于审计)
|
||
- 结果反馈阶段:seq 取最新 seq 的绝对值 +1(用于展示)
|
||
|
||
### Seq 设计细节
|
||
|
||
**意图识别和任务执行阶段(审计用):**
|
||
- seq 取负数(如 -1, -2)
|
||
- role: "assistant"(标记为 agent 输出)
|
||
- content: 阶段的完整输出(用于审计/调试)
|
||
- 重建会话时通过 `WHERE seq > 0` 过滤,不展示给用户
|
||
|
||
**结果反馈阶段(展示用):**
|
||
- seq 取正数(取最新负数的绝对值 +1)
|
||
- role: "assistant"
|
||
- content: OrganizationResult.assistant_text
|
||
- 重建会话时通过 `WHERE seq > 0` 展示给用户
|
||
|
||
**示例:**
|
||
```
|
||
| seq | role | content | 展示 |
|
||
|------|----------|----------------------------|------|
|
||
| -2 | assistant| ExecutionResult (完整) | 否 |
|
||
| -1 | assistant| IntentResult (完整) | 否 |
|
||
| 1 | user | 用户输入 | 是 |
|
||
| 2 | assistant| OrganizationResult | 是 |
|
||
```
|
||
|
||
### 编排层职责
|
||
|
||
```python
|
||
@listen(intent_stage)
|
||
async def persist_intent(self, state: FlowState) -> FlowState:
|
||
# seq 取负数
|
||
seq = await message_repo.get_next_negative_seq(state.session_id)
|
||
await message_repo.create(
|
||
session_id=state.session_id,
|
||
seq=seq, # 负数
|
||
role="assistant",
|
||
content=state.intent_result.model_dump_json(),
|
||
...
|
||
)
|
||
return state
|
||
|
||
@listen(execution_stage)
|
||
async def persist_execution(self, state: FlowState) -> FlowState:
|
||
# seq 取负数
|
||
seq = await message_repo.get_next_negative_seq(state.session_id)
|
||
await message_repo.create(
|
||
session_id=state.session_id,
|
||
seq=seq, # 负数
|
||
role="assistant",
|
||
content=state.execution_result.model_dump_json(),
|
||
...
|
||
)
|
||
return state
|
||
|
||
@listen(organization_stage)
|
||
async def finalize_flow(self, state: FlowState) -> FlowState:
|
||
result = state.organization_result
|
||
|
||
# seq 取正数(最新负数绝对值+1)
|
||
seq = await message_repo.get_next_positive_seq(state.session_id)
|
||
await message_repo.create(
|
||
session_id=state.session_id,
|
||
seq=seq, # 正数
|
||
role="assistant",
|
||
content=result.assistant_text,
|
||
...
|
||
)
|
||
|
||
# 触发 AG-UI 事件(由 bridge 处理)
|
||
return state
|
||
```
|
||
|
||
### Token 和 Cost 累加
|
||
|
||
**策略:在内存中累加所有阶段的 token 和 cost,organization 完成后统一落库。**
|
||
|
||
```python
|
||
@dataclass
|
||
class FlowState:
|
||
# ...
|
||
tokens: dict[str, dict] = field(default_factory=dict)
|
||
cost: Decimal = Decimal("0")
|
||
currency: str = "CNY"
|
||
```
|
||
|
||
---
|
||
|
||
## CrewAI Flow 三阶段设计
|
||
|
||
### 架构概览
|
||
|
||
```
|
||
User Input + UserAgentContext
|
||
↓
|
||
@start() begin()
|
||
↓
|
||
@listen() intent_stage() → 判断 can_answer_directly
|
||
↓ (router)
|
||
├─ DIRECT_RESPONSE → 直接返回
|
||
└─ NEEDS_EXECUTION
|
||
↓
|
||
@listen() execution_stage() → 任务执行/工具调用
|
||
↓
|
||
@listen() organization_stage() → 结果组织与表达
|
||
↓
|
||
返回给用户
|
||
```
|
||
|
||
### 三阶段职责
|
||
|
||
**1. Intent Recognition(意图识别)**
|
||
- Agent Type: `INTENT_RECOGNITION`
|
||
- 输出结构(最小化设计):
|
||
```python
|
||
class IntentResult(BaseModel):
|
||
direct_answer: bool # 是否可以直接回答
|
||
intent_analysis: str # 意图分析文本(用于调试/审计)
|
||
execution_prompt: str # 给 execution 阶段的提示词(direct_answer=false时使用)
|
||
direct_response: str # 直接回复文本(direct_answer=true时使用)
|
||
```
|
||
- 短路逻辑:
|
||
- `direct_answer=true` → 完全跳过 execution 和 organization,直接返回 direct_response
|
||
- `direct_answer=false` → 进入 execution 阶段
|
||
- 输出约束:使用 `output_pydantic=IntentResult`
|
||
- **落库策略**:落库到 messages 表,但重建会话时不展示
|
||
|
||
**2. Task Execution(任务执行)**
|
||
- Agent Type: `TASK_EXECUTION`
|
||
- 输入:IntentResult.execution_prompt + IntentResult.intent_analysis
|
||
- 职责:
|
||
- 执行复杂任务(查询数据库、调用工具、多步骤推理)
|
||
- 返回结构化执行结果
|
||
- 输出结构(最小化设计):
|
||
```python
|
||
class ExecutionResult(BaseModel):
|
||
execution_summary: str # 任务执行摘要(用于调试/审计)
|
||
organization_prompt: str # 给 organization 阶段的提示词
|
||
execution_data: dict = {} # 执行结果的结构化数据
|
||
```
|
||
- 输出约束:使用 `output_pydantic=ExecutionResult`
|
||
- **落库策略**:落库到 messages 表,但重建会话时不展示
|
||
|
||
**3. Result Reporting(结果报告)**
|
||
- Agent Type: `RESULT_REPORTING`
|
||
- 输入:
|
||
- IntentResult(意图识别结果)
|
||
- ExecutionResult(任务执行情况)
|
||
- 职责:
|
||
- 结合意图分析和执行结果,格式化为用户友好的响应
|
||
- 应用个性化模板(基于 UserAgentContext)
|
||
- 输出结构(最小化设计):
|
||
```python
|
||
class OrganizationResult(BaseModel):
|
||
assistant_text: str # 最终回复文本
|
||
response_metadata: dict = {} # 响应元数据(可选)
|
||
```
|
||
- 输出约束:使用 `output_pydantic=OrganizationResult`
|
||
- **唯一展示阶段**:重建会话时只展示此阶段的 message
|
||
- **唯一转发阶段**:只有此阶段的输出需要通过 AG-UI 事件转发
|
||
|
||
### Flow 状态管理
|
||
|
||
```python
|
||
@dataclass
|
||
class FlowState:
|
||
user_input: str
|
||
context: UserAgentContext
|
||
stage_trace: list[str] = field(default_factory=list)
|
||
intent_result: IntentResult | None = None
|
||
execution_result: ExecutionResult | None = None
|
||
organization_result: OrganizationResult | None = None
|
||
assistant_text: str = ""
|
||
tokens: dict = field(default_factory=dict)
|
||
cost: Decimal = Decimal("0")
|
||
```
|
||
|
||
### 数据流向
|
||
|
||
```
|
||
User Input + UserAgentContext
|
||
↓
|
||
@start() begin()
|
||
↓
|
||
@listen() intent_stage()
|
||
├─ IntentResult.direct_answer=true
|
||
│ ↓
|
||
│ 跳过 execution,直接 organization
|
||
│ ↓
|
||
│ organization_stage(IntentResult.next_stage_prompt, IntentResult.metadata)
|
||
│ ↓
|
||
│ OrganizationResult → AG-UI 事件 + 落库
|
||
│
|
||
└─ IntentResult.direct_answer=false
|
||
↓
|
||
execution_stage(IntentResult.next_stage_prompt, IntentResult.metadata)
|
||
↓
|
||
ExecutionResult
|
||
↓
|
||
organization_stage(ExecutionResult.next_stage_prompt, ExecutionResult.metadata)
|
||
↓
|
||
OrganizationResult → AG-UI 事件 + 落库
|
||
```
|
||
|
||
### 三阶段输出约束
|
||
|
||
**所有阶段使用 `output_pydantic` 约束输出:**
|
||
|
||
```python
|
||
from pydantic import BaseModel
|
||
|
||
class IntentResult(BaseModel):
|
||
direct_answer: bool
|
||
next_stage_prompt: str
|
||
metadata: dict = {}
|
||
|
||
class ExecutionResult(BaseModel):
|
||
next_stage_prompt: str
|
||
metadata: dict = {}
|
||
|
||
class OrganizationResult(BaseModel):
|
||
assistant_text: str
|
||
metadata: dict = {}
|
||
|
||
# Task 定义
|
||
intent_task = Task(
|
||
description="Analyze user intent",
|
||
expected_output="Intent analysis",
|
||
agent=intent_agent,
|
||
output_pydantic=IntentResult,
|
||
)
|
||
|
||
execution_task = Task(
|
||
description="Execute tasks",
|
||
expected_output="Execution result",
|
||
agent=execution_agent,
|
||
output_pydantic=ExecutionResult,
|
||
)
|
||
|
||
organization_task = Task(
|
||
description="Format response",
|
||
expected_output="User-friendly response",
|
||
agent=organization_agent,
|
||
output_pydantic=OrganizationResult,
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 系统选模逻辑设计
|
||
|
||
### 问题背景
|
||
|
||
旧逻辑:`order_by(...).limit(1)` 随机选择一个系统 agent,不区分阶段。
|
||
|
||
新逻辑:按 `agent_type` 显式映射到三阶段。
|
||
|
||
### 选模规则
|
||
|
||
**必需的 Agent Types:**
|
||
- `INTENT_RECOGNITION` → 用于 intent_stage
|
||
- `TASK_EXECUTION` → 用于 execution_stage
|
||
- `RESULT_REPORTING` → 用于 organization_stage
|
||
|
||
**查询逻辑:**
|
||
|
||
```python
|
||
REQUIRED_TYPES = {"INTENT_RECOGNITION", "TASK_EXECUTION", "RESULT_REPORTING"}
|
||
|
||
@dataclass(frozen=True)
|
||
class StageModels:
|
||
intent: SystemAgentCatalog
|
||
execution: SystemAgentCatalog
|
||
organization: SystemAgentCatalog
|
||
|
||
def resolve_stage_models(rows: list[SystemAgentCatalog]) -> StageModels:
|
||
by_type = {row.agent_type: row for row in rows}
|
||
missing = REQUIRED_TYPES - set(by_type.keys())
|
||
if missing:
|
||
raise ValueError(f"Missing required agent types: {missing}")
|
||
|
||
return StageModels(
|
||
intent=by_type["INTENT_RECOGNITION"],
|
||
execution=by_type["TASK_EXECUTION"],
|
||
organization=by_type["RESULT_REPORTING"],
|
||
)
|
||
```
|
||
|
||
**初始化数据约束:**
|
||
- `system_agents` 表必须包含三种类型的记录
|
||
- 运行时启动时验证完整性
|
||
|
||
---
|
||
|
||
## 人民币结算策略设计
|
||
|
||
### 设计原则
|
||
|
||
1. **保留 LiteLLM 语义**:`completion_cost()` 始终返回 USD
|
||
2. **业务层映射**:根据用户国家(`profiles.settings.preferences.country`)决定落库货币
|
||
3. **默认人民币**:中国用户或无国家信息默认 CNY
|
||
4. **汇率配置**:USD/CNY 汇率通过环境变量配置
|
||
|
||
### 货币来源
|
||
|
||
```
|
||
UserAgentContext.settings.preferences.country
|
||
↓
|
||
resolve_billing_currency(country)
|
||
↓
|
||
CN → CNY
|
||
US → USD
|
||
其他 → USD
|
||
```
|
||
|
||
### 结算流程
|
||
|
||
```
|
||
LiteLLM completion_cost()
|
||
↓ (USD)
|
||
resolve_billing_cost(usd_cost, country)
|
||
↓
|
||
├─ country="CN" or None → CNY (乘以汇率)
|
||
└─ country="US" → USD (保持原值)
|
||
↓
|
||
messages.cost + messages.currency
|
||
sessions.total_cost (同一货币)
|
||
```
|
||
|
||
### 汇率配置
|
||
|
||
```python
|
||
# 环境变量
|
||
BILLING_USD_CNY_RATE=7.2
|
||
|
||
# 默认值
|
||
DEFAULT_USD_CNY_RATE = Decimal("7.2")
|
||
```
|
||
|
||
### 结算模型
|
||
|
||
```python
|
||
@dataclass(frozen=True)
|
||
class BillingCost:
|
||
currency: str # "CNY" or "USD"
|
||
cost: Decimal # 6位小数精度
|
||
|
||
def resolve_billing_cost(
|
||
usd_cost: Decimal,
|
||
country: str | None,
|
||
usd_cny_rate: Decimal = DEFAULT_USD_CNY_RATE,
|
||
) -> BillingCost:
|
||
currency = "CNY" if (country or "CN").upper() == "CN" else "USD"
|
||
if currency == "CNY":
|
||
cost = usd_cost * usd_cny_rate
|
||
else:
|
||
cost = usd_cost
|
||
return BillingCost(
|
||
currency=currency,
|
||
cost=cost.quantize(Decimal("0.000001"))
|
||
)
|
||
```
|
||
|
||
### 数据库落库
|
||
|
||
**messages 表:**
|
||
- `cost`: NUMERIC(12,6) - 业务货币金额
|
||
- `currency`: VARCHAR(3) - "CNY" or "USD"
|
||
|
||
**sessions 表:**
|
||
- `total_cost`: NUMERIC(12,6) - 同一货币累计
|
||
|
||
**约束:**
|
||
- 同一 session 内所有 messages 的 currency 必须一致
|
||
- sessions.total_cost 累加时保持货币一致
|
||
|
||
---
|
||
|
||
## Session 状态一致性设计
|
||
|
||
### 问题背景
|
||
|
||
旧逻辑:
|
||
- `sessions.status` 与 `state_snapshot.status` 不同步
|
||
- 失败时状态不一致
|
||
- title 未自动赋值
|
||
|
||
### 状态机
|
||
|
||
```
|
||
pending (创建)
|
||
↓
|
||
running (开始执行)
|
||
↓
|
||
├─ completed (成功)
|
||
└─ failed (异常)
|
||
```
|
||
|
||
### 状态同步规则
|
||
|
||
**创建时:**
|
||
```python
|
||
session = AgentChatSession(
|
||
user_id=user_uuid,
|
||
status=AgentChatSessionStatus.PENDING,
|
||
state_snapshot={
|
||
"status": "pending",
|
||
"pending_tool_call_id": None,
|
||
},
|
||
)
|
||
```
|
||
|
||
**运行时:**
|
||
```python
|
||
# 开始执行
|
||
session.status = AgentChatSessionStatus.RUNNING
|
||
session.state_snapshot["status"] = "running"
|
||
|
||
# 成功完成
|
||
session.status = AgentChatSessionStatus.COMPLETED
|
||
session.state_snapshot["status"] = "completed"
|
||
|
||
# 失败
|
||
session.status = AgentChatSessionStatus.FAILED
|
||
session.state_snapshot["status"] = "failed"
|
||
session.state_snapshot["error_id"] = error_id
|
||
```
|
||
|
||
### 自动 Title 赋值
|
||
|
||
**规则:**
|
||
- 首次运行时,如果 `session.title` 为空,使用 `user_input[:255]` 赋值
|
||
- 只在第一次运行时赋值,后续不覆盖
|
||
|
||
**实现:**
|
||
```python
|
||
async def _set_title_if_empty(self, session_id: UUID, title: str) -> None:
|
||
stmt = (
|
||
update(AgentChatSession)
|
||
.where(AgentChatSession.id == session_id)
|
||
.where(AgentChatSession.title.is_(None))
|
||
.values(title=title[:255])
|
||
)
|
||
await self.db.execute(stmt)
|
||
```
|
||
|
||
### Repository 方法
|
||
|
||
```python
|
||
class SessionRepository:
|
||
async def mark_running(self, session_id: UUID) -> None: ...
|
||
async def mark_completed(self, session_id: UUID) -> None: ...
|
||
async def mark_failed(self, session_id: UUID, error_id: str) -> None: ...
|
||
```
|
||
|
||
---
|
||
|
||
## 全局 Prompt 构建设计
|
||
|
||
### 分层结构
|
||
|
||
```
|
||
全局系统 Prompt
|
||
├─ 身份段(username/bio)
|
||
├─ 偏好段(language/timezone/country)
|
||
└─ 阶段段(动态注入)
|
||
├─ intent stage prompt
|
||
├─ execution stage prompt
|
||
└─ organization stage prompt
|
||
```
|
||
|
||
### 构建函数
|
||
|
||
```python
|
||
def build_global_system_prompt(ctx: UserAgentContext) -> str:
|
||
lines = [
|
||
"# User Identity",
|
||
f"username: {ctx.username}",
|
||
f"bio: {ctx.bio or 'N/A'}",
|
||
"",
|
||
"# User Preferences",
|
||
f"interface_language: {ctx.settings.preferences.interface_language}",
|
||
f"ai_language: {ctx.settings.preferences.ai_language}",
|
||
f"timezone: {ctx.settings.preferences.timezone}",
|
||
f"country: {ctx.settings.preferences.country}",
|
||
"",
|
||
"# Instructions",
|
||
"Use the user's preferences to personalize responses.",
|
||
"Respond in the user's preferred AI language.",
|
||
"Consider the user's timezone for time-related queries.",
|
||
]
|
||
return "\n".join(lines)
|
||
```
|
||
|
||
### 阶段注入
|
||
|
||
每个阶段运行时,在全局 prompt 基础上追加阶段特定的指令:
|
||
|
||
```python
|
||
def build_stage_prompt(
|
||
base_prompt: str,
|
||
stage: str, # "intent" | "execution" | "organization"
|
||
ctx: UserAgentContext,
|
||
) -> str:
|
||
stage_prompts = {
|
||
"intent": "Analyze the user's intent and decide if direct response is possible.",
|
||
"execution": "Execute the required tasks and tools to fulfill the user's request.",
|
||
"organization": "Format the execution results into a user-friendly response.",
|
||
}
|
||
return f"{base_prompt}\n\n# Stage: {stage}\n{stage_prompts[stage]}"
|
||
```
|
||
|
||
---
|
||
|
||
## 依赖关系图
|
||
|
||
```
|
||
UserAgentContext (核心上下文)
|
||
↓
|
||
├─ ProfileSettings (用户配置)
|
||
│ └─ preferences.country → 人民币结算
|
||
│
|
||
├─ build_global_system_prompt() (全局 Prompt)
|
||
│ └─ 三阶段 Flow 使用
|
||
│
|
||
└─ resolve_stage_models() (选模逻辑)
|
||
└─ 三阶段 Agent 配置
|
||
```
|
||
|
||
---
|
||
|
||
## 相关文档
|
||
|
||
- [Runtime Database Schema](../runtime/runtime-database.md)
|
||
- [AG-UI Protocol](.opencode/skills/ag-ui/SKILL.md)
|
||
- [CrewAI Framework](.opencode/skills/crewai/SKILL.md)
|