上下文工程小项目学习 序言部分这是在AI指导下进行的一个小小项目所以让AI的回答·里涉及一些企业级目的是初步了解吧不过准确像不能保证目的是为了巩固昨天前两天上下文工程的学习涉及的方面可能小而浅。代码就在这里https://github.com/sensanyou/agent-context-lab.git首先今天最有价值的发现是Agent 能否可靠运行不只取决于大模型够不够聪明还取决于程序怎样管理上下文、执行工具、处理异常以及保存对话状态。一、今天遇到的经典问题这四个问题背后其实是同一个原则发给模型的上下文不仅要足够短还必须足够完整、可信并且符合模型 API 的消息协议。二、如何保护具有删除文件功能的 Agent这是一个典型的工具安全问题。假设你开发了一个文件管理 Agent用户说帮我清理项目目录里不需要的文件。模型可能选择调用delete_file工具但不能因为模型选择了工具就让程序直接删除文件。1. 必须把模型的决定和程序的授权分开这里要区分三个概念模型决策模型认为应该调用什么工具。程序授权程序判断这次操作是否被允许。实际执行通过文件系统 API 真正实施操作。模型只能提出动作不能成为最终的安全裁判。2. 初学项目与企业级系统应该怎么做安全措施学习项目企业级系统路径检查拒绝越界路径规范化路径、符号链接检查、处理并发路径变化操作权限只允许指定目录基于用户身份、角色和资源的细粒度授权删除确认删除前要求确认高风险操作审批、策略引擎、可撤销工作流影响范围一次只操作一个文件批量操作预览、数量与大小限制、配额审计打印操作记录持久化审计日志、操作者、参数、结果和关联请求 ID恢复尽量移动到回收区备份、保留策略、恢复演练和灾难恢复机制企业系统还需要防范路径穿越、符号链接绕过、TOCTOU检查和使用之间的状态变化等问题。仅仅检查字符串是否以某个目录开头是不够的。3. 一条关键原则危险工具要遵循最小权限原则并在执行前做服务端授权。对不可逆删除、批量删除等操作通常应先展示计划和影响范围再由用户明确确认。不能把模型生成的“用户已同意”当成真实授权。三、怎么看模型是否调用工具而不是自己直接回答这是 Agent 开发中最基础、也最容易误解的地方。在你的core.py中关键代码是message response.choices[0].message if message.tool_calls: # 模型请求调用工具 ... else: # 模型没有请求调用工具 self.messages working_messages return message.content or 1. 判断依据是什么当你使用支持工具调用的聊天 API 时模型的响应通常包含两种不同的情况。情况 A请求调用工具message.tool_calls通常是一个非空列表包含工具名称、调用 ID 和参数。程序需要执行对应的工具。情况 B直接回答not message.tool_calls没有工具调用请求程序通常把 assistant 的回答作为本轮最终结果。例如用户问“现在几点”如果模型请求调用get_current_time程序就执行时间工具。如果模型直接输出“现在是下午三点”但没有调用工具那么它可能只是根据已有信息生成了回答并没有查询真实时间。因此不能通过回答内容来判断模型是否调用了工具要检查 API 返回的结构化字段。需要补充tool_calls是否存在与模型是否真的执行过工具是两回事。模型只是提出调用请求真正执行的是你的 Python 程序。2. 工具调用的完整生命周期你现在的项目已经包含这个基本流程为什么执行完工具后还要再请求一次模型因为工具返回的可能是原始数据例如时间戳、搜索结果或者文件列表。模型通常还需要根据工具结果组织自然语言回答。3. 企业级还要检查什么不能只写一个if message.tool_calls就认为万事大吉。还应检查工具名称是否在服务端注册表中。参数是否符合 Schema类型和取值范围是否合理。当前用户是否有权调用该工具。工具是否需要额外确认。工具执行是否成功以及返回结果是否可信。多个工具调用是否都获得了匹配的结果。如果工具调用失败应将失败结果明确地返回给模型或者结束本轮并报告错误不能伪造成功结果。四、API 请求失败如何避免错误消息污染对话历史这个问题与你的core.py有直接关系。你现在采用了一个很好的思路working_messages self.messages.copy()先复制已有历史在临时列表中处理本轮请求成功后才更新正式历史。但要注意这只是浅拷贝。列表虽然是新的里面的字典仍可能与self.messages中的字典共享引用。当前代码如果不修改这些旧字典通常没有问题更复杂的程序则应考虑深拷贝或不可变消息结构。1. 为什么不能失败一次就把错误塞进历史假设你把 API 错误直接当作一条 assistant 消息保存self.messages.append({ role: assistant, content: API 请求失败请稍后重试。, })下一轮请求时这条消息可能被模型理解成正常的对话内容。连续发生错误后历史中就会混入很多并非模型真实生成的回答导致上下文越来越长。模型把错误提示当成之前的对话。后续摘要把错误提示也当成有效历史。调试时难以区分真正的模型回答与程序错误。2. 你当前代码中做对了什么你在 API 请求异常处使用了except APIError as exc: print(f【API 错误】请求模型失败{exc}) return 抱歉当前无法正常请求大模型请稍后重试。如果异常发生时正式历史仍是self.messages而且你只在成功生成最终回答后执行self.messages working_messages那么这次失败通常不会污染正式历史。这里的核心原则是临时处理状态与已确认的对话历史分开管理。3. 企业级建议把一轮对话当成事务真实系统还应把以下信息分开保存信息建议存放位置用户输入与模型真实响应对话历史API 超时、限流、服务不可用应用日志或请求状态工具执行失败工具执行记录必要时将明确的失败结果反馈给模型请求 ID、耗时、重试次数可观测性系统还要注意不要把所有错误都当成可安全重试的错误。 例如API 请求超时后你未必知道服务端是否已经处理请求工具执行超时后也未必知道文件操作是否已经发生。重试前需要考虑幂等性和操作状态。五、工具调用没有完整结束时如何安全恢复这就是你今天编写validate_tool_calls()的直接意义。假设模型返回了这样的工具调用{ role: assistant, tool_calls: [ { id: call_123, type: function, function: { name: get_current_time, arguments: {} } } ] }正常情况下程序执行工具后还应该追加一条与调用 ID 对应的结果{ role: tool, tool_call_id: call_123, content: 当前时间是 15:00 }如果只有第一条消息没有第二条就留下了一个未完成的工具调用。1. 为什么不能直接把它交给模型因为上下文表达的是一个尚未完成的执行过程。模型可能无法正确理解当前状态API 也可能因消息结构不符合要求而拒绝请求。所以你今天增加的validate_tool_calls()就是在发送请求前检查每个工具调用有没有 ID。同一组调用中有没有重复 ID。工具结果是否对应一个尚未完成的调用。是否还有工具调用没有结果。它属于协议完整性检查而不是完整的故障恢复系统。2. 发现不完整以后应该怎么办要根据实际情况处理而不是一律删除或重试。情况 A工具已经执行成功结果尚未保存如果有可靠的执行记录可以补齐结果消息避免重复执行。情况 B工具还没有执行可以在校验参数和权限后继续执行或者安全地取消这一轮。情况 C工具是否执行成功无法确定先查询执行状态不能盲目重复操作。对于删除、支付等副作用操作尤其如此。3. 你当前项目的局限你的validate_tool_calls()可以发现部分不完整的工具调用链但还没有实现将每个工具调用及执行状态持久化。进程崩溃后恢复未完成的任务。对重复执行进行幂等控制。记录工具调用究竟处于待执行、执行中、成功还是失败状态。对于当前的学习项目这些功能暂时不必开发。你只要理解校验能发现问题恢复机制才负责把系统带回安全状态。企业级 Agent 通常还会使用持久化任务状态、幂等键、超时控制、执行日志和可恢复的工作流。涉及外部副作用时还需要处理执行结果未知的情况。六、如何区分 API 错误、工具错误和循环次数耗尽这是你当前core.py最值得掌握的异常分类问题。你已经有三种不同的失败情形只是它们发生在不同阶段。类型发生阶段示例处理方向API 错误请求模型时超时、限流、服务端错误判断是否可重试必要时向用户返回友好提示工具错误执行工具时参数解析失败、文件不存在、权限不足返回明确的工具失败结果或安全终止循环次数耗尽Agent 控制循环中达到max_iterations 5仍未完成停止执行记录原因避免无限循环1. API 错误模型请求失败你当前的代码try: response self.client.chat.completions.create( modelDEEPSEEK_MODEL, messagesworking_messages, toolsTOOLS, ) except APIError as exc: print(f【API 错误】请求模型失败{exc}) return 抱歉当前无法正常请求大模型请稍后重试。这是基本的错误处理但企业级代码还需要考虑连接超时、速率限制、临时服务故障以及 SDK 抛出的其他相关异常。2. 工具错误模型请求成功但工具没有成功完成你当前有类似逻辑try: arguments json.loads(raw_arguments) if not isinstance(arguments, dict): raise ValueError(工具参数必须是 JSON 对象) tool_result execute_tool(tool_name, arguments) except (json.JSONDecodeError, TypeError, ValueError) as exc: tool_result f工具参数解析失败{exc}这段代码可以处理部分参数错误但需要注意一个细节execute_tool()内部可能抛出其他异常。 如果没有在合适的边界处理这些异常它们可能直接中断整个 Agent 的执行流程。而且工具运行时发生的所有错误不应该都被叫作“参数解析失败”。参数无效、工具不存在、权限不足、外部服务不可用应当分别记录和处理。3. 循环次数耗尽Agent 没有在预算内完成任务你设置了max_iterations 5 for _ in range(max_iterations): ...这是为了避免模型反复调用工具永远无法结束。如果 5 次循环后仍然没有最终回答你返回return Agent 已达到本轮最大执行次数请尝试简化问题。这是一个合理的学习版保护措施。但在企业系统中除了循环次数还通常需要限制总执行时间、总 Token 消耗、工具调用次数和费用。循环次数耗尽也应该作为一种明确的终止状态记录而不是与 API 错误混为一谈。4. 用一个统一的状态思维理解异常七、从你的学习项目走向企业级 Agent需要建立哪些意识这里最重要的不是现在继续写更多代码而是理解学习版与生产系统的差别。维度你当前的学习项目企业级 Agent 的常见做法上下文内存中的消息列表结构化状态、持久化会话、版本与一致性管理历史压缩关键词优先、截断摘要重要事实保留策略、摘要验证、可追溯来源工具执行直接调用 Python 函数权限校验、参数验证、超时、隔离执行与审计错误处理try/except和日志输出分类错误、重试策略、退避、熔断和监控恢复能力基本的工具链校验持久化执行状态、幂等、断点恢复资源限制字符预算和循环上限Token、费用、时间、调用次数等多维预算安全性基本的程序判断最小权限、身份认证、授权策略、风险操作审批这里有三个特别重要的认识。第一摘要不是永久记忆。 你今天观察到长期目标逐渐丢失说明摘要是有损压缩。真正重要的长期事实不能只依赖不断重写的普通摘要。第二模型的输出不是程序的事实。 模型说自己执行成功不代表工具真的成功模型认为用户授权了也不代表程序已经验证授权。关键事实要由可信的执行结果和授权系统确认。第三失败处理是 Agent 能力的一部分。 一个 Agent 不只是需要会做事还需要知道什么时候不能做、什么时候没做成以及怎样在失败后保持状态一致。八、附上就几个问题一、上下文管理与 KV Cache1. 如何避免滑动窗口导致重复调用工具同时控制上下文长度并保护 KV Cache核心方案固定前缀 持久化执行状态 分层历史记录 按需摘要。单纯保留最近 N 条消息的问题在于较早的工具调用记录可能被删除模型因此看不到自己已经做过什么进而重复调用工具。我会把信息分成三层层次保存内容保存位置静态上下文System Prompt、稳定的工具说明消息前缀任务状态已完成步骤、工具调用记录、结果引用、待办事项独立的结构化状态对象对话历史最近几轮交互、重要历史摘要消息列表或摘要存储例如task_state { completed_steps: [ 已搜索官方文档, 已确认 API 版本 ], tool_results: { official_docs: results/docs_001.json }, pending_steps: [ 检查兼容性 ]}每次模型决定调用工具之前程序检查任务状态。已经完成的工作就不必重复执行如果工具操作具有副作用还应使用幂等键或调用记录防止重复执行。如何控制上下文近期对话保留在上下文中。早期重要决策压缩成结构化摘要。大型工具结果保存到外部存储消息中保留摘要和引用。任务状态独立保存不因为滑动窗口而消失。如何保护 KV Cache把 System Prompt 和稳定的工具定义放在最前面避免无意义地重写它们。压缩时尽可能处理动态历史部分并在接近上下文阈值时批量进行。但必须补充一个边界只要前缀后面的 token 序列改变后续缓存通常就不能直接复用前缀不变也不代表服务端一定命中缓存。 因此无法保证完全不破坏缓存只能减少不必要的失效。最终设计原则是任务状态负责记住做过什么消息历史负责提供推理所需的上下文KV Cache 负责加速计算。三者不要混为一谈。2. 超长 ReAct 循环中的 thinking 应该怎样保留这道题需要先区分三个东西对话历史用户消息、工具调用、工具结果和模型回答。Thinking / reasoning模型生成的推理内容具体能否读取和回传取决于模型与 API。KV Cache模型计算中用于复用历史 token 的 Key/Value 状态。如果 ReAct 循环持续上百轮完整保留所有 thinking 会带来上下文膨胀。但全部删除也可能损失任务连续性尤其当特定模型协议要求回传这些内容时。我的设计把任务连续性与原始 thinking 分开管理近期推理按模型协议保留。 不擅自删除模型服务明确要求回传的内容。较早的推理轨迹归档。 将重要决策、证据、已尝试方案、失败原因记录在结构化状态中。在支持的边界进行压缩。 通过模型服务支持的上下文管理机制或者新建基于摘要的推理阶段避免随意修改不兼容的历史消息。保留可追溯证据。 重要结论关联工具结果 ID、文件或 URL而不是只保存一段模糊的推理摘要。DeepSeek R1 与题目所述的 DeepSeek V4 策略对比以下比较以题目给出的协议描述为前提不将其当成所有版本、所有 API 的通用规则。策略优点风险与代价剥离全部历史思考上下文更短历史整理更简单可能损失推理连续性如果协议要求回传推理内容可能导致调用不兼容强制回传全部reasoning_content更完整地保留推理轨迹符合特定协议对历史内容的要求上下文持续增长token 成本上升长任务更容易触及窗口限制这两种策略的反转说明了什么推理内容的保留规则不是一个脱离模型协议的通用优化开关。 模型的训练方式、服务端协议以及推理状态的管理方式都可能影响正确的消息组织方式。不能仅凭策略反转就断言某种方法普遍更先进。工程上应该为不同模型实现独立的消息适配层并通过实际 API 文档和测试确认要求。3. 从 148K 字符压缩到 2K 字符怎样解决不可逆信息损失答案是不要把摘要当成原始证据的替代品而应当把摘要作为原始证据的索引。从约 148K 字符压缩到约 2K 字符意味着大部分原始文本不会出现在摘要中。即使摘要写得很好也无法保证保留所有细节。我会设计三层存储第一层任务摘要保存当前结论、关键时间、实体关系和未解决的问题。第二层证据索引为每条重要事实保留来源 URL、原文位置、工具结果 ID 等。第三层原始数据将完整搜索结果或文档存入外部存储必要时重新检索。具体实现还可以包括对人物、日期、金额、版本号等关键字段进行结构化保存。对重要决策和相互矛盾的证据单独记录。当模型无法从摘要中回答问题时按来源索引重新读取原文。对关键事实进行验证避免摘要中的错误被后续轮次反复引用。这相当于把“压缩”从一次性的信息删除改造成可回溯的信息管理。需要注意外部存储只能让原始信息有机会被找回来并不意味着它永远可用网页可能失效因此重要资料可以考虑保存允许留存的原始快照或文件。二、Agent 的可靠性与提示工程4. 状态栏本身出错怎么办状态栏能够把隐式状态显式化但显式并不等于正确。例如Agent 状态栏显示已完成搜索10 次 剩余预算5 次但实际只完成了 7 次搜索。模型可能据此错误地认为已经覆盖了足够多的资料。我会从四个方面处理。第一明确数据来源。工具调用次数应由程序计数而不是让 LLM 自己估算。状态栏应注明数据的来源和更新时间。第二区分事实、估计和未知。例如tool_calls: 7 # 程序计数 verified_sources: 4 # 验证后计数 coverage: unknown # 尚未可靠评估不要把模型推测出来的完成度伪装成确定的事实。第三进行一致性校验。将状态栏与实际的工具执行日志、任务记录进行对账。如果状态不一致暂停依赖该状态的高风险操作先恢复可信状态。第四关键决策使用独立验证。例如不能仅因为状态栏说“已检查全部文件”就执行修改或删除操作程序应验证实际检查范围。需要记住状态栏是给模型看的状态摘要不应成为唯一可信的事实来源。5. 如何防止多人维护的 System Prompt 发生“熵增”这里的“熵增”可以理解为随着多人不断追加规则提示词越来越长、重复越来越多、规则冲突越来越难以察觉。我的方案是把 Prompt 当成正式的软件资产管理。工程实践具体做法模块化把角色定义、规则、工具说明、任务流程分开维护单一事实来源同一条规则只在一个权威位置定义避免复制多份版本控制在 Git 中维护 Prompt记录修改人、原因和版本差异自动化测试使用固定测试用例验证输出、工具选择和安全边界消融测试暂时移除某个模块比较成功率识别无效或有害规则变更审查每次修改说明解决什么问题、可能引入什么副作用定期清理删除重复、过时和已经由程序保证的规则例如可以采用这样的项目结构prompts/ ├── system.md ├── safety_rules.md ├── tool_policy.md └── workflows/ ├── research.md └── coding.md tests/ ├── prompt_regression.json └── tool_selection_cases.json这只是一个示例目录结构不是唯一标准。还要注意模块化不代表最终一定把所有文件拼接进一个巨大的 System Prompt。可以根据任务按需加载模块从而减少无关内容。最终目标是每条规则都应当有明确的目的、适用范围和验证方法。三、从“把更多信息塞进上下文”走向更好的信息组织6. 如果上下文学习本质上是检索应该怎样突破局限我认为这里需要先限定书中命题的适用范围。“上下文学习本质上是检索而非推理”是一个用于提醒我们重视信息选择的观点但不能直接理解为所有上下文学习都只是检索。模型仍可能对上下文中的信息进行归纳、组合、比较和推导。不过这个观点确实指出了一个关键问题给模型更多信息不代表给了它更多有效信息。我会从五个方向改进。1. 从全文输入转向相关信息检索先根据问题查找相关文档、代码或事实只把必要内容交给模型。复杂任务可以分阶段检索而不是一次性塞入所有资料。2. 从原始文本转向结构化知识对人物、时间、事件、代码调用关系等内容建立结构化记录减少模型每轮重复寻找相同信息的工作。3. 把复杂任务拆成多个阶段检索阶段负责找到证据分析阶段负责比较证据验证阶段负责检查结论。每阶段只接收自己需要的信息。4. 把长期信息放到外部记忆不必每轮重新携带全部历史。可以使用数据库、文件、向量检索或其他存储方式在需要时取回信息。5. 分离检索与验证检索到某个结论不代表结论成立。要检查来源可靠性、证据是否充分以及不同来源之间是否存在冲突。一个具体例子假设用户问“这个项目的支付回调为什么失败”低效方式是把整个代码仓库、所有日志和全部历史对话都放入上下文。更合理的方式是检索支付回调函数及调用链。找出相关错误日志。根据错误信息定位可能原因。读取相关配置和测试。给出结论并指出证据和不确定性。这实际上把上下文管理变成了一个检索、执行和验证相结合的过程。因此我对这道题的结论是突破局限的方向不是无限扩大上下文而是提高每次进入上下文的信息相关性、可验证性和组织质量。7. 如果模型不知道自己不知道什么如何保证正确加载 Skill这是一个典型的元认知问题。假设你有 30 个 Skills其中一个专门负责分析支付错误。但用户只说“帮我看看这个程序为什么报错。”如果模型不知道有这个 Skill或者无法判断当前问题属于支付领域就可能一直使用通用工具根本不会加载相关 Skill。只依赖模型自行决定何时加载确实存在漏触发风险。解决方案让 Skill 发现不只依赖模型的主观判断我会组合使用以下机制1. 更清晰的 Skill 元数据每个 Skill 的目录信息应明确写出触发条件、适用场景和不适用场景。例如name: payment-debugging description: 当任务涉及支付接口、支付回调、 交易状态不一致或支付错误日志时使用。这里的元数据示例仅用于说明结构。2. 规则驱动的候选筛选可以先根据用户请求中的关键词、项目类型、工具返回的错误码等筛选出几个候选 Skills再交给模型判断而不是让模型面对全部 Skills 自由猜测。3. 两阶段判断先识别任务类型再从候选 Skill 中选择。例如用户请求 ↓ 任务分类器 ↓ 候选 Skills ↓ LLM 选择并读取 Skill ↓ 执行任务4. 设置兜底机制如果通用处理连续失败或者出现特定错误信号可以触发重新分类与 Skill 检查。但不要无条件加载所有 Skills否则又会失去渐进式披露节省上下文的优势。5. 通过测试发现漏触发准备一组任务样例包括容易识别的场景、描述模糊的场景和容易误判的场景统计相关 Skill 的召回率和误触发率。这里有一个重要的工程权衡召回率太低会漏掉有用的 Skill误触发率太高则会增加上下文噪声和执行成本。最终原则是渐进式披露不能只靠模型自觉还应该配备元数据、路由机制、兜底逻辑和评估测试。8. 动态读取 SKILL 文件后模型一定会遵从吗不同模型有什么区别不一定。读取指令不等于正确执行指令。一个 Skill 被加载之后至少还要经过三个环节Harness 将 Skill 内容放入模型实际能够看到的上下文。模型理解并遵循其中的指令。Harness 提供必要的工具并正确执行模型发起的操作。任何一个环节出现问题都可能导致 Skill 失效。例如Skill 写着“修改代码之前先运行测试”但模型可能忽略这条规则也可能模型正确理解了要求却没有可用的终端工具来运行测试。不同模型的支持有什么区别需要区分三种能力能力含义指令遵循能力能否理解并执行 Skill 中的要求工具调用能力能否正确选择和使用工具协议兼容性是否支持当前 Harness 使用的消息格式、工具协议和上下文加载方式即使两个模型都能理解 Markdown 格式的 Skill 文件它们的工具调用能力、长上下文表现和指令遵循稳定性也可能不同。另外Skill 的动态加载方式由 Harness 决定。一个系统可能把 Skill 内容加入用户消息另一个系统可能通过专门工具加载。不能因为模型能够阅读 Markdown就认定它原生支持所有 Skills 协议。怎样让它更可靠把复杂指令拆成明确步骤。明确说明触发条件、前置条件和完成标准。要求关键操作提供可验证的结果而不是只让模型声称完成。为重要规则编写自动化测试。对不同模型分别测试不要默认一种模型的表现可以推广到另一种模型。例如“请确保代码正确”是一条很难直接验证的指令“修改后运行指定测试并检查退出码和失败信息”则更容易形成可验证的执行流程。9. 大量工具频繁变化时怎样最大化 KV Cache 命中率这是整章内容的一次综合应用。关键是把稳定信息放在前面把容易变化的信息放在后面。推荐的上下文布局第一区稳定前缀System Prompt、通用行为规范、稳定的输出要求。尽量避免无关改动。第二区稳定的工具入口少量通用工具例如搜索工具、文件读取工具、Skill 加载工具。工具定义保持稳定具体任务能力按需发现。第三区动态信息当前任务参数、临时工具列表、时间戳、近期历史和工具结果。把经常变化的内容放在稳定前缀之后。具体的工程措施第一避免每次重建整个工具定义。如果系统有几百个工具不一定要在每次调用中都暴露全部工具。可以先提供稳定的工具发现入口再根据任务选择相关工具。但必须确保当前模型和 API 支持这种调用方式模型无法调用未提供或未授权的工具。第二工具顺序保持确定。不要因为字典遍历顺序、随机排序或临时注册顺序不同就让同一批工具每次以不同顺序进入上下文。可以采用稳定排序或固定的工具分组。第三动态信息不要插入稳定前缀中间。例如不要因为当前时间变化就把时间戳插到 System Prompt 的开头。如果接口允许可以把时间等动态任务信息放在稳定前缀后面的动态消息中。第四工具发生变化时只更新必要部分。当一个工具被添加或删除时尽量避免同时重写无关的系统指令和所有工具定义。不过工具集合的变化仍可能改变相关 token 序列不能保证缓存完全不受影响。第五测量真实缓存命中情况。通过服务端提供的缓存 token 统计、输入 token 数和调用延迟观察布局调整前后的实际效果。不要仅凭上下文看起来更稳定就认定缓存一定命中。这里有个容易忽略的边界工具定义往往属于模型请求中的一部分。如果某个 API 将全部工具定义放在消息前缀之后或其他位置具体缓存行为可能不同。因此布局方案要结合实际 API 的缓存规则验证。而且缓存命中率不是唯一目标。为了追求缓存而保留过时工具、错误时间或不相关信息会损害任务正确性。安全、正确和工具权限始终优先于缓存优化。