· LLM Agent 工程实践 · 第 2 / 4 讲

LLM Agent 的系统提示词工程:动态按需加载、自愈回执与反幻觉契约

系统提示词越写越长、每次 debug 都加硬编码补丁、模型工具调用频频格式错误?本文从工程落地视角,深入分析 System Prompt 的分层渐进披露、工具 Schema 动态注入、结构化错误自愈回执以及泛化反幻觉契约的设计与实战。

在上一篇《LLM Agent 的上下文管理实践:文件、图片与指令标记的协调》中,我们讨论了对话消息正文(User / Assistant / Tool 消息流)中附件折叠、多模态分流与标记隔离的设计。但除了消息历史,LLM 每次请求的头顶还顶着一个更重、更敏感的载荷——系统提示词(System Prompt)与工具定义(Tool Definitions / Schema)

随着 Agent 功能日益丰富(引入数十个 MCP、文件读写、深度搜索、知识库),系统提示词很容易陷入两难:写得太简略,模型工具调用频频格式错乱;写得太详尽,每轮对话常驻白白浪费数千 Token,且提示词迅速“过拟合”变成一堆难以维护的个案补丁

本文将从真实的生产实践出发,系统拆解如何设计一套低 Token 消耗、高鲁棒性且具备自愈能力的 System Prompt 与工具交互体系。


一、 System Prompt 的三大工程陷阱

在构建真实的 Agent 系统时,很多团队最初的 System Prompt 演化路径往往是这样的:

  1. 初始阶段只有一两句话;
  2. 加了几个工具,把完整的参数格式和大段 JSON 示例写进系统提示词;
  3. 接入了 GitHub、Notion、Google Workspace 等 MCP,把所有工具 Schema 全量常驻挂载;
  4. 发现模型在某个边角场景下犯了蠢(例如模型把 /news 当成了系统命令),于是在提示词里加了一行:“禁止使用 /news”
  5. 两个月后,System Prompt 膨胀到 4000+ Tokens,充斥着几十条互有冲突的负面规则和冗长示例。

这种演进方式会迅速踩中三大工程陷阱:

flowchart LR
    A[全量常驻挂载] -->|无差别计费| E1[Token 成本与 TTFT 爆炸]
    B[针对个案打补丁] -->|规则碎片化| E2[提示词过拟合 无法泛化]
    C[长文本规则堆砌] -->|注意力稀释| E3[关键指令被忽略 格式频错]

1.1 常驻 Token 账单与延迟放大

LLM API 的计费是按请求的所有输入 Token 计算的。如果你的 System Prompt + 全量 Tool Schema 常驻占用了 3000 Tokens,那么用户即便只是简单问一句“你好”,也会产生 3000 Tokens 的输入计费;在一个 10 轮的普通日常对话中,仅仅是系统提示词本身就被重复传输和计费了 3 万 Tokens。更致命的是,过长的 System Prompt 会直接拖慢首字延迟(TTFT),让响应变得笨重。

1.2 针对单点问题的“提示词过拟合(Overfitting)”

当模型在测试中把某个未实现的命令当成内置命令时,直觉上的修法往往是加一条专门的负向约束:

❌ 反模式补丁:
- 严格禁止使用 /news 和 /wiki 命令!
- 不要把 set_text、str_replace 当作编辑操作名!

这种补丁是典型的过拟合:它只防住了 /news,模型下次可能还会编造 /calc/weather;它只防住了 str_replace,模型下次还会造出 insert_text。规则越堆越多,不仅破坏了提示词的一致性,还无法真正解决同类问题。

1.3 注意力稀释与“粉色大象效应”

大量研究与实践表明,大模型对长上下文中的多重否定指令(“不要做 A,不要做 B,千万不要做 C”)极其脆弱。连续的负面穷举不仅会分散模型的注意力权重,还容易让模型更关注被禁用的词汇。


二、 双轨制架构:三层渐进式披露(Progressive Disclosure)

解决 System Prompt 膨胀与准确性矛盾的核心思想,是借鉴经典交互设计中的 渐进式披露(Progressive Disclosure)——在用户和模型不需要的时候保持极致精简,在发生错误或明确需要深入时才下发具体上下文。

我们将整个系统提示与工具协议划分为三层:

