前言:

这一篇只做一件事:

把链路跑通到:模型 → 触发工具调用 → 读取本地 PDF → 回填工具结果 → 模型生成课程大纲(带引用)

先不做向量库、不做 embedding,这一步的目标是:先把 Agent 的“工具调用骨架”跑起来,后面再迭代到 RAG。

一、 MVP 目标与边界

1.1 本阶段目标(须满足)

  • 工具调用须真实发生(tool_calls 可观测)

  • 工具读取本地 PDF(用户输入路径)

  • 模型最终输出 Markdown 大纲

  • 每条要点必须带 弱追溯引用(来源:文件名 摘录:...)

1.2 本阶段不做(先不做)

  • 不做 embedding / FAISS / 向量库

  • 不做多 Agent / 复杂工作流

  • 不做前端嵌入编辑器(先用命令行跑通)

二、项目结构(最小化)

只保留一个文件:main.py

原因:阶段1只验证“工具调用链路 + 数据接入”,结构越简单越容易理解和调试。

三、核心流程(执行链路)

整个程序只有两次模型调用:

3.1 第一次调用:让模型自动决定是否调用工具

  • 输入:用户给一个 PDF 路径

  • 传入:tools schema(包含 read_local_pdf

  • tool_choice:auto

  • 预期:模型输出 tool_calls,触发 read_local_pdf(file_path=...)

3.2 执行工具:读取本地 PDF

  • 工具入参:file_path

  • 工具出参:必须包含

    • 文件名(demo.pdf)

    • 摘录(从 PDF 抽取文本截断)

3.3 第二次调用:禁用工具,只做生成

  • 把工具返回内容以 role="tool" 回填到 messages

  • 第二次调用不再传 tools(避免模型再次发起工具调用)

  • 预期:生成 Markdown 课程大纲,并强制每条要点带引用

四、可观测日志(

阶段1打印 4 类日志,原因是:Agent 链路如果不可观测,很容易“以为调用了,实际没调用”。

须看到这些日志才算跑通:

  1. 模型第一次回复 content

  2. tool_calls(工具名 + arguments)

  3. 工具入参/出参

  4. 模型最终输出(Markdown 大纲)

五、实跑结果(关键输出示例)

成功现象应该长这样:

tool_calls 触发:

tool_calls:
{name: read_local_pdf, arguments: {"file_path":"...demo.pdf"}}

工具确实执行:

工具入参: {"file_path":"...demo.pdf"}
工具出参: 文件: demo.pdf
摘录1: ...

最终输出是大纲且带引用:

# 课程大纲:AI能力演示功能
## 第1章 背景与目标
- 产品需要在教学编辑器内提供AI能力演示功能…(来源:demo.pdf 摘录:…)
...

六、测试视角:本阶段的验收点(6条)

  1. 运行 python main.py 不报错

  2. 第一次回复须出现 tool_calls

  3. 工具入参须能解析出 file_path

  4. 工具出参必须包含 文件名+摘录(弱追溯证据)

  5. 最终输出必须是 Markdown 大纲结构(# / ## / -)

  6. 每条要点须带(来源:文件名 摘录:…),缺失则判失败

七、踩到的坑

7.1 CompletionMessage 不能 json.dumps

现象:

  • 直接 json.dumps(message) 报错:Object of type CompletionMessage is not JSON serializable

修复:

  • 只打印你关心的字段:content / tool_calls,避免 dump 整个对象。

7.2 第二次调用模型“又回到开场白”

现象:

  • 工具已经读到 PDF,但最终输出仍然是“我先读取文件内容…”

根因(典型):

  • 第二次调用仍带 tools/tool_choice,或第二次 prompt 不够硬,模型再次走工具调用路径

最小修复:

  • 第二次调用 禁用 tools(不传 tools)

  • 第二次 prompt 强制:基于 tool 返回生成大纲,并禁止再次调用工具

八、阶段1核心逻辑(最短闭环)

  • 定义工具(tools schema)+ 约束 prompt

    • 工具:read_local_pdf(file_path)

    • 约束:遇到需要读 PDF 的场景必须调用工具;输出要 Markdown 大纲+引用

  • 用户输入本地文件路径

    • 你输入:D:\...\demo.pdf

  • 第一次模型调用:让模型“决策”是否调用工具

    • tool_choice="auto"

    • 模型判断“这是 PDF,需要读取内容” → 返回 tool_calls(read_local_pdf, file_path=...)

  • 程序执行工具:读取 PDF,产出“证据”

    • 工具真正读文件

    • 返回:文件名 + 摘录(证据)(弱追溯)

  • 第二次模型调用:把证据喂回去,让它生成大纲

    • 把工具结果以 role="tool" 追加进 messages

    • 第二次调用不再启用 tools(避免它又去调用工具)

    • 模型基于证据输出 Markdown 大纲(章节→要点),每条要点带来源摘录

这条链路里 prompt 非常关键,因为它直接决定三件事:

  1. 模型会不会触发 tool_calls(什么时候该用工具)

  2. 工具返回后模型怎么用(把返回当证据、还是当闲聊素材)

  3. 输出格式能不能稳定可测(Markdown结构、引用是否齐全)

可以把 prompt 当成测试对象,做最小的 prompt 用例:

  • 用例1:输入 pdf 路径 → 断言出现 tool_calls(否则 prompt 不合格)

  • 用例2:给一段工具摘录 → 断言输出是 Markdown 且每条要点有引用(否则 second_prompt 不合格)

这是“Agent & RAG 测试工程笔记”的含金量点:
prompt 不是玄学,是可回归的控制面。

prompt模板:

Prompt v0.1 — First(触发工具调用):

你是一个课程大纲助手。
当用户提供的是本地 PDF 文件路径时,你必须调用工具 read_local_pdf(file_path) 来读取内容,禁止凭空编造。
调用工具后先不要生成大纲,等待工具返回。

这段的核心是:必须调工具 + 不要猜 + 等工具结果

Prompt v0.1 — Second(基于证据生成大纲):

你将收到工具返回的“文件名 + 摘录”。请仅基于这些摘录生成 Markdown 课程大纲:
- 结构:# 课程大纲 -> ## 章节 -> - 要点
- 规模:3~8章,每章3~7要点
- 每条要点末尾必须带(来源:文件名 摘录:...)
禁止再次要求读取PDF,禁止调用任何工具,禁止补充证据中没有的信息。

这段的核心是:只用证据 + 固定结构 + 强制引用 + 禁用工具/禁编造

九、下一步计划(阶段2)

阶段2做 RAG-lite(仍然不做 embedding):

  • read_local_pdf 从“1条摘录”升级为“前 N 页多摘录”

  • 增加“引用校验/剔除”机制:保证输出稳定可验收

  • 然后再进入阶段3:embedding + FAISS topK(真正 RAG)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