ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Agent 框架探秘:拆解 OpenHands(13)--- Memory 配置与验证实战

AI Agent 框架探秘:拆解 OpenHands(13)--- Memory 配置与验证实战 1. 为什么 OpenHands 的 Memory 值得单独拆一节如果你正在本地跑 OpenHands大概率遇到过这种场景任务跑到第 30 步Agent 突然开始重复执行已经完成的命令或者把前面确认过的文件路径又搞错了。这不是模型变笨了而是 Memory 模块没有把关键历史正确压缩并注入到下一轮上下文里。OpenHands 的 Memory 不是简单的“聊天记录数组”它由 View、ConversationMemory、Condenser 三层组成分别负责事件过滤、消息格式化和历史压缩。理解这三层怎么协作才能让 Agent 在长任务里保持逻辑连贯。这一篇聚焦落地配置与验证不铺开讲记忆系统的学术分类。我会给出可直接复制的config.toml骨架把模型请求统一走 TaoToken 的 API 通道然后通过两个可观测的动作确认 Memory 是否真的在工作一是看 Condenser 是否在事件数超阈值时触发摘要二是看 ConversationMemory 输出的消息列表里工具调用与响应是否配对完整。适合已经在本地装好 OpenHands、想调优长任务稳定性的开发者。2. TaoToken 前置统一 Key 与 API 通道OpenHands 在运行时会多次调用 LLM主 Agent 决策、Condenser 做历史摘要、可能还有浏览器观察压缩。如果每个环节各配一套 Key调试时很难定位是哪个请求出了问题。TaoToken 提供统一的 API 入口把模型对话、Coding Plan、控制台和 API Keys 管理集中在一个后台本地开发时只需要维护一个 Key。具体来说你需要先拿到一个 API Key。访问控制台创建即可https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite创建后到 API Keys 页面复制完整 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewriteOpenHands 的 LLM 配置走 OpenAI 兼容协议所以 base_url 填https://taotoken.net/api模型名按你实际使用的填。如果你还没确定用哪个模型可以先在模型对话页面试几条长上下文指令观察摘要质量https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite注意API 地址不要加 UTM 参数否则部分 OpenAI SDK 在拼接/chat/completions时可能出问题。官网首页可以加 UTM 用于来源统计但代码里的 base_url 保持干净。如果你打算长期跑编码类 Agent 任务Coding Plan 的额度模型比按次计费更适合反复调试 Condenser 阈值https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite3. 可复制的 config.toml 骨架OpenHands 的配置文件通常放在项目根目录或~/.openhands/下。下面这份骨架把 LLM 通道、Memory 相关参数、Condenser 策略都列出来了。你只需要替换api_key和model两个值。[core] # 工作目录Agent 的文件操作默认限制在这里 workspace_base ./workspace # 单次任务最大步数配合 Memory 观察长任务表现 max_iterations 100 # 缓存目录View 和事件流会落盘 cache_dir ./cache [llm] # 统一走 TaoToken 的 OpenAI 兼容通道 base_url https://taotoken.net/api api_key sk-你的TaoTokenKey model claude-sonnet-4-20250514 # 主 Agent 的温度编码任务建议低一些 temperature 0.2 # 单次请求最大输出 token max_output_tokens 8192 [llm.condenser] # Condenser 单独指定模型摘要任务可以用更便宜的模型 model claude-haiku-3-5-20241022 temperature 0.0 max_output_tokens 2048 [memory] # 是否启用 Condenser 管道 enable_condenser true # 事件数超过这个值触发压缩 max_events 80 # 保留最近的事件数其余进入摘要 keep_first 2 # 浏览器输出观察的最大保留数量 max_browser_outputs 5 # 单条消息最大字符数超出截断 max_message_chars 8000 [memory.condenser_pipeline] # 压缩器执行顺序先窗口限制再 LLM 摘要 condensers [ ConversationWindowCondenser, BrowserOutputCondenser, LLMSummarizingCondenser ] [memory.condenser.llm_summarizing] # 摘要提示词模板文件路径不填用内置默认 prompt_file # 摘要中必须保留的段落标记 preserve_sections [USER_CONTEXT, TASK_TRACKING, CURRENT_STATE]这份配置里最关键的是max_events和keep_first。max_events决定 View 里事件累积到多少条时触发 Condenserkeep_first保证最早的系统消息和初始用户指令不被压缩掉否则 Agent 会丢失任务目标。我试过把max_events设成 40结果摘要触发太频繁反而增加了 LLM 调用次数设成 120 又容易在复杂任务里撑爆上下文。80 左右对多数编码任务比较平衡。4. 验证 Memory 是否生效的两个动作配置写完不代表 Memory 真的在工作。你需要两个可观测的验证动作一个看 Condenser 有没有产出摘要事件一个看 ConversationMemory 输出的消息列表是否结构正确。4.1 动作一观察 CondensationAction 是否出现启动 OpenHands 后跑一个需要多步的任务比如“在当前目录创建一个 Python 项目写三个模块并跑通测试”。任务执行过程中查看事件流日志。OpenHands 会把事件写入cache_dir下的会话文件通常是 JSON 格式。# 找到最新的会话事件文件 ls -lt ./cache/sessions/ | head -5 # 统计 CondensationAction 出现次数 grep -c CondensationAction ./cache/sessions/session_id/events.json # 查看最近一次摘要内容 grep -A 20 CondensationAction ./cache/sessions/session_id/events.json | tail -40如果CondensationAction计数为 0说明事件数还没到max_events阈值或者 Condenser 管道没启用。你可以临时把max_events改成 10 来强制触发验证管道通畅后再调回去。摘要事件里应该包含summary和summary_offset两个字段。summary是 LLM 生成的压缩文本summary_offset指示这个摘要应该插入到事件列表的哪个位置。View 的from_events方法会读取这两个字段把摘要插入到正确位置同时把被压缩的原始事件 ID 加入forgotten_event_ids集合。4.2 动作二检查消息列表的工具调用配对ConversationMemory 的process_events方法负责把 View 里的事件转成List[Message]。这里最容易出问题的是工具调用和工具响应不配对Agent 发起了CmdRunAction但对应的CmdOutputObservation还没产生或者因为压缩导致响应丢失。你可以在 OpenHands 的调试日志里打开DEBUG级别搜索_filter_unmatched_tool_calls的输出。如果看到有消息被过滤掉说明存在不配对的工具调用。# 在本地写个小脚本直接调用 ConversationMemory 验证 from openhands.memory.conversation_memory import ConversationMemory from openhands.core.config import AgentConfig from openhands.utils.prompt import PromptManager config AgentConfig() prompt_manager PromptManager(config) memory ConversationMemory(config, prompt_manager) # 构造一组测试事件一个 Action 加一个对应的 Observation from openhands.events.action import CmdRunAction from openhands.events.observation import CmdOutputObservation action CmdRunAction(commandecho hello) action.id 1 obs CmdOutputObservation(contenthello, commandecho hello) obs.id 2 obs.tool_call_id call_001 messages memory.process_events( condensed_history[action, obs], initial_user_actionNone, max_message_chars8000, vision_is_activeFalse, ) for m in messages: print(m.role, getattr(m, tool_calls, None), m.content[:80])预期输出里应该能看到一条assistant角色的消息带tool_calls紧接着一条tool角色的消息带tool_call_id。如果tool消息缺失说明_filter_unmatched_tool_calls把它过滤了需要检查tool_call_id是否一致。5. 本篇常见错排查5.1 Condenser 不触发事件一直累积最常见的原因是enable_condenser没设成true或者condenser_pipeline里的压缩器名称拼写不对。OpenHands 对压缩器类名是大小写敏感的ConversationWindowCondenser不能写成conversation_window_condenser。另外检查max_events是否设得过大导致任务在触发前就结束了。5.2 摘要后 Agent 丢失任务目标如果keep_first设成 0最早的初始用户消息会被压缩进摘要LLM 摘要时可能丢掉关键约束。建议keep_first至少为 2保留系统消息和第一条用户指令。同时检查preserve_sections是否包含USER_CONTEXT和TASK_TRACKING这两个段落是 Condenser 提示词里强制保留的。5.3 工具调用与响应不配对导致消息被过滤当 Agent 并发发起多个工具调用时如果某个响应还没返回就触发了 CondenserView 里可能只有 Action 没有 Observation。ConversationMemory 的_filter_unmatched_tool_calls会把这类消息整组过滤掉导致 LLM 看不到工具调用历史。排查方法是看日志里pending_tool_call_action_messages字典是否在任务结束时还有残留。如果有说明有工具调用永远没等到响应需要检查工具执行超时设置。5.4 TaoToken 通道返回 401 或 404401 通常是 Key 没复制完整或有多余空格。404 多半是 base_url 写成了https://taotoken.net/api/带了尾部斜杠OpenAI SDK 拼接后会变成//chat/completions。正确写法是https://taotoken.net/api不带尾部斜杠。如果确认 Key 和地址都没问题到接入文档页对照最新的参数说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite5.5 摘要质量差Agent 反复问同样的问题Condenser 用的模型如果能力太弱摘要会丢失关键状态。建议 Condenser 单独配一个模型不要和主 Agent 共用。摘要提示词里已经要求保留CODE_STATE、TESTS、CHANGES等段落但如果你的任务不是编码类这些段落会显得冗余。可以在prompt_file里指定自定义模板把段落改成适合你任务类型的字段。6. 把 Memory 调稳之后下一步做什么Memory 配置调通后你会明显感觉到 Agent 在长任务里的行为更稳定不会重复执行已完成的步骤也不会因为上下文溢出而突然“失忆”。接下来可以做的优化方向有两个一是把 Condenser 的摘要结果持久化到外部存储跨会话复用二是针对特定任务类型自定义 View 的过滤规则把不相关的事件类型提前排除。如果你还在选模型阶段建议先在模型对话页面用长上下文指令测试摘要质量确认模型能稳定输出结构化摘要后再接入 OpenHands。长期跑编码任务的话Coding Plan 的额度模式比按次调用更适合反复调试 Condenser 阈值。接入过程中遇到报错优先对照接入文档检查 base_url 和 Key 格式这两个地方占了本地调试问题的大半。
返回列表