从零构建可演进Agent框架:记忆分层与任务图规划实战

📅 2026/7/10 4:07:46
从零构建可演进Agent框架:记忆分层与任务图规划实战
1. 项目概述这不是又一个“Hello World”式的Agent玩具“如何从零构建一个 Agent 框架五”——看到这个标题我第一反应不是点开而是先去翻前四篇的更新时间。因为真正做Agent开发的人心里都清楚能坚持写到第五篇的大概率不是在画大饼而是在泥里趟过几轮、踩过至少三类坑、把memory和planning模块重写了两遍以上。这不是教你怎么调用一个现成的LangChain模板也不是教你用Cursor Pro一键生成个“AI助手”Demo这是在讲当你决定甩开所有封装好的胶水代码亲手搭起一个能记住上周你让它查过的竞品财报、能主动把“整理Q3销售数据”拆解成“拉取CRM导出表→清洗缺失值→按区域聚合→生成PPT大纲→调用本地PPT生成器”这五个步骤、并且在第三步失败时自动回退并尝试替代方案的可演进系统。核心关键词“Agent”、“框架”、“记忆”、“任务规划”、“复杂任务”每一个词背后都藏着现实世界的重量。“Agent”不是API调用的别名它意味着自主性、状态维持与目标导向行为“框架”不是一堆装饰性的接口而是可插拔的执行管线、可热替换的记忆策略、可审计的决策日志“记忆”不是简单地把聊天记录存进SQLite而是要区分短期上下文缓存、中期会话状态快照、长期知识图谱索引“任务规划”不是GPT-4o随便分个步骤而是需要显式建模子目标依赖、评估执行成本、预留回滚锚点而“复杂任务”就是那个让你凌晨两点还在调试plan_repair_loop函数的、拒绝被LLM单次推理搞定的真实业务需求。这篇文章面向的是已经写过至少一个完整Agent Demo、但发现它在真实场景中“一跑就崩、一记就乱、一拆就散”的开发者。你可能刚用RAGFunction Calling搞定了客服问答但当用户说“对比上季度和本季度的客户流失率并分析TOP3流失原因再给销售总监发一封带图表的邮件”你的系统就开始沉默。你缺的不是更多模型而是一套能让Agent像人一样思考、犯错、修正、沉淀经验的底层骨架。接下来的内容全部基于我在三个生产级Agent项目中沉淀下来的实操路径没有幻灯片式的概念堆砌只有每一步为什么这么选、参数怎么算、配置文件里哪一行改错了会导致整个记忆链断裂的硬核细节。2. 整体架构设计与核心思路拆解为什么必须放弃“单线程思维”2.1 传统Agent实现的致命陷阱把LLM当万能CPU用很多初学者构建Agent的第一步是写一个run()函数里面塞满llm.invoke(prompt)、tool_call()、if/else判断。这种模式在处理“今天天气怎么样”这类原子任务时很流畅但一旦进入“复杂任务”领域立刻暴露三大结构性缺陷状态黑洞每次调用LLM都是无状态的上一轮规划的子目标、中间结果、失败原因全靠prompt里那几行文字硬塞。当任务链超过5步prompt token就爆了更别说LLM对长上下文的记忆准确率断崖式下跌。我实测过在Claude 3.5 Sonnet的128K上下文中让模型复述自己3步前生成的子目标ID错误率高达37%——这不是模型问题是设计范式问题。规划即执行把“分解任务”和“执行任务”混在同一循环里。结果就是当第四个子任务因网络超时失败时整个规划树被丢弃系统只能从头开始“重新思考”而不是定位到失败节点、加载当时的环境快照、尝试备用工具或降级策略。这就像修车时每次螺丝拧不紧不是换把扳手而是把整辆车拆了重装。记忆无分层所有信息——用户刚说的“把报表发给张总”上周存的“张总是销售总监”行业常识“销售总监关注转化率而非点击量”——全塞进同一个向量库。检索时语义相似度高的“张总喜欢蓝色PPT”可能比“张总是销售总监”排得更靠前导致规划逻辑错乱。这不是检索不准是记忆没有按时效性、作用域、抽象层级做物理隔离。所以本框架的设计原点就是把Agent拆解为四个正交且可独立演化的子系统Planner规划器、Executor执行器、Memory记忆中枢、Orchestrator编排器。它们之间不共享内存只通过明确定义的协议通信。这种设计不是为了炫技而是为了可测试、可监控、可替换。比如你可以把默认的LLM Planner换成基于规则的决策树用于高确定性流程而Memory模块完全不受影响或者把本地SQLite Memory替换成支持向量图谱的Neo4jWeaviate混合存储Executor照样跑。2.2 四层架构的物理落地每个模块的职责边界与交互契约我们不画UML图直接看代码目录结构和核心接口定义这才是工程师能抄作业的地方agent_framework/ ├── planner/ # 规划器只负责“想”不负责“做” │ ├── base.py # 定义PlanResult: List[SubTask] 和 PlanContext │ ├── llm_planner.py # 基于LLM的规划器输入当前GoalMemory摘要输出结构化SubTask列表 │ └── rule_planner.py # 基于YAML规则的规划器适合固定流程如报销审批 ├── executor/ # 执行器只负责“做”不负责“想” │ ├── base.py # 定义ExecutionResult: {status, output, error, context} │ ├── tool_executor.py # 调用外部工具API、CLI、DB管理超时、重试、熔断 │ └── code_executor.py # 安全沙箱内执行Python代码用于数据清洗、图表生成 ├── memory/ # 记忆中枢只负责“记”和“忆”不参与决策 │ ├── short_term/ # 短期记忆当前会话的token级上下文用LRU Cache实现 │ ├── medium_term/ # 中期记忆会话级状态快照存JSON到本地文件memdir/ │ └── long_term/ # 长期记忆知识图谱向量库存实体关系语义嵌入 └── orchestrator.py # 编排器Agent的“操作系统内核”协调四大模块关键交互契约有三条Planner → OrchestratorPlanner不返回字符串而是返回PlanResult对象其中每个SubTask必须包含id唯一标识、name人类可读名、dependencies: List[str]依赖的其他SubTask ID、tool_name要调用的工具名、input_schema输入参数JSON Schema。这强制规划结果可被程序解析而非依赖LLM自由发挥。Orchestrator → ExecutorOrchestrator不传原始prompt而是传ExecutionRequest包含subtask_id、tool_name、resolved_input已从Memory注入变量后的实际参数。Executor执行后必须返回带context字段的ExecutionResult该context会被Orchestrator自动存入medium_term作为下一步规划的依据。All → Memory任何模块需要记忆时必须调用memory.get(key, scopelong)其中scope参数强制指定记忆层级。short走内存Cachemedium走本地文件long走向量库。绝不允许模块直接操作底层存储。这个设计带来的直接好处是当用户反馈“Agent记不住我昨天设的偏好”你不用全局debug只需检查medium_term模块的序列化逻辑当规划结果越来越离谱你只需替换llm_planner.py里的prompt模板甚至接入微调后的专用规划模型其他模块零改动。2.3 为什么选择“显式任务图”而非“隐式链式调用”市面上很多Agent框架包括早期LangChain采用Chain模式A工具输出→B工具输入→C工具输入。这种模式简洁但面对复杂任务时它本质上是一个脆弱的单向管道。我们的框架强制使用TaskGraph——一个有向无环图DAG每个节点是SubTask边是dependencies。举个真实例子“生成月度运营报告”任务规划器可能输出[ SubTask(idfetch_data, name拉取数据库原始数据, dependencies[], tooldb_query, input{sql: SELECT * FROM events WHERE date 2024-05-01}), SubTask(idclean_data, name清洗数据, dependencies[fetch_data], toolpandas_clean, input{columns: [user_id, event_type]}), SubTask(idgen_charts, name生成折线图, dependencies[clean_data], toolmatplotlib_plot, input{x: date, y: count}), SubTask(idwrite_summary, name撰写文字摘要, dependencies[clean_data, gen_charts], toolllm_summarize, input{focus: 用户活跃度趋势}) ]这个图的价值在于可并行化fetch_data和clean_data不能并行但gen_charts和write_summary可以只要它们的依赖都满足。Orchestrator内置线程池自动识别可并行节点。可追溯性当write_summary失败Orchestrator能立刻定位到它依赖的clean_data和gen_charts并检查这两个节点的ExecutionResult.context判断是数据质量问题还是图表生成失败。可干预性运维人员可以直接修改TaskGraph比如把gen_charts的tool从matplotlib_plot临时改成plotly_interactive无需重启服务。我们放弃“链式调用”是因为真实业务中90%的故障不是模型不会思考而是系统无法在思考出错时精准定位到哪一环的输入出了问题。TaskGraph就是给Agent装上的“黑匣子”。3. 核心模块深度解析与实操要点记忆与规划的硬核实现3.1 记忆中枢Memory三层存储的协同机制与避坑指南记忆不是“存进去取出来”那么简单。真正的挑战在于如何让不同时间尺度、不同抽象层级的信息在需要时以正确的形式、正确的精度、正确的上下文被召回我们的三层记忆不是简单的“缓存→数据库→向量库”堆叠而是有明确的职责划分和协同协议。3.1.1 短期记忆Short-TermToken级上下文的智能裁剪短期记忆对应LLM的上下文窗口它的核心矛盾是窗口有限但对话历史无限增长。常见做法是“保留最后N轮”但这在复杂任务中会丢失关键信息。比如用户说“基于刚才生成的图表再加一个同比分析”如果“刚才的图表”在N轮之外LLM就懵了。我们的解决方案是语义感知的滑动窗口Semantic-Aware Sliding Window不按“轮数”裁剪而按语义单元Semantic Unit裁剪。每个ExecutionResult返回时会附带一个context_summary字段由Executor自动生成如“成功生成2024年5月用户活跃度折线图X轴为日期Y轴为DAU”。Orchestrator将所有context_summary存入一个轻量级向量库我们用chromadb内存模式当需要填充LLM上下文时先用当前Goal如“加同比分析”查询最相关的3个context_summary再把它们对应的原始ExecutionResult.output片段拼接进prompt。实操要点context_summary的生成不能靠LLM总结必须由Executor硬编码。例如matplotlib_plot工具执行后固定返回{ output: /tmp/chart_abc123.png, context_summary: 生成折线图数据源clean_dataX轴dateY轴dau时间范围2024-05-01至2024-05-31 }这样保证摘要的准确性和一致性。我踩过的坑是曾用LLM生成summary结果它把“DAU”错写成“MAU”导致后续规划完全跑偏。提示短期记忆的向量库必须是内存模式chromadb.PersistentClient(path/dev/shm/chroma)避免磁盘IO成为瓶颈。/dev/shm是Linux内存文件系统读写速度是SSD的10倍以上。3.1.2 中期记忆Medium-Term会话状态的可靠快照中期记忆解决的是“跨请求状态保持”问题。HTTP无状态但Agent需要记住“用户正在审批这份报销单当前在‘财务复核’环节”。它的存储必须满足强一致性、低延迟、可回溯。我们采用本地文件系统版本号方案目录结构为memdir/{session_id}/v{version}.json。每次Orchestrator完成一个SubTask就生成一个新版本快照// memdir/sess_789/v5.json { session_id: sess_789, version: 5, timestamp: 2024-05-22T14:23:01Z, current_goal: 完成Q3销售数据分析报告, task_graph: { /* 当前TaskGraph的精简版 */ }, executed_tasks: [ {id: fetch_data, status: success, output_ref: /tmp/data_123.csv}, {id: clean_data, status: success, output_ref: /tmp/clean_456.csv} ], user_preferences: {chart_style: dark_mode, report_format: pptx} }关键设计点版本号非递增而是哈希v5不是数字5而是hash(task_graph executed_tasks)。这样当用户中途修改GoalTaskGraph变化版本号自动变更避免覆盖旧状态。output_ref不存内容只存引用/tmp/data_123.csv是文件路径不是CSV内容。这避免快照文件过大也方便Executor直接读取。自动清理Orchestrator启动时扫描memdir/{session_id}/只保留最近3个版本按timestamp其余rm -f。实测下来单个会话快照平均2KB3个版本不到10KB对磁盘毫无压力。注意memdir必须挂载在SSD上且设置noatime挂载选项mount -o remount,noatime /path/to/memdir避免每次读取都触发磁盘写入这是线上环境卡顿的隐形杀手。3.1.3 长期记忆Long-Term知识图谱与向量库的混合检索长期记忆要解决“Agent如何像人一样积累经验”。纯向量检索如Chroma擅长找“相似”但无法回答“张总去年批准过几次超过5万的报销”——这需要结构化查询。纯图数据库如Neo4j擅长关系查询但无法理解“帮我找和‘用户留存率下降’语义相近的历史分析报告”。我们的方案是双引擎协同向量库Weaviate负责语义检索图数据库Neo4j负责关系查询Orchestrator根据Query类型自动路由。向量库Weaviate只存两类对象Document报告、邮件、会议纪要和Entity人、部门、产品。每个Document的text字段被嵌入metadata字段存来源、作者、时间。检索时用nearText查询返回Document列表。图数据库Neo4j存三元组(Subject, Predicate, Object)如(张总, APPROVED, 报销单_001)、(报销单_001, HAS_AMOUNT, 52000)。查询时用Cypher语句如MATCH (u:User)-[r:APPROVED]-(b:Billing) WHERE b.amount 50000 RETURN count(*)。协同协议当Planner需要长期记忆时Orchestrator先用Query的关键词如“张总”、“报销”、“5万”在Neo4j中查出相关Billing节点ID再把这些ID作为whereFilter传给Weaviate检索这些报销单关联的Document。这样既保证了关系查询的精确性又利用了向量检索的语义泛化能力。实操难点在于数据同步。我们不写ETL脚本而是用事件驱动每次ExecutionResult返回含entity_mentions字段如[张总, 销售部, Q3]Orchestrator自动触发Neo4j写入事务。这个字段由Executor根据其input_schema和output硬编码生成确保源头可信。3.2 任务规划器Planner从LLM自由发挥到结构化输出的工程化改造让LLM输出结构化JSON是Agent开发中最容易翻车的环节。直接response llm.invoke(prompt)然后json.loads(response)90%概率报JSONDecodeError。我们的方案是三重保险机制Prompt Engineering Output Parser Fallback Strategy。3.2.1 Prompt Engineering用“思维链”约束LLM的发挥空间我们不用“请输出JSON格式”而是用结构化思维链Structured Chain-of-Thought你是一个专业的任务规划专家。请严格按以下步骤思考 1. 分析用户Goal{{goal}}。识别其中的核心动词如“对比”、“分析”、“生成”、名词如“客户流失率”、“TOP3原因”、约束条件如“上季度和本季度”、“给销售总监”。 2. 列出必需的原子操作每个操作必须是单一、可执行、有明确输入输出的。禁止出现“思考”、“分析”等模糊动词必须是“调用CRM_API获取数据”、“运行Python脚本清洗数据”。 3. 确定操作依赖哪个操作必须在另一个操作之后例如“生成图表”必须在“获取数据”之后。 4. 为每个操作分配工具从可用工具列表中选择最匹配的一个。工具列表{{tools}}。 5. 生成JSON严格按以下Schema输出不要任何额外字符 { subtasks: [ { id: 唯一小写字母数字如fetch_crm_data, name: 人类可读名如从CRM拉取销售数据, dependencies: [其他subtask的id如[]], tool_name: 工具名如crm_api_get, input: { 参数名: 参数值 } } ] }这个Prompt的关键在于把LLM的“自由创作”压缩到“填空”级别。它不需要发明新工具只需要从给定列表中选择不需要发明新参数只需要从Goal中提取已有名词填入预设Schema。实测下来Claude 3.5 Sonnet在此Prompt下JSON格式正确率从62%提升到98.7%。3.2.2 Output ParserJSON Schema校验与自动修复即使Prompt再好LLM仍有1-2%概率输出非法JSON。我们不重试而是用Pydantic V2的Strict Model做实时校验和修复from pydantic import BaseModel, Field, validator from typing import List, Optional, Dict, Any class SubTask(BaseModel): id: str Field(..., patternr^[a-z0-9_]{3,32}$) # 强制小写数字下划线 name: str dependencies: List[str] Field(default_factorylist) tool_name: str input: Dict[str, Any] class PlanResult(BaseModel): subtasks: List[SubTask] # 自动修复当JSON解析失败用正则提取id/name/tool_name等关键字段补全默认值 def parse_plan_output(raw_text: str) - PlanResult: try: return PlanResult.model_validate_json(raw_text) except Exception as e: # 正则提取idxxx, nameyyy, tool_namezzz extracted extract_fields(raw_text) # 补全默认值dependencies[] if not found, input{} if not found subtasks [SubTask(**{**extracted, dependencies: [], input: {}})] return PlanResult(subtaskssubtasks)这个Parser不是摆设。它让系统在LLM偶尔“抽风”时依然能拿到一个可执行的、哪怕不完美的Plan而不是直接崩溃。这是生产环境稳定性的底线。3.2.3 Fallback Strategy当LLM彻底失灵时的保底方案最坏情况LLM连续三次输出无法解析的垃圾。这时我们启用规则引擎Fallback。Orchestrator检测到规划失败立即切换到rule_planner.py它是一个YAML配置驱动的静态规划器# planner/rules/report_generation.yaml goal_pattern: 生成.*报告 subtasks: - id: fetch_data name: 拉取基础数据 tool_name: db_query input: {sql: SELECT * FROM {{table}} WHERE date {{start_date}}} - id: gen_charts name: 生成图表 dependencies: [fetch_data] tool_name: matplotlib_plot input: {x: date, y: value}Orchestrator用goal文本匹配goal_pattern找到匹配规则再用Jinja2模板渲染input中的变量{{table}}从Memory中查出。这保证了即使LLM服务器宕机Agent仍能执行预设的标准化流程。我们在金融风控场景中就靠这套Fallback扛过了两次大模型API的区域性中断。4. 实操过程与核心环节实现从零搭建一个可运行的Agent框架4.1 环境准备与最小可行框架MVP搭建我们不追求一步到位先用200行代码跑通一个“能规划、能执行、能记事”的最小闭环。所有依赖都选最轻量、最易安装的# 创建虚拟环境 python -m venv agent_env source agent_env/bin/activate # Linux/Mac # agent_env\Scripts\activate # Windows # 安装核心依赖总计15MB无GPU要求 pip install pydantic2.7.1 chromadb0.4.25 neo4j5.21.0 weaviate-client4.5.0框架主入口main.py仅197行但已包含四大模块的骨架# main.py from orchestrator import Orchestrator from planner.llm_planner import LLMPlanner from executor.tool_executor import ToolExecutor from memory.memory_manager import MemoryManager # 1. 初始化记忆中枢三层存储 memory MemoryManager( short_termChromaShortTerm(), # 内存向量库 medium_termFileMediumTerm(base_dir./memdir), # 本地文件 long_termHybridLongTerm(weaviate_client, neo4j_driver) # 双引擎 ) # 2. 初始化规划器带Fallback planner LLMPlanner( modelclaude-3-5-sonnet-20240620, # 或本地Ollama模型 fallback_rule_path./planner/rules/fallback.yaml ) # 3. 初始化执行器带工具注册 executor ToolExecutor() executor.register_tool(db_query, lambda sql: run_db_query(sql)) executor.register_tool(matplotlib_plot, lambda x, y: save_chart(x, y)) # 4. 启动编排器 orchestrator Orchestrator(memorymemory, plannerplanner, executorexecutor) # 5. 运行一个真实任务 result orchestrator.run( session_idtest_sess_001, goal对比2024年4月和5月的用户注册量并生成柱状图 ) print(f最终结果: {result})这个MVP的价值在于它把所有模块的初始化、依赖注入、接口调用都显式写出来没有任何隐藏的“魔法”。你可以逐行打断点看planner.plan()返回什么executor.execute()传了什么参数memory.save()存了什么内容。这是理解框架脉络的最快路径。4.2 核心环节TaskGraph的构建、执行与动态修复TaskGraph不是静态的它在执行过程中会动态演化。Orchestrator的run()方法内部是一个状态机循环def run(self, session_id: str, goal: str) - ExecutionResult: # Step 1: 加载或初始化会话状态 state self.memory.medium_term.load_latest(session_id) if not state: state SessionState(session_idsession_id, current_goalgoal) # Step 2: 规划可能触发Fallback plan_result self.planner.plan(goal, state) # Step 3: 构建初始TaskGraph graph TaskGraph(plan_result.subtasks) # Step 4: 执行循环核心 while not graph.is_complete(): # 找出所有ready状态的节点依赖都已完成 ready_nodes graph.get_ready_nodes() # 并行执行所有ready节点 for node in ready_nodes: try: # 从Memory注入变量如input.sql中的{{table}}被替换 resolved_input self.memory.inject_variables(node.input, state) exec_result self.executor.execute(node.tool_name, resolved_input) # 更新状态存入medium_term更新graph节点状态 state.executed_tasks.append({ id: node.id, status: success, output_ref: exec_result.output_ref, context_summary: exec_result.context_summary }) self.memory.medium_term.save(state) graph.mark_success(node.id, exec_result) except Exception as e: # Step 5: 动态修复关键 repair_result self._repair_task(graph, node, e) if repair_result abort: raise RuntimeError(fTask {node.id} failed and no repair possible) elif repair_result retry: continue # 重试当前节点 else: # replace_tool graph.replace_tool(node.id, repair_result) return ExecutionResult.from_state(state)这个循环的精妙之处在于_repair_task()方法。它不是简单重试而是根据错误类型智能决策如果是TimeoutError则增加超时时间标记为retry如果是ToolNotFoundError则查fallback_tools.yaml找功能相近的替代工具标记为replace_tool如果是ValueError如SQL语法错误则调用llm_planner.refine_subtask()用LLM重写input参数再retry。这个动态修复能力让Agent第一次拥有了“容错性”。它不再是一条死胡同而是一张可以绕路的地图。4.3 配置文件详解让框架真正可配置、可维护所有硬编码都是魔鬼。我们的框架95%的行为由YAML配置驱动。核心配置文件config.yaml# config.yaml orchestrator: max_retries: 3 parallel_workers: 4 task_timeout: 30 # 秒 planner: llm: model: claude-3-5-sonnet-20240620 temperature: 0.3 fallback_rules: ./planner/rules/fallback.yaml memory: short_term: vector_db: chroma cache_size: 1000 # 最多缓存1000个context_summary medium_term: base_dir: ./memdir max_versions: 3 long_term: weaviate: url: http://localhost:8080 api_key: your-key neo4j: uri: bolt://localhost:7687 auth: [neo4j, password] tools: - name: db_query description: 查询数据库返回CSV文件路径 input_schema: type: object properties: sql: {type: string, description: 标准SQL查询语句} output_schema: type: object properties: output_ref: {type: string, description: CSV文件路径} context_summary: {type: string} - name: matplotlib_plot description: 生成折线图 input_schema: {...}这个配置文件的意义在于运维人员无需改代码就能调整Agent行为。比如把parallel_workers从4改成8就能压榨更多CPU把max_retries从3改成1就能让失败更快暴露把weaviate.url指向集群地址就能无缝升级存储。我们在客户现场就是靠这份配置文件让非开发人员也能完成80%的日常调优。5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 “Agent记不住我昨天说的话”——中期记忆失效的根因分析这个问题90%不是代码bug而是文件权限和路径问题。FileMediumTerm依赖os.makedirs()创建./memdir/{session_id}/但如果进程以root启动而Web服务器以www-data用户运行www-data就无法写入root创建的目录。排查步骤在FileMediumTerm.save()方法开头加一行日志logger.info(fSaving to {file_path}, uid{os.getuid()}, gid{os.getgid()})查看日志确认uid是否与Web服务器用户一致。用ls -ld ./memdir检查目录权限应为drwxrwxr-x且group为Web服务器组如www-data。修复命令sudo chgrp -R www-data ./memdir sudo chmod -R gw ./memdir实操心得永远不要在生产环境用./memdir相对路径。必须用绝对路径如/var/lib/agent-framework/memdir并在启动脚本中chown www-data:www-data /var/lib/agent-framework。5.2 “规划结果越来越离谱”——LLM提示词漂移的静默灾难LLM的输出会随时间“漂移”同一Prompt月初输出规范月底就开始胡言乱语。这不是模型退化而是向量库中积累了大量低质量context_summary污染了短期记忆的检索结果。现象planner.plan()的输入Prompt中context_summary列表里混入了“用户说‘随便’”、“系统报错Connection refused”这类无意义摘要导致LLM被带偏。根治方案在MemoryManager.get_context()中加入质量过滤器def get_context(self, goal: str, top_k: int 3) - List[str]: # 1. 用goal查询chroma得到候选summary列表 candidates self.short_term.query(goal, top_ktop_k*2) # 多取一倍 # 2. 过滤掉低质量summary长度5或含敏感词 filtered [] for s in candidates: if len(s) 5 or any(word in s for word in [error, failed, Connection refused, timeout]): continue filtered.append(s) return filtered[:top_k] # 返回高质量的top_k个这个过滤器上线后规划准确率提升了22%。它提醒我们Agent的记忆也需要“内容审核”。5.3 “执行器卡死CPU 100%”——工具调用的资源泄漏黑洞ToolExecutor调用外部命令如subprocess.run([python, script.py])时如果script.py陷入死循环subprocess会一直等待拖垮整个Orchestrator。解决方案是双重熔断进程级熔断subprocess.run(..., timeout30)超时后kill -9。线程级熔断用concurrent.futures.ThreadPoolExecutor包装设置max_workers1避免一个卡死的工具阻塞所有线程。# tool_executor.py from concurrent.futures import ThreadPoolExecutor, TimeoutError class ToolExecutor: def __init__(self): self.executor ThreadPoolExecutor(max_workers1) # 关键 def execute(self, tool_name: str, input: dict) - ExecutionResult: future self.executor.submit(self._run_tool, tool_name, input) try: return future.result(timeout30) # 线程级超时 except TimeoutError: future.cancel() raise RuntimeError(fTool {tool_name} execution timed out after 30s)这个设计让系统具备了“外科手术式”的故障隔离能力一个工具卡死只影响当前请求不影响其他会话。5.4 “长期记忆检索慢得像蜗牛”——Weaviate性能调优实战Weaviate默认配置10万文档查询要2秒。优化后降到200ms以内。关键三步**向量化模型换用all-MiniLM-L6-v2