☰
端侧Agent工程化实战:架构分层与上下文管理要点
2026/10/5 5:14:11 网站建设 项目流程

这系列写到现在,前面两篇把端侧 Agent 的底层原理和典型场景聊得差不多了。之前我们讨论过为什么端侧推理更适合隐私敏感、延迟敏感的任务,也梳理过模型如何落地到手机和嵌入式设备上。今天这篇进到一个更现实、也更让人头疼的层面——Agent 工程化。说白了,就是怎么把一个能在云端跑通的 Agent Demo,挪到端侧设备上,变成一个稳定、可控、能扛住真实使用压力的产品级系统。

我自己踩过不少坑,从树莓派上跑一个简单的工具调用 Agent,到在量产设备上做整套 Agent 服务,中间的差距并不是“多加几个功能”那么简单。端侧 Agent 的工程化,本质上是做一套资源约束下的取舍系统:算力、内存、功耗、包体积、网络波动、设备碎片化,每一样都在逼着你重新设计。这篇先聊“上”的部分——架构分层、harness 与 Agent 的边界、上下文与记忆管理,后续再补并发、安全、部署这些更硬的骨头。

这一篇适合两类人:一类是在把云端 Agent 往端侧迁移,发现动不动就超时、爆内存的开发者;另一类是刚开始做端侧 AI 硬件部署,想直接避开常见大坑的新手。我会把原因讲透,也会给出能直接抄作业的方案。

1. 端侧 Agent 的工程化边界:先认清对手再动手

1.1 端侧与云端 Agent 的本质差异

云端 Agent 跑在数据中心,网络稳定、算力富裕、内存按需扩容、GPU 随便调用。你给它一个任务,它可以在几十个 API 之间来回调度,中途做十几次 tool calling,等多久都不算事。端侧完全是反过来的。

以手机端为例,现在的旗舰机 NPU 虽然理论算力不差,但实际跑一个 7B 参数的量化模型,推理速度也就是每秒十几到二十几个 token。如果是 3B 级别的模型在千元机上跑,速度能到每秒三四十 token,但内存占用也得 3~4GB 起步。量产 App 不可能把所有内存都让给模型,后台的页面栈、网络框架、图片缓存哪个不是吃内存的大户?更别提老设备的功耗约束——连续高负载推理五分钟,机身温度就上来了,系统立刻开始降频,推理速度断崖式下跌。

所以端侧 Agent 的工程化,第一课就是接受一个事实:端侧 Agent 不是在更差的环境里重跑云端逻辑,而是按端侧的环境重新设计整个系统。模型要选对尺寸,Agent 的执行逻辑要更简洁,工具调用的粒度要设计得刚好合适,甚至交互模型本身都得改——用户不能像一个云端使用者那样,把一个大任务丢给 Agent 然后耐心等待两分钟。

典型的端侧 Agent 应该把任务拆得更细,每一小步都在用户可视范围内完成,并随时准备把回答打断重来。这不是体验设计问题,而是资源约束下的必然选择。

1.2 部署形态决定工程复杂度

端侧 Agent 的部署形态五花八门,我把常见的几类列了一张表,方便你对号入座:

部署形态典型设备算力参考内存上限工程化重点
手机端 App 内置中高端智能手机NPU,7B INT4 约 15~25 tok/s500MB~1GB(分配给推理)功耗、核心权限、版本兼容
嵌入式开发板RK3588、树莓派 5、Jetson Orin NanoCPU/GPU,3B INT4 约 20~40 tok/s1~4GB稳定性、断点续跑、散热
桌面应用内置macOS / Windows 笔记本统一内存或独立 GPU,7B 可跑到 30~60 tok/s2~8GB多任务并发、插件生态
端网协同手机 + 本地局域网服务器模型在服务器,端侧只做采集和展示端侧仅需几十 MB通信协议、断线处理

不同形态的工程化重点差异很大。手机端除了性能和功耗,最麻烦的是系统权限,比如访问相册里的图片做多模态识别,iOS 和 Android 的权限模型完全不同。嵌入式设备倒是权限上放得开,但掉电、进程被杀是家常便饭,所以“任务恢复到上次中断点”是刚需。桌面端看似资源最多,但用户同时开着浏览器、编译器、视频会议,你抢太多内存一样会被骂。

