☰
端侧Agent工程化实战:状态机、工具调用与资源约束下的系统设计
2026/10/7 13:11:20 网站建设 项目流程

在三个月前的小型语音助手上跑步,还是拿到一台只有 8GB 内存的国产开发板上跑通端侧 Agent,这两件事给我带来的心理冲击完全不同。跑通 Demo 的那一刻确实很爽,但半个月之后,当我把这个"能回答问题的玩具"拿去接真实业务时,才发现真正的战争才刚刚开始。之前的文章已经聊过端侧 Agent 的概念和架构,这次想认真讲讲工程化。

先说结论:端侧 Agent 工程化,核心不是"把模型塞进设备",而是围绕"模型在设备上做决策"这件事,把任务编排、状态存续、工具调用、上下文管理、能力降级、部署升级这些原本在服务端被忽视的问题,重新按端侧的资源约束做一遍。这一篇是"上",先讲骨架和基础,推理加速、并发控制、评测迭代这些偏"术"的内容放在"下"篇。

1. 能跑通的 Demo 和能交付的 Agent 之间,隔着一条什么河

1.1 云端那一套,在端侧直接水土不服

很多从云端转过来的同学,第一个反应是把 LangChain 或者 Dify 那套思路搬过来,在设备上起一个 Python 服务,把模型 API 换成端侧推理引擎,就觉得万事大吉。我一开始也是这么干的,结果被现实教育得很彻底。

举一个具体差异:云端的 Agent 默认模型是一个"远程黑盒",上下文再大,也不过是 token 计费的问题;端侧模型跑在本机,上下文窗口直接吃内存带宽和显存。我的测试机上跑 7B 模型,上下文从 4K 涨到 8K,推理延迟翻了一倍多,显存占用也肉眼可见地涨。这意味着端侧 Agent 的"思考过程"必须剪枝——能不看长文档就不看,能压缩历史就压缩,而不是像云端那样"全部塞进 prompt 让模型自己挑"。

再比如工具调用。云端 Agent 调一个 API,网络延迟几十毫秒,失败重试也很从容;端侧 Agent 要调的是本机应用、传感器、文件系统、蓝牙设备、甚至另一个端侧模型的输出。这些工具接口千奇百怪:有的是 Unix socket,有的是 Intent 广播,有的是共享内存,有的根本没有稳定接口,只能靠 shell 命令拼凑。工程质量完全取决于这一层工具抽象做得好不好。

1.2 四个被低估的维度:资源、不确定性、状态、边界

我梳理了一下端侧 Agent 工程化必须面对的四个核心维度,每个都是"河"的一部分:

资源维度。芯片算力、内存带宽、电池、发热,都是硬约束。模型推理要省,非模型逻辑也要省。一个 Agent 框架本身如果吃掉 80MB 内存,在手机上还能忍,在智能音箱上就是灾难。

任务不确定性。Agent 的输入不是一个固定 schema 的请求,而是自然语言描述的目标。用户说"帮我把今晚的菜谱根据冰箱里的东西定一下",Agent 要先感知冰箱库存(可能需要视觉识别或手动录入),再规划菜谱,再生成购物清单,每一步都可能出现歧义。这种不确定性要求在架构上留出"人机确认"和"中途改主意"的弹性。

状态长存。与一次性的 RPC 请求不同,Agent 是一个长时间运行的"会话体"。它要记住用户偏好,记得上次任务执行到哪一步,要在设备重启后恢复上下文。这些状态放在哪里、怎么序列化、怎么保证一致性,是端侧特有的难题。

边界问题。端侧 Agent 拥有设备的控制权——能发消息、能调应用、能读写文件。这就把安全问题从"网络安全"变成了"本机权限治理":Agent 执行一个工具调用前,是否需要用户授权?授权粒度怎么设计?如何防止提示词注入导致的越权操作?这些不解决,工程化就是空中楼阁。

2. 先分清两个词:Agent 是决策主体,Harness 是承载它的运行环境

2.1 为什么"harness"这个词在端侧尤其重要

