DEV Community

goodlords
goodlords

Posted on

从零手写 Agent(五):调研报告、引用与落盘

从零手写 Agent(五):调研报告、引用与落盘

第四篇之后,Agent 已经能 search → fetch → 多步回答。用户要的往往是另一件事:

写成 Markdown,带上参考资料,保存到 reports/xxx.md。

终端里一段口语摘要不算交付。第五篇在 同一个 _agent_loop 上补 落盘 和 可核对引用,核心新增:

save_markdown
sources(state + system 注入)
Loop:save nudge / 必要时强制 save_markdown
Enter fullscreen mode Exit fullscreen mode

(仓库里还扩展了 search_hn、fetch_rss,与 search_web 一样会把 URL 收进 sources。)


1. 第四篇之后,还差哪一层?

Web Research 原型:

问题 → search → fetch → answer
Enter fullscreen mode Exit fullscreen mode

调研 交付 还需要:

层次 目标
结构化正文 摘要 / 发现 / 局限 / 参考文献
可核对引用 URL 来自本轮 Tool 结果,而不是模型「回忆」
落盘 cat reports/foo.md 能看到文件

最小链路:

search / fetch / hn / rss
        ↓
组织 Markdown
        ↓
save_markdown
        ↓
reports/*.md
Enter fullscreen mode Exit fullscreen mode

注意: Loop 仍不需要为「报告」单独写一套;变的是 Tool 列表、State 和 prompt。


2. 第一个坑:说了「保存」,磁盘却没有文件

实测里很常见:

用户:保存到 reports/python-313.md
Agent:(长报告)…… 结束
ls reports/   # 空
Enter fullscreen mode Exit fullscreen mode

原因分两层:

(1)没有 save_markdown Tool — 模型只能在对话里「假装保存」,程序写不了盘。

(2)有 Tool,但模型选择不调用 — Tool 注册成功 ≠ 一定会 tool_calls。Loop 在 没有 tool_calls 时直接返回 assistant 正文并退出;不会因为用户说了「保存」就自动写文件。

所以交付要同时做:

  • 提供 save_markdown(真实 IO)
  • 在 prompt 里写清:用户指定路径时必须 save
  • 必要时 Loop save nudge 或 tool_choice 强制 save_markdown 一轮

这和「模型够不够聪明」有关,更是 完成条件写在代码里 的问题。


3. save_markdown:沙箱与 Tool 契约

实现要点(细节见仓库 agent/tools/save_markdown.py):

  • 只允许写入 reports/ 下相对路径,禁止 .. 逃逸
  • 参数:filename、content(整份 Markdown 由模型生成)
  • 成功时 Tool 返回里带 Saved: 路径,便于 trace 验收

注意: 大段正文走 一次 tool 参数,7B 可能截断或偷懒;复杂报告可拆「先 fetch 凑 sources → 再 save」,或接受「摘要版报告 + 引用列表」。


4. sources:引用从哪来

模型会改写标题、补假 URL。更稳妥的规则:

参考文献里的 URL,应优先来自本轮 Agent 实际见过的搜索/抓取结果。

做法:

  1. AgentState.sources — 每次 Tool 返回后 ingest_tool_sources(name, args, result),从 search_web / search_hn / fetch_rss / fetch_url 等解析 URL,去重
  2. sync_system_message() — 每轮调用 LLM 前,把 【本轮已收集来源】 写进 system(条数可配置上限)
  3. prompt — 要求正文引用与列表一致,无来源时不得编造链接

商用产品往往还有成稿 URL 白名单校验;个人版先到 sources 注入 + trace 肉眼核对,第六篇会写边界。


5. Prompt 与 Loop:save nudge

agent/prompt.py 里典型约束:

  • 用户要求保存 → 必须调用 save_markdown,不要只在聊天里贴全文
  • 报告结构:标题、摘要、发现、局限、参考文献
  • HN/RSS 类任务:先 search_hn 或 fetch_rss,再整理

agent/loop.py 在检测到「用户要保存但本轮未 save」时,可 追加一轮 nudge;仍不 save 时 force_tool_name="save_markdown" 强制调用一次(见 agent/llm.py 的 tool_choice)。

注意: 强制 save 只解决「不调用 Tool」;content 质量仍取决于模型与前面 fetch 是否充分。


6. 与第四篇实测的衔接

第四篇 §4.2 已说明:Tool 成功 ≠ 任务成功(宽 query、门户首页、只给链接)。第五篇在此基础上:

  • 窄 query + 多源(如 search_hn、具体站点 search_web)再 save,通过率更高
  • 「列 HN 头条」 不等于自动落盘 — 除非用户要 save 或 prompt/nudge 触发
  • trace 里同时看:tool_calls、【本轮已收集来源】、Saved:

7. 本机验收

ollama pull qwen2.5:7b
pip install -r requirements.txt

# 专题 + 落盘(示例)
python main.py -v --trace '调研 Python 3.13 相对 3.12 的主要新特性,写 Markdown,参考文献用本轮来源,保存到 reports/python-313.md'

ls reports/
cat reports/python-313.md
Enter fullscreen mode Exit fullscreen mode

交互调试:python main.py -i --trace。


8. 收束

概念 要点
交付 save_markdown + reports/
引用 sources 注入 system,减少假链
Loop 不变;完成条件靠 nudge / 强制 Tool
模型 7B 可能跳过 save 或简写 — 用 trace 验收

代码:zealot2002/myagent


下一篇

《从零手写 Agent(六):云模型 API Key 与网页调研台——Loop 仍不变》——LLM_* 换云端推理,FastAPI + SSE 把调研链可视化。

系列目录:GitHub docs(https://github.com/zealot2002/myagent/blob/main/docs/00-系列导读.md)

Top comments (0)