☰
端侧Agent工程化实战:Function Calling与MCP的Schema设计与容错策略
2026/10/5 22:27:34 网站建设 项目流程

1. 端侧 Agent 工程化的核心命题

1.1 从 Demo 到产品:端侧 Agent 的工程化鸿沟

很多人在端侧跑通第一个 Agent Demo 的时候都会有一种错觉:这东西已经成了。本地模型加载起来,Function Calling 调通一两个工具,命令行里问一句“帮我查一下明天天气”,模型返回一个结构化的 JSON,工具执行完把结果塞回去,模型再吐出一段自然语言——整个链路跑通了,感觉离产品就差一个 UI。

但真正把端侧 Agent 往产品方向推的时候,问题会成倍地冒出来。模型在 PC 上跑得好好的,到了手机端内存直接爆掉;Function Calling 在单轮对话里没问题,一旦进入多轮、多工具、带状态的场景,模型开始胡编参数;JSON Schema 写得稍微复杂一点,小模型的输出就开始不稳定,该填字符串的地方给你填了个对象,该用枚举的地方给你编了一个不存在的值。这些问题在 Demo 阶段都可以靠“换个 prompt”糊过去,但在工程化阶段,每一个都是必须系统性解决的硬骨头。

端侧 Agent 工程化要解决的核心矛盾,其实就一句话:在算力、内存、功耗都受限的端侧环境里,让一个能力有限的小模型稳定地完成复杂的工具调用任务。这个矛盾决定了端侧 Agent 的工程化思路和云端 Agent 有本质区别。云端 Agent 可以堆模型参数、堆上下文长度、堆并发,端侧不行,端侧每一兆内存、每一次推理延迟都要精打细算。

我自己的体会是,端侧 Agent 的工程化难点集中在三个层面:协议层(Function Calling 的格式定义与约束)、调度层(多工具、多轮次的状态管理与编排)、运行时层(模型加载、内存管理、推理加速)。这三个层面里,协议层是地基,调度层是骨架,运行时层是血肉。这一篇主要聊协议层和调度层,也就是 Function Calling 和 MCP 相关的工程化实践,运行时层的内容留到下一篇展开。

1.2 为什么 Function Calling 是端侧 Agent 的命门

Function Calling 这个概念本身不复杂,就是让模型输出一段结构化的内容,告诉外部系统“我要调用哪个函数、传什么参数”。但在端侧环境里,这件事的难度会被放大好几倍。

原因在于,端侧跑的多半是 1B 到 7B 级别的小模型,这些模型的指令遵循能力和格式化输出能力远不如云端的大模型。你让 GPT-4 输出一个符合 JSON Schema 的对象,它基本不会出错;但你让一个 3B 的量化模型做同样的事,它可能会在 JSON 外面包一层解释文字,可能会把字段名拼错,可能会在数值字段里填一个字符串,甚至可能直接编造一个 Schema 里不存在的字段。

所以端侧 Agent 的 Function Calling 工程化,核心不是“怎么让模型调用工具”,而是“怎么让模型在能力受限的情况下,尽可能稳定地输出符合预期的结构化内容”。这里面涉及到 Schema 设计、Prompt 约束、输出解析、错误恢复等一系列工程手段,每一个环节都有很多细节可以抠。

我见过不少团队在这个环节踩坑,最常见的就是直接把云端 Agent 的那套 Schema 搬到端侧,结果小模型根本扛不住那么复杂的结构,输出成功率惨不忍睹。正确的做法是反过来,先摸清楚端侧模型的能力边界,然后在这个边界内设计尽可能简单的 Schema,再用工程手段去兜底。

1.3 MCP 在端侧 Agent 里的定位

MCP(Model Context Protocol)这两年被讨论得很多,它的核心价值是给模型和外部工具之间定义了一套标准化的通信协议。在云端 Agent 场景里,MCP 解决的是“工具生态碎片化”的问题——不同的工具提供方各自定义接口,Agent 开发者要一个个适配,成本很高。有了 MCP,工具提供方按协议暴露能力,Agent 侧按协议调用,双方解耦。