热搜里有个词反复出现:harness 和 agent 的区别。我发现在端侧工程化语境下,这两个概念必须切开,否则后面所有设计都会拧巴。

按我的理解,Agent是决策主体,它由三样东西组成:模型(推理能力)、上下文(当前任务相关的所有信息)、工具集合(它能调用的能力清单)。Agent 负责"想",它接收目标,产出计划,决定下一步调什么工具。

Harness是承载 Agent 运行的执行环境,它负责"做":管理 Agent 的生命周期(启动、暂停、恢复、终止)、维护与用户的对话循环、把 Agent 的决策翻译成真实的工具调用、处理工具返回结果并回填给 Agent、记录轨迹日志、实施安全策略。

用生活化类比:Agent 是司机,负责看路、判断、打方向盘;Harness 是车——底盘、发动机、刹车、仪表盘,还有交警(安全策略)。没有好的车,再好的司机也跑不了长途。

2.2 端侧 harness 的最低配置清单

在端侧,harness 不应该做得太重。我给出的最低配置清单是这样:

  • 一个运行循环(run loop):接收用户输入 -> 组装上下文 -> 调用模型 -> 解析决策 -> 执行工具 -> 更新状态 -> 循环或结束
  • 一个任务状态机:至少包含 idle、running、waiting_user、waiting_tool、error、done 这几种状态
  • 一个工具注册表:统一管理工具的描述、参数 schema、执行函数、超时策略
  • 一个会话管理器:负责上下文的持久化与恢复
  • 一个审计日志:记录每次模型输入输出和工具调用,用于调试与安全追溯

这套东西如果自己写,核心代码大约几百行就能搞定,但关键在细节。我见过太多团队在跑 Demo 阶段用最简单的事件循环糊一个,后面接真实工具时发现没有状态机、没有超时控制、没有日志,只能推倒重来。

2.3 框架选型:自己写 harness,还是套开源框架

这是端侧 Agent 开发最先遇到的问题。我的个人结论是:能自己写核心 harness 就自己写,可以借鉴框架思想,但别把开源框架直接搬进端侧。

理由有三个:

一是依赖体积和平台适配。LangChain 这类框架是为服务端设计的,依赖链很长,在 Android/iOS/嵌入式 Linux 上跑起来非常痛苦。Dify 更是重度依赖云端部署,和端侧场景八竿子打不着。CrewAI 的多 Agent 编排思想可以参考,但它本身不是为单机资源受限环境设计的。

二是抽象层次不匹配。端侧 Agent 需要的不是"千行配置的 Chain",而是"足够底层的状态机 + 工具协议"。开源框架的抽象级别越高,你越难针对硬件做优化。

三是调试路径。端侧出问题时要看的是"模型推理输出了什么""工具调用为什么超时""状态是怎么丢失的",这些在自研 harness 里一目了然;在大框架里可能被封装成"callback 没触发""pipeline 状态不可见"这类玄学问题。

我并不是说所有框架都不能用。如果你的目标是快速验证业务逻辑,在电脑上跑通一套 LangChain 完全没问题;但落到端侧设备,我更建议把它当作"设计参考书",而不是"运行依赖"。

3. 任务编排:把多步决策变成可暂停、可恢复、可中断的状态机

3.1 为什么 ReAct 循环在端侧不够用

上篇文章讲过 ReAct 是 Agent 的基础决策范式——推理(Reasoning)-> 行动(Action)-> 观察(Observation)循环。但工程化之后你会发现,纯 ReAct 循环在端侧有很实际的痛点:

第一,它是一次性的。用户问一句答一句,Agent 没有"任务进行到一半"的概念。但真实场景是:用户让 Agent 帮忙规划周末行程,Agent 查到一半,用户说"等等,周五晚上先不加这个"——这时候如果 Agent 只能从头再来,体验非常差。

第二,模型推理和工具执行是串行的。模型每输出一个动作,就要等工具执行完才能继续。在云端这个延迟勉强可接受,在端侧,一次模型调用可能就要 1~3 秒,串行循环一旦超过三四轮,用户就以为设备死了。

第三,没有一个清晰的"任务边界"。什么时候算完成?卡住了算失败还是算等用户?这些问题在纯循环里没有答案。

