导学阶段-LLM 为什么需要工具与上下文
本文整理自 CNB 上的开源课程 《Vibe Agent:从零搭建 Coding Agent》。课程讲义、示例代码与练习仓库均在 CNB 上公开,文中示例出自该仓库对应章节。
问模型现在几点,它给不出准确答案。原因在训练数据:这类信息从来没有出现过。把模型的边界划清楚,才能决定该在提示词里放什么、该给它配什么工具。
四类够不到的信息
课程把模型的处境比作一个读遍图书馆、却从未出过门的人。有四类信息它无法从自身获得:
| 类型 | 例子 |
|---|---|
| 实时信息 | 当前时间、今天的天气、某只股票的现价 |
| 私域知识 | 公司内部文档、仓库里的代码 |
| 训练截止之后的事 | 最新的新闻、论文、政策 |
| 执行结果 | 一段代码实际输出什么、一次 API 调用返回什么 |
参数是训练语料的压缩结果。训练结束之后发生的事、没有公开过的文档、需要动手运行才知道的结果,都不在这份压缩里。模型规模再大也补不上,缺口来自信息本身不在训练分布中,与推理能力无关。
缺口还有一种表现。被问到超出知识范围的内容时,模型没有”拒答”的强制机制,倾向于给出最像的续写,于是答案格式完整、内容错误。这类输出通常被称为幻觉。遇到模型胡说时,先看问题落在上面四类里的哪一类,多数情况能直接定位。
模型能用的信息全部来自参数与这次传入的提示词,二者之外没有第三个来源。参数固定,所以它感知不到时间的变化。
在 system 里写”请回答当前时间”也不会让模型获得一只时钟。它只会更努力地猜一个像样的时间,而猜出来的数字和真实时间没有关系。
第一条路:把资料拼进提示词
最直接的做法是把外部资料写进 user 消息,让模型基于资料回答。
1 | import os |
提示词里放了多少,模型就能用多少。这是 RAG 的最朴素形态:检索由人完成,增强是把文档塞进消息,生成是模型基于文档作答。三个词对应三步,程序没有替人做检索。
好处是门槛低。任何支持 chat 接口的模型都能用,不需要额外的调度逻辑,一份文档加一段问题就能验证。局限同样明显:塞哪份资料要由人决定;资料一大就撞上下文窗口;模型不会在需要时主动去找下一份文档。
第二条路:让模型自己选工具
更彻底的做法是把选择权交给模型。程序先声明有哪些工具可用,模型在需要时输出一次工具调用,程序执行后把结果回传,再由模型组织语言。流程可以拆成四步:用户提问;模型输出工具名与 JSON 参数;程序执行工具并拿到结果;结果以 role 为 tool 的消息回传,模型生成最终回答。
1 | import datetime |
两个细节容易出错。第二轮请求要把带 tool_calls 的 assistant 消息原样带回,每条工具结果都要配上对应的 tool_call_id,少一样模型就接不上这次调用。工具能否被选中,取决于 name、description、parameters 三段文字,描述含糊时模型会选错工具,或者干脆不用。
工具返回的内容同样计入上下文。读一次日志或一个大文件,可能吃掉窗口的大半,工具设计因此要控制返回长度。
一次工具调用要经过两次请求:第一次让模型决定调什么,第二次让它用结果作答。轮次比单次调用多,成本也更高,Agent 循环必须设上限,原因之一就在这里。
两种方式怎么分工
| 把资料塞进提示词 | 让模型自己调工具 | |
|---|---|---|
| 谁决定查什么 | 开发者或使用者 | 模型自己 |
| 灵活性 | 低,查什么写在代码里 | 高,按需选择 |
| 复杂度 | 极低 | 需要实现工具调度 |
| 适用场景 | 固定文档问答 | 通用 Agent |
实际工程里两条路常一起用。检索把与问题最相关的资料放进上下文打底,工具负责补齐检索覆盖不到的实时数据和执行动作。
工具不是权限边界
有一处边界需要单独说明。工具的 description 只影响模型如何选择,不产生约束力。以常见的命令行工具为例,一个 cwd 参数只是设定默认工作目录,模型传绝对路径就能绕开,把它当成安全边界是误判。真正需要禁掉的操作要在执行层拦:参数校验、允许清单、危险动作的人工确认。system prompt 里写”不要执行危险命令”,模型通常遵守,但这是倾向,不是保证。
对搭 Agent 意味着什么
Agent 由三部分构成:模型、一组工具、自主决定用哪个工具的能力。围绕这三部分的工作也很具体:配哪些工具,怎么写描述让模型选对,怎么管理上下文不让它溢出。工具描述写得好不好,直接决定模型的选择质量,这段文字实质上也是提示词的一部分。
第 1 章的四节拼在一起,回答了 LLM 是什么、能做什么、不能做什么。第 2 章把上面那条手工流程固化下来,也就是 Function Calling 协议:模型自己选工具、调工具、用结果作答。