但在端侧 Agent 场景里,MCP 的定位需要重新思考。端侧 Agent 的工具集通常是固定的、有限的,不像云端 Agent 那样需要动态发现和接入大量第三方工具。所以端侧引入 MCP,更多是为了统一内部的工具调用抽象,让 Agent 的核心逻辑和具体工具实现解耦,方便后续替换和扩展。

举个例子,你在端侧做了一个支持“查天气、设闹钟、发消息”三个工具的 Agent,如果不用 MCP,你可能在代码里硬编码三个函数的调用逻辑;如果用 MCP,你可以把这三个工具都封装成 MCP Server,Agent 侧只负责按协议发请求,具体工具怎么实现、用什么语言实现、跑在哪个进程里,都不影响 Agent 的核心逻辑。这样一来,后续要加新工具、要换工具实现,改动量会小很多。

不过端侧引入 MCP 也有代价,主要是协议本身的开销。MCP 的通信基于 JSON-RPC,每次调用都有序列化和反序列化的成本,在端侧这种资源紧张的环境里,这个开销不能忽略。所以我的建议是,端侧 Agent 是否引入 MCP,要看工具集的规模和变化频率。如果工具就三五个且基本不变,直接硬编码可能更划算;如果工具有十几个且经常增删,那 MCP 带来的解耦收益就值得那点协议开销。

2. Function Calling 的 Schema 设计与约束策略

2.1 JSON Schema 在端侧的精简原则

JSON Schema 是 Function Calling 的基础,它定义了模型可以调用的函数长什么样、参数是什么类型、哪些是必填的。在云端场景里,Schema 可以写得很详细,字段描述可以很长,枚举值可以很多,因为大模型有足够的上下文窗口和理解能力去消化这些信息。

但端侧不行。端侧模型的上下文窗口通常只有 2K 到 8K,Schema 本身就要占掉一部分;端侧模型的理解能力有限,Schema 里的描述文字太长反而会干扰它的判断。所以端侧 Function Calling 的 Schema 设计,核心原则就是精简。

具体怎么精简,我总结了几个实操要点。第一,字段描述能短则短,不要写“请填写用户想要查询的城市名称,支持中文和英文”,直接写“城市名”就够了。第二,枚举值不要太多,超过五个枚举值的字段,考虑改成字符串让模型自由填写,然后在外部做校验和映射。第三,嵌套结构能扁平就扁平,端侧模型处理嵌套对象的准确率明显低于扁平结构。第四,必填字段尽量少,非必要字段都设成可选,减少模型漏填导致的调用失败。

下面是一个对比示例,左边是云端风格的 Schema,右边是端侧精简后的 Schema:

// 云端风格,字段描述详细,枚举值多 { "name": "search_flight", "description": "搜索符合条件的航班信息,返回航班列表", "parameters": { "type": "object", "properties": { "departure_city": { "type": "string", "description": "出发城市名称,支持中文或英文,例如北京、上海、Beijing" }, "arrival_city": { "type": "string", "description": "到达城市名称,支持中文或英文" }, "date": { "type": "string", "description": "出发日期,格式为YYYY-MM-DD" }, "cabin_class": { "type": "string", "enum": ["economy", "premium_economy", "business", "first"], "description": "舱位等级" } }, "required": ["departure_city", "arrival_city", "date"] } }
// 端侧精简,描述短,枚举少,结构扁平 { "name": "search_flight", "description": "搜索航班", "parameters": { "type": "object", "properties": { "from": {"type": "string", "description": "出发城市"}, "to": {"type": "string", "description": "到达城市"}, "date": {"type": "string", "description": "日期YYYY-MM-DD"}, "cabin": {"type": "string", "description": "舱位:经济/商务/头等"} }, "required": ["from", "to", "date"] } }

精简后的 Schema 在端侧模型上的调用成功率,实测下来比云端风格的高出不少。原因很简单,模型要处理的信息少了,出错的概率自然就低了。

2.2 参数类型的选择与陷阱

端侧 Function Calling 里,参数类型的选择有很多讲究,选错了类型会直接导致模型输出不稳定。