所以我在工程化时,把"决策循环"升级成了"任务状态机"。

3.2 一个最小可用的任务状态机设计

我把 Agent 的每次任务抽象成这样一个状态机:

  • IDLE:空闲,等待用户新任务
  • PLANNING:模型正在生成计划(可暂停)
  • EXECUTING:正在执行当前步骤
  • WAITING_TOOL:已发起工具调用,等待结果(可中断)
  • WAITING_USER:遇到歧义或需要授权,等待用户输入
  • COMPLETED:任务完成
  • FAILED:任务失败,走降级处理
  • CANCELLED:用户取消

关键点在于:状态必须可持久化。当 Agent 进入 WAITING_TOOL 时,我们要把当前上下文、计划列表、已完成的步骤、当前步骤的中间输出,一次性序列化存下来。这样,即使设备因为省电策略杀掉了进程,下次拉起时也能恢复现场。

这个设计带来的直接收益是"断点续聊"。我的朋友在做的一个端侧助手,用户说"帮我把这份文档翻译成英文然后总结要点发给我"。传统实现要一口气跑完翻译和总结;状态机实现可以让 Agent 先翻译(耗时很长,走后台任务),翻译完成进入 WAITING_USER 问用户"翻译好了,现在总结吗?"——用户说"先不用了",任务在总结步骤挂起,下次用户说"继续吧",直接从总结步骤恢复,不用重新加载全文。

3.3 并行展开:一个 Agent 如何同时执行多个独立子任务

端侧 Agent 的"多任务"常常被误解成"多 Agent 并发",但在资源受限设备上,跑多个模型实例是不现实的。更务实的做法是:单个 Agent 的决策循环中,允许展开多个并行的工具调用分支。

比如用户说"查一下明天的天气,同时帮我把购物清单里的牛奶加到购物车"。这里有两个完全独立的工具调用:天气查询和购物车操作。如果串行执行,至少两次往返;如果并行展开,一次模型推理输出两个 action,harness 同时发起两个工具调用,总耗时接近最慢的那个。

实现上,我在 harness 的工具执行层维护一个 task group:模型输出一个"并行计划块"(plan block),包含多个 action,harness 将它们分发给独立的 worker 并发执行,每个 worker 有各自的超时时间,全部返回后合并 observation 再送回模型。这里有一个坑:并行执行时,如果某个工具改了共享状态,可能会导致数据竞争。所以工具调用要尽量设计成"无副作用或可回滚",对写操作特别是涉及用户数据的,一律串行。

3.4 人机协同:把 WAITING_USER 当成一等公民

端侧 Agent 最容易被忽略、但工程上必须设计好的一环,是"什么时候该问用户"。

我的原则是:涉及不可逆操作、涉及花钱、涉及隐私数据的操作,必须 WAITING_USER。比如删除文件、发送消息、下单购买、读取通讯录,都需要用户确认。这条策略现在很多端侧设备厂商也都这么做了,典型的就是手机上的 AI 助手调用系统级能力前弹授权框。

但 WAITING_USER 不能只是"弹个窗等输入"那么简单。你要设计好"悬置任务的恢复协议":Agent 在等待用户时,要保存一个临时上下文快照;用户可能隔很久才回复,这期间设备可能重启、应用可能被清理;恢复时要能精准回到当初那个等待节点,并且把用户新输入与旧任务上下文合并成"延续决策"。

我在实现里给每次 WAITING_USER 分配了一个悬浮通知,通知里带上"任务 ID",用户点通知就能恢复会话。恢复时把快照反序列化,重新进入决策循环,但这次输入的是"用户对问题的回复"而不是"全新任务"。

4. 端侧记忆与状态:从"对话历史"到"可恢复的会话状态"

4.1 记忆不该是一段字符串,而是一组结构化记录

做 Agent 开发的人都知道 context 的重要性,但端侧工程化对"记忆"的要求和云端完全不一样。云端可以无限堆 token;端侧必须把记忆当作一种"存储系统"来设计。

我从三个层面管理端侧 Agent 的记忆:

