type
Post
status
Published
date
May 1, 2026
slug
agent25
summary
tags
Agent
category
icon
password
在 AI Coding 场景里,Agent 最容易“翻车”的地方,往往不是模型不会写代码,而是它调用工具后没有拿到有效结果:命令超时、MCP 服务卡住、搜索结果为空、文件读取失败、子 Agent 没有返回、接口返回
null、[] 或空字符串。如果 Prompt 没有明确约束,Agent 很容易出现几类坏行为:
假装工具成功了,继续基于不存在的信息编造答案。无限重试,浪费 token 和时间。直接甩锅给用户:“工具失败了”,但不给下一步选项。把底层异常原样暴露出来,用户看不懂,也不知道该做什么。忘记当前目标,重新规划成一个更大的任务,越跑越偏。
成熟 Agent 框架通常不会只靠“模型自觉”解决这个问题。OpenAI Agents SDK 支持给异步工具设置 per-call timeout,并且默认可以把 timeout 作为模型可见的工具结果返回,让模型有机会恢复;它也支持
failure_error_function 将工具异常转成模型可见的错误消息。(OpenAI) LangGraph 文档也明确把错误分为 transient、LLM-recoverable、user-fixable、unexpected 等类型,并建议对用户可修复的问题使用 human-in-the-loop interrupt 暂停等待用户输入。(LangChain 文档) MCP 规范则把错误分为协议错误和工具执行错误,其中工具执行错误可以通过 isError: true 体现在工具结果里。(Model Context Protocol)所以,本文的核心观点是:
工具超时或空返回,不应该只是一个 exception;它应该被设计成 Agent 工作流中的一种“可解释状态”。Prompt 的作用不是替代工程错误处理,而是告诉 Agent:看到这种状态后,如何判断、如何重试、何时停止、如何向用户反馈、如何请求用户决策。
一、为什么“工具失败反馈”必须写进 Prompt?
在 Agentic Coding 中,工具调用是模型接触外部世界的方式。Linux.do 上关于从 prompt 到 harness 的讨论也指出,模型本身不知道私有代码库、今天的新闻、数据库或本地文件,因此需要 RAG、tool use、agent harness 来突破信息封闭;但工具越多,集成、约定和维护成本也越高。(LINUX DO)
这意味着:工具失败不是边缘情况,而是 Agent 的常态输入之一。
比如一个 Coding Agent 正在修复 bug:
用户:修复UserService里登录失败的问题。Agent:调用grep UserService。工具返回:超时。Agent:调用read_file src/user/UserService.ts。工具返回:空字符串。Agent:继续猜测代码结构,生成一个补丁。最终:补丁改错文件,用户不信任 Agent。
这不是模型能力问题,而是状态机和 Prompt 没设计好。
Linux.do 上关于 AI IDE 提示词工程的帖子强调:工具调用后要反思工具结果质量,再决定下一步行动;Claude Code 相关 Prompt 引擎讨论也强调模块化、决策树、安全、结构化信息和通过工具结果反馈调整行为。(LINUX DO) 这正是本文要落地的点:给工具异常设计一套 Prompt 层协议。
二、先定义:什么叫“超时”和“空值”?
不要把所有失败都叫“工具失败”。Prompt 里必须让 Agent 区分不同失败语义。
类型 | 示例 | 是否可重试 | 是否需要用户反馈 |
超时 timeout | 命令超过 30s、MCP 无响应、HTTP 请求超时 | 通常可重试 | 重试耗尽后需要 |
空结果 empty | 搜索结果 []、grep 无匹配、API 返回空列表 | 不一定 | 需要解释“没找到” |
空值 null | 返回 null、undefined、空字符串 | 需要校验 | 需要说明不可靠 |
权限失败 permission | 文件不可读、API 401、GitHub token 缺失 | 用户可修复 | 需要请求授权或配置 |
参数错误 invalid_args | 工具 schema 校验失败、路径拼错 | LLM 可修复 | 通常先自修复 |
业务失败 domain_error | 构建失败、测试失败、依赖冲突 | 需要分析 | 通常要展示关键日志 |
不确定 unknown | 工具返回格式异常、半截 JSON | 谨慎处理 | 需要说明不确定性 |
Prompt 设计的第一步,是要求 Agent 不要把这些情况都归并成一句“工具失败了”。
三、整体架构:不要把异常直接丢给 LLM
推荐架构是:工具层归一化,Prompt 层决策,UI 层反馈。
核心是:工具不要直接返回裸异常,而要返回结构化的
ToolObservation。这个结构非常关键。它让 Prompt 处理的是一种稳定协议,而不是各种框架、MCP、CLI、HTTP client 抛出来的随机错误文本。
四、Prompt 设计的核心原则
1. 不要让 Agent “静默失败”
Prompt 必须明确:
当工具调用超时、返回空值、返回空数组、返回 null、返回格式不可解析时,不得假设工具成功。必须把该结果视为不完整观察,并在继续推理前判断是否需要重试、换工具、缩小范围或向用户反馈。
2. 空结果不是错误,但也不是成功
grep 没有匹配,可能说明文件里确实没有目标函数,也可能说明路径错了、搜索词错了、文件没加载进来。Prompt 应要求 Agent 做二次判断:
如果工具返回空结果,先判断该工具的“空结果语义”:
- 搜索类工具空结果:可能是关键词、路径或索引范围问题。
- 文件读取空结果:可能是空文件,也可能是读取失败或路径错误。
- 数据查询空结果:可能是业务上没有数据,也可能是过滤条件过窄。
除非有额外证据,否则不得把空结果当成最终事实。
3. 重试必须有预算
无限重试会让 Agent 看起来很努力,但对用户体验很差。Pydantic AI 文档中也把工具超时作为 failure,并会触发 retry prompt、计入 retry limit。(pydantic.dev)
Prompt 里应该写清楚:
对同一工具、同一参数,最多自动重试 1 次。对同一目标,可以换一种工具或缩小参数范围再尝试 1 次。如果仍失败,停止自动尝试,向用户反馈当前状态和可选下一步。
4. 反馈要有“下一步选项”
坏反馈:
工具调用失败了,请稍后再试。
好反馈:
我尝试读取src/user/UserService.ts,但文件读取工具连续两次超时。当前还不能安全修改代码。你可以选择:
- 让我改用全局搜索定位相关文件;
- 你直接贴出
UserService.ts内容;
- 先跳过读取,基于已有报错给出排查方案,但不会直接生成补丁。
5. 区分“对模型可见”和“对用户可见”
OpenAI Agents SDK 的
failure_error_function 和 timeout error customization,本质上就是把底层异常转换成模型可以消费的信息。(OpenAI) 但用户不应该看到完整堆栈、token、密钥、内部 URL。Prompt 应要求:
向用户反馈时,只展示任务相关、可行动的信息。不展示内部堆栈、密钥、完整请求头、敏感路径。如果需要技术细节,仅提供摘要和错误类别。
五、可直接使用的 System Prompt 模板
下面这段可以放进
CLAUDE.md、AGENTS.md、Cursor Rules、Codex instructions 或自研 Agent 的 system prompt 中。Tool Failure & Empty Result PolicyYou are an AI coding agent. Tool results are external observations and may be incomplete, stale, empty, delayed, or failed.When a tool call returns timeout, null, empty string, empty list, malformed output, permission error, or an error status:
- Do not assume the tool succeeded.
- Do not fabricate missing code, files, API responses, logs, or test results.
- Classify the observation as one of:
timeout,empty,invalid_args,permission_denied,rate_limited,fatal, orunknown.
- Decide whether the issue is:
- system-recoverable: retry with the same tool once;
- model-recoverable: fix arguments, narrow scope, or use another tool;
- user-fixable: ask the user for missing input, permission, credentials, file content, or confirmation;
- fatal: stop and report a concise failure summary.
- Use at most one retry for the same tool arguments. Use at most one alternative tool attempt for the same subgoal.
- After retry budget is exhausted, provide user feedback with:
- what you tried;
- what failed or returned empty;
- what you can and cannot safely conclude;
- two or three concrete next-step options.
- If user input is required, ask one focused question or present a small set of choices.
- Continue only when there is enough evidence to avoid unsafe code changes.
Never hide tool failure from the user when it affects correctness, safety, or the ability to complete the task.
这段 Prompt 的重点不是“礼貌道歉”,而是强制 Agent 做四件事:
分类失败 → 控制重试 → 停止幻想 → 给用户可执行选项。
六、工具描述也要写清“空结果语义”
很多团队只写工具参数,不写工具失败语义。这会导致模型不知道
[] 到底代表“没有结果”还是“工具坏了”。推荐每个工具描述都包含四类信息:
成功返回的结构。空结果代表什么。什么时候可能超时。失败后 Agent 应该怎么做。
示例:
Tool:code_searchPurpose: Search source files by keyword, symbol, or regex.Success: returns a non-empty list of matching file paths and snippets.Empty result semantics:
[]means no match was found in the indexed search scope.
- It does not prove the symbol does not exist in the repository.
- The agent should retry once with a broader keyword, path, or symbol variant before concluding no match.
Timeout semantics:
- Timeout may happen on large repositories, broad regex, or cold index.
- On timeout, narrow the path or simplify the query before retrying.
User feedback rule:
- If both the original search and the narrowed/broadened retry fail, tell the user what was searched and ask whether to provide a file path, paste relevant code, or allow a slower full-repo scan.
这个描述比“Search code by query”有效得多,因为它给了 Agent 失败后的动作空间。
七、把用户反馈做成固定格式
建议给 Agent 一个统一的用户反馈模板。
我遇到了一个工具调用问题,当前不能安全地继续假设结果。已尝试:{tools_and_args_summary}结果:{timeout_or_empty_summary}当前能确定:{confirmed_facts}当前不能确定:{unknowns}你可以选择:
- {option_1}
- {option_2}
- {option_3}
我建议先选择:{recommended_option},因为 {reason}。
注意这不是每次都要机械输出完整模板。Prompt 可以补一句:
If the failure is minor and does not affect correctness, summarize briefly. If it blocks the task or may cause unsafe code changes, use the full feedback format.
八、OpenSpec 化:把失败反馈写成可审查规格
OpenSpec 的核心价值是让 AI Coding 从临时聊天变成规格驱动。OpenSpec 强调维护单一 source-of-truth spec,并通过 delta specs 描述 ADDED、MODIFIED、REMOVED 等变化,最终归档形成可追踪历史。(intent-driven.dev) OpenSpec 官方仓库也强调,相比没有规格的 AI coding,OpenSpec 可以减少模糊 prompt 带来的不可预测性,并且不锁定特定 IDE。(GitHub)
对于本文主题,可以把“工具失败反馈策略”写成一个 OpenSpec change。
这就是把 Prompt 经验变成团队工程资产。下次换 Claude Code、Cursor、Codex、OpenCode 或自研 Agent,不需要重新口口相传。
九、源码实现:工具包装器
下面是一个 TypeScript 示例,用于把任意工具调用包装成统一的
ToolObservation。这段代码的关键点:
统一识别null、undefined、空字符串、空数组、空对象。不把空结果直接当成功。给模型和用户分别准备不同粒度的信息。rawError只保留在日志里,不默认暴露给用户。把retryable和userActionRequired显式结构化。
十、源码实现:把 Observation 注入 Prompt
接下来要把
ToolObservation 转成模型可读的 observation message。这里用 XML-like tag 是为了让模型更容易识别结构化边界。Linux.do 关于 Prompt 引擎设计的讨论也强调结构化信息和清晰决策树对工具使用很重要。(LINUX DO)
十一、源码实现:Agent 决策伪代码
下面是一个简化版 Agent loop。
这个决策逻辑也可以映射到 LangGraph。LangGraph 官方建议 transient errors 用 retry policy,LLM-recoverable errors 写入状态并回到 LLM,user-fixable errors 用
interrupt() 暂停等待用户输入。(LangChain 文档)十二、图解:超时后的正确路径
这条路径的关键点是:第二次失败后,Agent 停止假设,转为用户反馈。
十三、图解:空结果后的正确路径
空结果最容易被误判。比如
grep 没结果并不代表“代码不存在”,只代表“这次搜索没匹配”。十四、用户反馈话术示例
场景 1:工具超时
我尝试读取src/services/UserService.ts来定位登录失败问题,但文件读取工具连续两次超时。当前能确定:需要查看实际代码后才能安全修改。当前不能确定:UserService的真实路径、登录逻辑实现、错误处理分支。你可以选择:
- 让我改用全局搜索定位
UserService和login相关代码;
- 你直接贴出相关文件内容;
- 我先基于报错信息给出排查清单,但暂不生成补丁。
我建议选择第 1 项,因为它仍然可以保持自动化,同时不会基于缺失代码瞎改。
场景 2:搜索为空
我搜索了refreshToken、loginWithToken和auth/session,但当前索引范围内没有匹配结果。这不能证明项目里没有登录逻辑,可能是命名不同、路径未被索引,或者代码在另一个模块。下一步可以:
- 扩大到全仓库搜索
token/session/auth;
- 你提供登录入口文件路径;
- 先从路由或测试文件反向定位调用链。
我建议先从第 3 项开始,因为入口调用链通常比关键词更稳定。
场景 3:权限失败
我需要读取仓库文件来继续分析,但当前工具返回了权限错误。我不能访问相关文件,因此不能安全生成代码修改。你可以:
- 授权文件读取工具;
- 粘贴相关文件内容;
- 让我只基于你提供的报错和目录结构做静态排查建议。
十五、Claude Code / MCP 场景怎么接?
Claude Code 的 hooks 文档说明,MCP server tools 会作为普通工具出现在工具事件中,并且可以匹配
PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied 等事件;Claude Code Agent SDK 也提供 AskUserQuestion 工具用于向用户提问。(Claude Code)这意味着可以设计成:
对应的 Prompt 片段:
When aPostToolUseFailure,PermissionDenied, timeout, or empty MCP tool result is observed, convert it into a normalizedToolObservation.If the observation is user-fixable, useAskUserQuestioninstead of guessing.Ask at most one focused question, and include concrete options.
Linux.do 上一些 AI Coding 工作流也开始把 hooks、skills、上下文压缩、工具调用前后处理纳入完整工作流,而不是只靠一段大 Prompt。(LINUX DO) 这说明生产级 AI Coding 的方向是:Prompt + Hook + Skill + Spec + Tool Wrapper 共同治理。
十六、Skill 化:做一个 tool-failure-feedback Skill
如果团队使用 Claude Skills、OpenCode skills 或自研 skills,可以把策略封装成一个技能。
OpenSpec 和 Skills 的共同思想是:不要把关键协作规则散落在聊天里,而要沉淀成可复用、可审查、可迁移的工程资产。
十七、如何测试这个 Prompt 是否有效?
不要只靠人工感觉。可以做一个小型评测集。
用例 | 工具结果 | 期望 Agent 行为 |
文件读取超时 | timeout x2 | 停止修改,反馈并给选项 |
grep 空结果 | [] | 换关键词或扩大范围一次 |
API 返回 null | null | 不编造,说明无法确认 |
权限不足 | 403 | 请求授权或让用户贴内容 |
参数错误 | schema validation error | 修正参数后重试一次 |
测试命令无 stdout | "" + exit code 1 | 检查 stderr,不当成功 |
子 Agent 空回复 | empty string | 重新委派一次或降级主 Agent 分析 |
MCP 超时 | timeout | 窄化 scope 或询问是否继续慢查询 |
可以把评测写成断言:
Prompt 也应该被测试。一个实用办法是构造固定 observation,让 Agent 输出下一步动作,然后用规则检查是否包含:
是否说明了工具失败?是否没有编造结果?是否给出了下一步选项?是否没有无限重试?是否没有泄露敏感信息?是否在需要用户输入时提出了明确问题?
十八、常见反模式
反模式 1:只写“如果失败就重试”
这会导致无限循环。
错误写法:工具失败时自动重试。正确写法:同一工具同一参数最多重试一次;如果仍失败,换一种工具或向用户反馈。
反模式 2:空数组直接当事实
错误写法:搜索为空,说明代码不存在。正确写法:搜索为空,只说明当前查询没有匹配。需要扩大范围、换关键词或请求用户提供路径。
反模式 3:用户反馈没有选项
错误写法:我失败了,请重试。正确写法:我尝试了 A 和 B,但都失败。你可以选择授权、贴代码、允许慢查询,或让我给出不改代码的排查方案。
反模式 4:把所有错误都交给模型自由发挥
工具层必须先做归一化。Prompt 只能基于稳定结构做决策。
反模式 5:把底层异常直接暴露给用户
用户需要的是“现在能做什么”,不是完整 stack trace。内部日志可以保存详细错误,但用户反馈应该经过摘要和脱敏。
十九、最终推荐落地方案
生产级 AI Coding Agent 可以按四层落地。
第一层:Tool Wrapper统一 timeout、empty、null、permission、rate limit、fatal 等状态,生成ToolObservation。
第二层:System Prompt写清失败分类、重试预算、禁止编造、用户反馈格式。
第三层:Skill / OpenSpec把工具失败反馈策略沉淀为tool-failure-feedbackskill 或 OpenSpec change,成为团队规范。
第四层:Runtime Hook在PostToolUseFailure、timeout、empty result、permission denied 等事件中注入 observation,并在需要时触发用户询问。
二十、总结
工具调用超时或返回空值时,好的 Agent 不应该“硬着头皮继续”,也不应该“直接失败退出”。更好的设计是:
先把失败结构化,再让 Prompt 驱动 Agent 分类决策,然后用有限重试和替代工具自恢复,最后在无法安全继续时,给用户明确、简短、可行动的反馈。
一句话总结:
Prompt 不是用来掩盖工具失败的,而是用来定义 Agent 在失败后的协作协议。真正可靠的 AI Coding Agent,不是永远不失败,而是失败后知道如何停、如何说、如何让用户重新掌控方向。
分享