工程化的第一个原则:先明确你的部署形态,再做技术选型。不要在手机端上硬套桌面端的 Agent 编排框架,也不要在嵌入式设备上幻想跑 70B 模型外加十几路并发。所有后续的设计决策,都从这张表出发。

2. 工程化架构:从 Demo 到可交付的分层设计

2.1 端侧 Agent 的四层架构

我在自己项目里逐渐把端侧 Agent 的服务端拆分成了四个明确层级,从下往上依次是:模型推理层、Agent 编排层、工具执行层、应用接入层。

模型推理层处理的是“把 token 跑出来”这件事。这里的选择通常是 llama.cpp 或 ONNX Runtime,再往上套一层针对自己业务的推理封装:流式输出、会话分批、KV cache 管理、中断信号处理。这个层要做得足够薄,尽量不要塞业务逻辑进去,因为模型可能会被替换(比如从 3B 升级到 7B,或者换一家推理引擎),封装稳定的话,替换的代价就小很多。

Agent 编排层是核心,它负责控制 Agent 的思考循环:拿到用户指令之后,要不要调用工具、调用哪个工具、传什么参数、拿回结果后如何继续推理。在这层里你不会直接接触模型,而是面向一个“能回复文本,也能输出结构化工具调用请求”的统一接口。云端做这个层一般用 LangChain 或者自研的 workflow 引擎,但端侧要轻量得多,通常就是一个循环加几个工具函数,配合 prompt 模板和调度规则。

再往上,工具执行层把 Agent 编排层发出的调用请求真正落地。比如访问本地文件、查询日历、调起相机、执行 Shell 命令等。这个层必须做权限和沙箱隔离,因为 Agent 的推理结果天生不可靠,它可能生成一个合法但完全不符合用户预期的命令,你的工具执行层就是要拦住这种风险。

最上面的应用接入层是窗体和 UI。对用户而言,看到的是一个聊天界面或者一个悬浮球;对上层业务而言,它提供的是“把这个任务交给 Agent”的 API。这一层还负责把用户的多模态输入标准化,比如拍照取词、录屏分析,转换成 Agent 能理解的指令格式。

2.2 harness 与 Agent 的职责边界:别再混为一谈

“harness”这个词最近很热,很多人把它和 Agent 混着用,导致团队里沟通成本极高。我个人的理解是:Agent 是策略,harness 是执行环境。Agent 负责“想”——分析输入、规划步骤、决定调用哪个工具;harness 负责“做”——拉起模型、管理循环、解析模型输出、把工具结果送回去、处理超时和重试。

类比一下:Agent 是司机,harness 是车。司机决定怎么开,车负责执行。你不能让司机去检查机油和轮胎,那是车的事;车也不能替司机决定走哪条路。如果这两个概念不分开,代码很快就变成一个没法维护的巨型函数:既要写推理逻辑,又要处理模型输出格式,还要管超时重启,最后谁都不敢改。

实际工程中,harness 通常是一套通用的运行时,不感知具体业务。它只处理四件事:

  • 维护 Agent 的循环结构(思考 → 行动 → 观察 → 再思考)
  • 与模型层通信,获取推理结果并做结构化解析
  • 管理上下文窗口,自动做裁剪和摘要
  • 处理异常——最大循环次数、单次推理超时、工具调用失败重试

你的 Agent 业务逻辑尽量写成纯函数,输入一个 Request 上下文,输出一个 Response 上下文。harness 负责在每次循环中把当前上下文喂给你的 Agent 函数,再把 Agent 的输出喂回上下文。这样工具一换、模型一换,Agent 策略代码几乎不用动。

我见过有人把工具调用的 JSON schema 定义、权限校验、上下文压缩这些活儿全部堆在 Agent 代码里,刚开始跑得通,后来 Agent 应用规模一上来就崩得稀里哗啦。职责分离不是代码洁癖,是工程化最基本的保命手段。

