
端侧 Agent 这几年被聊得很多但真正把它从“能跑通 demo”推进到“能上线抗流量”中间隔着一条叫工程化的鸿沟。这篇是《深入理解端侧 Agent》的第三篇重点聊工程化的上半段框架怎么选、Harness 和 Agent 到底什么关系、端侧并发怎么扛、记忆怎么管。如果你正在做端侧 AI 硬件部署或者刚把手里的 Agent 项目从 Jupyter 搬到真实设备这篇文章应该能帮你少踩不少坑。端侧 Agent 不是把大模型塞进设备就完了它是一套完整的系统。这句话听起来像正确的废话但我在实际项目里见过太多团队模型量化、推理加速做得非常漂亮一到了 Agent 层就开始手写 if-else最后被各种边缘情况击穿。模型能力只是地基真正决定产品能不能稳定跑起来的是工程化能力。1. 端侧 Agent 工程化到底在解决什么问题1.1 为什么单独的模型部署远远不够上一篇文章聊过端侧模型部署量化、推理引擎、内存优化这些都是让模型“跑得动”的手段。但等 Agent 要落地时光是模型跑得动远远不够。Agent 是一个循环不是一次推理。LLM 单次推理就是“输入一段文本输出一段文本”的函数而 Agent 需要感知、规划、行动、观察至少四个步骤而且往往要迭代多轮。这里需要一个运行时来支撑循环、控制工具调用、管理状态。如果这些逻辑全部散落在业务代码里每个请求都重写一遍很快就会失控。举个例子一个简单的需求是“明天杭州天气怎么样”。如果做成普通聊天应用直接把问题丢给模型生成回答就行。但做成 Agent就要判断用户意图、确定城市、选择天气查询工具、构造参数、发起请求、解析返回、格式化回答。这个过程里每一步都可能失败API 超时、城市别名识别错、响应数据格式异常。这些失败处理本身就是工程化要解决的问题。而且端侧设备环境和云端完全不同CPU 核数少、内存有限、网络不稳定、可用的工具 API 也不固定。如果每个设备端的 Agent 都靠业务代码裸奔逻辑会越来越难维护。要解决这些问题需要把模型之上的部分抽象成一个独立的运行时。这就是 Agent 工程化的起点模型部署解决“模型怎么跑”工程化解决“Agent 怎么活”。1.2 工程化的核心边界模型能力 vs 系统能力我习惯把端侧 Agent 的能力分成两层模型能力和系统能力。模型能力包括语言理解、意图分类、生成、Function Calling系统能力包括上下文组装、工具调度、记忆管理、错误处理、并发控制、安全策略。能力类型典型范围常见问题模型能力语言理解、意图识别、文本生成、Function Calling输出格式不稳定、幻觉、指令遵循弱系统能力上下文组装、工具调度、记忆持久化、错误处理、并发控制、安全策略状态丢失、工具误调、并发压垮设备模型能力决定 Agent 的“智力上限”系统能力决定产品能不能用。端侧模型通常是 7B、3B 甚至 1B 的小模型在复杂规划上明显弱于云端大模型所以更需要系统层面去兜底。举几个真实场景模型在 Function Calling 时可能漏掉参数系统可以通过 schema 校验和强制补全来兜底模型可能陷入重复循环系统可以设置最大迭代次数模型可能调用错误工具系统可以用白名单和参数校验来拦截。这些都不需要升级模型而是工程化该做的事。很多团队一遇到 Agent 效果不好第一反应就是换更大模型或者调提示词。但端侧设备换模型成本极高硬件不变的情况下靠系统能力去补模型能力的短板往往比升级模型更省成本效果也更可控。这也是工程化的价值所在。2. 框架选型别急着拥抱最火的全栈框架2.1 从 LangChain 到轻量级框架的选择思路现在主流的 Agent 框架不少LangChain、LlamaIndex、AutoGPT、smolagents、PydanticAI还有面向 JVM 生态的 Spring AI。但端侧选框架第一原则是“能剪则剪”。LangChain 这类全栈框架抽象层次多依赖重很多功能在端侧用不上反而成了负担。更关键的是全栈框架把太多决策细节藏在内部出了问题排查链路很长。端侧调试本来就难再套上几层框架一个问题可能要追到框架源码里才能定位。我在项目里吃过这个亏后来就把框架层拆了。我的建议是先自己写一个最小可用的 Agent 循环跑通业务。一个最简单的循环只需要几个核心模块读取请求、组装上下文、调用模型、解析输出、执行工具、判断是否继续。等你把这套循环写清楚再决定要不要引入框架。端侧通常用不上复杂的图编排线性循环加条件分支就够。与其引入重型的编排引擎不如把决策逻辑写在代码里用单元测试覆盖。选型时要关注框架是否支持运行时裁剪是否能方便地替换模型服务是否允许你接管上下文管理。框架的核心价值应该是帮你稳定跑循环而不是帮你把所有事情都做完。2.2 Harness 与 Agent 的本质区别Harness 这个词最初来自测试领域指测试夹具。放在 Agent 语境里我理解成承载 Agent 运行的所有外部设施调度循环、上下文组装、工具注册、记忆注入、安全拦截、日志追踪。Agent 本身是模型、提示词、工具定义和决策策略的组合。Harness 与 Agent 的关系打个比方Agent 是演员Harness 是舞台、灯光、提词器和安全网。演员决定怎么演Harness 决定演出能不能顺利进行。实际工程中Harness 通常是一个独立的运行时进程或服务负责接收请求、维护会话状态、组装上下文、调用模型、解析输出、执行工具、循环往复最后返回结果。Agent 的“大脑”还是模型但 Harness 负责所有外围稳定性的细节。很多文章把 Harness 和 Agent 混用其实层次完全不同。这个区分对团队分工很重要算法同学负责 Agent 的决策策略工程同学负责 Harness 的稳定性。如果混为一谈最后往往导致工程逻辑里掺杂大量算法判断算法又背着不该背的运行时责任两边都很痛苦。2.3 端侧框架应该具备的四个基础能力不管选什么框架我觉得端侧 Agent 运行时都应该具备四个基础能力缺一个后面都会补得很痛苦。第一可插拔的工具注册与执行。工具不能散落在业务代码里要有统一接口、schema 声明、超时控制、错误码映射。工具最好是动态加载和卸载的尤其是在设备端某些工具可能依赖硬件能力Harness 需要在运行时探测并决定启用哪些工具。第二上下文组装与裁剪。端侧模型上下文窗口通常很小系统提示词、工具 schema、历史消息、检索出来的记忆片段都要按预算动态拼接。不能简单地把所有消息都丢给模型否则模型很容易因为上下文混乱产生幻觉。第三可观测的循环控制。要能配置最大迭代次数、每轮最大输出 token、工具调用超时、重试策略。每一轮循环的输入和输出都要有日志方便出问题时回放。没有可观测性Agent 出了 bug 就只能靠猜。第四策略热更新。端侧设备不方便频繁发版所以 Agent 的提示词、工具列表、权限策略要支持远程下发。如果每次调一个行为都要重新 OTA成本会非常高。这四项属于框架的默认能力不是加分项。如果一个框架对其中任何一项支持很差落地时大概率会变成开发体验的噩梦。3. 端侧并发与性能Agent 怎么扛得住真实请求3.1 端侧并发的瓶颈不在模型而在调度有朋友问过“AI Agent 怎么扛并发”。我直接说结论端侧 Agent 的并发瓶颈绝大多数时候不是模型推理算力而是同步阻塞的调度方式。一次 Agent 会话里真正花在模型上的时间可能只有一秒钟但一次工具调用可能要等网络响应三秒钟。如果用同步线程模型一个 Agent 实例占用一个线程来十个并发请求就要开十个线程线程上下文切换和内存占用立刻告急。设备 CPU 核数本来就不多最终整体性能急剧下降。可以打个比方食堂里有三个灶台但每个厨师做菜时都要等食材送货。如果每个订单都包给一个厨师十个厨师挤在厨房里互相干扰。更好的做法是厨师只管炒菜送食材的等货交给专门的人一个厨师可以异步处理多张订单。所以瓶颈在调度不在模型我们要管理的是哪些步骤可以异步、哪些资源需要排队、超时怎么处理。把 Agent 循环里的 IO 等待释放出来并发能力立刻上一个台阶。3.2 并发模型选择单线程事件循环 vs 多进程端侧常见的并发方案有三种我整理成表格对比一下。方案原理优点缺点适用场景同步多线程一个请求占一个线程编写简单直观线程占用高上下文切换大低并发、工具调用少异步事件循环单线程处理 IO 等待内存占用低吞吐高编写更复杂CPU 密集推理仍需线程池IO 密集型 Agent 循环多进程/独立容器每个 Agent 独立进程隔离性强能用多核内存开销大启动慢高并发边缘服务器端侧通常用“异步事件循环 推理线程池”的组合。异步事件循环把大量等待时间释放出来token 生成这种 CPU 密集操作放到独立线程池里执行避免阻塞事件循环。实现上Python 可以用 FastAPI 加 asyncioJVM 可以用 Spring WebFlux 或 Kotlin 协程。还要关注另一个重点请求队列和背压。设备能力有限不能让所有请求同时进入 Agent 循环。用队列削峰超出的请求可以先排队给用户返回处理中状态。这是工程上最直接有效的兜底手段比无限开线程靠谱得多。3.3 实测参数与调优记录这里记录一组我在边缘设备上实测的数据配置是 6 核 ARM 边缘盒子、8G 内存部署 7B 对话模型4-bit 量化。基线是同步多线程模式10 个并发请求每个请求三到四轮对话结果 P95 时延 12 秒CPU 长期跑满内存抖动非常严重。第一次优化改成异步工具调用用 asyncio.wait_for 设置 3 秒超时P95 降到 8 秒左右。第二次优化把模型推理服务开启动态 batch并发上限调到 4超过的请求进入队列P95 降到 5.5 秒。第三次优化把上下文历史从 16 轮裁剪到 8 轮减少 prompt 长度P95 降到 4.8 秒。整套调优让同一台设备的支撑并发从 10 路提升到 20 路左右。这个数字不算普适结论但调优方向是有参考价值的并发上不去时先看日志里有没有大量线程在等待工具调用工具超时降下来效果通常非常明显上下文裁剪既降时延又省内存端侧并发上限宁可设置得保守一点也要防止内存被打爆。设备端最怕的不是慢而是 OOM 后整个进程一起挂掉。4. 记忆与上下文管理工程化最容易翻车的地方4.1 Working Memory 与 Long-term Memory 的分层Agent 记忆是工程化里最容易被低估的部分。很多人以为记忆就是把聊天记录存下来下次一起塞给模型结果上下文很快就爆了。正确做法是把记忆分成两层Working Memory 和 Long-term Memory。Working Memory 是当前会话进行中必须保留的状态比如用户刚才提到的城市、消费偏好、中间查询结果。它要求访问快、实时更新、可裁剪。Long-term Memory 是跨会话的知识包括用户画像、业务规则、历史关键结论。它要求持久化、可检索、带时间戳。这两个层次的存储目标和访问方式完全不同。Working Memory 适合放在内存里用一个带淘汰策略的队列管理Long-term Memory 适合放 SQLite 或本地向量库每次请求时根据当前查询做相似度检索只注入 Top-K 片段而不是全量加载。分开管理后上下文质量会明显提升。还有一个工程细节很容易被忽略聊天历史和工作记忆不是一回事。聊天历史是流水账可以裁剪甚至丢弃工作记忆里写的是 Agent 的中间状态比如“当前正在查询城市杭州”就算对话记录被裁剪这些状态也能保住。没有工作记忆的话多轮对话很容易出现 Agent 忘了自己刚才在干什么。4.2 上下文窗口的预算控制上下文窗口是有限的但 Agent 想往里塞的东西太多了。我们要像管理内存一样管理 token 预算每一部分都有明确的分配上限。假设模型上下文窗口是 4K可以按这张表分配组成部分预算 tokens说明系统提示词400固定的角色和行为约束工具定义 schema600当前启用的工具列表工作记忆500会话状态和关键中间结果长期记忆检索片段400检索命中的 Top-3 片段用户输入500最新一轮用户问题输出预留1600保证模型有足够的生成空间当某一部分超预算时按优先级裁剪先丢长期记忆片段再裁剪历史对话最后压缩工作记忆。尤其要注意输出预留预留太小模型会在回答中途被截断体验非常差。实现上可以写一个 BudgetManager 类用 tokenizer 而不是字符串长度来估算 token 数。同样的内容中文字符和英文 token 的占比差异很大只靠字节数估算是不准的。class BudgetManager: def __init__(self, total_budget: int, output_reserve: int): self.total_budget total_budget self.output_reserve output_reserve self.items {} def add(self, name: str, content: str, tokenizer, priority: int): tokens tokenizer.count(content) self.items[name] {tokens: tokens, content: content, priority: priority} def build(self): available self.total_budget - self.output_reserve sorted_items sorted( self.items.items(), keylambda x: x[1][priority], reverseTrue ) final_parts [] used 0 for name, item in sorted_items: if used item[tokens] available: final_parts.append(item[content]) used item[tokens] return \n.join(final_parts)这份代码只是一个思路实际项目里要再加缓存和增量更新避免每一轮都重新 tokenize 全部历史。4.3 记忆持久化与端侧存储选型端侧持久化存储不能照搬云端方案。SQLite 几乎是首选单文件、事务、零配置、低内存非常适合设备端。LevelDB 适合做纯 Key-Value 场景。向量检索可以用 sqlite-vec 或轻量版 FAISS直接内嵌在进程里。不建议在设备上起独立的数据库服务那是给自己找麻烦。记忆数据一定要有过期和清理策略。每条长期记忆可以带时间戳和重要度评分定期清理低价值内容。比如对话历史默认保留 30 天用户偏好长期保留。不同设备的容量差异很大保留策略应该是可配置的。隐私方面端侧的记忆属于个人数据本地存储要加密。SQLite 可以用 SQLCipher敏感字段单独加密后再落盘。如果要做跨设备同步上传前先做脱敏和加密不能直接同步原始数据。这些工作在工程上都是必须的不是可选项。5. Skill 工具化与安全边界5.1 Skill 的本质把不确定变成确定端侧 Agent 面对的任务相对固定与其让模型每次调用都自由发挥不如把高频任务固化成 Skill。Skill 是一套可复用的能力单元包含触发条件、输入 schema、执行流程、输出模板和错误处理策略。举个例子“查询天气”这个 Skill不是让模型直接调天气 API而是先定义一个包含城市、日期、单位的参数结构模型只负责从用户输入里抽取参数并触发 Skill剩下的参数合法性检查、API 调用、响应解析、结果格式化全部由 Skill 内部代码完成。这样模型只做自己最擅长的意图识别和参数抽取流程的稳定性由代码保证。Skill 和工具是不同层次的抽象。工具是原子操作比如“获取当前天气”“发送通知”Skill 是流程编排可以调用多个工具。工程上要建立一套 Skill 管理机制包括版本、依赖、权限、热更新。Skill 之间有依赖时用命名空间和显式声明来管理不能靠约定俗成。5.2 端侧工具调用的安全约束工具调用意味着 Agent 能改变外部状态发消息、操作文件、控制设备。在端侧安全边界必须比云端更紧因为设备里全是用户的个人数据。首先是工具白名单。Agent 只能调用当前场景配置允许的工具比如个人助理可以用日历和通知但不能随意读取短信记录。其次是参数校验。模型生成的参数必须经过 schema 校验日期必须符合格式、城市必须在枚举列表里、文件路径必须落在允许目录。模型输出不可信这是工程上必须默认的前提。然后是沙箱执行。涉及文件、网络、命令的工具最好放到独立沙箱进程或容器里执行限制文件系统和网络权限。权限最小化原则也要贯彻每个工具只暴露完成业务必需的最小权限不要提供一个通用 shell 或万能文件读写工具除非业务确实需要。最后是操作确认和审计日志。付款、删除、发送消息这类敏感操作必须有二次确认。端侧可以弹窗让用户确认确认时要附带上下文不能只给一个抽象按钮。每次工具调用的触发者、参数、结果都要记录结构化日志方便回溯。5.3 一套可落地的 Agent 安全检查清单我在实际项目里总结过一套检查清单分享出来给需要的人参考。检查项检查说明级别落地建议工具白名单当前 Agent 能调用哪些工具高启动时加载运行时动态控制参数 schema 校验参数类型、范围、枚举高用 JSON Schema 校验工具超时与重试防止工具卡死高设置 3 到 10 秒超时有限重试敏感操作二次确认付款、删除、发送消息等高等用户确认并附带上下文注入防护防止用户内容控制 Agent 指令中用户输入和系统提示严格分离审计日志记录每次工具调用中结构化日志本地轮转循环次数限制防止死循环高设置最大迭代次数输出校验防止生成危险内容或错误指令中关键词过滤或分类器这里面最容易出问题的是参数校验和循环次数限制。我踩过一个大坑多步任务里Agent 调工具失败后模型会自己“发明”一个参数继续调反复重试把整个请求拖到超时。后来加了严格 schema 校验和最大重试次数问题才算解决。Agent 安全的本质不是防外部攻击是防止 Agent 因为幻觉或注入产生误操作。端侧设备的个人数据一旦被误读或误删后果非常严重。6. 一点工程体会最后说点我做端侧 Agent 这几年的体会。工程化从来没有一个框架能一劳永逸每个团队的设备形态和业务场景都不一样真正重要的是把模型、Harness、工具、记忆、安全这五层想清楚然后让每一层都可观测、可配置、可回归。我见过太多项目 demo 阶段效果惊艳一上真实设备就崩原因不是模型不行而是 Harness 不稳。现在我做技术方案一定会先问三个问题工具调用失败时系统怎么兜底上下文超了怎么裁剪并发高了怎么排队这三个问题能回答清楚工程化地基就算打牢了。下篇我会接着聊部署、OTA 和评测相关的内容先留个口子后面继续填。