开发指南:从工具描述到多 Agent 生产架构
本文与 Hermes 七天子站课表 Day 2–Day 6 逐日对齐。Morning 读概念、Afternoon 写代码、Evening 在 Persona Lab / Code Sandbox 验证假设。文档站负责「可检索的深度」;子站负责「可交互的肌肉记忆」。
Hermes Agent 开发的核心不是「让模型多说几句」,而是 把不确定性关进可观测、可限幅、可回滚的 Tool Graph。本指南按工程落地顺序展开:先写好工具描述(模型选对的概率),再设计 System Prompt 五层(角色不漂移),再编排多 Agent 与工具链,最后接入记忆、推理模式与框架选型。
一、工具描述工程(Tool Description Engineering)
工具描述是 模型与外部世界之间的 API 文档。Hermes 系列模型在 Function Calling 上训练充分,但描述质量仍决定 工具选择准确率(Tool Selection Accuracy, TSA) 与 参数合法率(Argument Validity Rate, AVR)。
1.1 Active vs Passive 描述
| 类型 | 写法特征 | 模型行为 | 适用场景 |
|---|---|---|---|
| Passive(被动) | 只列参数名、类型 | 模型常在「该用却不用」与「乱用」间摇摆 | 内部调试、模型已强熟悉域 |
| Active(主动) | 明确 何时调用 / 何时禁止 / 失败时怎么办 | 边界清晰,误调用下降 | 生产、多工具并存 |
Passive 反例(不要照搬):
{
"name": "search_web",
"description": "Search the web.",
"parameters": { "type": "object", "properties": { "query": { "type": "string" } } }
}
Active 正例(Hermes 推荐风格):
{
"name": "search_web",
"description": "当用户问题需要实时信息、新闻、股价或你不确定的 factual 断言时调用。禁止用于:纯数学推导、已在上文给出的事实复述、用户明确要求「不要搜索」。若 query 超过 120 字,先 summarize 再搜。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "英文关键词检索串;中文问题请翻译为 3–8 个英文关键词"
},
"max_results": {
"type": "integer",
"enum": [3, 5, 10],
"default": 5,
"description": "返回条数;闲聊用 3,研究报告用 10"
}
},
"required": ["query"]
}
}
设计原则:
- 触发条件写在前半句——模型先匹配意图,再填参数。
- 否定条件写在后半句——减少与其他工具抢调用。
- 与相邻工具做 diff——若
read_file与grep_code并存,各自 description 必须互斥。
1.2 Schema 设计:枚举、默认值、嵌套
| 技巧 | 作用 | 示例 |
|---|---|---|
enum | 消除幻觉参数 | "format": {"enum": ["json", "markdown", "plain"]} |
default | 降低必填字段认知负担 | 可选 timeout_sec 默认 30 |
嵌套 object | 表达结构化动作 | git_operation: { branch, files[] } |
minLength / pattern | 拦截非法输入 | 邮箱、路径前缀 |
字段级 description | 比顶层 description 更精准 | 每个 property 单独说明单位 |
DevOps Agent(Day 3 实战)工具片段:
from pydantic import BaseModel, Field
from typing import Literal
class TailLogInput(BaseModel):
"""拉取 CI 日志尾部——仅在用户询问构建失败原因或指定 job_id 时调用。"""
job_id: str = Field(..., pattern=r"^[a-z0-9-]{8,64}$")
lines: Literal[50, 200, 500] = Field(200, description="行数;初步排查 50,深挖 500")
severity: Literal["error", "warn", "all"] = "error"
将 Pydantic 模型 model_json_schema() 导出为 OpenAI Tools JSON,可保证 运行时校验与描述同源。
1.3 Few-shot 嵌入策略
在 System Prompt 的 Tools 层(见下文五层结构)嵌入 1–2 个 完整工具调用轨迹,比堆叠自然语言规则更有效。
## 工具调用示例(仅供格式参考,勿复述示例内容)
用户:把 report.pdf 总结成三点并算总页数。
Assistant 思考:需要 read_pdf → summarize → count_pages 线性链。
Tool: read_pdf {"path": "report.pdf", "max_pages": 20}
Observation: [二进制已转文本,省略]
Tool: summarize {"text": "...", "bullets": 3}
Tool: count_pages {"path": "report.pdf"}
Few-shot 纪 律:
- 示例中的工具名必须与实际 Registry 完全一致。
- 展示 错误恢复:第二个示例可以是「第一次参数非法 → 读 error → 修正再调」。
- 不超过 2 个完整链,否则挤占工作记忆 token。
1.4 描述版本化与回归
每次改 description,用固定 benchmark(见 Day 6)跑 TSA/AVR。建议维护 tools/CHANGELOG.md:
| 版本 | 工具 | 变更 | TSA 变化 |
|---|---|---|---|
| v1.2 | search_web | 增加「禁止搜已给事实」 | 71% → 89% |
| v1.3 | run_shell | 增加路径前缀 enum | AVR +12% |
二、System Prompt 五层结构(Day 2 核心)
角色工程不是「你是一个友好的助手」,而是 可测试的行为契约。推荐五层,从上到下优先级递增:
2.1 各层详解与验收标准
| 层 | 必答问题 | 验收( 人工或自动) |
|---|---|---|
| Identity | 你是谁、为谁服务 | 100 轮抽样角色自称一致率 ≥ 95% |
| Expertise | 专业边界在哪 | 越界问题拒答率、转介率 |
| Tone | 用户感知 | 情感标注一致、禁词零出现 |
| Constraints | 什么绝对不能做 | 红队用例通过率 |
| Tools | 工具优先序 | TSA、越权调用率 |
心理咨询工作室(Day 2 实战)Constraints 示例:
## Constraints(高于一切友好语气)
- 不提供医学诊断、不开处方、不替代危机热线。
- 检测到自伤关键词 → 立即停止探索式提问 → 输出本地危机资源 → handoff 至 supervisor Agent。
- 禁止调用 send_email / 任何对外通信工具,除非用户签署 informed consent 且 supervisor 批准。
2.2 五层与 Token 预算
| 层 | 建议 token 占比 | 压缩策略 |
|---|---|---|
| Identity + Expertise | 15% | 稳定不变,可缓存 |
| Tone | 5% | 合并为 3 条 bullet |
| Constraints | 25% | 不可压缩 |
| Tools + Few-shot | 35% | 按场景动态裁剪工具子集 |
| 预留 | 20% | 给 episodic 记忆注入 |
三、多 Agent 架构(Day 2 & Day 6)
单 Agent 在跨域任务上易出现 能力稀释 与 约束冲突。多 Agent 的本质是 分治 + 显式 Handoff 协议。
3.1 模式对照表
| 模式 | 拓扑 | 典型场景 | Hermes 实现要点 |
|---|---|---|---|
| 专家委员会 | 并行分析 → 主席综合 | 架构评审、投资分析 | 各专家独立上下文,主席只看摘要 |
| 师徒(Mentor–Apprentice) | 师傅审学徒产出 | 代码生成、合规文书 | 学徒工具权限 ⊂ 师傅 |
| 红蓝对抗 | 攻击 Agent vs 防守 Agent | Prompt 注入、越权测试 | Day 6 必修;见 §8 |
| Handoff | A 打包 → B 续跑 | 接待 → 专科 → 督导 | 结构化 handoff payload |
| 意图路由 | Router 选下游 | 客服分流、DevOps 分类 | 小模型或规则 + LLM fallback |
| 防漂移 | 周期提醒 + 记忆隔离 | 长对话、多轮咨询 | 每 N 轮注入 Identity 摘要 |
3.2 Handoff Payload 规范
Handoff 不是把整个 chat history 粘贴给下一个 Agent——那是 上下文炸弹。推荐 JSON 包:
{
"handoff_version": "1.0",
"from_agent": "intake_reception",
"to_agent": "cbt_specialist",
"user_goal": "近期失眠与焦虑",
"facts_verified": ["持续 2 周", "无用药史"],
"open_questions": ["触发事件是否明确"],
"risk_flags": [],
"forbidden_actions": ["diagnosis"],
"suggested_tools": ["breathing_exercise_guide"],
"transcript_digest": "用户描述工作压力…(≤500 tokens)"
}
3.3 意图路由实现
| 层级 | 实现 | 延迟 | 准确 |
|---|---|---|---|
| L0 规则 | 关键词 + 正则 | 毫秒级 | 中 |
| L1 分类器 | 小模型 / embedding | 百 ms | 高 |
| L2 LLM | route_intent 工具 | 秒级 | 最高 |
推荐: L0 拦截明确指令(「查 CI」「翻译」)→ L1 处理模糊句 → L2 仅兜底。
3.4 防漂移(Anti-Drift)
长对话中模型会逐渐 忘记 Constraints。三道防线:
- 周期性 Identity 注入(每 8–12 轮):「Reminder: 你是 X,禁止 Y。」
- 角色记忆隔离:业务 Agent 不读其他 Agent 的私有 scratchpad。
- 输出校验器:正则 / 小模型检测禁词、越权工具名。
Day 2 子站 Persona Lab 可 A/B 测试不同 reminder 间隔对漂移率的影响。
四、工具链编排(Day 3)
Tool Graph 有三种基础拓扑;生产系统常组合使用。
4.1 线性链(Pipeline)
A → B → C
read_pdf → summarize → send_slack
适用: 数据形态逐步变换,后步依赖前步输出。
代码骨架(LangChain 风格伪代码):
async def linear_chain(state):
doc = await tools.read_pdf(state["path"])
summary = await tools.summarize(doc.text, bullets=3)
await tools.notify_slack(channel=state["channel"], text=summary)
return {"summary": summary}
4.2 条件链(Router / Branch)
实现方式:
- 显式: Planner 输出
branch字段,Executor 解释执行。 - 隐式: 模型在 ReAct 环中自行选择下一工具(需强 description)。
4.3 并行链(Fan-out / Fan-in)
┌→ metrics_query ─┐
用户请求 ─┼→ log_search ─┼→ aggregate_report
└→ git_blame ─┘
要点:
- Fan-out 工具应 无写冲突 或写不同资源。
- Fan-in 需要 聚合策略:按时间排序、去重、摘要超长结果。
- 设置 并行超时:最慢分支决定总延迟,用
asyncio.wait(..., timeout=30)。
| 拓扑 | 失败传播 | 重试策略 |
|---|---|---|
| 线性 | 中断或 skip 后续 | 从失败步重试 |
| 条件 | 仅执行分支内 | 分支独立 retry |
| 并行 | 部分失败 | 标记 partial,Fan-in 说明缺失 |
五、自定义工具安全(Day 3 & Day 6)
Agent 的工具等于 给模型的 shell 账号。安全不是可选项。
5.1 输入验证:Pydantic 双层
from pydantic import BaseModel, Field, field_validator
import os
class ReadFileInput(BaseModel):
path: str = Field(..., description="相对 workspace 的路径")
@field_validator("path")
@classmethod
def no_path_traversal(cls, v: str) -> str:
normalized = os.path.normpath(v)
if normalized.startswith("..") or normalized.startswith("/"):
raise ValueError("path must stay inside workspace")
return normalized
| 层 | 位置 | 作用 |
|---|---|---|
| Schema 层 | 模型填参前 | 引导合法 JSON |
| Runtime 层 | 工具 execute 前 | 硬拦截注入与越界 |
5.2 超时分级
| 等级 | 超时 | 典型工具 | 失败处理 |
|---|---|---|---|
| T0 | 10s | 计算器、格式化 | 立即重试 1 次 |
| T1 | 30s | HTTP GET、DB 读 | 指数退避 ×2 |
| T2 | 60s | 大文件解析、编译 | 转异步 job + 通知 |
| T3 | 120s+ | 仅 E2B 沙箱 | 必须人工审批 |
5.3 E2B 沙箱执行
对用户代码、不可信脚本,使用 E2B 等 一次性 VM:
from e2b_code_interpreter import Sandbox
async def run_untrusted_python(code: str) -> str:
with Sandbox(timeout=60) as sbx:
execution = sbx.run_code(code)
if execution.error:
return f"RuntimeError: {execution.error}"
return execution.text
纪律: 主进程 never exec() 用户代码;沙箱 无网络 或仅 allowlist;每次调用 销毁实例。
5.4 失败策略矩阵
| 策略 | 配置 | 适用 |
|---|---|---|
| Retry | max=2, backoff | 瞬时网络错误 |
| Fallback Tool | primary→secondary API | 数据源冗余 |
| Self-Correct | error as Observation | 参数格式错误 |
| Abort + Handoff | 人工 ticket | 支付、删库类 |
六、推理模式(Day 5)
推理模式决定 Agent 如何规划、调用工具、修正错误。
6.1 模式总览
| 模式 | 核心循环 | 优势 | 风险 |
|---|---|---|---|
| ReAct+ | Thought → Action → Observation(+ 反思句) | 简单、可解释 | 步数膨胀 |
| Plan-and-Execute | Planner 出计划 → Executor 逐步执行 | 长任务稳定 | 计划过时 |
| Tree of Tools | 多分支试探 → 选最优叶 | 探索性任务 | Token 成本高 |
| Self-Refine | 生成 → 自评 → 修订 | 质量提升 | 延迟 ×2–3 |
| HTN | 层次任务网络分解 | 复杂域可复用 | 需领域模板 |
6.2 ReAct+ 增强点
标准 ReAct 在 Observation 后直接 Thought。ReAct+ 增加 Reflect 步:
Observation: search 返回 0 条结果
Reflect: 关键词过窄,应改用英文同义词并放宽时间范围
Thought: 调用 search_web,query="LLM agent benchmark 2024"
研究 Agent(Day 5)建议 max_steps=12,超过则强制 summarize 已收集证据。
6.3 Plan-and-Execute 计划模板
{
"goal": "撰写某技术对比报告",
"steps": [
{"id": 1, "action": "search", "success_criteria": "≥5 篇 primary source"},
{"id": 2, "action": "read_and_note", "depends_on": [1]},
{"id": 3, "action": "outline", "depends_on": [2]},
{"id": 4, "action": "draft", "depends_on": [3]},
{"id": 5, "action": "self_refine", "depends_on": [4]}
],
"replan_triggers": ["step_failed_twice", "new_user_constraint"]
}
Executor 不得擅自增删步骤;需 replan 时交还 Planner。