2.3 框架选型:重型框架并不是好选择

端侧 Agent 框架选型是个反复踩坑的领域。云端的成熟框架——LangChain、CrewAI、AutoGen——功能丰富,但它们的依赖链和抽象层数量在端侧设备上是沉重负担。我一个朋友在 RK3588 上装了 LangChain,光导入包就花了十几秒,跑一次简单任务内存涨了 300MB,最后全部推翻重写。

端侧工程化更建议这种组合:

  • 模型推理:用 llama.cpp、ONNX Runtime 这类底层引擎,不引入大框架
  • Agent 编排:自研一个轻量 harness,核心就三五个文件
  • 工具执行:按业务手写 tiny tools,每个工具一个模块,额外加一层权限校验
  • 会话与记忆:本地方案比如 SQLite 加 JSON 文件,避免引入重量级向量数据库

如果是桌面端的 Agent 应用,可以考虑 Hermes Agent 这类已经做好 harness 和 agent 分离的开源项目,直接在上层做业务扩展。它的设计理念我很认同:把任务运行的控制逻辑(shell 执行、进程管理、工具调用)和模型策略分开,对工程化非常友好。但如果你做的是嵌入手持设备这种极端受限环境,还是得砍到只剩核心循环。

框架选型的核心标准只有三个:启动耗时、内存新增占用、可裁剪度。其他花里胡哨的功能都往后排。端侧设备不是服务器,装一个 200MB 依赖的 AI 框架,用户手机上多一个大几百 MB 的应用,根本没法接受。

3. 上下文、记忆与状态管理:端侧 Agent 的命门

3.1 上下文膨胀是推理变慢的头号元凶

我在实际项目里测过一个现象:一个 7B INT4 模型,输入 200 token 时首 token 延迟大概 300ms;输入 2000 token 时直接涨到 1.2 秒;到了 5000 token,首 token 延迟接近 2.5 秒。这还只是 7B,换个更大的模型,衰减更夸张。端侧 Agent 跑不了几轮,上下文就膨胀上去了,因为每一轮工具调用的结果、用户的追加消息,全都在往上下文里塞。

所以上下文管理不是可选项,是端侧 Agent 工程化里最高优的优化点。常用的手段有以下几种:

  • 自动裁剪:设一个硬性 token 上限,超过就把最旧的对话轮次直接扔掉。注意别只丢用户消息,工具结果和 Agent 的思考链也得一起丢,否则上下文语义会断裂。
  • 动态摘要:每跑完一个子任务,把这段对话压缩成一个几十字的摘要,放回上下文里作为长期记忆的一部分。端侧用小模型做摘要就够了,因为摘要不需要多高质量,能抓住关键信息就行。
  • 关键信息抽取:不要啥都往上下文里塞。用户的偏好、当前任务的中间结果,抽出来放到结构化的 working memory 里,而不是堆在聊天历史里。

经验上,一个端侧 Agent 的单轮会话上下文控制在 4K token 以内体验最好,8K 是极限,超过 8K 就必须做压缩或摘要。这不是模型窗口不够——很多模型支持 32K——而是推理延迟和内存占用不允许你那么挥霍。

3.2 工作记忆与长期记忆的分层设计

端侧 Agent 的记忆设计,我这里推荐一个简单的三层结构,参考了很多项目的做法,也踩过不少坑。

第一层是即时工作记忆,对应 Agent 当前任务的临时状态。比如用户正在写一段文案,让 Agent 帮忙改了三版,那么“第三版内容”和“用户的修改意见”就属于这一层。它们只存在于当前会话里,会话结束就销毁。实现上用进程内对象即可,不需要持久化。

第二层是会话级记忆,对应一次完整任务的上下文。包括用户的目标、已经完成的步骤、当前工具结果摘要。这个要持久化到本地存储,因为端侧进程很可能中途被杀,App 重启后至少能把任务恢复到上一个状态。我通常用一个 JSONL 文件记录这个 session 的全部事件,恢复时重放就行。