工作记忆:当前任务相关的短期信息,比如用户刚刚说的目标、当前正在看的文件内容。这部分直接存在于模型上下文里,通常只有几百到几千 token。工程化要做的是"及时清理"——每完成一个子任务,就把不再需要的中间输出从上下文摘除,防止上下文膨胀吃掉推理速度。

会话记忆:一次完整交互中值得长期保留的内容,比如用户偏好("我喜欢简洁的回复""不要用专业术语")、重要事实("用户家里有两个孩子")、历史任务的结论。这部分建议落地为本地结构化存储(SQLite 或轻量 KV),在每次 Agent 开始决策前,按需注入相关条目到上下文。

程序记忆:Agent 学会的技能或沉淀的经验,对应现在大热的 skill 概念。比如一个"网页转 Markdown"技能,它包含提示词片段、调用哪个工具、参数怎么填、常见错误怎么处理。这部分不是 prompt 字符串,而应该是一段可执行、可编排的"技能脚本"。

4.2 会话状态的序列化协议设计

一个非常实际的工程问题是:当设备进入省电模式或进程被杀,Agent 的会话状态怎么保存?

我的方案是引入一个统一的 SessionRecord 结构,JSON 序列化。核心字段包括:

  • session_id:会话唯一 ID
  • user_profile_refs:关联的用户画像条目 ID 列表
  • task_stack:当前任务状态机(包含计划列表、完成/未完成标记)
  • context_handle:指向一段压缩后的对话摘要(而不是原始全文)
  • tool_memory:本次会话中工具调用产生的重要中间结果
  • resume_point:用于恢复的节点指针(比如"等待用户确认删除")

每次状态变化(进入 WAITING_TOOL、WAITING_USER、完成一个步骤)都会触发一次持久化。写入频率不能高,不然 IO 会拖慢整体,所以我采用"关键节点同步写 + 中间过程异步写"的策略:只有进入 WAITING_USER 和任务完成/失败时同步 flush,其他中间状态只更新内存,等进入关键节点再一起落盘。

4.3 上下文压缩:端侧 Agent 的保命技能

端侧模型的上下文窗口有限,对话一长,要么爆窗,要么延迟飙高。工程化必须设计一套上下文压缩机制。

我用的招数是"三段式摘要":

  • 对话开始阶段:原始消息完整保留
  • 对话中期:超过一定轮数后,每轮对话实时压缩成"一句话语义摘要",同时保留最近 N 轮原始文本
  • 长对话后期:只保留全局摘要 + 最近一轮完整内容 + 关键事实列表

压缩动作本身也要花钱(调用模型做摘要),所以不能每轮都做。我的经验值是:原始累计超过上下文窗口的 60% 时触发一次压缩。压缩后的摘要可能丢失细节,所以我在压缩时把"用户明确表达的需求、约束、偏好"单独抽取成 facts 清单,这部分是结构化存储,不参与摘要压缩。

有一次用户在一个会话里连续问了十几个问题,从菜谱到旅游攻略再到孩子教育,每个问题跨度很大。如果没有系统,整个上下文会乱成一锅粥。用三段式摘要后,模型始终能看到最近一轮的完整诉求和全局事实清单,虽然中间过程的细节丢了,但用户体验反而是上升的——因为模型不再被无关的旧话题干扰。

5. 工具调用的落地形态:让 Agent 真正"上手干活"

5.1 工具抽象:别让 Agent 直接面对杂物

在端侧,Agent 要调用的不是云端的 REST API,而是设备上的各种能力。如果直接让模型对着 shell 命令或者 system API 做决策,安全性和稳定性都会失控。

我的做法是定义一个中间层——Tool Adapter。每个工具对外暴露一个统一的接口描述:

  • name:工具名(给模型看的)
  • description:用途说明,要写清楚"什么时候用、什么时候不要用"
  • input_schema:参数定义的 JSON Schema,模型严格按这个生成参数
  • execute:真正执行的那个函数
  • timeout_ms:超时上限
  • requires_auth:是否需要用户授权
  • execution_mode:串行(默认)还是允许并行

