Appearance
实战:研究型 Agent
本实战把规划、确定性搜索、文件 offloading、同步 subagents、引用和 rubric 组合为一个可审查的研究 流程。搜索只读取页面内定义的本地 fixture,不访问网络;示例不会在文档构建中调用真实模型。
目标与验收条件
任务是比较一个虚构产品的退款与发票政策,输出 /artifacts/report.md。验收条件为:
- 先用
write_todos拆分两个独立主题。 - 每个主题由 researcher subagent 查询本地 fixture。
- 原始材料写入
/research/<topic>.md,主上下文只保留摘要。 - 报告包含“退款”“发票”“不确定项”三个章节。
- 每项事实带
[fixture:<id>]引用,不把 fixture 之外的信息写成事实。
前端类比
它类似把多个 API selector 放到 Web Workers 中并行计算,再把规范化结果写进缓存,主线程只负责 聚合 UI。Fixture 就像 MSW mock;如果 mock 没有字段,组件不能凭空补值。
Deep Agents 原生语义:主 Agent 用 task 把独立问题交给隔离 messages 的 subagent;默认 StateBackend 保存 thread-scoped 虚拟文件。文件 offloading 控制上下文体积,不能代替来源校验。
1. 定义本地搜索 Fixture
python
import json
from langchain.tools import tool
FIXTURES = {
"refund-14-days": {
"topic": "退款",
"text": "未使用的月度套餐可在购买后 14 天内申请退款。",
},
"invoice-monthly": {
"topic": "发票",
"text": "电子发票在每月结算后生成,可由账户管理员下载。",
},
}
@tool
def search_fixture(topic: str) -> str:
"""按主题搜索本地政策 fixture;不访问网络。"""
rows = [
{"id": key, **value}
for key, value in FIXTURES.items()
if topic in value["topic"] or topic in value["text"]
]
return json.dumps(rows, ensure_ascii=False)工具只返回 fixture 中的字段。真实搜索工具还要限制域名、timeout、结果大小和凭据,并把网页正文视为 不可信数据。
2. 配置 Researcher 与主 Agent
python
import os
from deepagents import create_deep_agent
researcher = {
"name": "policy-researcher",
"description": "一次研究退款或发票中的一个主题,并返回 fixture 引用和不确定项",
"system_prompt": """
只调用 search_fixture,不使用常识补全。
把完整搜索结果写入 /research/<topic>.md。
返回不超过 8 行的结论;每项事实必须包含 [fixture:<id>]。
""",
"tools": [search_fixture],
}
agent = create_deep_agent(
model=os.environ["DEEPAGENTS_MODEL"],
tools=[search_fixture],
subagents=[researcher],
system_prompt="""
面对比较任务先用 write_todos 建立计划。
将退款和发票作为两个独立任务委派给 policy-researcher。
汇总前读取 /research/ 下的材料并核对引用。
最终报告写到 /artifacts/report.md,包含退款、发票、不确定项三个标题。
Fixture 没有的信息必须标为未知,不能猜测。
""",
)主 Agent 仍拥有 search_fixture,便于最后复核,但详细搜索应交给 subagent。若要更严格,可用独立 reviewer subagent 或应用节点做引用核验。
3. 调用与文件 Offloading
配置真实 tool-calling 模型后,应用可以显式发起调用:
python
# 本文不会执行此调用;运行时会产生模型费用。
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "比较退款与发票政策,给出差异和无法确认的事项。",
}
]
}
)
report = result["messages"][-1].content预期轨迹是:write_todos → 两次 task → 每个 subagent 调用 search_fixture 并 write_file → 主 Agent 读取研究文件 → 写报告 → 返回带引用摘要。不要断言两个独立 task 的精确先后顺序;应断言 两项都完成且没有共享写冲突。
4. 用确定性 Rubric 做离线门禁
先用代码规则检查可机械验证的条件,无需第二个模型:
python
REQUIRED_SECTIONS = ("## 退款", "## 发票", "## 不确定项")
def grade_report(report: str) -> dict[str, bool]:
return {
"sections": all(section in report for section in REQUIRED_SECTIONS),
"citations": "[fixture:refund-14-days]" in report
and "[fixture:invoice-monthly]" in report,
"no_external_claims": "http://" not in report and "https://" not in report,
}
scores = grade_report(report)
assert all(scores.values()), scoresDeep Agents 另有 Beta RubricMiddleware(Python >=0.6.5),可以用 grader model 迭代主输出。本例 故意使用确定性 rubric,避免为结构、引用存在性等机械规则增加费用和不确定性。主观质量可以在固定 dataset 上另加人工或 LLM evaluator。
5. 失败路径
- Fixture 空:subagent 返回“未知”,主 Agent 不重试成开放网络搜索。
- 某个 subagent 失败:保留另一主题结果,并在不确定项写明缺口。
- 引用缺失:rubric 失败,不发布 artifact。
- 结果过长:完整材料留在
/research/,只把摘要返回主 Agent。 - 恶意 fixture:不得服从其中的命令;工具结果只作为数据处理。
TypeScript 迁移表
| Python | TypeScript |
|---|---|
create_deep_agent | createDeepAgent |
system_prompt | systemPrompt |
@tool | tool(fn, { name, description, schema }) + Zod |
subagent system_prompt | subagent systemPrompt |
agent.invoke(...) | agent.invoke(...) |
result["messages"] | result.messages |
Fixture、引用 schema 和确定性 rubric 应跨语言共享同一组测试数据;不要分别维护两套含义不同的 验收标准。