第三层是用户级长期记忆,对应跨会话的稳定信息。用户的称呼、常用的工具偏好、经常访问的文件路径、可复用的技能等。这层优先存 SQLite,结构化查询方便,字段清晰。如果需要做语义检索,可以在 SQLite 里挂一个轻量向量扩展,但对端侧来说,向量检索的性价比通常不高,能不用就不用。

为什么要分层?因为不同层级的记忆有不同的生命周期和访问频率。如果你把所有东西都堆在一个内存对象里,会话一多就爆;如果全部落盘,每次推理都去查数据库,latency 又拉满。分层的本质是给不同数据匹配不同的存储策略。

3.3 状态机不是过度设计,是端侧保命技能

端侧 Agent 和云端最大的一个区别在于:云端进程基本稳定,端侧进程随时可能被系统回收。iOS 的后台墓碑机制,Android 的默认后台杀进程,嵌入式设备的突然断电,都是常态。

所以在工程化时,务必给 Agent 的执行过程定义一套状态机。我用的最小集是:INIT(初始化)、PLANNING(规划中)、EXECUTING(工具执行中)、OBSERVING(观测结果中)、FINISHED(完成)、FAILED(失败)、INTERRUPTED(中断)。

每次 Agent 循环到达一个稳定点(比如等待用户输入、工具执行完毕),就把当前状态写进本地事务日志。进程被杀了,重启之后先读日志,发现状态是 EXECUTING 且工具执行到一半,就根据工具本身是否具备幂等性决定是重跑还是标记失败。这个过程有点像数据库的 WAL 日志,思路是完全一样的。

比状态机更务实的一点是:工具调用的幂等性设计。比如“创建文件夹”这种操作天然幂等,重复执行没问题;“发送消息”这种操作不幂等,重复执行会重复发送。你的工具执行层在实现时就要标记每个工具是否幂等,Agent 恢复时才能决定是否安全重放。这个细节我见过太多项目忽略,最后都是在线上收到了重复短信或重复扣费才知道后悔。

4. 工程化核心实践:从设计到落地的关键细节

4.1 轻量 harness 的最小实现结构

前面反复强调自研轻量 harness,我直接把我验证过的最小结构写出来,你照着搭建即可。

agent_core/ ├── harness.py # 循环控制、超时与中断管理 ├── context.py # 上下文对象,包含 messages、working_memory、状态 ├── parser.py # 模型输出解析,提取 tool_call ├── tools/ │ ├── registry.py # 工具注册表,名称到函数的映射 │ ├── permission.py # 工具调用前权限检查 │ └── builtin/ # 具体工具实现 ├── memory/ │ ├── session.py # 会话记忆 │ ├── longterm.py # 长期记忆 │ └── summarizer.py # 上下文摘要 └── storage/ ├── journal.py # 事件日志,用于恢复 └── sqlite_store.py # 持久化存储封装

harness.py 的核心循环可以简化成:

def run_agent(request): ctx = build_context(request) for step in range(MAX_STEPS): savepoint(ctx) # 记录日志,方便恢复 response = model.generate(ctx.to_messages()) if not response.tool_call: return response.text tool = registry.get(response.tool_call.name) if not permission.check(response.tool_call): return "权限不足,已拒绝执行该操作" result = tool.run(**response.tool_call.args) ctx.add_observation(result) return "已达到最大执行步数,终止任务"

这段代码看起来简单,但背后要处理的问题一点不少。比如model.generate可能是流式的,需要支持中途取消;tool.run可能长时间卡住,需要带超时控制;ctx.to_messages()里要做上下文裁剪和摘要。把这些细节都做扎实,一个可用harness 差不多就需要几百行代码。

4.2 上下文压缩与摘要的实操参数

上下文压缩的时机和参数直接决定体验。我分享一下自己调出来的经验值。

触发裁剪的硬阈值设为 6K token 比较合适:模型窗口如果是 8K,留出 2K 的余量,避免某次工具结果特别长时超限。裁剪时保留前三轮对话(约 1K token,包含系统提示和用户首条指令),中间部分统一压缩成摘要。

