错误降级三原则与性能建议 本节摘要:SDK 四步的最后一步「错误降级」,以及贯穿四步的性能建议。记忆系统是「增强」而非「必需」——它挂了,Agent 仍应正常工作。本节讲清降级三原则(gather 用 returnexceptions、关键路径 try-catch、工具返回错误串而非抛异常),以及性能建议(召回<200ms、注入大小控制、session 粒度)。读完本节你能让自己的 Agent 在记忆系统故障时优雅降级,在日常运行时性能达标。 一、核心理念:记忆是增强,不是必需 降级设计的核心理念只有一句:记忆系统是 Agent 的增强,不是 Agent 的必需。 这个理念决定了所有降级策略的取向:宁可记忆不工作,也要让 Agent 继续响应用户。
本节摘要:SDK 四步的最后一步「错误降级」,以及贯穿四步的性能建议。记忆系统是「增强」而非「必需」——它挂了,Agent 仍应正常工作。本节讲清降级三原则(gather 用 return_exceptions、关键路径 try-catch、工具返回错误串而非抛异常),以及性能建议(召回<200ms、注入大小控制、session 粒度)。读完本节你能让自己的 Agent 在记忆系统故障时优雅降级,在日常运行时性能达标。
降级设计的核心理念只有一句:记忆系统是 Agent 的增强,不是 Agent 的必需。
理念的含义 记忆系统正常 → Agent 更聪明(有记忆) 记忆系统故障 → Agent 仍能工作(无记忆,但不崩) → 不能让「记忆挂了」导致「Agent 挂了」 → 记忆是"nice to have",不是"must have"
这个理念决定了所有降级策略的取向:宁可记忆不工作,也要让 Agent 继续响应用户。用户可以容忍「这次 Agent 没记住」,但不能容忍「Agent 完全没响应」。
关键概念:这与第 8 章代理层的降级哲学一致——「记忆可丢,响应不可丢」。SDK 接入要把这个哲学落实到代码里:每一步涉及记忆的操作,都要有「失败了怎么办」的处理,且「失败的处理」是「继续无记忆地工作」,而非「报错中断」。
具体怎么降级?三个原则覆盖绝大多数场景:
当多个记忆操作并发(如同时召回 Chat Memory 和查 Wiki),用 gather 并发,且设置 return_exceptions=True,让单个失败不拖累其他:
# 概念性:并发降级 import asyncio async def recall_all(query, ctx): # 并发执行多个召回,任一失败不拖累其他 results = await asyncio.gather( client.recall_memory(query, **ctx), client.search_wiki(query, **ctx), client.search_code(query, **ctx), return_exceptions=True # 关键:失败返回异常对象,而非抛出 ) # 过滤掉失败的(异常对象),只保留成功的 return [r for r in results if not isinstance(r, Exception)]
| 做法 | 后果 |
|---|---|
| 串行 + 抛异常 | 一个失败全停 |
| gather + return_exceptions | 单个失败不影响其他 |
关键路径(如召回)要 try-catch,失败时降级为「无记忆」继续:
# 概念性:关键路径降级 async def chat(user_message, ctx): try: memories = await client.recall(query=user_message, **ctx) prompt = inject(user_message, memories) except Exception: prompt = user_message # 降级:无记忆,用原始消息 reply = await llm.complete(prompt) # 推理照常 return reply
工具暴露给模型后,模型调用工具若失败,应返回「错误描述字符串」而非抛异常——让模型能理解错误并继续:
# 概念性:工具错误降级 async def handle_tool_call(tool_name, params): try: return await client.call_tool(tool_name, params) except Exception as e: # 不抛异常,返回错误串让模型理解 return f"[工具 {tool_name} 调用失败: {str(e)}]" # 模型看到这个,可以决定:重试/换工具/无工具继续
💡 技巧:原则三尤其重要——工具失败返回错误串,模型能「知道工具坏了」并自行调整(如改用其他工具或直接答)。若抛异常,模型直接崩。这种「把错误作为信息传递给模型」是 Agent 工程的常见模式。
降级之外,日常运行要满足几个性能指标:
| 指标 | 建议 | 原因 |
|---|---|---|
| 召回延迟 | < 200ms | 用户在等,慢了影响体验 |
| 注入大小 | 受控(有阈值) | 防挤掉用户问题 |
| 粒度 | session 级 | 避免每次重新初始化 |
召回 < 200ms 怎么保证 ├─ 召回预算(条数/字符)控制,不全量检索 ├─ 装配过滤先缩小候选集(第3章),减少检索量 ├─ 索引优化(BM25+向量索引建好) └─ 超时上限兜底(第6章召回预算),超时立即返回
⚠️ 注意:召回 < 200ms 是个重要指标——它直接决定「带记忆的 Agent」是否比「无记忆的 Agent」明显慢。如果召回要 2 秒,用户会觉得「这 Agent 怎么这么慢」,记忆的好处被延迟抵消。所以第 6 章召回预算里的「超时上限」就是为这个服务的:宁可少召回点,也要在 200ms 内返回。
「session 粒度」是个常被忽略的性能点——记忆操作尽量在 session 级复用,而非每次请求重来:
session 级复用 一个 session 内: ├─ 装配范围(team/agent/task)不变 → 只算一次 ├─ 稳定记忆(persona/scene)不变 → inject 部分复用 └─ 会话上下文累积 → 增量更新 → 避免每次请求都「从头算装配、从头召回稳定记忆」
| 操作 | 每次请求做 | session 级复用 |
|---|---|---|
| 装配范围计算 | 慢 | 复用(三元组不变) |
| 稳定记忆召回 | 慢且重复 | 复用(append 区不变) |
| 动态 L1 召回 | 必须每次(随问题变) | 每次做 |
session 级复用的核心是「区分稳定与动态」——稳定的(session 内不变)复用,动态的(随问题变)每次算。这与第 8 章 inject/toolize、第 9 章第 2 节 prepend/append 的思路一脉相承:都是按「稳定性」优化。
把降级与性能合起来,一个稳健的 SDK 接入具备两个特征:
稳健接入的两特征 ① 故障能扛(降级):记忆系统任何环节挂,Agent 仍响应 ② 日常够快(性能):正常时召回<200ms,注入受控 → 两者统一于「记忆是增强」的理念: 增强要快(不拖慢)+ 增强可丢(不拖垮)
这就是 SDK 四步法的完整图景——召回(快且准)、捕获(持续生长)、工具(按需用知)、降级(故障能扛),再加性能优化(日常够快)。四步+性能,构成一个「既能增强 Agent、又不会拖累 Agent」的接入方案。第 10 章会把这些落到五类真实框架的接入实战。
第 9 章到此完成——四步法(召回/捕获/工具/降级)加性能建议,SDK 接入的全套技能都覆盖了。下一章把这些落到五类真实 Agent 框架的接入实战。