Prompt 设计:Agent 的系统提示词怎么写
系统解释 Agent 的 system prompt 应该包含哪些部分、为什么这些部分重要,以及常见 prompt 设计错误该如何避免。
Hex4C59 12 分钟阅读 更新于 2026-10-17
AI 与 Agent·原创·概念与设计
在前面几篇文章里,代码示例里都有一个 system 字段,每次都用几行占位文字一笔带过。这篇文章要把这件事正面讲清楚。
system prompt 是应用给模型的系统指令,用来说明角色、工具规则和处理边界。即使工具调用已经跑通,模型仍可能不知道何时搜索、失败后重试几次,或什么时候该停止。
本文面向会写简单代码、了解工具调用的读者,逐项解释这些要求怎样写进提示词。提示词只是影响行为的一部分;问题也可能来自工具、状态或模型,不能把所有失败都归因于措辞。
先给结论 #
- Agent 的 system prompt 和普通聊天 prompt 是两件不同的事。 聊天 prompt 主要定义风格和人格;Agent prompt 需要定义角色、能力边界、工具使用规则、推理格式和失败处理策略。
- Prompt 是 Agent 决策的隐性框架。 模型在每一步推理时都在这个框架内做选择。框架设计得清不清晰,直接决定了 Agent 在边界情况下的行为质量。
- 工具描述是 prompt 的一部分,而且往往比 system prompt 本身更重要。 很多开发者在 system prompt 上花了大量精力,却把工具描述写得一塌糊涂。
- 好的 prompt 不是越长越好。 过长的 prompt 会分散模型注意力,把真正重要的指令淹没在冗余内容里。每一行都应该有存在的理由。
- Prompt 需要像代码一样维护。 随着 Agent 功能的扩展和失败案例的积累,prompt 需要持续更新,而不是写完一次就不动了。
Agent Prompt 的结构 #
一个完整的 Agent system prompt 通常包含五个部分,每个部分解决不同的问题:
图 1:一个可维护的 Agent system prompt,通常由角色定义、工具使用规则、推理格式、约束与边界,以及动态上下文注入五个部分构成。
前四个部分相对稳定,构成 prompt 的骨架;最后一个部分是动态的,每轮推理前根据当前状态更新注入。
角色定义:不只是“你是一个助手” #
角色定义是 prompt 的第一句话,也是最容易写废的地方。
常见的错误写法:
你是一个有用的 AI 助手,帮助用户完成各种任务。这句话几乎没有提供任何有效信息。“有用”是废话,“各种任务”没有范围,模型从中得不到任何关于自己应该如何行动的指引。
更好的写法应该回答三个问题:
- 你在这个系统里扮演什么角色
- 你的主要职责是什么
- 你的能力边界在哪里
# 代码调试 Agent 的角色定义
ROLE = """
你是一个代码调试 Agent。你的职责是:分析用户描述的代码问题,
通过读取代码文件、执行测试、搜索相关文档,定位 bug 的根本原因,
并给出可验证的修复方案。
你只处理代码相关的问题。如果用户的请求与代码调试无关,
礼貌地说明你的专注范围,不要尝试回答超出范围的问题。
"""
# 研究助手 Agent 的角色定义
ROLE = """
你是一个研究助手 Agent。给定一个研究问题,你负责:
系统性地搜集相关资料、整理关键信息、识别不同来源之间的矛盾,
最终输出一份有数据支撑的结构化分析报告。
你只陈述能从来源中找到证据的结论。当信息不足时,
明确说明"现有资料不足以得出结论",不要推测或补充训练知识。
"""这两段角色定义都说明了职责、可用动作和边界。代码调试者需要读取代码并验证修复,研究助手则需要对照来源判断结论。重要的是要求具体、前后一致;不能仅凭放在角色定义还是约束段,就判断模型会更好地遵守。
工具使用规则:最被忽视的关键部分 #
Tool Use 那篇讲过,工具描述的质量直接影响模型的调用决策。但除了工具描述本身,system prompt 里还需要明确告诉模型工具调用的整体策略。
什么时候该调用工具 #
如果不说明触发条件,模型可能在信息已经足够时继续调用,也可能在需要文件内容或实时资料时直接猜测。规则应把“还缺什么信息”与“调用哪个工具能补上”对应起来。
TOOL_RULES = """
工具调用原则:
需要调用工具的情况:
- 任务需要实时或动态信息(当前价格、最新文档、文件内容)
- 需要执行计算或代码验证
- 需要读写外部系统(数据库、文件、API)
不需要调用工具的情况:
- 问题可以直接用已有信息回答
- 用户只是在澄清上下文或确认理解
- 工具上一步已经返回了足够的信息
判断标准:如果我在不调用工具的情况下能给出准确、完整的答案,
就直接回答,不要为了"显得认真"而调用不必要的工具。
"""工具失败时怎么办 #
这是很多 prompt 完全没有覆盖的地方,导致工具失败时模型行为完全不可预测。
TOOL_FAILURE = """
工具调用失败的处理策略:
1. 工具返回空结果:
- 分析是否是查询参数问题,调整参数重试(最多 2 次)
- 如果仍然为空,在回答里说明"未找到相关信息",不要推测
2. 工具返回错误:
- 记录错误信息,判断是否可以换用其他工具达到同样目的
- 如果无法绕过,明确告知用户工具不可用,不要继续执行
3. 工具返回了结果但结果可疑(与已知信息矛盾):
- 不要直接使用可疑结果
- 尝试用其他工具验证,或在回答里标注"该信息需要进一步确认"
核心原则:工具失败不等于任务失败。要尝试恢复,
但不要在错误信息的基础上继续推进。
"""过度调用的防止 #
模型有时会在信息已经足够时继续调用工具,这既浪费 token 也增加延迟。
STOP_CRITERIA = """
停止调用工具的时机:
当以下条件满足时,直接整合已有信息给出答案,不再调用工具:
- 已经收集到回答问题所需的全部信息
- 新的工具调用不会带来增量信息
- 已经达到合理的信息饱和点(继续搜索只会找到重复内容)
不要为了"更全面"而无限调用工具。80% 的情况下,
3-5 次有针对性的工具调用已经足够完成任务。
"""推理格式:让思考过程可见 #
ReAct 那篇展示了如何记录当前状态、动作理由和工具结果。这里约定的是应用能检查的简短动作说明,帮助开发者核对调用是否针对任务;它不代表模型内部思考的完整记录。
结构化推理格式 #
REASONING_FORMAT = """
每次决策前,按以下格式组织思考:
【当前状态】:简述目前已知的信息和完成进度
【下一步判断】:基于当前状态,判断下一步应该做什么
【行动】:如果需要调用工具,说明调用哪个工具和原因;
如果信息已足够,说明准备给出最终答案
这个格式不需要在最终回答里显示给用户,
但在执行过程中要始终保持这样的思考结构。
"""是否展示动作说明由应用决定。使用原生 function calling(结构化函数调用)时,只有工具定义中自行声明了 reasoning 参数,模型才会按该接口填写;它不是所有工具调用自动具备的字段。使用文本版 ReAct 时,可以用 Thought 标签记录动作理由,再与实际调用核对。
输出格式 #
输出格式和推理格式是两回事。输出格式规定了最终回答的结构:
OUTPUT_FORMAT = """
最终答案的格式要求:
- 直接回答问题,不要重复用户的问题
- 如果答案来自工具调用,说明信息来源
- 如果有多个步骤或要点,用清晰的结构组织(列表、小标题)
- 如果任务未能完成,说明原因和已经完成的部分
- 长度与任务复杂度匹配:简单问题给简洁答案,复杂分析给完整报告
不要在回答末尾加"如有其他问题请随时告知"之类的套话。
"""约束与边界:防御性设计 #
约束部分说明不允许的行为,以及遇到这些情况应怎样处理。提示词可以引导模型遵守,但不能代替执行模块的权限检查、参数校验或批准流程。
常见需要约束的行为:
幻觉引用:模型在工具没有返回相关信息时,从训练知识里补充内容,但表现得像是从工具里得到的一样。
"只使用工具返回的信息作为答案依据。如果工具没有返回某条信息,"
"不要从其他来源补充,即使你认为这条信息是正确的。"任务范围蔓延:用户在对话中带偏了方向,Agent 跟着偏移,忘记了原始任务。
"始终保持对原始任务目标的追踪。如果对话偏离了原始目标,"
"在完成偏离部分之前,先确认用户是否希望改变任务方向。"过度自信:在信息不足的情况下仍然给出确定性的结论。
"当现有信息不足以得出确定结论时,明确说明不确定性,"
"而不是用模糊的语言掩盖不确定性。"
"'根据目前的信息,X 的可能性更大,但需要进一步确认' 比 'X 是正确的' 更诚实。"危险操作的确认缺失:对有副作用的工具调用(删除文件、发送邮件、修改数据库)没有二次确认。
"以下操作在执行前必须向用户确认:删除或覆盖文件、"
"发送任何形式的外部通信、修改生产数据库、调用付费 API。"
"即使用户在任务描述里已经提到了这些操作,执行前也要再次确认。"动态上下文注入 #
前四个部分构成了 prompt 的静态骨架,最后一个部分是动态的:每轮推理前,把当前任务状态、用户信息、执行进度等注入到 prompt 里。
上下文与记忆 那篇里详细讲过任务状态对象的设计,这里重点说注入的格式和位置。
注入位置 #
动态上下文有两个可选的注入位置:
def build_system_prompt(static_prompt: str, task_state: TaskState) -> str:
# 方式一:追加在 system prompt 末尾
return static_prompt + "\n\n---\n" + task_state.to_context_string()
def build_messages(static_prompt: str, task_state: TaskState,
history: list, user_input: str) -> list:
# 方式二:作为独立的 user 消息注入
return [
{"role": "system", "content": static_prompt},
{"role": "user", "content": task_state.to_context_string()},
{"role": "assistant", "content": "已了解当前任务状态,继续执行。"},
*history[-10:],
{"role": "user", "content": user_input}
]两种写法都是在调用前重新构造本轮输入。第一种把状态与静态规则拼成一条系统消息;第二种把状态放在独立消息里,便于区分任务数据与长期规则。无论采用哪一种,都要控制总长度,并确保状态确实是最新的。
选择应看所用接口、指令层级和评测结果,不能仅用固定 token 数决定位置。示例中的“已了解”消息也是应用添加的文本,不代表模型已经验证了状态。
注入格式 #
任务状态的注入格式要简洁、结构化,避免冗长的自然语言描述:
def to_context_string(self) -> str:
return f"""
## 当前任务状态
**目标**:{self.goal}
**当前步骤**:{self.current_focus}
**已完成**:{len(self.completed_subtasks)} 个步骤
**关键发现**:
{chr(10).join(f"- {f}" for f in self.key_findings[-5:])}
**已确定的决策**:
{chr(10).join(f"- {d}" for d in self.decisions_made[-3:])}
""".strip()注意 key_findings[-5:] 和 decisions_made[-3:]:示例只保留最近几条来控制长度。但“较新”不等于“更重要”,原始目标、关键约束和仍有效的早期结论也应按当前任务需要保留;其他历史再按需检索。
完整示例:一个代码调试 Agent 的 Prompt #
把上面所有部分组合起来,看一个完整的例子:
SYSTEM_PROMPT = """
你是一个代码调试 Agent。你的职责是:分析用户描述的代码问题,
通过读取代码文件、执行测试、搜索错误文档,定位 bug 的根本原因,
并给出可验证的修复方案。
你只处理代码相关的问题。对于与代码调试无关的请求,
礼貌说明专注范围,不要尝试回答。
---
## 工具使用原则
需要调用工具时:需要查看文件内容、运行代码验证、搜索错误文档。
不需要调用工具时:问题是通用的语言/框架知识,不依赖具体代码内容。
工具失败处理:
- 读取文件失败 → 请用户确认路径是否正确
- 执行代码报错 → 分析错误信息,这通常就是 bug 的线索
- 搜索无结果 → 换关键词重试一次,仍无结果则说明
---
## 推理要求
每次决策前在内部明确:当前已知什么 → 还缺少什么 → 下一步做什么。
不要在没有读取代码文件的情况下猜测 bug 原因。
不要在没有执行验证的情况下声称修复方案一定有效。
---
## 约束
- 只引用从工具中获取的代码内容,不要补充假设的代码片段
- 修改文件前必须向用户展示修改内容并确认
- 如果 bug 原因不明确,说明"目前无法确定根本原因",
给出排查方向,而不是给出不确定的猜测
---
## 输出格式
调试报告结构:
1. 问题定位:bug 在哪里,是什么
2. 根本原因:为什么会出现这个 bug
3. 修复方案:具体的代码修改(附修改前后对比)
4. 验证方式:如何确认修复有效
如果无法完成调试,说明:已完成的分析、遇到的阻碍、建议的下一步。
"""这个示例把角色、调用条件、失败策略、约束和输出格式放到一起。它们分别告诉模型要做什么、缺信息时怎样查、失败后怎样处理、哪些动作需批准,以及如何交付结果。提示词中的要求仍需通过实际执行和评测确认,不能把“写了规则”当成“已经遵守”。
前面的调用次数、比例和后面的版本评测分数都只用于展示提示词或记录格式,不是实测结论。应按任务设置上限,并记录实际测量。
常见的 Prompt 错误 #
错误一:用“请”和“尽量”代替明确指令 #
# 错误:模糊的期望
"请尽量在调用工具之前先思考是否真的需要。"
# 正确:明确的规则
"只有在以下情况才调用工具:[具体条件]。其他情况直接回答。"关键不在是否用了“请”,而在有没有可判断的条件。“尽量少调用”没有说明何时能调用、何时必须停止;明确条件后,执行程序和评测也才有依据检查。
错误二:规则之间互相矛盾 #
# 矛盾:同时要求快和全
"尽量减少工具调用次数,提高响应速度。"
"确保信息全面,不要遗漏任何重要细节。"当这两条规则冲突时(快速 vs 全面),模型会随机选择,行为不可预测。需要明确优先级:
"在保证核心问题有答案的前提下,控制工具调用在 5 次以内。
如果核心问题需要更多工具调用,优先完成核心问题,
再决定是否补充周边信息。"错误三:Prompt 越写越长,关键规则被淹没 #
随着失败案例积累,prompt 可能加入许多只针对特定情境的补丁。重复或矛盾的规则会让当前要求更难辨认,但不能据此断言每加一条规则,遵守率就按固定幅度下降。
解决方法不是无限堆砌,而是定期重构:把功能相同的规则合并,把只在特定场景才需要的规则移到动态注入里,保持 prompt 骨架的精简。
错误四:忘记测试边界行为 #
写完 prompt 之后,大多数人只测试“正常路径”——用户提了一个清晰的问题,Agent 顺利完成了。但真正决定 prompt 质量的,是边界情况下的行为:
- 用户给了一个超出 Agent 能力范围的任务,怎么办?
- 工具连续失败三次,还要继续吗?
- 用户中途改变了任务目标,Agent 怎么响应?
这些边界情况应该明确写进测试用例(参考评测篇),而不只是靠直觉判断。
Prompt 版本管理 #
把 prompt 像代码一样管理,而不是散落在各个文件里的字符串。
# prompts/debug_agent/v3.py
METADATA = {
"version": "3.2.1",
"last_updated": "2026-03-23",
"changelog": [
"3.2.1: 在工具失败处理里加了'不要继续在错误信息基础上执行'",
"3.2.0: 重构输出格式,加入修改前后对比要求",
"3.1.0: 加入范围约束,处理非代码相关请求",
],
"eval_scores": {
"task_completion": 0.83,
"tool_precision": 0.91,
"stability": 0.87,
}
}
SYSTEM_PROMPT = """..."""每次修改 prompt 都记录 changelog 和对应的评测分数。这样有两个好处:当某次修改导致指标下降时,可以快速定位是哪个版本引入了问题;当团队成员看到当前 prompt 时,能理解每条规则背后的来历,而不是把它当成不可动的黑盒。
总结 #
Agent 的 system prompt 不是一段介绍文字,而是一套决策框架。它定义了 Agent 在每个推理时刻的行为边界:什么时候调工具、怎么处理失败、如何组织思考、哪些事情不能做。
一个好的 Agent prompt 应该:
- 角色定义清晰,包含能力边界,而不只是笼统的“有用的助手”
- 工具调用规则明确,覆盖触发条件、失败处理和停止时机
- 记录可检查的动作理由,并与实际请求和结果核对
- 写清常见失败的处理规则,并由执行层检查权限与参数
- 动态上下文注入精简,只放当前决策需要的信息
写完不是终点。每次评测发现新的失败模式,都应该问一个问题:这个失败是因为 prompt 没有覆盖这种情况吗?如果是,就更新 prompt,记录变更,重新跑评测。
Prompt 和工具描述、代码逻辑一样,是需要持续维护的工程产物。
下一篇:Reflection——Agent 如何在执行完成后审视自己的输出,发现并纠正错误
一起讨论
选中正文中的文字,可以针对这段内容发起讨论。
正在读取讨论…
在 GitHub 查看与管理讨论 ↗