摘要的 prompt 要专门写,不要复用通用总结的 prompt。我的摘要 prompt 大概是这个逻辑:“你是会话摘要器,提取用户的核心需求、已完成步骤、关键结果、未完成事项,压缩成 200 字以内,保留可以继续执行任务的信息。”用 llama.cpp 跑一个 3B 模型做摘要,大约耗时 300ms,完全可接受。

每轮对话结束后判断一次:如果当前上下文超过 6K,就触发摘要;否则不动。只在超出阈值时做摘要,不要每个循环都跑,否则反而增加延迟。

工具结果尤其要控制大小。我之前写过一个工具,它会把一个目录下的所有文件内容读回来,结果一个工具结果就几千 token,直接把上下文打爆。后来统一改成:工具返回内容上限 800 token,超长时只返回前 800 token 加一个“内容已截断,如需完整内容请调用另一个工具”的标记。让 Agent 自己去决定要不要继续读取,比把海量内容硬塞给模型强得多。

4.3 工具调用链路中的稳定性设计

工具执行层的稳定性,是端侧 Agent 工程化里最容易翻车的地方。大问题集中在三类:模型输出格式不可靠、工具本身不稳定、工具调用的权限和安全漏洞。

先说格式不可靠。端侧模型普遍比云端的大模型更容易在结构化输出上翻车,JSON 写错、参数类型不对是很常见的。所以我建议在 harness 里做一个容错解析链:先用严格 JSON 解析,失败后用正则提取{...}片段重试,再不行就提示 Agent 重新输出。链路设计完,解析成功率能从 80% 拉到 95% 以上。重点不是让解析器多聪明,而是让失败模式可控。

然后是工具本身不稳定。端侧设备的 IO 比云端服务器慢得多,尤其访问外部存储或网络时,经常会有几秒甚至十几秒的等待。每个工具调用都要带超时机制——我用的是 10 秒默认值,超时后返回一个明确的超时信号,让 Agent 可以决定重试还是换方案。还要给工具调用加最大重试次数,一般是 2 次,超过就直接返回失败,避免 Agent 陷入无限重试的死循环。

权限和安全这里多说一句。工具执行层必须做白名单校验:Agent 想调用的每个工具、每个工具的参数范围,都要经过权限检查。比如一个写文件的工具,要限制它能写的路径前缀,不能让它去覆盖系统目录;一个执行 Shell 的工具,要限制可执行命令的集合,不能让它随便rm -rf。这层校验可以在 harness 循环里做,也可以在工具函数入口做,关键是必须有。没做这层,你的 Agent 就是一个随时可能做出违法操作的后门。

4.4 版本升级与技能(Skill)机制

端侧 Agent 上线之后,最难的不是第一次把功能做完,而是持续迭代。模型在更新,Agent 的规划策略在调整,工具在增加,每一处改动都影响整体行为。要做好工程化,版本管理必须上升到“可独立发布的模块”粒度。

这里要说一下 skill 机制。“Claude Agent Skills” 是个很好的思路,在端侧同样适用。一个 skill 本质上是一个文件包,包含:一段描述性 prompt(什么时候用这个 skill)、若干工具定义(JSON schema)、执行逻辑(Python/JS 脚本)、以及示例用法。skill 粒度刚好介于“单个工具”和“完整 Agent 行为”之间,特别适合端侧灰度发布。

我的做法是,把所有 skill 放在一个独立的目录里,每个 skill 自带版本号。Agent 编排层启动时扫描 skill 目录,把可用 skill 的 prompt 拼进系统提示词,工具注册表也由 skill 自己注册。这样加一个新技能、改一个现有技能,都只要替换对应的 skill 目录,不影响 Agent 核心代码。

热更新可以做,但不是必须。在嵌入式设备上,我一般是在应用重启时检测 skill 目录的版本变更;桌面端则可以做一个轻量的文件监听,检测到变更后热加载。这种设计让工程迭代节奏快很多——模型不用换,Agent 逻辑不需要动,上传一个新 skill 包,就上线了一个新能力。

5. 实战中的高频问题与排查经验

5.1 一组真实高频问题速查表

