端侧Agent工程化实战:Function Calling、MCP协议与LLM输出可控性

发布时间:2026/10/1 7:51:59
端侧Agent工程化实战:Function Calling、MCP协议与LLM输出可控性 1. 端侧 Agent 工程化到底在解决什么问题端侧 Agent 这个词这两年热度一直没降过但真正动手做过的人都知道Demo 跑通和工程落地之间隔着一道巨大的鸿沟。我见过太多团队拿着一个能在本地跑起来的对话机器人就宣称“我们做了端侧 Agent”结果一上真实场景就原形毕露工具调用不稳定、上下文爆炸、模型输出格式飘忽不定、多轮任务执行到一半就断了。这一篇主要聊的是 Agent 工程化上半部分核心聚焦在Function Calling 的工程化封装、MCP 协议的接入与适配、以及LLM 输出可控性这三个最要命的问题上。端侧 Agent 和云端 Agent 最大的区别在于端侧意味着算力受限、内存受限、网络可能不稳定同时用户对响应延迟的容忍度极低。你不能像云端那样随便堆 GPU 集群也不能假设永远有稳定的网络连接。所以端侧 Agent 工程化的本质是什么我的理解是在资源受限的环境下让 LLM 能够可靠地调用外部工具、管理上下文、并完成多步骤任务。这里面每一个词都是工程挑战。“可靠”意味着你要处理模型幻觉、格式错误、超时重试“资源受限”意味着你要做模型量化、上下文压缩、缓存策略“多步骤任务”意味着你要设计状态机、错误恢复、任务编排。适合谁来读这篇内容如果你已经了解 LLM 的基本原理知道什么是 prompt engineering甚至跑过一些简单的 Agent Demo但一到工程化落地就踩坑不断那这篇就是写给你的。如果你是完全的新手建议先去看看端侧 Agent 的基础概念再回来读工程化的部分否则可能会觉得跳跃太大。接下来我会按照实际项目中的推进顺序来展开先讲整体架构怎么设计再拆 Function Calling 的工程细节然后是 MCP 协议的接入实践最后是 LLM 输出可控性和常见问题排查。每一块都会给出具体的代码示例、参数配置和踩坑经验。2. 端侧 Agent 的整体架构设计与选型思路2.1 为什么端侧 Agent 不能照搬云端架构云端 Agent 的典型架构是用户请求打到 API 网关网关转发到 Agent 编排服务编排服务调用 LLM APILLM 返回 tool_call编排服务执行工具把结果再喂回 LLM循环直到任务完成。这个架构在云端跑得很好因为你有无限的计算资源、稳定的网络、以及可以水平扩展的服务实例。但端侧完全不是这个逻辑。端侧 Agent 运行在用户的手机、PC、或者嵌入式设备上你面对的是内存限制一个 7B 参数的模型量化到 4bit 大概占 3.5GB 内存加上 KV Cache、工具调用的中间结果、上下文历史很容易就爆了。算力限制端侧 NPU 的算力可能只有几 TOPS跑一次推理可能要几百毫秒到几秒你不能像云端那样并行调用多个模型。网络不确定性端侧 Agent 可能需要调用云端 API 来补充能力但网络可能时断时续你不能假设每次请求都能成功。隐私与合规端侧 Agent 的一大卖点就是数据不出本地所以你要尽量减少把用户数据传到云端的次数。基于这些约束端侧 Agent 的架构设计原则应该是本地优先、云端兜底、异步执行、状态可恢复。2.2 分层架构从 UI 到模型到工具我在实际项目中采用的端侧 Agent 架构大致分为四层第一层是交互层负责和用户打交道包括语音输入、文本输入、UI 渲染。这一层的关键是流式输出因为端侧推理慢用户等不了你一次性生成完再显示必须边生成边显示。第二层是 Agent 编排层这是核心。它负责管理对话状态、决定什么时候调用工具、什么时候直接回复、什么时候需要向用户确认。这一层通常是一个状态机每个状态对应一个动作。第三层是能力层包括 LLM 推理引擎、Function Calling 执行器、MCP 客户端、RAG 检索模块。这一层是“干活”的每个模块都有明确的输入输出接口。第四层是基础设施层包括模型管理、缓存、日志、错误处理。这一层最容易被忽视但工程化做得好不好很大程度上取决于这一层。为什么这么分因为端侧 Agent 的复杂度主要来自“不确定性”模型输出不确定、工具执行结果不确定、网络状态不确定。分层的目的就是把不确定性隔离在特定层内让其他层可以假设输入是可靠的。2.3 模型选型端侧 LLM 的取舍端侧 LLM 的选型是一个多目标优化问题。你要在模型能力、推理速度、内存占用、功耗之间做权衡。我试过几种方案模型规模量化方式内存占用推理延迟端侧 NPU适用场景1B-2B4bit0.5-1GB50-150ms简单意图识别、槽位填充3B-4B4bit1.5-2.5GB150-400ms一般对话、简单工具调用7B-8B4bit3.5-4.5GB400-1000ms复杂推理、多步工具调用13B4bit7GB1s端侧基本不现实我的建议是端侧主模型选 3B-4B 级别复杂任务通过 MCP 调用云端大模型兜底。这样既能保证日常交互的响应速度又能在遇到复杂任务时借用云端能力。模型格式方面ONNX 是目前端侧部署最通用的选择因为它在不同硬件平台上的兼容性最好。如果你用的是高通的 NPU可以用 QNN 格式如果是苹果的 Neural Engine可以用 CoreML。但 ONNX 的好处是“一次导出到处运行”虽然性能可能不是最优但工程复杂度最低。2.4 上下文管理端侧最容易被忽视的瓶颈端侧 Agent 的上下文窗口通常比云端小得多。云端模型动辄 128K 上下文端侧模型可能只有 4K 或 8K。这意味着你不能把所有的对话历史、工具调用结果都塞进上下文。我的做法是三级上下文管理热上下文最近 3-5 轮对话完整保留直接进 prompt。温上下文更早的对话做摘要压缩保留关键信息。冷上下文工具调用结果、RAG 检索结果按需检索不常驻。具体实现上我会用一个滑动窗口 摘要缓冲区的结构。滑动窗口保留最近 N 轮对话当窗口满了就把最老的对话做摘要存入摘要缓冲区。摘要缓冲区也有上限超了就丢弃最老的摘要。这里有个坑摘要本身也要消耗推理资源。如果你每轮对话都做摘要端侧算力根本扛不住。我的经验是每 5-10 轮做一次摘要或者当上下文长度超过阈值时才触发摘要。注意摘要的质量直接影响 Agent 的长期记忆能力。我试过用规则做摘要提取实体、动作、结果也试过用 LLM 做摘要。规则摘要快但容易丢信息LLM 摘要好但慢。最终我采用的是混合方案规则摘要做粗筛LLM 摘要做精炼只在关键节点触发。3. Function Calling 工程化从 Demo 到生产3.1 Function Calling 的本质与常见误区Function Calling 的本质是让 LLM 输出结构化的工具调用请求。很多人以为 Function Calling 是模型“理解”了工具的功能其实不是。模型只是根据你给的函数描述学会了在特定输入下输出特定格式的 JSON。它并不真正理解工具是干什么的它只是在做模式匹配。这就导致了一个常见误区以为写好函数描述就够了。实际上函数描述只是第一步你还需要考虑模型可能输出不存在的函数名模型可能输出参数缺失或参数类型错误模型可能同时调用多个函数但你的执行器不支持并发模型可能在不需要调用函数的时候调用了函数这些问题在 Demo 阶段可能不会暴露因为 Demo 的输入都是精心设计的。但一到生产环境用户输入千奇百怪这些问题就会集中爆发。3.2 函数描述的设计原则函数描述是 Function Calling 的“接口文档”它的质量直接决定了模型调用的准确率。我总结了几个设计原则第一函数名要语义明确。不要用func1、tool_a这种名字要用get_weather、search_flights这种一看就知道干什么的名字。模型对函数名的语义理解比你想象的重要。第二参数描述要包含类型、范围、示例。比如{ name: search_flights, description: 搜索航班信息返回符合条件的航班列表, parameters: { type: object, properties: { departure: { type: string, description: 出发城市必须是中文城市名如北京、上海 }, arrival: { type: string, description: 到达城市必须是中文城市名 }, date: { type: string, description: 出发日期格式为YYYY-MM-DD如2024-03-15 } }, required: [departure, arrival, date] } }注意description里我特意强调了“必须是中文城市名”和“格式为YYYY-MM-DD”。这些约束看起来啰嗦但能显著降低模型输出错误格式的概率。第三函数数量要控制。我试过一次性给模型 20 多个函数结果模型经常选错。后来我把函数分成几组每组不超过 5 个根据对话上下文动态加载。这样准确率提升了很多。第四要提供“不需要调用函数”的选项。很多模型在不确定的时候会强行调用一个函数哪怕这个函数根本不相关。你需要在 system prompt 里明确告诉模型“如果现有函数都无法满足用户需求直接回复用户不要调用函数。”3.3 输出解析与容错处理即使你函数描述写得再好模型还是可能输出格式错误。所以输出解析必须做容错。我的解析流程是这样的尝试直接 JSON 解析。如果成功进入下一步。如果失败尝试提取 JSON 片段。模型有时候会在 JSON 前后加一些解释性文字用正则把{...}提取出来。如果还失败尝试修复常见错误。比如单引号转双引号、尾逗号删除、缺失括号补全。如果最终失败触发重试。把错误信息喂回模型让它重新生成。重试策略也很关键。我一般设置最多重试 2 次每次重试时把上一次的错误信息加到 prompt 里。如果 2 次都失败就降级处理要么直接回复用户“我暂时无法完成这个操作”要么走规则兜底。def parse_tool_call(model_output, max_retries2): for attempt in range(max_retries 1): try: # 尝试直接解析 return json.loads(model_output) except json.JSONDecodeError: # 尝试提取 JSON 片段 match re.search(r\{.*\}, model_output, re.DOTALL) if match: try: return json.loads(match.group()) except json.JSONDecodeError: pass # 尝试修复常见错误 fixed fix_common_json_errors(model_output) try: return json.loads(fixed) except json.JSONDecodeError: if attempt max_retries: # 触发重试 model_output retry_with_error_feedback(model_output) else: raise ToolCallParseError(无法解析模型输出)3.4 工具执行的安全与超时控制工具执行是端侧 Agent 最容易出问题的环节。我踩过的坑包括工具执行卡死导致整个 Agent 无响应、工具返回超大结果撑爆上下文、工具执行出错但没有被捕获。我的解决方案是沙箱执行 超时控制 结果截断沙箱执行每个工具调用都在独立的线程或进程中执行避免一个工具卡死影响整个 Agent。超时控制每个工具设置独立的超时时间一般 5-10 秒。超时后强制终止返回超时错误。结果截断工具返回的结果如果超过一定长度比如 2000 字符做截断或摘要避免撑爆上下文。实操心得工具执行超时时间不要设得太短。我一开始设了 3 秒结果很多网络请求类的工具经常超时。后来改成 10 秒配合异步执行体验好很多。但也不能太长否则用户会觉得 Agent 卡死了。我的经验是本地工具 5 秒网络工具 10 秒复杂计算工具 15 秒。4. MCP 协议接入端侧 Agent 的能力扩展4.1 MCP 是什么为什么端侧 Agent 需要它MCPModel Context Protocol是一个让 LLM 应用与外部工具、数据源进行标准化交互的协议。你可以把它理解成“AI 世界的 USB 接口”以前每个工具都要写一套适配代码现在只要工具实现了 MCP 协议任何支持 MCP 的 Agent 都能直接调用。端侧 Agent 为什么需要 MCP因为端侧资源有限你不可能把所有能力都本地实现。比如你想让 Agent 能查天气、搜航班、读邮件、控制智能家居这些能力如果都本地实现代码量和维护成本会爆炸。但通过 MCP你可以把这些能力做成独立的 MCP ServerAgent 只需要实现一个 MCP Client 就能调用所有能力。MCP 的核心概念包括MCP Server提供工具能力的服务端可以是本地的也可以是远程的。MCP ClientAgent 侧的客户端负责和 MCP Server 通信。ToolsMCP Server 暴露的具体工具每个工具有名称、描述、参数 schema。ResourcesMCP Server 暴露的数据资源比如文件、数据库记录。PromptsMCP Server 预定义的 prompt 模板。4.2 MCP Client 的端侧实现要点在端侧实现 MCP Client有几个关键点第一传输层选择。MCP 支持多种传输方式包括 stdio、HTTP、WebSocket。端侧 Agent 通常用 stdio 调用本地 MCP Server用 HTTP 或 WebSocket 调用远程 MCP Server。stdio 的好处是简单、低延迟适合本地工具HTTP 的好处是通用、易调试适合远程工具。第二连接管理。MCP Server 可能启动慢、可能崩溃、可能网络断开。你需要做连接池管理、健康检查、自动重连。我的做法是维护一个 MCP Server 注册表每个 Server 有状态标记connected、disconnected、error定期做健康检查。第三工具发现与缓存。MCP Server 启动后Client 需要调用tools/list获取工具列表。这个列表要缓存起来避免每次调用都重新获取。但也要支持刷新因为 Server 可能动态增加工具。第四调用转发与结果处理。当 Agent 决定调用某个 MCP 工具时Client 需要把调用请求转发给对应的 Server等待结果然后返回给 Agent。这里要注意超时控制和错误处理。class MCPClient: def __init__(self): self.servers {} # server_name - server_info self.tool_cache {} # tool_name - server_name def register_server(self, name, transport, endpoint): 注册 MCP Server self.servers[name] { transport: transport, endpoint: endpoint, status: disconnected, tools: [] } async def connect(self, name): 连接 MCP Server 并获取工具列表 server self.servers[name] try: if server[transport] stdio: # 启动本地进程 process await asyncio.create_subprocess_exec( server[endpoint], stdinasyncio.subprocess.PIPE, stdoutasyncio.subprocess.PIPE ) server[process] process elif server[transport] http: # HTTP 连接 server[session] aiohttp.ClientSession() # 获取工具列表 tools await self.list_tools(name) server[tools] tools server[status] connected # 更新工具缓存 for tool in tools: self.tool_cache[tool[name]] name except Exception as e: server[status] error raise MCPConnectionError(f连接 {name} 失败: {e}) async def call_tool(self, tool_name, arguments, timeout10): 调用 MCP 工具 server_name self.tool_cache.get(tool_name) if not server_name: raise MCPToolNotFoundError(f工具 {tool_name} 未注册) server self.servers[server_name] if server[status] ! connected: raise MCPConnectionError(fServer {server_name} 未连接) try: result await asyncio.wait_for( self._send_request(server, tools/call, { name: tool_name, arguments: arguments }), timeouttimeout ) return result except asyncio.TimeoutError: raise MCPTimeoutError(f工具 {tool_name} 调用超时)4.3 MCP 与 Function Calling 的协同MCP 和 Function Calling 不是替代关系而是互补关系。Function Calling 是 LLM 的输出格式MCP 是工具调用的传输协议。你可以把 MCP 工具转换成 Function Calling 的格式喂给 LLM然后把 LLM 输出的 tool_call 转换成 MCP 调用。具体流程是Agent 启动时连接所有 MCP Server获取工具列表。把 MCP 工具列表转换成 Function Calling 的 schema 格式。把 schema 注入到 LLM 的 prompt 中。LLM 输出 tool_call 时解析出工具名和参数。根据工具名找到对应的 MCP Server发起 MCP 调用。把 MCP 返回的结果转换成 LLM 能理解的格式喂回 LLM。这个流程看起来简单但实际实现时有很多细节。比如 MCP 工具的参数 schema 可能和 Function Calling 的 schema 不完全兼容需要做转换。再比如 MCP 工具可能返回非文本结果图片、文件需要做特殊处理。4.4 远程 MCP Server 的接入实践端侧 Agent 经常需要接入远程 MCP Server 来扩展能力。远程 MCP Server 通常通过 HTTP 或 WebSocket 暴露接口。接入时要注意认证与鉴权远程 MCP Server 通常需要 token 认证。token 要安全存储不能硬编码在代码里。我一般用系统密钥链或加密存储。网络容错远程调用可能失败要做重试和降级。我的策略是失败后重试 2 次每次间隔 1 秒如果还失败返回错误给 Agent让 Agent 决定是告知用户还是尝试其他工具。延迟优化远程调用延迟高要尽量做并行调用。如果 Agent 需要同时调用多个远程工具用asyncio.gather并发执行。结果缓存有些远程工具的结果可以缓存比如天气查询、航班查询。缓存时间根据数据更新频率设定天气 10 分钟航班 5 分钟。注意远程 MCP Server 的稳定性直接影响 Agent 的可用性。我建议在 Agent 启动时做一次全量健康检查把不可用的 Server 标记出来避免运行时才发现问题。同时要提供手动刷新机制让用户可以重新连接。5. LLM 输出可控性让模型按你的要求干活5.1 为什么 LLM 输出总是“不听话”LLM 输出不可控是端侧 Agent 工程化最大的痛点之一。你明明在 prompt 里写了“只输出 JSON”它偏偏要在前面加一句“好的以下是 JSON”。你明明要求“不要调用函数”它偏偏要调用一个不存在的函数。这些问题的根源在于LLM 是概率模型不是确定性程序。它根据训练数据中的模式生成输出而不是根据你的指令执行逻辑。你给的指令只是“影响”它的输出分布而不是“决定”它的输出。所以工程化的目标不是“让模型完全听话”而是“让模型在大多数情况下输出可用的结果并在异常时能够恢复”。5.2 Prompt 工程约束输出的实用技巧我在实践中总结了几条约束 LLM 输出的实用技巧第一用 few-shot 示例锚定输出格式。与其告诉模型“输出 JSON”不如给它 2-3 个输入输出示例。模型对示例的模仿能力远强于对指令的理解能力。第二用分隔符明确输出边界。比如要求模型把 JSON 放在output和/output之间这样解析时更容易提取。第三用负面指令排除常见错误。比如“不要输出任何解释性文字”、“不要在 JSON 前后添加 markdown 代码块标记”。第四用 temperature 控制随机性。工具调用场景下temperature 设 0 或 0.1让输出尽量确定。创意生成场景可以设高一些。第五用 logit bias 强制特定 token。有些推理引擎支持 logit bias可以强制模型在特定位置输出特定 token。比如强制第一个 token 是{这样模型就只能输出 JSON。SYSTEM_PROMPT 你是一个端侧 Agent负责理解用户意图并调用工具。 规则 1. 如果用户意图需要调用工具输出 JSON 格式的工具调用请求。 2. 如果不需要调用工具直接回复用户。 3. 输出 JSON 时不要添加任何解释性文字不要用 markdown 代码块包裹。 4. JSON 格式必须为{tool: 工具名, arguments: {...}} 示例 用户北京今天天气怎么样 输出{tool: get_weather, arguments: {city: 北京, date: today}} 用户你好 输出你好有什么我可以帮你的吗 5.3 结构化输出与 JSON Schema 约束除了 prompt 工程还可以用结构化输出Structured Output来约束模型。结构化输出的原理是在解码阶段限制模型只能输出符合特定 schema 的 token 序列。比如 OpenAI 的 Structured Output 功能你给它一个 JSON Schema它保证输出符合这个 schema。端侧推理引擎也在逐步支持类似功能比如 llama.cpp 的 GBNF 语法、ONNX Runtime 的 constrained decoding。如果你的推理引擎不支持结构化输出可以自己实现一个简单的约束解码器。原理是在每一步解码时根据当前已生成的 token 和 JSON Schema计算哪些 token 是合法的然后把非法 token 的概率设为 0。class JSONSchemaConstraint: def __init__(self, schema): self.schema schema self.state start # 当前解析状态 def get_allowed_tokens(self, generated_tokens): 根据已生成的 token 和 schema返回允许的下一个 token 集合 # 简化版只处理对象类型 if self.state start: return [{] elif self.state in_key: # 返回 schema 中定义的 key return list(self.schema[properties].keys()) # ... 其他状态处理5.4 输出校验与自动修复即使做了这么多约束模型还是可能输出错误。所以最后一道防线是输出校验与自动修复。校验的内容包括JSON 是否合法工具名是否存在参数是否完整参数类型是否正确参数值是否在允许范围内如果校验失败触发自动修复。修复策略包括格式修复补全括号、删除尾逗号、单引号转双引号参数补全如果缺少必填参数尝试从上下文推断工具替换如果工具名不存在尝试找最相似的已注册工具降级处理如果无法修复返回错误给 Agent让 Agent 决定下一步实操心得自动修复要谨慎使用。我一开始做了很激进的自动修复结果有时候会把模型原本正确的输出“修坏”。后来我改成保守策略只修复明确的格式错误不做语义层面的修改。语义层面的问题让模型重试而不是自己猜。6. 常见问题与排查技巧实录6.1 工具调用失败排查速查表问题现象可能原因排查方法解决方案模型不调用工具函数描述不清晰检查函数名和描述是否语义明确优化函数描述增加示例模型调用错误的工具函数数量太多检查当前加载的函数数量分组加载每组不超过 5 个JSON 解析失败模型输出格式错误打印原始输出检查格式增加 few-shot 示例启用结构化输出工具执行超时工具本身慢或网络问题检查工具执行日志增加超时时间异步执行上下文爆炸工具返回结果太大检查上下文长度截断工具结果做摘要多轮任务中断状态管理有问题检查状态机日志增加状态持久化支持恢复模型输出重复temperature 太低或 prompt 问题检查 temperature 设置适当提高 temperature优化 prompt6.2 端侧推理性能优化技巧端侧推理慢是常态但可以通过一些技巧优化KV Cache 复用多轮对话时如果 system prompt 不变可以复用 KV Cache避免重复计算。这个优化能显著降低首 token 延迟。模型量化4bit 量化通常能减少 60-70% 内存占用推理速度提升 30-50%。但要注意量化可能损失精度需要做评估。算子融合把多个小算子融合成一个大算子减少 kernel launch 开销。ONNX Runtime 和 TensorRT 都支持自动算子融合。批处理如果有多个请求可以批处理。但端侧通常只有一个用户批处理收益不大。预热Agent 启动时先跑一次推理把模型加载到内存避免首次调用时延迟过高。6.3 MCP 连接问题排查MCP 连接问题通常表现为工具列表获取失败、工具调用超时、连接频繁断开。排查步骤检查 MCP Server 是否启动。本地 Server 检查进程是否存在远程 Server 检查网络是否可达。检查认证信息。token 是否过期权限是否足够。检查传输层。stdio 检查管道是否正常HTTP 检查状态码WebSocket 检查连接状态。检查日志。MCP Server 和 Client 的日志都要看定位问题出在哪一侧。检查超时设置。连接超时、读取超时、调用超时是否合理。6.4 上下文管理常见坑坑一摘要丢失关键信息。摘要算法如果太激进会把关键的工具调用结果丢掉。我的做法是工具调用结果单独存储不参与摘要需要时按需检索。坑二上下文顺序错乱。多轮对话中消息顺序很重要。如果顺序错了模型会困惑。我的做法是用消息 ID 排序确保顺序正确。坑三上下文长度计算错误。不同模型的 tokenizer 不同token 数量计算可能不准。我的做法是用实际 tokenizer 计算留 10% 余量。坑四上下文切换开销大。每次切换上下文都要重新计算 KV Cache开销很大。我的做法是尽量保持上下文稳定避免频繁切换。6.5 端侧 Agent 的调试与日志端侧 Agent 的调试比云端难因为端侧环境封闭日志获取不方便。我的做法是分级日志DEBUG、INFO、WARN、ERROR 四级生产环境只开 INFO 以上。关键路径埋点在 Agent 编排、工具调用、LLM 推理等关键路径埋点记录耗时和结果。日志落盘端侧日志要落盘方便事后分析。但要注意日志大小避免占满存储。远程日志可选功能用户授权后可以把日志上传到远程服务器方便开发者排查问题。回放机制记录完整的对话轨迹支持回放。这样遇到问题时可以复现。注意端侧日志要注意隐私。用户对话内容、工具调用参数可能包含敏感信息落盘前要做脱敏处理。7. 工程化落地的几点个人体会端侧 Agent 工程化这件事我做了大半年踩过的坑比写过的代码还多。最大的体会是不要追求一步到位要迭代式推进。一开始我想做一个“全能 Agent”什么工具都接什么任务都做。结果发现模型根本驾驭不了那么多工具上下文也撑不住。后来我改成“场景化 Agent”每个场景只做几件事工具不超过 5 个反而效果好很多。另一个体会是端侧 Agent 的体验瓶颈往往不在模型而在工程。模型能力再强如果工具调用超时、上下文爆炸、状态丢失用户体验就是灾难。所以工程化不是“锦上添花”而是“雪中送炭”。还有一点MCP 是趋势但不要为了 MCP 而 MCP。MCP 解决了工具标准化的问题但它也引入了额外的复杂度和延迟。如果你的工具都是本地的、固定的直接 Function Calling 可能更简单。MCP 的价值在于“动态扩展”和“生态复用”如果你的场景不需要这些就不用强行上 MCP。最后分享一个小技巧端侧 Agent 的 prompt 要尽量短。端侧模型上下文小prompt 越长留给对话和工具结果的空间越少。我的做法是把 system prompt 压缩到 500 token 以内把详细的工具描述放到工具 schema 里而不是塞进 prompt。这个内容后续还可以这样扩展端侧 Agent 的多模态能力语音、图像、端侧 RAG 的工程化、Agent 的安全与隐私保护、以及端侧 Agent 的 OTA 更新机制。这些话题每一个都值得单独展开后面有机会再聊。