多模态输入、Harness与MCP:Agent开发三大核心实践指南
直接开写。这篇笔记不绕弯子聊的是我最近几个月在搞 Agent 过程中被问得最多、踩得最深的三块东西多模态输入、Harness 结构以及 MCP 接入。如果你正打算从一个“只会调 API 的试用者”往“能设计 Agent 骨架的开发者”迈一步这篇文章应该能帮你省掉不少试错的成本。我先把话放在前面大模型能力再强边界也卡在“信息的输入形式”和“行动的约束范围”这两堵墙上。多模态解决的是第一堵墙MCP 解决的是第二堵墙而 Harness 是你在两堵墙之间搭的脚手架。三样东西分开看不新鲜但放进一个真实可用的 Agent 里环环相扣的问题才会暴露出来。1. 多模态接入让 Agent 从“听懂话”进化到“看得懂活”从纯文本对话转向多模态输入不是一个“把图片塞进去就行”的简单操作。我在这块反复折腾了大半个月过程中逐渐梳理清楚了几个必须决策的层面。1.1 纯文本 Agent 的真实瓶颈不是模型笨是信息被过滤了很多开发者在初期习惯性忽略一个问题用户的问题往往不只是“文字”而是携带着大量的上下文信息。比如用户发来一张截图说“这个页面的登录逻辑有问题”如果你只把这句话丢给模型模型能做的只有猜测和反复追问但如果能把截图内容准确抽取、识别出弹窗文案、校验按钮位置、报错提示的层级关系模型给出的判断就会从“泛泛而谈”变成“可以直接执行”。我试过直接把截图用 base64 塞给支持视觉的模型效果确实有提升但工程上马上遇到两个麻烦第一是 token 消耗急剧上升一张普通截图动辄占几百个 token复杂界面可能要上千第二是模型的“注意力”会被无关内容分散截图上的一块装饰性配图、一段水印都可能把规划器带偏。所以多模态接入的第一个原则就是不要盲目追求“模型自己看全图”而是先做视觉信息的结构化提取。这里的“结构化提取”不是说必须接入专门的 OCR 服务而是说在图像进入模型之前先做一些针对性的预处理裁剪无关区域、放大关键区域、压缩色彩层次。实测下来把一张 1920x1080 的截图先按逻辑区块切分成几个 512x512 的局部图再配合标注好的区块顺序让模型按区块读取理解和规划的准确率比一次性全图输入高了不止一个档次。1.2 视觉语言模型VLM与工具管线的分工现在市面上的主流 VLM 大体分两类一类是 Gemini、GPT-4o、Qwen-VL 这类“端到端图文理解”模型另一类是纯粹的视觉模型比如专门做 OCR、目标识别、图像分割的专用模型。在 Agent 架构里两者的角色完全不同。我个人的分工习惯是理解类任务交给 VLM提取类任务交给专用模型。比如用户上传了一张系统的部署架构图VLM 负责理解“这是一个前后端分离的结构前端有三个模块”但具体的“这张图里有多少个节点、每个节点叫什么名字”我会先调用 OCR 模型把文字提取出来再结合结构信息让 VLM 做归纳。这样的拆分带来了一个额外的好处当工程出问题时你能更快定位到底是“文字没提取出来”还是“模型理解错了”而不是面对一整块黑盒不知所措。这里有一个我踩过的坑早期图省事把一切视觉任务都交给一个 VLM 处理结果在协议理解类的任务上表现还行一到细节提取的任务就频繁出现“幻觉”模型会把不存在的文字内容补充进去而且一本正经地给你加粗说明。后来把任务拆开之后小节失效的比率才降下来。如果让我给一个建议任何视觉任务都不建议跳过预处理直接抛给大模型哪怕只是一个简单的基础框标注。1.3 动态帧采样多模态在视频场景下的工程化路径视频输入是另一个常见的坑。把视频直接丢给多模态大模型无论是转成帧序列还是抽帧token 消耗都会爆炸而且效果往往并不理性。我尝试过几种方案最后找到一个比较平衡的路径先按秒级切帧然后把相邻帧做差异分析相似的帧丢弃只保留关键帧随后关键帧进入 VLM 做场景描述最后把描述结果打包成一个“场景流”交给后续的文本模型做整体推理。这套方案看起来多走了一步但在处理真实业务场景时非常值得。比如用户上传了一段操作录屏问“这里面哪里点错了”按秒切帧会产生几百张图直接喂模型会让上下文迅速膨胀且模型容易丢失前后因果做了关键帧抽选之后输入的图压缩到二三十张模型反而能把“点击顺序-界面变化-报错出现”的因果链串起来。1.4 多模态 Prompt 设计的特殊技巧空间位置描述法还有一处容易忽略的细节多模态模型的“提示词”和纯文本模型的提示词写法逻辑不完全一样。纯文本模型的提示词偏向描述目标和约束而多模态模型的提示词需要额外加入空间关系的锚定。直接在提示词里写“图里左边区域的那个输入框”是一种写法但这种写法对模型的输出稳定性不高。实践中更稳的方式是先用预处理步骤把图片做九宫格分区左上、中上、右上、左中、中心……然后在提示词里明确写“请重点观察中下区域该区域包含目标操作按钮”。这个小小的调整能显著减少模型因为“看偏了”而产生的答非所问。本质上多模态模型的注意力机制虽然强大但依然有明显的偏置我们可以用空间锚定去主动牵引它。2. HarnessAgent 的骨骼而不是简单地“调模型”聊完模型接入接下来是更工程化的部分Harness。这个词现在有不少解释但在我的语境里它就是构建 Agent 时用于约束、编排、追踪大模型调用链路的那个框架层。如果说模型是 Agent 的大脑Harness 就是连接大脑与身体动作的神经系统它决定了模型的输出如何转成可执行的实际步骤。2.1 为什么不能只靠“一个 while 循环”很多人初写 Agent 时最大的幻觉是以为只需要一个循环调模型、拿输出、执行工具、把结果再接回去。表面上这个过程能跑通 demo但它会在三个点上彻底失灵状态丢失、工具权限失控、无法定位失败链路。举个我曾经跌进去的例子Agent 在交互过程中需要分三步完成一个任务第一步调用 A 工具拿到一个临时凭证第二步拿着凭证去调用 B 工具第三步依赖前两步的结果组装一份报告。如果用裸循环实现第二步一旦失败就需要重跑整个任务且第一步生成的凭证已经失效整个链路就卡死了。而 Harness 提供的核心价值是明确的状态管理、确定性的工具调度顺序、以及可观察的调用日志。Harness 的核心职责在我看来有四个维护会话级状态session state、约束工具调用边界permission boundary、调度步骤级执行顺序orchestration、提供完整的调用观测observability。这四个职责缺一不可。否则你就是在用“胶带纸”粘一个随时可能散架的机器人。2.2 会话状态与会话长度Harness 中状态管理的实操设计状态管理是 Harness 里最容易被忽略、但绝对是最核心的模块。纯写代码的人容易把状态直接存在全局变量里这种做法在临时脚本里没有大碍一旦接入真实业务多用户并发、多轮对话、中途断连重连全局变量马上成为灾难。我最终采用了比较传统的方案把会话状态持久化到独立的存储层基于 JSON 文档结构每次模型调用之前从存储中加载完整上下文每次工具执行完成之后立刻把变更写回存储。这个模式的优点是可以随意横向扩展不依赖单机内存但代价是 IO 次数增加。为了缓解这部分开销我做了两层优化一是在内存中维护一份热数据缓存读写时优先操作缓存仅在关键节点强制落盘二是把会话中的大字段如图像的 base64、长文档全文拆分到单独的对象引用中会话状态自身只保存引用路径避免每次会话恢复时的反序列化开销。一个经验性的建议所有会话状态必须设计成可序列化结构不要在状态对象里塞函数、塞活跃连接、塞临时文件句柄。我遇到过 Agent 崩溃后恢复时状态对象里存了一个打开的文件读写器反序列化直接失败整个任务只能从零开始。现在我的规则很粗暴会话状态里只允许出现 JSON 原生类型。2.3 工具调度顺序看似自由的 Agent 也需要步骤确认第二道关键关卡是工具调用的顺序控制。大模型在生成行动计划时本质上是在做概率选择它可能漏掉一个必要步骤也可能跳过中间校验。Harness 的作用是要在“模型的自由度”和“流程的确定性”之间做平衡。我通常把工具调用方式分成两类一类是必然步骤required step无论模型怎么规划都必须按照预设顺序执行比如登录凭证校验、权限确认、输入参数格式校验另一类是可生成步骤generated step模型可以按任务需求自主选择。这个分离极其重要。如果所有步骤都强制使用“自由工具调用”function calling模型可能因为一些微小的原因跳过关键技术导致下游执行出现奇怪的错误。在实践中我用了一个很简单的策略把整个 Agent 的执行编排定义为一个“步骤声明表”在表中声明每个步骤是 required 还是 optional并标明前置依赖。模型只负责在 optional 步骤中进行规划required 步骤由 Harness 在执行流中强制插入。这看起来牺牲了一点“智能感”但换来的是任务成功率的显著提升。让 Agent 完全自由地决定执行路径在一个真正复杂的工作流里几乎等于失控。2.4 Harness 的可观测性没有日志追踪的 Agent 就像黑盒子第三个常被新手忽略的是 Harness 的可观测性。很多人在开发 Agent 的时候最容易烦躁的就是模型到底为什么调用了这个工具这个工具的返回数据为什么和预想的不一致如果 Harness 层不记录完整的调用链排查这类问题只能靠猜。我在 Harness 内部固定记录三类日志模型决策日志模型为什么选择这个方法目标的原始输入是什么、工具输入输出日志进入工具之前的完整参数以及工具返回的原始结果、状态变更日志每一步执行完会话状态增加了什么、修改了什么。这三类日志拼接起来之后整个 Agent 的行为逻辑就完全可回溯了。每次 Agent 执行结果与预期不符第一件事永远是拉日志看清是决策错、执行错还是预期本身错。3. MCP把“接工具”从“写胶水代码”变成“定义协议”多模态和 Harness 解决的是 Agent 的“输入”和“执行框架”MCP 解决的则是“Agent 怎么跟外部世界握手”。没有接触过 MCP 的人第一次听到它容易觉得这又是一个新框架其实它的核心思想特别朴素定义一套统一的标准让任何 Agent 都能通过同一种方式调用不同的外部服务。3.1 没有 MCP 之前接一个工具要付出多少代价我先回忆一下没有 MCP 时接一个工具的过程。假设要让 Agent 能查询某个业务系统的订单数据传统做法是写一个自定义的 Function Calling 描述把接口参数解释成 JSON Schema再写一段代码负责把模型的输出映射成 HTTP 请求然后处理鉴权、超时、错误重试。一个工具一套写法十个工具十套写法。更头疼的是换一个模型提供商这套映射逻辑可能完全失效因为不同模型对函数定义格式的支持存在差异。MCP 出现后把这一整套逻辑统一了。MCP 的核心概念非常简单Agent 侧安装一个 MCP Client工具提供方实现一个 MCP Server两者通过标准化的 JSON-RPC 协议通信。工具的定义、调用、结果返回、错误处理全部走同一种格式。Agent 再也不需要知道工具背后的实现细节只需要知道“这个 MCP Server 提供了哪些工具”就能一键对接。用一个生活化的类比MCP 之前的对接方式就像给每种电器都配一个专用插座电压不同、接口不同、正负极也不同MCP 之后的对接方式就像统一了 USB-C 接口只要产品支持标准任何充电器都能给任何设备供电最多是功率大小有差别但协议层完全统一。3.2 Agent 消费 MCP 的接入步骤一个可复制的实操路径在实际接入中Agent 消费 MCP Server 的路径可以拆成几个环节第一步通过工具发现Tools/List接口拿到 Server 提供的工具清单。这个清单不仅包含工具名称还包含完整的输入参数描述。值得注意的坑是不同 Server 对参数描述的详细程度差异很大。有的 Server 把参数描述写得非常仔细Agent 能直接根据描述生成合理参数有的 Server 则只写了参数名没有给出具体约束规则Agent 就只能靠猜。所以在选型外部 MCP Server 时参数描述的信息密度是一个关键考量指标。第二步根据任务目标构造工具调用请求Tools/Call把参数按照 JSON Schema 组织好发送出去。这里有一个工程细节不要在同一个上下文中堆叠太多工具描述信息。如果把 20 个工具的完整定义全部塞进提示词模型在工具选择时的判断力会显著下降。建议的方式是做“工具分组”根据任务类型提前预选一组相关工具只把这组工具的定义提供给模型。第三步处理结果集Tools/Result。MCP 返回的结果通常是标准化的 content 块里面既可以有纯文本也可以有图片或结构化数据。Agent 需要根据参数判断这段返回应该直接展示给用户还是作为中间过程转给下一个工具。一个容易被忽略的点是MCP 的结果并不保证是“人类可读”的有些 Server 返回的是原始内部结构需要 Agent 提前在其描述里明确自己对结果的解读要求。3.3 MCP 配置中的三类典型工程问题与解决记录配置 MCP 的过程中我遇到过的典型问题大体分三类第一类是并发安全。多个 Agent 实例同时使用同一个 MCP Server 的连接时如果 Server 端没有做连接管理会出现消息串号的情况。表现是一个 Agent 的请求结果被另一个 Agent 接收导致上下文混乱。解决方案是为 Agent 会话绑定独立的 Client 实例或者引入更简单的串行队列确保一个连接同一时间只处理一个请求。实测并发请求数在个位数级别时不做锁处理也能跑通但一旦上升到并发任务调度必须加状态隔离。第二类是模型对工具参数的命名猜测。有的工具定义不够清晰模型会自行脑补参数名结果工具调用时报参数不存在。这个问题的根源在于 Server 的工具描述不完整不完全是模型的问题。解决的方法是先用一个小模型跑一次工具发现把发现的工具定义“翻译”成更详细的自然语言描述再把它作为 Agent 的上下文提示让模型在调用前拥有充分的参数理解。第三类是Server 超时或无响应。如果某个 MCP Server 依赖外部网络而外部网络有延迟整个 Agent 的执行就会被卡住。我的处理方式是在 Harness 层为每个 MCP 调用设置独立的超时控制并配套降级策略。如果工具调用失败Agent 不是直接中断而是自动尝试两次重试重试仍失败则跳过该步骤并在总结中明确标注“该环节未能执行”把决策权交还给用户或上级编排逻辑。4. 三者协同一次完整的多模态 Harness MCP 全链路复现分开讲完三个技术点我们再把它全部拼到一个真实场景中。假设现在要做一个 Agent任务目标用户上传一张界面截图Agent 通过理解截图内容调用某个外部 MCP Server 上的工具把界面中的报错信息替换成修改建议并生成一份报告。这个场景用到了上面提到的所有知识点我来完整拆一遍执行链路。4.1 全链路流程的事件时间线用户上传图片后Agent 的事件时间线大致是这样的事件一多模态预处理阶段Harness 接收图片执行图片切割、关键区域放大、OCR 文字提取。提取到的文字先存储为结构化对象与图像切片共同组成一个多模态输入包。事件二视觉理解阶段Harness 调用 VLM输入设计好的空间锚定提示词输出对截图的语义描述。模型识别出这是一个“登录失败的报错界面”并定位到具体报错文案的所在区域。事件三规划阶段Harness 将视觉理解的输出与任务目标一起交给文本模型生成行动计划。模型根据需求判断需要调用“查询错误码字典”这个外部工具而这个工具正是通过 MCP Server 暴露的。事件四工具调用阶段Harness 检查计划中的步骤确认查询错误码是“必然步骤”插入到执行序列中。随后通过 MCP Client 向 Server 发送工具调用请求传参为从截图中提取到的错误码字符串。事件五结果处理阶段MCP Server 返回错误码对应的解释与修改建议。Harness 把它存储在会话状态中并作为上下文再交给模型让模型基于建议生成面向用户的最终报告。事件六状态落盘与日志记录整个任务完成会话状态更新调用链日志写入存储。用户不仅拿到了报告还能清楚看到 Agent 每一步的执行依据和中间结果。4.2 关键执行配置一份结构化的会话与调用配置参考因为不同团队的技术栈不同我这里不附具体代码而是给出一个通用的 JSON 配置结构设计。凡是实现过类似系统的开发者应该能很快映射到自己的代码里。{ session: { id: sess_2025_xxxx, status: running, storage_ref: path/to/storage, state_snapshot: { extracted_blocks: [block_a, block_b], current_step: tool_calling, pending_plan: [ocr_extract, visual_understand, call_error_code_service, generate_report] } }, harness_policy: { required_steps: [ocr_extract, error_code_validate], allowed_tools: [query_error_code, search_solution], permission_model: confirm_on_unknown, timeout_ms: 15000 }, mcp_config: { server_endpoint: https://mock-server/mcp, transport: streamable-http, selected_tools: [query_error_code, search_solution], retry_policy: { max_retries: 2, backoff_ms: 1000 } }, multimodal_config: { image_preprocess: block_split, anchor_mode: grid_9, vlm_model: vision_model_name, max_input_tokens: 4096 } }这份配置里最核心的设计点是required_steps区分“模型可自由编排”和“必须由 Harness 强制插入”的步骤避免模型因为上下文理解偏差而漏掉关键动作。permission_model: confirm_on_unknown表示当模型请求调用一个不在allowed_tools列表内的工具时Harness 不直接放行而是暂停执行并提示用户确认。这个设置对生产环境至关重要。image_preprocess: block_split表示图像必须经过区块化处理再进入模型就算后续想换成实时视频流也只是扩展预处理模块的问题不触发架构级改动。mcp_config下的selected_tools限定当前会话只能使用一组固定工具这个限制有效避免了模型在大量工具定义之间“挑花眼”的情况。4.3 逐阶段检视三个层面如何衔接不脱节整个流程看起来顺滑但实际落地时三个层面最容易脱节的位置依次是一层脱节多模态识别结果与规划器的衔接。OCR 提取出错误码“E12345”但这个错误码以什么字段名传给规划器如果字段名不明确规划器可能把它当作普通文本而不是可查询的键值。我的做法是在多模态输出阶段就规范为“结构化数据包”规定必须包含error_code: string字段规划器直接消费该字段不依赖自然语言描述。二层脱节规划器的计划标注与 Harness 步骤声明的对应。模型生成的计划可能叫“查询错误消息”但 Harness 步骤表里声明的是“error_code_validate”。这需要在规划器和 Harness 之间做一层术语映射否则 Harness 无法判断模型是否在尝试执行流程内步骤。我的建议是提前做一个“别名映射表”把模型可能的描述方式映射到统一的步骤 ID。三层脱节MCP 工具返回值对模型可读性的转译。有些工具返回内部状态码模型并不认识有些返回大段 JSON会干扰模型对后续步骤的判断。可以在 MCP Client 层加一个“转译器”把原始返回格式转换成自然语言摘要再喂给模型。这个转译器本身不复杂但能显著提升下一轮决策的准确度。这三层脱节表面上是技术细节但本质上对应了同一个原则Agent 不是“一个大模型包打天下”而是多个模块拼接出来的系统模块之间的接口契约必须显式化。5. 常见问题与排查技巧实录来自一线调试的速查笔记最后一部分整理我在实际调试中遇到频率最高的几个问题。这些问题网上资料往往只讲理论我想把现场排查的亲历感受写出来方便大家遇到类似问题时能快速锁定原因。5.1 多模态 Token 爆炸一次无意识的冗余输入教训场景某次调试中Agent 在读取用户上传的长截图时直接把原始大图塞给了 VLM导致单次请求 token 消耗接近 3 万。排查时发现除了图片本身大系统还把多张相似切片重复送入上下文模型在推理时需要反复“翻看”重复内容响应时间直接翻倍。解决思路很直接在预处理阶段增加相似度去重。具体做法是计算相邻切片的感知哈希相似度超过阈值的切片直接丢弃只保留变化最大的关键帧。这个技巧对长图和多图场景效果尤其明显。实操建议是给多模态模块加一个“token 预算上限”如果预处理后的输入仍然超出预算先按清晰度降级压缩而不是直接报错。5.2 Harness 崩溃恢复会话状态里的“函数指针陷阱”场景Agent 在长任务执行中途进程因为外部资源不足崩溃。重启后从存储恢复会话状态结果反序列化失败。原因查找过程发现状态对象里存了一个函数引用导致 JSON 序列化/反序列化流程中断。这是个典型错误但非常隐蔽。我的排查思路后来总结成三步先检查状态对象里的所有字段类型是否都是纯 JSON 类型再检查是否引用了无法持久化的资源打开的文件句柄、数据库连接、临时文件路径最后检查反序列化后的对象是否还带有原有的上下文标识。永远不要相信“内存里能跑”的对象就是“可恢复”的对象。5.3 MCP 调用超时上游慢、下游卡、Agent 跟着堆场景Agent 调用某个 MCP Server 上的“查询天气”工具偶尔出现长时间无响应整个任务被卡住。排查时发现Server 端依赖了一个缓慢的第三方数据源在数据源延迟时Server 本身也不设置响应超时于是 Agent 侧就无限等待。我的处理分两层在 MCP Client 侧设置硬性超时我一般用 10 秒起步根据业务验证调整超时后记录错误日志并按重试策略处理在 Server 侧如果是自己实现的服务建议在工具实现里也设置内部任务超时负责兜底。一个更防御性的做法是在工具定义里明确声明timeout_hint字段让 Agent 在选择调用前就知道这个工具可能较慢然后决定是同步等待还是异步轮询。5.4 工具调用权限误判当 Agent 想做“计划外”的事场景模型在生成计划时决定调用一个并不在预选工具列表里的工具。如果 Harness 直接拦截并报错Agent 任务会失败如果 Harness 直接放行又可能引入未预期的行为。我最终采用的是“分级策略”不在白名单中的工具分为“可自动批准”“需用户确认”“完全禁止”三档。对低风险、幂等性强的工具如查询类可自动批准对能改变状态的工具如创建订单、删除资源一律要求人工确认对涉及敏感数据的工具直接禁止放行。这个分级策略可以通过 Harness 的配置轻松调整是最能平衡“智能性”与“安全性”的实践方案。5.5 排查问题的最优顺序一个固定的排错路径在多次踩坑后我沉淀了一个固定的排查顺序分享给大家先看 Harness 日志确认模型的每一步决策依据是什么这一步能确定“是不是模型选错了工具或者漏掉了一个步骤”再看工具输入输出日志确认进入工具的数据是否符合预期这一步能确定“是不是参数传错了或者工具返回了异常结果”最后再查会话状态快照确认状态在哪个环节发生了偏差。如果状态在预期内那大概率是模型决策质量的问题需要调整提示词或增加约束如果状态本身与预期不符那就是 Harness 编排逻辑的问题需要检查调度代码或状态更新时机。这套顺序救了我很多次至少比一头扎进代码里漫无目的地找要高效得多。写在最后一点实操体会这三块东西如果分开看每一个都能找到现成的库和文档但真正决定 Agent 好用不好用的是它们之间的“接缝”处理得是否精细。我个人在实际开发中的体会是与其花大量时间去追新模型、新特性不如先把现有链路里的每一段接缝打磨好。多模态的预处理干净了Harness 的状态不丢不乱MCP 的工具边界清楚纵然模型能力原地踏步Agent 的整体表现都会明显上一个台阶。最后再分享一个经验刚开始搞 Agent 的开发者总想把所有能力都装进去模型选最强的工具接最多的流程整最复杂的结果一个都跑不稳。我现在的习惯是先“做窄再拓宽”拿一两个高频率场景切入把完整链路跑通跑稳再考虑扩展。这个内容后续扩展的方向其实也清晰像多模态可以加实时音视频输入Harness 可以做成支持多 Agent 协作的调度中枢MCP 可以多接几类企业级服务但底层的接缝处理好才能有拓展的余地。