我在端侧 Agent 调试过程中把最常见的问题整理成了一张速查表,按“症状→原因→解法”给你列出来:

症状常见原因排查与解法
跑几轮后推理越来越慢上下文膨胀,有效 token 超过 4K打开日志观察每次请求的 token 数,设置 6K 阈值自动压缩
模型经常输出无法解析的 JSON端侧模型指令遵循能力弱加容错解析链,用更简单的 schema,或换成指令遵循更好的模型
工具调用超时率高端侧 IO 慢,工具设计太重缩短单次加载内容,分页获取;给每个工具调用设独立超时
App 切换后台再回来,Agent 状态丢了进程被回收,状态没持久化按上文状态机设计,每个 stable point 落日志,恢复时重放
多轮对话后 Agent 忘记最初目标上下文裁剪把关键信息丢了摘要时显式提取用户目标和未完成事项,放回系统提示词
首次调用模型特别慢模型文件没预热,或内存不足触发 swap首次启动做模型预加载并把 KV cache 预热;检查内存占用,必要时换更小的量化模型

这六类问题占了端侧 Agent 调试工作量的七成以上。其中绝大部分不是算法问题,而是工程问题——上下文没管好、边界条件没处理、恢复机制缺失。

5.2 端侧 Agent 的调试技巧与工具链

端侧调试比云端痛苦很多:没有随时可看的集中式日志,没有随时可进的在线 Debugger,有些嵌入式设备甚至没有屏幕。我摸索了一套相对高效的调试方法。

第一,所有 Agent 决策过程都要输出 trace。从用户请求到最终响应,中间每个步骤:模型原始输出、经过解析后的 tool_call、工具返回结果、上下文更新后的状态,全部写成结构化的日志(JSON Lines 格式),落盘到一个 trace 目录。出问题时,直接回放这个 trace 就能定位到具体是哪个环节出了问题。这比让用户描述“它表现不对”然后盲猜高效得多。

第二,做一批固定的回归用例。我维护了一个测试集,里面包括“简单问答”“多轮工具调用”“需要拒绝的危险请求”“上下文超长压缩”这类典型场景,每次改完代码跑一遍,看输出和预期是否有大偏差。端侧模型有随机性,同一份代码在不同设备上输出也可能不同,所以回归的重点是检查“是否答非所问”和“是否调用错误工具”,而不是要求逐字一致。

第三,在端侧设备本地留一个最小复现入口。我在手机上写了一个 Debug 页面,可以直接输入测试请求、查看 Agent 的中间 trace、手动触发工具调用。没有这个入口,你每次改完代码都得走一遍完整 UI 流程,效率极低。桌面上我还会用命令行跑一个 headless 版 Agent,直接用 curl 发请求,调试速度能快一个量级。

第四,刚开始实验阶段,端着“失败是常态”的心态。端侧 Agent 不像云端那样长时间稳定,随机性、硬件差异、上下文干扰都会导致偶发问题。不要追求 100% 的确定性输出,而是把错误处理做扎实,让用户遇到问题时至少能得到一个合理的兜底回应,比如“这个请求我没能完成,你可以换个方式再试一次”。系统设计上留足容错,比让模型每次都完美更实际。

6. 关于工程化节奏的最后一件事

做了这么多端侧 Agent 项目,我最大的体会是:工程化的节奏决定了项目的生死。不要一开始就追求大而全的功能框架,也不要因为“端侧”就觉得要省到极致。先把最小可用链路跑通——一个模型、一个轻量 harness、三五把工具、二层记忆,然后放到真实设备上,一边跑一边补。

尤其要养成“先跑 trace 再写代码”的习惯。任何一次异常表现,先看 trace 里模型到底输出了什么、harness 是怎么解析的、工具返回了什么,再决定改哪一层。别一上来就调模型、换 prompt,八成问题不在那里。

这篇先把架构分层、harness 边界、上下文与记忆管理这些工程化的地基聊透了,下一篇我会展开讲并发模型怎么设计、端侧安全怎么落地、以及热更新和可观测性体系怎么搭。这些部分实操性更强,我们下篇见。

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

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

立即咨询