字符串类型是最安全的,端侧模型对字符串的处理能力最强,几乎不会出错。所以能用字符串的地方尽量用字符串,哪怕这个参数本质上是数字或布尔值。比如“是否开启某功能”这个参数,用布尔类型的话,模型可能会输出"true"(字符串)而不是true(布尔值),导致解析失败;但如果用字符串类型,约定"yes"和"no",模型输出的稳定性会高很多。

数字类型要小心,端侧模型经常会把数字输出成字符串,或者在数字里混入单位。比如让它填“温度”参数,它可能输出"25度"而不是25。解决办法是在 Schema 里明确说明“只填数字,不要带单位”,同时在外部解析时做容错处理,用正则把数字提取出来。

枚举类型在端侧要慎用,尤其是枚举值较多的时候。模型可能会输出一个不在枚举列表里的值,或者输出枚举值的变体(比如大小写不一致、多了空格)。如果一定要用枚举,建议枚举值用简单的英文单词,不要用中文或复杂字符串,同时在外部做映射和兜底。

数组类型在端侧是最容易出问题的,模型经常会把数组输出成字符串,或者数组元素类型不一致。如果确实需要数组参数,建议限制数组长度,并且在 Schema 里明确说明元素类型。比如“标签列表”这个参数,可以约定最多三个标签,每个标签是字符串。

2.3 多工具场景下的 Schema 组织

端侧 Agent 通常需要支持多个工具,怎么把这些工具的 Schema 组织起来喂给模型,也是一个工程化问题。

最直接的做法是把所有工具的 Schema 拼成一个大 JSON 数组,一次性塞进 system prompt 里。这种做法在工具数量少的时候没问题,但工具一多,prompt 长度会迅速膨胀,端侧模型的上下文窗口根本扛不住。

我的做法是分层组织。第一层是工具分类,把功能相近的工具归为一组,比如“出行类”“通讯类”“设备控制类”。第二层是组内工具,每个组内的工具 Schema 放在一起。在对话时,先让模型判断用户意图属于哪个分类,然后只把该分类下的工具 Schema 加载进上下文。这样每次推理时上下文里只有少量工具的 Schema,长度可控。

这个思路类似于“路由”,用一个轻量的分类步骤换取上下文空间的节省。分类步骤本身也可以用一个很小的模型或者规则引擎来做,不一定非要走大模型推理。

还有一种做法是动态加载,根据对话历史判断当前可能需要哪些工具,只加载这些工具的 Schema。这种做法更灵活,但实现复杂度也更高,需要维护一个工具和意图的映射关系。在端侧资源紧张的环境里,我倾向于用分层组织这种简单可靠的方案。

2.4 Prompt 约束与输出格式控制

Schema 定义好了,接下来要解决的是怎么让模型按 Schema 输出。端侧模型的指令遵循能力有限,光靠 Schema 本身不够,还需要在 prompt 里加约束。

约束的核心是明确输出格式。我通常会在 system prompt 里写清楚:如果需要调用工具,只输出一个 JSON 对象,不要输出任何其他文字;JSON 对象必须包含name和arguments两个字段;arguments必须符合对应工具的 Schema。这些约束要写得直白、具体,不要用抽象的描述。

除了文字约束,还可以用示例约束。在 prompt 里给一两个输入输出的示例,让模型模仿。端侧模型对示例的模仿能力比对文字描述的理解能力更强,给示例往往比写一堆规则更有效。