举个例子,一个"打开应用"工具。对模型暴露的接口很简单:输入是应用包名或应用名。但适配层内部要做的事包括:查应用列表、确认应用是否已安装、通过 Intent/AM 启动、处理启动失败、加上 5 秒超时。模型不需要知道这些细节,它只管说"打开微信"。

5.2 模型输出到工具调用的翻译:JSON 解析的脆弱性治理

这是端侧 Agent 工程化最脏最累的活之一。模型可能输出格式不完整的 JSON,可能在 JSON 外包了 markdown 代码块,也可能把参数名拼错。云端可以把模型换成语义更强的版本或做多次重试,端侧推理成本高,重试一次的代价太大。

我的处理链路是分层兜底:

  1. 优先让模型输出结构化 JSON(在 system prompt 里给死格式)
  2. 拿到原始输出后,先做一次"提取器":用正则找到最外层花括号,剥离所有非 JSON 前缀后缀
  3. 如果标准 JSON 解析失败,用宽松解析器容错(允许单引号、去掉尾逗号、修正缺失括号这类常见问题)
  4. 再不行,进入"增量校验"流程:把模型输出按 token 流重新解码,在解码过程中使用工具参数的 JSON Schema 做约束解码,只保留符合 schema 的 token

第四步听着复杂,其实端侧推理引擎一般都能支持,只是要花点功夫和引擎团队对接。它的效果是"让模型根本不可能生成非法 JSON",副作用是解码稍慢。我的建议:对高频工具在关键参数上做约束解码,对低频工具容忍前端解析失败并重试一次。别在重试上死磕,多花时间提高 prompt 的约束力,比事后修补有效得多。

5.3 MCP 在端侧要不要用:我的裁剪方案

现在聊 Agent 工具协议,绕不开 MCP(Model Context Protocol)。这个是现在最主流的一种开放工具协议,社区生态也起来了。但端侧直接用标准 MCP 服务端是有问题的:标准 MCP 大量依赖 JSON-RPC over stdio 或 HTTP,还有初始化握手、能力协商、资源订阅这些机制,对于一个嵌入式设备的资源预算来说太重了。

我的裁剪思路是:在端侧实现一个 MCP 的"最小子集",只保留 tools/list 和 tools/call 两个方法,走本地 JSON-RPC over Unix socket 或共享内存。把 capability negotiation 固化成一份静态配置,不做动态订阅,不做 resource 玩法。

这套裁剪给后续接外部工具留了很好的扩展性。社区里丰富的 MCP server(比如各类 skill 仓库)很多都兼容这个子集,稍作适配就能接入。可以说,端侧 Agent 的工具生态未来大概率会长在"轻量级 MCP 兼容层"之上,而不是孤岛式的私有协议。

5.4 工具执行的错误处理与降级

工具调用失败的场景在端侧比云端多得多——蓝牙断开、文件被占用、用户取消了授权、权限不足、设备电池过低……我通常的做法是,给工具执行结果加一个"状态码 + 可读错误 + 建议下一步"的三元组:

  • 状态码:SUCCESS / FAILED / TIMEOUT / NEED_USER_INPUT / PERMISSION_DENIED
  • 可读错误:一句话说明给模型看
  • 建议下一步:比如"权限不足时建议引导用户去设置页打开权限";"蓝牙未连接时建议先执行连接流程"

模型在下一次决策中看到这个三元组,就能直接修正方向,而不是把同一个失败动作再执行一遍。这个设计特别重要,不然模型会在同一个坑里绊倒两次甚至反复绊倒。

6. 端侧部署与升级:让 Agent 跑进真实设备的最后一公里

6.1 程序与模型的解耦:热更新的前提

端侧 Agent 的更新问题常常被忽略。很多团队把 prompt 和工具逻辑一起编译进 App,导致改一句提示词都要发版。工程化的目标是"策略与代码分离"。

我的结构是三板斧:

  • 模型文件独立存储,按版本管理,支持增量下载
  • 提示词模板和技能脚本以配置文件形式下发,不走代码发版
  • 工具适配层保持稳定接口,具体实现可以通过插件机制热替换

