ARTICLE DETAIL

资讯详情

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

告别“上下文爆炸”!深度拆解 LMM-Searcher:让多模态 Agent 拥有长程搜索的“过目不忘”之能

告别“上下文爆炸”!深度拆解 LMM-Searcher:让多模态 Agent 拥有长程搜索的“过目不忘”之能 1. 多模态 Agent 长程搜索为什么总在“上下文爆炸”上翻车如果你正在做多模态 Agent 的长程搜索大概率遇到过这个场景让 Agent 去查一份跨十几页的图文报告前几轮还正常到第十几轮突然报 OOM或者响应慢到不可用再或者模型开始“胡编”——明明图里没有的数据它硬说看到了。这不是模型不够聪明而是上下文窗口被视觉 Token 撑爆了。LMM-Searcher 这个框架值得拆是因为它把“感知”和“推理”做了物理隔离图片不直接进主上下文而是存到外部文件系统只留一个轻量文本存根UID Caption。Agent 平时只看存根做规划真需要看细节时才调用fetch-image工具按需加载。这套思路把长程搜索的有效轮次从个位数拉到 100 轮级别同时把视觉 Token 成本压掉一个数量级。这篇文章面向三类人正在搭多模态 RAG/Agent 的工程师、被上下文长度卡住的产品开发者、想复现长程搜索链路的研究者。我会给出可复制的config.toml骨架、TaoToken 统一 Key/API 通道配置、一轮多模态长程检索的验证动作以及我踩过的几个坑。你跟着做能在本地跑通一条“过目不忘”的搜索链路。2. 前置准备TaoToken 统一 Key 与 API 通道在复现 LMM-Searcher 之前先把模型调用通道理顺。多模态长程搜索会频繁调用两类模型一类是轻量 VLM 做 Caption 生成便宜、快一类是主脑模型做推理和工具调度贵、需要长上下文。如果每个模型都单独配 Key、单独处理计费调试成本会很高。TaoToken 在这里的作用是提供一个统一的 API 通道把不同模型的调用收敛到一套 Key 和一套 base_url 上。你只需要在配置里改模型名不用改调用代码。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个地址不加 UTM 参数直接用于代码里的 base_url。具体操作分三步。第一步去控制台创建 API Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建后在 API Keys 页面复制页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。第二步如果你想先验证模型通不通用模型对话页面发一条测试消息地址 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。第三步如果你打算长期跑编码类 Agent 任务可以看 Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。注意Key 只存在环境变量里不要写进config.toml提交到 Git。下面配置里用${TAOTOKEN_API_KEY}占位。3. 可复制配置config.toml 骨架与视觉解耦参数这一节是核心。LMM-Searcher 的工程落地本质是把“图片进上下文”改成“存根进上下文 工具按需拉取”。下面这份config.toml骨架覆盖了模型通道、视觉解耦网关、渐进式加载工具、上下文预算四个部分。你可以直接复制改掉模型名和路径就能跑。# config.toml - LMM-Searcher 本地复现骨架 [api] # TaoToken 统一通道所有模型调用走这里 base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} timeout_seconds 120 max_retries 3 [models] # 主脑负责长程推理与工具调度需要长上下文 brain qwen3-vl-plus # 轻量 VLM只做 Caption 生成越便宜越快越好 captioner qwen-vl-flash # 视觉问答fetch-image 拉回高清图后做细粒度 VQA vqa qwen3-vl-plus [vision_decoupler] enabled true # 图片存到本地文件系统不占上下文 storage_dir ./vision_store # 存根格式UID 全局唯一 stub_template VisualStub UID{uid} Caption{caption} / # Caption 最大长度控制存根 Token caption_max_tokens 40 # 超过这个尺寸的图才做解耦小图标直接忽略 min_image_bytes 20480 [progressive_loading] enabled true tool_name fetch-image # 单轮最多拉几张高清图防止一次拉太多又爆 max_fetch_per_turn 2 # 拉回的图只挂载当前轮次下一轮强制丢弃 ephemeral true # 触发条件模型在 thinking 中显式请求 trigger explicit_tool_call [context_budget] # 主上下文最大 Token留出余量给工具返回 max_tokens 96000 # 存根占用的预算上限超过就触发摘要压缩 stub_budget 8000 # 历史轮次保留策略只保留存根丢弃已加载的视觉块 history_policy stub_only [search] max_turns 100 top_k_per_turn 5配置里有三个参数最容易调错。caption_max_tokens设太大存根本身就变成新的上下文负担设太小模型判断不出该不该拉图。我实测 40 左右比较平衡。max_fetch_per_turn设成 2 是防止模型“贪心”一次拉五张图回来等于没解耦。history_policy stub_only是关键它保证已加载的高清视觉块在下一轮被强制移出历史只留存根。对应的视觉解耦网关逻辑用 Python 写出来大概是这样# vision_decoupler.py import hashlib, os, json from pathlib import Path class VisualDecoupler: def __init__(self, cfg): self.store Path(cfg[vision_decoupler][storage_dir]) self.store.mkdir(parentsTrue, exist_okTrue) self.caption_max cfg[vision_decoupler][caption_max_tokens] self.min_bytes cfg[vision_decoupler][min_image_bytes] def _uid(self, raw: bytes) - str: return img_ hashlib.sha1(raw).hexdigest()[:10] def intercept(self, element, captioner): # 纯文本直接放行 if element[type] ! image: return element[text] raw element[bytes] if len(raw) self.min_bytes: return element.get(alt, ) uid self._uid(raw) # 物理隔离原图落盘不占上下文 (self.store / f{uid}.bin).write_bytes(raw) # 知识压缩轻量 VLM 生成极简 Caption caption captioner.generate( raw, prompt用一句话描述这张图的主题不超过40字 )[: self.caption_max] # 只把存根塞进主上下文 return fVisualStub UID{uid} Caption{caption} /这段代码的要点是原图落盘后主上下文里只剩一行几十 Token 的存根。100 轮搜索下来存根累计也就几千 Token而传统做法早就几十万 Token 爆了。4. 验证请求跑通一轮多模态长程检索配置写完得验证链路真的通。我设计了一个最小验证场景给 Agent 一个跨页图文查询任务观察它是否在需要细节时才调用fetch-image以及上下文 Token 是否稳定。先写一个验证脚本模拟三轮检索。第一轮抓到一个含图表的页面第二轮模型基于存根推理发现信息不足第三轮触发fetch-image拉高清图做 VQA。# verify_longhorizon.py import os, json, requests BASE https://taotoken.net/api KEY os.environ[TAOTOKEN_API_KEY] HEADERS {Authorization: fBearer {KEY}, Content-Type: application/json} def chat(model, messages, toolsNone): payload {model: model, messages: messages} if tools: payload[tools] tools r requests.post(f{BASE}/chat/completions, headersHEADERS, jsonpayload, timeout120) r.raise_for_status() return r.json() # 模拟第一轮网页含图表被解耦成存根 stub VisualStub UIDimg_a1b2c3 Caption2025各车企毛利率对比柱状图 / messages [ {role: system, content: 你是长程搜索 Agent只基于存根规划需要细节时调用 fetch-image。}, {role: user, content: f请计算图中毛利率最高的车企。当前证据{stub}} ] tools [{ type: function, function: { name: fetch-image, description: 当文本摘要不足以回答细节时输入 UID 获取高清原图, parameters: { type: object, properties: {uid: {type: string}}, required: [uid] } } }] resp chat(qwen3-vl-plus, messages, tools) choice resp[choices][0][message] print(第一轮输出:, json.dumps(choice, ensure_asciiFalse, indent2)) # 如果模型请求了工具模拟拉图并回填 if choice.get(tool_calls): call choice[tool_calls][0] uid json.loads(call[function][arguments])[uid] print(f触发 fetch-imageUID{uid}) messages.append(choice) messages.append({ role: tool, tool_call_id: call[id], content: f已加载 {uid} 高清图图中最高毛利率为 18.5%车企B }) final chat(qwen3-vl-plus, messages) print(最终答案:, final[choices][0][message][content])跑通后你会看到两个关键信号。第一第一轮模型没有直接编答案而是输出了tool_calls说明它学会了“摘要不够就拉图”。第二最终答案基于回填的高清信息得出而不是靠猜。这就是渐进式加载在起作用。如果你想先确认模型通道本身没问题可以先用模型对话页面发一条带图的消息地址 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 确认返回正常再跑脚本。接入细节和参数说明在文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。验证成功的标准有三个一是tool_calls在需要细节的轮次被触发二是上下文 Token 随轮次增长平缓存根为主三是最终答案能引用高清图中的具体数值。三个都满足说明你的解耦链路是通的。5. 本篇常见错排查复现过程中我踩过几个坑列出来帮你省时间。报错一fetch-image从不被触发。模型一直基于 Caption 硬答哪怕信息不足。原因通常是系统提示词没写清楚“摘要不足时必须调用工具”或者 Caption 写得太详细模型觉得够了。解决把 Caption 限制在 40 Token 以内同时在 system prompt 里明确“若 Caption 不含具体数值必须调用 fetch-image”。报错二上下文还是爆。检查history_policy是否设成了stub_only。如果设成full已加载的高清视觉块会留在历史里几轮下来照样爆。另一个可能是max_fetch_per_turn设太大模型一轮拉五张图等于没解耦。报错三UID 找不到。fetch-image返回Image UID not found。多半是storage_dir路径不对或者进程重启后临时目录被清了。生产环境要把storage_dir指到持久化磁盘别用/tmp。报错四Caption 生成太慢。如果 captioner 用了大模型每张图都要等好几秒长程搜索会被拖垮。captioner 一定要用轻量 VLM比如qwen-vl-flash这类速度优先。报错五API 返回 401 或超时。先确认TAOTOKEN_API_KEY环境变量已导出再确认base_url是https://taotoken.net/api而不是带 UTM 的官网地址。如果还不行去 API Keys 页面重新生成一个 Key地址 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。报错六多轮后模型开始重复调用同一张图。这是模型没学会“阅后即焚”。在工具返回里加一句“该图信息已提取无需重复加载”并在下一轮历史里确保该视觉块已被移除。6. 从验证到长期运行把链路跑稳单轮验证通过后下一步是让它能长期跑。这里有两个实用技巧。第一给存根加一个轻量的去重层。同一张图在多次搜索中可能被反复抓到如果每次都生成新 UID存根会膨胀。用图片内容的哈希做 UID天然去重同一张图只存一份、只生成一个存根。第二监控上下文 Token 曲线。在每轮结束后打印messages的总 Token 估算值正常应该随轮次缓慢上升存根累积而不是阶梯式暴涨视觉块残留。一旦发现暴涨立刻检查history_policy和工具返回的处理逻辑。如果你打算把这个链路接到编码类 Agent 或长期运行的自动化任务上可以用 Coding Plan 的通道地址 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合持续调用场景。ClaudeCode 相关的接入说明在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 需要的话可以对照配置。最后说一个我自己的经验LMM-Searcher 这套思路的价值不在于某个具体实现而在于“感知与推理物理隔离”这个原则。你完全可以把图片换成音频切片、视频关键帧、3D 点云只要遵循“重资产落盘、轻存根进上下文、按需拉取”这三步长程搜索的上下文爆炸问题就能被系统性压住。先把上面那份config.toml跑通再按你的数据模态替换解耦网关链路就立起来了。
返回列表