提示词与工具设计

专栏:Agent 工程 · 第 5 / 18 篇
Agent提示词工具设计

上一篇的 100 行 agent 能跑,但你会立刻遇到两个问题:模型不听话(提示词)、模型不肯用工具(工具描述)。这篇用两个系统的真实源码回答。

:::info 学习目标 完成本篇后你能够:为 agent 设计分层且确定性的系统提示词;按「何时用/何时不用/边界行为」三要素重写工具描述;用 A/B 实验量化描述改动的收益。 前置:第 4 篇的 mini-agent 可用。预计时长:60 分钟。 :::

:::note 本章术语速查(新手建议先读)

  • System Prompt(系统提示词):对话开始前给模型的”人设与规则说明书”,用户看不到但影响每次回答。
  • 工具描述(Tool Description):模型决定用不用某个工具的唯一依据——相当于写给模型的”工具使用说明书”。
  • JSON Schema:描述”参数长什么样”的标准格式(类型、必填项),模型按它生成参数,你按它校验参数。
  • 稀疏 order:dsh 给提示词片段分配序号的技巧——用 -1000、0、1000 这种留有大空隙的数字,新片段插中间不影响别人。
  • KV 缓存友好:提示词如果每次都一样,模型服务方能复用之前的计算(省钱省时间)。所以提示词要稳定。 :::

系统提示词:不是一段话,是一个组装系统

小项目里 system prompt 是写死的字符串。但生产级 agent 的提示词来自几十个互不相识的来源:身份、安全策略、工具说明、用户配置、项目指令……必须有一套机制把它们合成稳定、可复现的文本

dsh 的答案:注册表 + 稀疏 order。 ctx.systemPrompt 服务接受插件注册片段(section)、动态上下文(context)、工具 schema(tools)、变量(variable),然后按集中分配的具名稀疏 order 排序:

// packages/core/system-prompt/src/index.ts:121-152(节选)
const SECTION_ORDERS = {
  HARNESS_IDENTITY: -1000,   // "You are an AI agent powered by DeepSeek Harness."
  DEPLOYMENT_PERSONA: 0,     // 部署 persona(可配置)
  PLAN_POLICY: 500,
  TOOL_BASH: 1000,
  TOOL_READ: 1100,
  TOOL_WEB_SEARCH: 2000,
  TOOL_SUBAGENT: 2800,
  STRUCTURED_OUTPUT: 9900,
} as const

三个必须抄的设计:数字之间留大量空隙——新插件插进任何缝隙都不动别人;同号按名称代码单元序——排序完全确定性,注册顺序不影响最终文本(第 6 篇会看到这是缓存命中的前提);动态内容不进 system——时间、工作区状态等走独立的 context 注册,变化时作为消息追加进历史尾部,system 逐字节稳定。

Codex 的答案:base instructions + AGENTS.md 级联。 Codex 的系统提示词由内嵌的 base instructions 与项目指令叠加而成,core/src/agents_md.rs 实现了 AGENTS.md 的发现逻辑:从项目根(默认以 .git 为根标记)向下逐级拼接所有 AGENTS.md——仓库根的放全局规范,子目录的放局部规范,越近越具体。

两个系统的共同结论:系统提示词 = 确定的框架 + 分层的项目指令,且层级之间有明确的优先序。

工具描述:面向模型的文档

工具描述不是注释,是模型决定用不用这个工具的唯一依据。Anthropic 的工具设计指南与社区共识三条核心:

  1. 说清”何时用”也要说清”何时不用”——重叠的工具比没有更糟;
  2. 参数描述写边界行为(单位、默认值、截断行为);
  3. 工具少而精——超过二三十个用检索或子 agent 收缩。

看 dsh 里 read_file 工具的真实描述怎么落实这些(tool-fs 经 system-prompt 自动注册,schema 结构即文档):