flowchart TD
    Layer1["第 1 层:全局常驻契约(SSOT)\n~300 Tokens · 纯原则性法则 · 0 具体工具细节"]
    Layer2["第 2 层:动态工具与指南懒加载\n按需激活 · 仅在工具开启/会话有对应实体时注入 Schema"]
    Layer3["第 3 层:结构化错误回执(Self-Correction)\n参数报错时返回精确 Example · 错误触发 · 0 常态开销"]

    Layer1 -->|日常轻量对话| Normal[极速低成本响应]
    Layer1 -->|开启 MCP 或附件触发| Layer2
    Layer2 -->|调用参数不合规| Layer3
    Layer3 -->|模型读取 example 字段| Retry[下一轮精准自愈重试]

2.1 第 1 层:全局常驻契约(SSOT)

全局常驻的 System Prompt 应当只占 ~300 Tokens,它是系统的顶层法典,只承载跨领域、不可动摇的行为原则:

  1. 真实性与回执一致性:没有收到工具成功的真实回执(ok: true),严禁在文本中声称文件已创建或已修改;
  2. 工具列表权威性:只能调用当前请求的 API tools 列表中真实下发的工具,严禁输出伪造的 XML/JSON 文本标记;
  3. 时钟锚点(Temporal Grounding):以最新的用户时间戳作为绝对客观的“当前时间”,防止模型退化到预训练截止期。

IMPORTANT

黄金法则:第 1 层绝对不出现任何具体工具的参数名字、类型要求或具体操作步骤。这些细节属于工具自身,不属于全局。

2.2 第 2 层:动态工具与说明的按需注入

未启用的工具和 MCP 描述,1 个 Token 都不应该出现在请求中