举个例子,模型要从 7B 换到 3B(为了省电),或者从英文模型换成中文能力更强的模型,代码一行都不用改,只要更新模型文件和相关配置文件。拖了半个月才想明白这个道理,前面吃了不少亏。

6.2 包体积与内存预算怎么算

端侧 Agent 不是"能跑就行",资源预算是硬指标。整理一份可以拿去对产品对齐的预算清单,具体参数因设备而异,但这个思路可供参考:

  • 模型文件:7B INT4 量化后约 4GB,3B INT4 约 2GB,1.5B 约 1GB。小模型不能只看大小,还要看效果够不够用
  • 运行时内存:推理引擎 + 模型权重 + KV cache + 框架逻辑,建议控制在设备可用内存的 30% 以内。8GB 的设备上,Agent 全栈吃超过 2.5GB 就危险
  • 包体积增量:Agent 框架本体(不含模型)压缩后控制在 10MB 以内,超过这个数就得反思是不是引入了太多服务端时代的依赖
  • 网络流量:纯端侧场景为 0,但涉及云侧增强(比如复杂的摘要生成可以发到云端),要按次计费并做用户提示

6.3 多级能力降级:设备再弱也要有响应

端侧设备千差万别,同一个 Agent 可能跑在旗舰手机,也可能跑在几十块钱的 MCU 上。工程化必须设计能力降级路径。

我的降级阶梯长这样:

  • L0 完美模式:完整大模型 + 全量工具 + 实时在线
  • L1 均衡模式:中等模型 + 核心工具集 + 对话压缩
  • L2 节能模式:小模型 + 只保留高频工具 + 每次决策强制精简上下文
  • L3 兜底模式:规则引擎 + 固定话术(不调用模型),保证设备"还有反应"

这个降级不是手动切的,而是 harness 根据设备状态(电量、温度、内存水位、模型推理延迟)自动触发。比如电量低于 15% 时从 L1 切到 L2,用户能明显感觉回复变简单了,但核心功能(问天气、设闹钟)还能用。产品层面要清楚:降级不是 bug,是续航和体验的取舍,要让用户理解而不是感受到"坏了"。

6.4 数据与隐私边界

端侧 Agent 之所以有存在价值,很大程度是因为"数据不出设备"。工程化要把这条优势变成可证明的承诺,而不是口号。我的做法是:

  • 默认所有决策链路完全本地执行(模型推理、工具调用、状态存储都在设备内)
  • 涉及云端的操作(比如搜索增强、云同步)必须明示并取得用户同意
  • 审计日志只记录必要信息,且记录在本地,不上传
  • 用户可以在设置里一键清空 Agent 的所有记忆与状态

这一点在面向消费者的产品里尤其重要。之前做智能音箱时,用户对"设备是否在录音"极度敏感。有了清晰的本地边界和用户控制权,信任问题会好解决很多。这个经验放在端侧 Agent 上同样适用。

7. 最后留几句实在话

这轮做端侧 Agent 工程化,踩了无数坑,最大的感受是:端侧 Agent 不是把 LLM 变小然后塞进设备,而是从零设计一个"在限制条件下做决策"的系统。框架是骨架,状态是血肉,工具是手脚,安全是免疫系统,缺一样都会出问题。

如果你正准备上手端侧 Agent,我的建议是先把上述六个方面画成一个总架构图,每块自己评估一下复杂度,然后从最弱的地方开始补齐。不用一上来追求完美——能跑通一个最小闭环(状态机 + 一个工具 + 持久化会话)再去扩展,是性价比最高的路径。

下一篇"(下)"我会重点写推理加速、模型量化选型、端侧 Agent 的抗并发策略(多实例管理、排队调度,这部分和云端思路差异挺大),以及怎么给端侧 Agent 做评测和迭代。这里面后续动作比较多,我们需要一步步把"工程化"这顶帽子戴实。

记住一句话:Agent 的能力上限由模型决定,但产品体验的下限由 harness 决定。把 harness 打磨好,是端侧 Agent 从玩具到生产力的关键一步。

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

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

立即咨询