// dsh 工具描述的构成要素:用途 + 反模式 + 边界行为
name: 'read',
description: '读取文件内容。用于查看具体文件。' +
  '按内容搜索请用 grep 工具;列目录请用 glob 工具。' +   // ← 反模式指引
  '大文件只返回前 readLimit 行,超出部分提示续读。'      // ← 边界行为

以及 Codex 的对应物:exec_command 的参数里连取输出的节奏都模型化了——yield_time_ms(多少毫秒先返回一批输出)与 max_output_tokens(最多带多少输出),让模型学会”启动服务器时轮询、跑测试时等足量输出”。工具的参数设计本身就在教模型正确的使用姿势。

dsh 的 defineTool 还在执行前自动校验参数(类型/必填/联合分支),校验失败以 INVALID_ARGS 结构化错误回流——模型看到错误描述就能自行修正重试,不炸轮次。这是第 4 篇”错误回流”原则的精细化。

组装与把关的时序

图表(prompts-and-tool-design.md)

框架对照:LangGraph 生态里的同构物

from langchain_core.tools import tool
from pydantic import BaseModel, Field

class GrepArgs(BaseModel):
    pattern: str = Field(description="正则表达式")
    glob: str = Field(default="**/*", description="文件过滤,如 *.py")

@tool(args_schema=GrepArgs)
def grep(pattern: str, glob: str = "**/*") -> str:
    """按正则搜索代码内容。

    用于:按内容定位代码。
    不要用于:按文件名查找(用 glob 工具)、读整文件(用 read 工具)。
    """
    ...

docstring 即工具描述、args_schema 即参数文档与校验——与 dsh 的 defineTool、Codex 的 ToolSpec 同构。

工具描述的反例集

描述写得差的三种典型(都在真实项目里见过):

✗ 「读取文件」                      —— 太泛:模型不知道该在什么场景用
✗ 「用 fread64 读取文件并返回内容」  —— 泄漏实现细节,模型被无关概念干扰
✗ 「读取文件(当用户想看文件时用)」 —— 没说反模式:模型会在该用 grep 的场景用它

正例三要素(第 9 篇的 MiniRAG 工具直接套用):用途场景 + 反模式指引 + 边界行为。另外两个源码级细节:dsh 的 defineTool 在执行前自动校验参数(失败返回 INVALID_ARGS 让模型自纠,不炸轮次);Codex 的 exec_command输出节奏参数化yield_time_ms / max_output_tokens)——工具的参数设计本身就在教模型正确的使用姿势。

提示词与工具的隐性契约

还有一个跨模块的细节:工具 schema 会进入系统提示词的组装(dsh 的 assembly.tools 同时是请求的 tools 字段)。这意味着:改一个工具的描述 = 改请求前缀 = KV 缓存失效(第 6 篇)+ 该工具的行为变化。两个系统因此都把工具顺序做成确定性的(dsh 的 toolOrder 配置、未列出者字典序插到标记位)——注册顺序的抖动不允许影响最终文本。

常见踩坑

  1. 描述与行为不符:描述说”返回全部匹配”,实现截断到 50 条——模型基于错误预期规划后续调用。描述必须如实到边界行为;
  2. 工具越多越强:重叠工具让模型选错率上升,先合并再考虑新增;
  3. 提示词里写死示例数据:示例会被模型当成”每次都一样的事实”,动态示例请放消息而非 system;
  4. 改提示词不做 A/B:感觉变好不是变好——用第 13 篇的评测集说话。

本篇产出(带验收标准)

  1. 给第 4 篇的 mini-agent 重写系统提示词与工具描述,逐条自查:何时用?何时不用?参数边界?反模式指引?
  2. 做 A/B 实验:同一批任务,改前 vs 改后统计工具调用次数与任务成功率——你会看到工具描述的 ROI 高得惊人;
  3. 有余力的话读 codex-rs/core/src/agents_md.rs,然后给你的项目写一份 AGENTS.md。

← 返回文章列表