工具 / 能力类别激活触发条件注入内容
未授权 / 未开启的 MCP(如 Notion, GitHub, Google)用户未连接 OAuth 或侧边栏关闭0 描述,0 Schema 注入
懒工具:file_read / image_understand仅当当前会话中真实存在文件附件或历史图片动态载入 file_read Schema 及精炼调用导引
懒工具:file_edit仅当登录用户且会话内存在**持久化文件(durable fileId)**时动态载入 file_edit Schema
详细产品使用说明(Detailed Product Guide)仅当用户明确询问“怎么用/有哪些命令”时(正则判定 wantsProductUsageHelp展开详细的功能与命令手册

这种设计让绝大多数纯文本、非工具调用的日常对话,系统提示词始终保持在几百 Token 的极小负载内。


三、 规则泛化:用正向契约与 Schema 替代负向穷举

如何避免每次遇到 Bad Case 就往 System Prompt 里打硬编码补丁?核心在于:用“正向白名单契约”与“结构化 Schema 约束”彻底替换自然语言负向穷举。

3.1 案例对比:斜杠命令(Slash Commands)防幻觉

3.2 案例对比:文件编辑操作名(Op Names)防错

在支持多格式就地编辑的 file_edit 工具中,模型需要支持文本替换(replace_text)、幻灯片替换(pptx_replace_on_slide)、表格单元格设置(xlsx_set_cell)等。


四、 错误自愈回执(Self-Correction Protocol):示例应该放在哪里?

复杂的工具调用(如生成 Excel 表格、多层嵌套的文档编辑)需要示例(Example)来辅助模型理解。但把完整的 JSON 示例常驻放在工具定义或 System Prompt 中,每轮请求都在消耗 Token。

4.1 示例的绝佳位置:错误发生时的回执(Tool Result)

更好的策略是:常态只给微型语法提示,完整的结构化示例只在模型调用出错时,由 Tool 返回给模型。

sequenceDiagram
    participant User as 用户
    participant LLM as 模型
    participant Tool as Tool 运行时

    User->>LLM: "帮我建一个上月销售业绩表"
    Note over LLM: 模型尝试调用 file_write<br/>但参数漏掉了二维 rows 结构
    LLM->>Tool: call file_write(mode="spreadsheet", sheets=[{name:"S1"}])
    Tool-->>LLM: role: tool 返回结构化错误与精准 example
    Note over LLM: 模型读取 example 字段<br/>对齐数据结构重新生成参数
    LLM->>Tool: call file_write(mode="spreadsheet", sheets=[{name:"S1", rows:[["日期","金额"],[...]]}])
    Tool-->>LLM: ok: true
    LLM->>User: "已为您生成表格并保存!"

4.2 统一的全域自愈回执形状

不论是顶层 Schema 校验失败,还是深层业务逻辑校验失败(例如传了空的 sheets[].rows),工具均统一返回如下形状的 JSON:

{
  "ok": false,
  "error": "spreadsheet mode requires non-empty sheets[].rows",
  "example": {
    "mode": "spreadsheet",
    "filename": "report.xlsx",
    "sheets": [
      {
        "name": "Sheet1",
        "rows": [
          ["日期", "金额"],
          ["2026-08", 100]
        ]
      }
    ]
  }
}

4.3 动态多态示例分发(避免文本关键词误伤)

对于一个同时支持纯文本与多种 Office 格式的通用编辑工具(如 file_edit),下发哪个自愈示例应当是自适应且精准的。

这里有一个极容易被忽视的细节:不能直接对入参字符串做简单的正则模糊匹配。例如,如果用户在 Python 脚本中查找替换包含 "styleSheet""cellPhone" 的变量名,简单的 /sheet|cell/ 正则会误把代码编辑当成 Excel 表格操作,错误下发 xlsx_set_cell 示例。

健壮的示例决策应当采用结构化优先原则:

export function resolveFileEditExample(
  error: string,
  rawArguments?: string,
): Record<string, unknown> {
  // 1. 优先解析入参中的结构化 ops 操作名,避免被 text payload 中的普通单词误导
  try {
    const parsed = JSON.parse(rawArguments || '{}');
    const ops = Array.isArray(parsed?.ops) ? parsed.ops : [];
    for (const item of ops) {
      const op = String(item?.op || item?.type || '').toLowerCase();
      if (op.startsWith('xlsx_') || op === 'xlsx_set_cell') {
        return FILE_EDIT_EXAMPLES.xlsx_cell;
      }
      if (op.startsWith('pptx_') || op === 'pptx_replace_on_slide') {
        return FILE_EDIT_EXAMPLES.pptx_replace;
      }
      if (op.startsWith('docx_') || op === 'docx_set_paragraph') {
        return FILE_EDIT_EXAMPLES.docx_paragraph;
      }
    }
  } catch {
    /* 忽略 JSON 解析异常 */
  }

  // 2. 带有严格词界的错误信息兜底判定
  const err = String(error || '').toLowerCase();
  if (/\b(?:xlsx|spreadsheet)\b/.test(err)) return FILE_EDIT_EXAMPLES.xlsx_cell;
  if (/\b(?:pptx|presentation)\b/.test(err)) return FILE_EDIT_EXAMPLES.pptx_replace;
  if (/\b(?:docx|wordprocessingml)\b/.test(err)) return FILE_EDIT_EXAMPLES.docx_paragraph;

  // 3. 默认回落到通用文本替换示例
  return FILE_EDIT_EXAMPLES.text_replace;
}

五、 多模态分流与系统提示词的协同

在现代多模态 Agent 中,同一套会话系统可能接入 原生多模态视觉模型(Vision-Language Models,如 GPT-4o / Claude 3.5 Sonnet),也可能接入 纯文本推理模型(如 DeepSeek-R1 / o1)

如果在 System Prompt 中对图文能力描述不清,很容易导致两头不讨好:

正确的系统描述契约应当明确能力与工具的分工

Image understanding is a built-in product capability:
1. Vision models inspect images directly as pixels;
2. Logged-in text-only models auto-transcribe recent attachments server-side, and use the lazy image_understand tool for earlier turns.
Do not claim you cannot understand images merely because the tool is absent this turn.

六、 总结与工程检查清单

将系统提示词从“臃肿的手写便签”升级为“工业级契约架构”,建议对照以下检查清单:

graph TD
    Check1["1. 常驻 System Prompt 是否控制在 300~500 Tokens 以内?"] --> Yes1[是]
    Check2["2. 未授权/未开启的 MCP 是否实现 0 Token 零开销?"] --> Yes2[是]
    Check3["3. 复杂参数示例是否已从 System Prompt 移至错误回执(Error Example)?"] --> Yes3[是]
    Check4["4. 负向规则是否已重构为正向白名单契约?"] --> Yes4[是]
    Check5["5. 枚举型参数是否由 JSON Schema 的 enum 承载而非自然语言硬背?"] --> Yes5[是]