NativeAgent 设计
AI 原生 Tool Calling 架构 — 单 runtime,LLM 自主编排。
替代旧的多 Agent 设计(PlannerAgent / ExecutorAgent / RouterAgent / ChatAgent / PatchAgent)。
架构对比
旧架构(已删除)
用户消息
↓
RouterAgent (if-elif 意图分类 + 置信度阈值 + 关键词正则)
↓
conversation.py (12+ handler 函数分发)
↓
┌─────────────────────┐
│ chat_response() │ → ChatAgent
│ _stream_build() │ → PlannerAgent → ExecutorAgent
│ _stream_quiz_*() │ → QuizSkill / UnifiedAgent
│ _stream_modify_*() │ → PatchAgent
│ _stream_followup*() │ → 各种 followup handler
└─────────────────────┘
~2500+ 行编排代码
新架构(AI 原生)
用户消息
↓
conversation.py (薄网关, ~100 行)
→ 鉴权、会话、限流、SSE 适配
↓
NativeAgent (单 runtime)
→ select_toolsets() → 宽松包含式选择 8-12 个 tools
→ Agent(tools=subset).run_stream()
→ LLM 自主决定调哪个 tool → 自动执行 → 自动循环
↓
stream_adapter → Data Stream Protocol SSE
↓
前端 (契约不变)
NativeAgent (agents/native_agent.py)
职责
- 每轮根据上下文调用
registry.get_tools(toolsets) 获取 tool 子集
- 构建 PydanticAI
Agent(tools=subset) 实例(每轮新建,开销 < 1ms)
- 调用
agent.run_stream() 或 agent.run()
- LLM 自主决定是否调 tool、调哪个、多少轮
核心流程
class NativeAgent:
async def run_stream(self, message: str, context: AgentContext):
# 1. 宽松选择 toolset
toolsets = self.select_toolsets(message, context)
# 2. 从 registry 获取 tool 子集
selected_tools = registry.get_tools(toolsets=toolsets)
# 3. 创建 PydanticAI Agent(每轮新建)
agent = Agent(
model=create_model(),
system_prompt=SYSTEM_PROMPT,
tools=selected_tools,
)
# 4. 加载历史并执行
history = await conversation_store.load_history(context.conversation_id)
result = await agent.run_stream(message, message_history=history)
# 5. 迭代流事件
async for event in result:
yield event
# 6. 保存历史
await conversation_store.save_history(context.conversation_id, result.messages)
Toolset 选择策略
宽松包含式选择 — 不是排他分类,误包含代价极低,误排除代价极高。
def select_toolsets(self, message: str, context: AgentContext) -> list[str]:
sets = ["base_data", "platform"] # 始终包含
if _might_generate(message): # 关键词: 出题/生成/PPT/quiz...
sets.append("generation")
if context.has_artifacts or _might_modify(message): # 关键词: 修改/改/换/删...
sets.append("artifact_ops")
if context.class_id or _might_analyze(message): # 关键词: 成绩/分析/统计...
sets.append("analysis")
return sets
与旧 RouterAgent 的区别: 旧 Router 做排他分类("这是 quiz 意图 → 只走 quiz 路径"),新 toolset 选择做宽松包含("可能需要生成 → 加载 generation 包,LLM 自己决定用不用")。
System Prompt 设计
你是教育 AI 助手。以下是你的工具使用规则:
1. 你有一组可用工具。对于每个用户请求,自主判断是否需要调用工具。
2. 涉及学生数据、成绩、作业提交等信息时,必须通过数据工具获取,不可编造。
3. 涉及教学文档内容时,必须通过 search_teacher_documents 检索,不可凭记忆回答。
4. 涉及实时信息时,必须通过相应工具获取,不可用训练数据回答。
5. 对于通用知识(语法规则、数学公式等),可以直接回答。
6. 当工具返回 status="error" 时,如实告知用户服务暂不可用,不可编造替代答案。
7. 不确定是否需要工具时,优先调用工具确认,而非猜测回答。
Tool Registry (tools/registry.py)
注册方式
from tools.registry import register_tool
@register_tool(toolset="generation")
async def generate_quiz_questions(
ctx: RunContext[AgentContext],
subject: str,
count: int = 5,
difficulty: str = "medium",
) -> QuizOutput:
"""Generate quiz questions for a given subject."""
return await _generate_quiz_impl(subject, count, difficulty)
查询
# 按 toolset 获取子集
tools = registry.get_tools(toolsets=["generation", "platform"])
# 获取全部 tool
all_tools = registry.get_all_tools()
5 个 Toolset
| Toolset | Tools 数 | 注入条件 | 说明 |
|---|
base_data | 5 | 始终注入 | 基础数据获取 + 实体解析 |
analysis | 5 | 涉及数据/成绩 | 统计分析 + 薄弱点 |
generation | 7 | 涉及生成/创建 | Quiz/PPT/文稿/互动 |
artifact_ops | 3 | 有 artifact 或涉及修改 | 获取/修改/重新生成 |
platform | 5 | 始终注入 | 保存/分享/RAG/澄清/报告 |
Artifact 编辑模型
生成用专用工具,编辑用通用 patch_artifact,避免工具爆炸。
工具分工
| 操作 | 工具 | 说明 |
|---|
| 首次生成 | generate_quiz_questions / generate_pptx / ... | 专用工具,按 artifact_type 分发 |
| 结构化修改 | patch_artifact(artifact_id, operations) | 通用工具,按 content_format 分发到 patcher |
| 全文重新生成 | regenerate_from_previous(artifact_id, instruction) | patch 失败的降级路径 |
编辑 vs 重新生成
由 LLM 自主判断,不硬编码规则:
- LLM 收到修改请求 → 调
get_artifact 获取当前内容
- 根据修改复杂度自主决定:
- 小改 →
patch_artifact
- 大改 →
regenerate_from_previous 或对应 generate_xxx
会话流程
POST /api/conversation/stream
│
▼
conversation.py (薄网关)
├── 鉴权: 验证 JWT → teacher_id
├── 会话: 加载/创建 conversation_id
├── 调用: NativeAgent.run_stream(message, context)
├── 适配: stream_adapter → SSE 事件
└── 校验: 确认 finish 事件已发送
│
▼
前端消费 SSE (Data Stream Protocol 契约不变)
场景示例
| 场景 | 用户消息 | NativeAgent 行为 |
|---|
| 闲聊 | "你好" | LLM 直接回复,不调 tool |
| RAG 问答 | "Unit 5 教学重点" | 自动调 search_teacher_documents |
| Quiz 生成 | "出 5 道英语选择题" | 自动调 generate_quiz_questions |
| Quiz 修改 | "把第 3 题改成填空题" | 自动调 get_artifact → patch_artifact |
| 数据分析 | "分析三班成绩" | 自动调 get_teacher_classes → calculate_stats |
| 跨意图 | 同一对话内 chat→quiz→修改 | conversation_id 上下文连续,LLM 自主切换 |
与旧 Agent 的对应关系
| 旧 Agent | 新架构替代 | 说明 |
|---|
| RouterAgent | 删除 | LLM 自主选 tool,无需意图分类 |
| PlannerAgent | 删除 | Blueprint 规划被 tool calling 取代 |
| ExecutorAgent | 删除 | 三阶段流水线被 NativeAgent 取代 |
| ChatAgent | 删除 | NativeAgent 直接处理对话 |
| PageChatAgent | 删除 | NativeAgent 通过 conversation_id 上下文处理追问 |
| PatchAgent | patch_artifact tool | 正则匹配被结构化 PatchOp 取代 |
| EntityResolver | resolve_entity tool | 状态机被 tool 取代 |
工程约束
失败分级
| 级别 | 类型 | 处理方式 |
|---|
| L1 | Tool 失败 | tool 返回错误信息给 LLM,LLM 自主重试或换策略 |
| L2 | Model 失败 | fallback 到备选 model |
| L3 | Protocol 失败 | stream_adapter 捕获 + 发送 error event |
| L4 | Budget 超限 | 强制停止 + 返回 partial result |
| L5 | System 失败 | 全局异常处理 → 500 + error event |
预算约束
| 约束 | 默认值 |
|---|
| max_tool_calls | 10 |
| max_total_tokens | 32k (input) + 8k (output) |
| max_turn_duration | 120s |
| per-tool timeout | 30s |