还有一个技巧是输出前缀。在 prompt 的末尾加上{"name":这样的前缀,引导模型从这里开始续写。这种做法能显著提高 JSON 输出的成功率,因为模型不需要自己决定从哪里开始输出 JSON,只需要接着前缀往下写就行。当然,这个前缀要在解析时补回去。

实测下来,这几种约束手段组合使用,端侧模型的 Function Calling 成功率能从百分之六七十提升到百分之九十以上。剩下的百分之十靠外部解析和重试来兜底。

3. 工具调用链路的工程化实现

3.1 从模型输出到工具执行的完整链路

一个完整的端侧 Function Calling 链路,从模型输出到工具执行再到结果回传,中间有很多环节,每个环节都可能出问题。

链路的第一步是模型推理,模型根据当前对话上下文和工具 Schema,输出一段文本。这段文本理论上应该是一个 JSON 对象,但实际上可能是 JSON 外面包了文字、可能是多个 JSON、可能是格式错误的 JSON。

第二步是输出解析,从模型输出里提取出 JSON 对象。这一步要做容错,比如去掉 JSON 前后的多余文字、修复常见的格式错误(比如单引号、尾逗号)、处理模型输出的多个 JSON(取第一个或最后一个)。

第三步是参数校验,检查解析出来的 JSON 是否符合 Schema。这一步要检查必填字段是否都有、字段类型是否正确、枚举值是否在范围内。校验不通过的话,要么重试,要么走兜底逻辑。

第四步是工具执行,根据name找到对应的工具实现,把arguments传进去执行。这一步要处理工具执行失败的情况,比如网络超时、参数不合法。

第五步是结果回传,把工具执行的结果塞回对话上下文,让模型基于结果生成最终回复。这一步要注意结果的格式,最好是结构化的,方便模型理解。

这五步里,第一步和第二步是最容易出问题的,也是工程化投入最多的地方。第三步到第五步相对标准化,但也不能掉以轻心。

3.2 输出解析的容错策略

输出解析是端侧 Function Calling 工程化里最脏最累的活,因为你要面对模型各种千奇百怪的输出格式。我总结了几种常见的异常输出和对应的处理策略。

第一种是JSON 外面包了文字,比如模型输出“好的,我来帮你查一下天气 {"name": "get_weather", "arguments": {"city": "北京"}}”。处理策略是用正则找到第一个{和最后一个},把中间的部分提取出来。

第二种是JSON 格式错误,比如用了单引号、多了尾逗号、少了引号。处理策略是先尝试标准 JSON 解析,失败的话用宽松的解析器(比如 Python 的json5或者自己写一个简单的修复逻辑)。

第三种是输出多个 JSON,比如模型先输出一个工具调用,然后又输出了一段解释,解释里又包含了一个 JSON。处理策略是只取第一个完整的 JSON 对象,忽略后面的内容。

第四种是字段名或类型错误,比如把arguments写成了args,把字符串写成了数字。处理策略是在解析后做字段映射和类型转换,尽量把模型的输出往正确的格式上靠。

第五种是完全无法解析,模型输出了一段自然语言,根本没有 JSON。处理策略是走重试逻辑,把模型的输出和错误信息一起塞回上下文,让它重新输出。

这些容错策略要组合使用,形成一个解析管线。我的经验是,解析管线要尽量宽松,能救则救,实在救不回来再重试。因为端侧模型推理一次的成本不低,能少重试一次就少一次。

3.3 多轮工具调用的状态管理

单轮工具调用相对简单,模型输出一个工具调用,执行完把结果塞回去,模型生成最终回复,结束。但实际场景里,很多任务需要多轮工具调用,比如“帮我查一下明天北京的天气,如果下雨就提醒我带伞”,这需要先查天气,再根据天气结果决定是否设置提醒。

多轮工具调用的状态管理,核心是维护一个清晰的对话状态。每一轮工具调用的输入、输出、模型的中间推理,都要记录下来,作为下一轮推理的上下文。同时要有一个终止条件,判断什么时候任务完成,可以生成最终回复了。

终止条件通常有两种:一种是模型明确表示不再需要调用工具,直接输出自然语言回复;另一种是达到了预设的最大轮次,强制终止。端侧场景里,最大轮次要设得小一些,比如三轮到五轮,因为端侧模型推理慢,轮次太多用户体验会很差。

状态管理还有一个容易忽略的点是工具调用的去重。模型有时候会重复调用同一个工具,传相同的参数,这时候要判断是不是真的需要重复调用,还是模型陷入了循环。如果是后者,要主动打断,避免无限循环消耗资源。

3.4 工具执行失败的降级处理

工具执行失败在端侧是常态,网络不稳定、权限不足、参数不合法,都可能导致工具执行失败。工程化要做的是,在工具失败的时候,Agent 能优雅降级,而不是直接崩溃。

降级策略分几个层次。第一层是重试,对于网络超时这类临时性失败,可以自动重试一到两次。第二层是参数修正,如果失败原因是参数不合法,可以尝试修正参数后重新执行,比如把城市名从“北京市”改成“北京”。第三层是工具替换,如果某个工具不可用,可以尝试用功能相近的替代工具。第四层是告知用户,如果以上都不行,就如实告诉用户工具执行失败,让用户决定下一步。

这四层降级策略要按顺序尝试,能自动解决的就自动解决,解决不了的再交给用户。端侧 Agent 的用户体验很大程度上取决于这些降级逻辑做得好不好。

4. MCP 在端侧 Agent 中的落地实践

4.1 MCP 协议的核心机制与端侧适配

MCP 的核心是 JSON-RPC 通信,客户端和服务端通过请求-响应模式交互。在端侧 Agent 场景里,Agent 本身是客户端,工具实现是服务端。客户端发送工具调用请求,服务端执行工具并返回结果。

MCP 协议定义了三种核心能力:Tools(可调用的函数)、Resources(可读取的数据)、Prompts(预定义的提示模板)。端侧 Agent 最常用的是 Tools,Resources 和 Prompts 在端侧场景里用得相对少一些。

端侧适配 MCP 的关键在于通信方式的选择。MCP 支持多种传输方式,包括标准输入输出、HTTP、WebSocket 等。端侧环境里,如果工具和 Agent 跑在同一个进程里,可以直接用函数调用,不需要走 MCP 协议;如果工具跑在独立进程里,可以用标准输入输出或本地 socket;如果工具跑在远端,才需要走 HTTP 或 WebSocket。

我的建议是,端侧 Agent 的工具尽量和 Agent 跑在同一个进程里,用函数调用直接交互,避免 MCP 协议的开销。只有在工具确实需要独立部署的时候,才引入 MCP。这样既能享受 MCP 带来的解耦好处,又能避免不必要的性能损耗。

4.2 端侧 MCP Server 的实现要点

如果确实需要在端侧实现 MCP Server,有几个要点需要注意。

第一是轻量化。端侧的 MCP Server 不要用重量级的框架,尽量用轻量的实现。JSON-RPC 的序列化和反序列化可以用现成的库,但不要引入太多依赖,端侧的包体积和内存都很宝贵。

第二是启动速度。端侧应用的启动速度直接影响用户体验,MCP Server 的启动要尽可能快。避免在启动时做耗时的初始化,比如加载大模型、建立网络连接,这些可以延迟到第一次调用时再做。

第三是资源隔离。MCP Server 和 Agent 主进程之间要做好资源隔离,避免一个工具的内存泄漏影响整个 Agent。如果工具的执行可能耗时较长,要考虑放到独立线程或进程里执行,避免阻塞主线程。

第四是错误处理。MCP Server 要把工具执行的各种错误都捕获住,转换成 MCP 协议定义的错误格式返回给客户端。不要让异常直接抛到协议层,导致通信中断。

4.3 工具注册与动态发现

MCP 的一个好处是支持工具的注册和动态发现。Agent 启动时,可以向 MCP Server 查询有哪些工具可用,然后把这些工具的 Schema 加载进来。这样新增工具的时候,Agent 侧不需要改代码,只要 MCP Server 注册了新工具,Agent 就能自动发现。

在端侧场景里,动态发现的价值主要体现在插件化上。你可以把 Agent 的核心逻辑和工具实现分开,工具以插件的形式提供,用户按需安装。Agent 启动时扫描已安装的插件,通过 MCP 协议获取插件的工具列表,然后把这些工具纳入可用工具集。

这种架构的灵活性很高,但实现复杂度也不低。端侧做插件化要考虑插件的加载、卸载、版本管理、权限控制等一系列问题。如果工具集相对固定,我建议还是用静态注册的方式,简单可靠。

4.4 MCP 与 Function Calling 的协同

MCP 和 Function Calling 不是替代关系,而是协同关系。Function Calling 解决的是“模型怎么表达要调用哪个工具、传什么参数”,MCP 解决的是“工具调用请求怎么从 Agent 传到工具实现”。

在一个端侧 Agent 里,典型的协同流程是这样的:模型通过 Function Calling 输出一个工具调用请求,Agent 侧解析这个请求,转换成 MCP 协议的格式,通过 MCP 客户端发送给 MCP Server,MCP Server 执行工具,把结果返回给 Agent,Agent 再把结果塞回对话上下文,让模型生成最终回复。

这个流程里,Function Calling 和 MCP 各司其职,Function Calling 负责模型和 Agent 之间的接口,MCP 负责 Agent 和工具之间的接口。两者解耦,各自可以独立演进。

5. 端侧 Agent 工程化的常见坑与排查

5.1 模型输出不稳定的排查思路

模型输出不稳定是端侧 Agent 最常见的问题,表现是同样的输入,有时候能正确调用工具,有时候不能。排查这个问题,我通常按以下顺序检查。

先看prompt 是否太长。端侧模型的上下文窗口有限,prompt 太长会导致模型注意力分散,输出质量下降。检查方法是把 prompt 打印出来,数一下 token 数,如果接近模型的上下文窗口上限,就要考虑精简。

再看Schema 是否太复杂。Schema 里的字段太多、嵌套太深、描述太长,都会增加模型的负担。检查方法是把 Schema 单独拿出来,看看能不能再精简。

然后看示例是否充分。如果 prompt 里没有给示例,或者示例太少,模型可能不知道该怎么输出。检查方法是加一两个示例,看看输出是否稳定。

最后看模型本身的能力。如果以上都排查了还是不稳定,可能是模型本身的能力不够,需要考虑换一个更大的模型,或者用量化程度更低的版本。

5.2 工具调用参数错误的修复技巧

参数错误是另一个高频问题,模型输出的参数不符合 Schema,导致工具执行失败。修复参数错误,我常用的技巧有以下几个。

类型转换:如果模型把数字输出成了字符串,解析时自动转成数字。如果模型把布尔值输出成了字符串,解析时自动转成布尔值。

枚举映射:如果模型输出的枚举值不在列表里,尝试做模糊匹配,找到最接近的枚举值。比如模型输出“经济舱”,枚举列表里是“economy”,就做一个中文到英文的映射。

默认值填充:如果模型漏填了某个可选字段,用默认值填充。默认值要在 Schema 里定义好,解析时如果发现字段缺失,就用默认值。

参数修正:如果模型输出的参数明显不对,比如城市名写错了,可以尝试用规则或小模型做修正。比如“北京市”修正为“北京”,“上海是”修正为“上海”。

这些技巧要组合使用,形成一个参数修复管线。修复管线要尽量宽松,能修则修,修不了再报错。

5.3 内存与性能瓶颈的优化方向

端侧 Agent 的内存和性能瓶颈,主要集中在模型推理和工具执行两个环节。

模型推理的优化方向包括:量化(用 4bit 或 8bit 量化减小模型体积和内存占用)、剪枝(去掉模型中不重要的参数)、蒸馏(用大模型教小模型,提升小模型的能力)、缓存(缓存常用的推理结果,避免重复计算)。

工具执行的优化方向包括:异步执行(工具执行不阻塞主线程)、结果缓存(缓存工具执行结果,避免重复调用)、批量执行(多个工具调用合并成一次执行)、超时控制(给工具执行设置超时,避免长时间阻塞)。

这些优化手段要根据具体场景选择,不是所有手段都适用。比如量化会损失一定的模型能力,如果模型本身能力就不够,量化后可能更差。所以优化要在保证功能的前提下进行,不能为了性能牺牲功能。

5.4 常见问题速查表

问题现象可能原因排查方法解决思路
模型不调用工具prompt 约束不明确检查 system prompt加明确的输出格式约束和示例
模型输出非 JSON模型能力不足换更大模型测试加输出前缀引导,或换模型
JSON 解析失败格式错误打印原始输出用宽松解析器,加修复逻辑
参数类型错误Schema 定义不清检查 Schema加类型说明,解析时做转换
工具执行超时工具实现问题单独测试工具加超时控制,异步执行
多轮调用死循环终止条件缺失检查轮次控制设最大轮次,加去重逻辑
内存占用过高模型太大监控内存量化模型,优化缓存策略
推理速度慢硬件限制测推理耗时用更小模型,加推理缓存

这张表是我在实际项目里总结出来的,覆盖了端侧 Agent 工程化里八成以上的常见问题。遇到问题的时候,可以先对照这张表快速定位,然后再深入排查。

6. 工程化实践中的经验沉淀

6.1 Schema 设计的迭代方法

Schema 设计不是一次成型的,需要反复迭代。我的做法是,先设计一个初版 Schema,然后在真实场景里跑一批测试用例,统计调用成功率。对于成功率低的工具,分析失败原因,是字段太多、描述不清、还是类型不对,然后针对性调整 Schema,再跑一批测试,看成功率是否提升。

这个迭代过程通常要重复三到五轮,才能把 Schema 打磨到一个比较稳定的状态。迭代的时候要注意,每次只改一个变量,这样才能知道是哪个改动带来了提升。如果一次改多个地方,成功率变了也不知道是哪个改动起的作用。

迭代过程中要积累一个测试用例集,覆盖各种典型的用户输入和边界情况。这个用例集是宝贵的资产,每次改 Schema 都用它来回归测试,确保改动不会引入新的问题。

6.2 工具粒度的权衡

工具粒度是端侧 Agent 设计里的一个重要决策。粒度太粗,一个工具干太多事,模型很难正确传参;粒度太细,工具数量太多,上下文装不下,模型也容易选错工具。

我的经验是,一个工具只做一件事,但这件事的边界要清晰。比如“查天气”和“查空气质量”可以是一个工具,因为它们都是查询环境信息,参数也相似;但“查天气”和“设闹钟”必须是两个工具,因为它们的功能完全不同。

工具的数量控制在十个以内比较合适,超过十个就要考虑分组或动态加载。端侧模型的上下文窗口有限,工具太多会挤占对话历史的空间,影响多轮对话的体验。

6.3 端侧 Agent 的测试策略

端侧 Agent 的测试比云端 Agent 更难,因为端侧环境复杂,模型行为不确定,很难用传统的单元测试覆盖。

我的测试策略分三层。第一层是单元测试,测试 Schema 解析、参数校验、工具执行这些确定性逻辑,用 mock 数据模拟模型输出,确保这些环节的正确性。第二层是集成测试,用真实的模型跑一批测试用例,统计工具调用成功率和最终回复质量,这层测试要跑多次,因为模型输出有随机性。第三层是端到端测试,在真实的端侧设备上跑完整的用户场景,测试内存占用、推理延迟、功耗等指标。

这三层测试里,集成测试是最重要的,也是最花时间的。我通常会准备一个包含几十个用例的测试集,覆盖各种工具调用场景,每次改动后都跑一遍,看成功率有没有下降。

6.4 从工程化到产品化的最后一公里

工程化做完了,离产品化还有一段路。产品化要考虑的东西更多,比如用户体验、错误提示、隐私保护、版本更新。

用户体验方面,端侧 Agent 的响应速度是关键。模型推理慢的时候,要有 loading 提示;工具执行慢的时候,要有进度反馈;任务完成的时候,要有清晰的回复。这些细节直接影响用户对产品的感知。

错误提示方面,端侧 Agent 出错的时候,要给用户友好的提示,而不是一堆技术术语。比如工具执行失败,不要直接说“HTTP 500”,而是说“网络好像不太稳定,稍后再试试”。

隐私保护方面,端侧 Agent 的优势就是数据不出本地,这个优势要在产品里体现出来。用户的数据、对话历史、工具调用记录,都要存在本地,不上传云端。

版本更新方面,端侧 Agent 的模型和工具都可能需要更新,要设计好更新机制,支持增量更新和回滚。

这一公里的路,技术含量可能不如前面的工程化,但重要性一点不低。很多端侧 Agent 项目就是死在这一公里上,技术做得很漂亮,但产品体验一塌糊涂,用户用一次就再也不用了。

我个人在实际操作中的体会是,端侧 Agent 的工程化没有银弹,每一个环节都要抠细节,每一个问题都要有兜底方案。模型能力不够,就用工程手段补;工程手段补不了的,就用产品设计绕。整个过程就是不断地在能力、资源、体验之间找平衡。这个平衡点每个项目都不一样,需要根据具体情况去摸索。但有一点是共通的:先把最简单的场景做到极致稳定,再逐步扩展复杂度。贪多求快,最后往往什么都做不好。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询