ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

端侧Agent架构设计与实操:从感知到执行的五层模型

2026/10/5 9:22:46 拓冰建站 浏览量
端侧Agent架构设计与实操:从感知到执行的五层模型 1. 端侧 Agent 到底是个什么东西先把概念钉死。端侧 Agent说白了就是把一个能自主感知、决策、行动的智能体完整跑在用户本地设备上——手机、PC、车机、嵌入式板子都算——而不是把请求发到云端等结果。它和云端 Agent 最大的区别不在于模型大小而在于数据不出设备、推理不依赖网络、响应延迟可控。我最早接触这个概念是在做一款离线语音助手的时候。当时团队想把对话能力塞进一台 4GB 内存的安卓设备第一反应是这不可能因为主流 LLM 动辄几十 GB。但后来发现端侧 Agent 的核心不是跑一个完整的大模型而是用一套架构把感知、规划、执行、记忆串起来让有限算力发挥最大价值。这个认知转变很关键。那端侧 Agent 能做什么举几个我实际落地过的场景一是本地文档问答用户把 PDF 丢进来Agent 自己切分、检索、总结全程不联网二是设备自动化比如根据用户习惯自动调整系统设置、整理相册、生成日程三是隐私敏感场景像医疗记录整理、个人财务分析这些数据用户根本不愿意上传。适合谁来读这篇如果你是有一定开发基础、想搞清楚端侧 Agent 怎么搭骨架的工程师这篇能给你一套可复现的架构思路。如果你是产品经理或者技术决策者想判断端侧 Agent 值不值得投入这篇也能帮你理清技术边界和成本结构。我不打算讲空泛的概念而是把架构拆到能动手的程度。2. 端侧 Agent 基础架构的整体设计思路2.1 为什么端侧不能照搬云端架构云端 Agent 的典型架构是大模型 工具调用 向量数据库 编排层这套东西在服务器上跑没问题因为算力和内存管够。但搬到端侧三个约束立刻卡死你内存、算力、功耗。我做过一个测算一个 7B 参数的模型FP16 精度下光权重就要 14GB 内存量化到 INT4 也要 3.5GB 左右。而一台中端手机的可用内存通常只有 2-3GB 给应用层。这意味着你不可能把大模型和一堆工具、向量库同时塞进去。所以端侧 Agent 的架构设计本质上是一场资源预算的博弈。我的思路是把 Agent 拆成常驻轻量层和按需加载层。常驻层负责意图识别、路由、简单响应用一个 0.5B 到 1B 的小模型或者规则引擎搞定按需层负责复杂推理、工具调用用 3B 到 7B 的模型只在需要时加载用完释放。这样内存峰值可控用户体验也不会因为频繁加载而卡顿。2.2 分层架构从感知到执行的五层模型我把端侧 Agent 的架构分成五层从下往上依次是硬件抽象层屏蔽不同芯片CPU、NPU、GPU的差异提供统一的推理接口。这一层的关键是选对推理框架后面会细讲。模型运行时层负责模型加载、量化、推理调度。端侧常用的是 llama.cpp、MLC-LLM、ONNX Runtime 这几套。能力层包括 LLM 推理、向量检索、工具执行、记忆管理四个核心模块。编排层这是 Agent 的大脑负责 ReAct 循环、任务规划、状态管理。应用层面向具体场景的交互界面和业务逻辑。这个分层的好处是每一层都可以独立替换。比如你今天用 llama.cpp 跑推理明天想换成 MLC-LLM只要硬件抽象层的接口不变上层代码几乎不用动。我在项目里吃过亏——早期把推理框架和业务逻辑耦合在一起后来换框架时改了几百行代码血的教训。2.3 ReAct 模式在端侧的适配改造ReActReasoning Acting是 Agent 的经典范式模型先输出思考Thought再决定动作Action拿到观察结果Observation后继续循环。云端跑 ReAct 很自然因为模型够大一次能处理很长的上下文。但端侧不行小模型的上下文窗口通常只有 2K 到 8K token而且每次推理都要消耗宝贵的算力。我的改造方案是压缩 ReAct 循环。具体做法把 Thought 阶段限制在 50 token 以内强制模型用极简语言表达推理Action 阶段只允许从预定义的工具列表里选不允许自由生成Observation 阶段做截断和摘要只保留关键信息回填。这样一轮循环的 token 消耗能控制在 300 以内小模型也能扛住。还有一个技巧是缓存中间状态。ReAct 循环里很多推理是重复的比如用户想查天气这个意图第一次识别后可以缓存后续几轮不用重新推理。我用一个简单的 LRU 缓存把重复推理降低了 40% 左右效果很明显。3. 核心模块拆解与实操要点3.1 LLM 推理引擎选型llama.cpp vs MLC-LLM vs ONNX Runtime端侧推理引擎的选择直接决定项目成败。我实测过三套主流方案给你一张对比表引擎优势劣势适用场景llama.cpp量化支持好CPU 推理快社区活跃GPU/NPU 加速有限中低端设备CPU 为主MLC-LLM跨平台编译GPU 加速强编译链复杂调试难高端设备有 GPU/NPUONNX Runtime生态成熟工具链完善大模型支持一般已有 ONNX 模型的迁移项目我最终选了 llama.cpp原因是量化方案最成熟。它支持 Q4_K_M、Q5_K_M 等多种量化级别我实测 Q4_K_M 在 7B 模型上能把内存压到 4GB 左右精度损失在可接受范围内。具体命令是这样的./quantize ./models/llama-7b-f16.gguf ./models/llama-7b-q4km.gguf Q4_K_M量化级别怎么选我的经验是内存优先选 Q4_K_M精度优先选 Q5_K_M极端受限选 Q3_K_S。Q3 以下精度掉得厉害除非实在没内存否则不建议。注意量化后的模型在中文任务上精度损失比英文更明显因为中文 token 分布更集中。如果你的场景以中文为主建议至少用 Q5_K_M。3.2 向量检索端侧 RAG 的内存优化端侧 Agent 要做知识问答离不开 RAG检索增强生成。但向量数据库在端侧是个内存杀手——一个 10 万条文档的向量库用 768 维 float32 存储光向量就要 300MB。加上索引结构轻松超过 500MB。我的优化方案是三级压缩第一级用 INT8 量化把向量从 float32 压到 int8内存直接降到 1/4第二级用 PQProduct Quantization进一步压缩把 768 维切成 96 个子空间每个子空间用 8 bit 编码内存再降一个数量级第三级做分层检索先用粗粒度索引筛出候选集再用精粒度索引排序。实测下来10 万条文档的向量库能压到 30MB 以内检索延迟在 50ms 左右完全可用。代码上我用的是 faiss 的 IVF-PQ 索引import faiss dim, nlist, m, nbits 768, 100, 96, 8 quantizer faiss.IndexFlatL2(dim) index faiss.IndexIVFPQ(quantizer, dim, nlist, m, nbits) index.train(vectors) index.add(vectors)提示PQ 压缩会损失精度召回率通常下降 5% 到 10%。如果你的场景对召回率要求极高可以只做 INT8 量化不做 PQ。3.3 工具调用端侧 Agent 的手脚怎么接Agent 要能干活必须能调用工具。端侧的工具调用和云端不一样云端可以随便发 HTTP 请求端侧得考虑权限、延迟、离线可用性。我把端侧工具分成三类系统工具如文件读写、剪贴板、通知、本地服务如本地数据库、传感器、外部 API如天气、搜索需要网络。系统工具和本地服务是端侧 Agent 的核心价值因为它们不依赖网络响应快。工具调用的实现上我用的是JSON Schema 描述 本地函数映射。每个工具用 JSON Schema 定义参数模型输出符合 Schema 的 JSON本地代码解析后调用对应函数。这样做的好处是模型不需要知道函数的具体实现只需要知道接口。{ name: read_file, description: 读取本地文件内容, parameters: { type: object, properties: { path: {type: string, description: 文件路径} }, required: [path] } }注意端侧工具调用一定要做权限校验。我见过一个案例Agent 被诱导读取了用户的私密文件。所有涉及敏感数据的工具调用前必须检查用户授权。3.4 记忆管理短期、长期、工作记忆怎么分Agent 的记忆系统是它区别于普通聊天机器人的关键。端侧的记忆管理要解决两个问题存什么和存哪里。我把记忆分成三层工作记忆当前对话的上下文存在内存里对话结束就清、短期记忆最近几天的交互摘要存在本地数据库、长期记忆用户偏好、重要事实向量化后存向量库。工作记忆的管理核心是上下文窗口的滚动策略。小模型窗口只有 4K token对话几轮就满了。我的做法是保留最近 3 轮完整对话更早的做摘要压缩。摘要用一个专门的 prompt 让模型生成控制在 100 token 以内。短期记忆我用 SQLite 存结构简单查询快。长期记忆用前面说的向量库检索时按相似度取 top-k。三层记忆的读写频率不同工作记忆每轮都读写短期记忆每轮写一次、偶尔读长期记忆只在特定触发条件下写。4. 完整实操从零搭一个端侧 Agent4.1 环境准备与依赖安装我以一台 Ubuntu 22.04 的开发机为例模拟端侧环境。实际部署到手机或嵌入式设备时步骤类似只是编译工具链不同。先装基础依赖sudo apt update sudo apt install -y build-essential cmake python3-pip git pip install numpy faiss-cpu sentence-transformers然后编译 llama.cppgit clone https://github.com/ggerganov/llama.cpp cd llama.cpp make LLAMA_CUBLAS1如果你要部署到安卓需要用 NDK 交叉编译命令会复杂一些但核心逻辑一样。4.2 模型量化与加载下载一个基础模型我用的是 Qwen2-1.5B因为它在中文任务上表现不错而且体积小。先转成 GGUF 格式再量化python convert.py ./models/qwen2-1.5b --outfile ./models/qwen2-1.5b-f16.gguf ./quantize ./models/qwen2-1.5b-f16.gguf ./models/qwen2-1.5b-q4km.gguf Q4_K_M加载模型用 llama-cpp-pythonfrom llama_cpp import Llama llm Llama( model_path./models/qwen2-1.5b-q4km.gguf, n_ctx4096, n_threads4, n_gpu_layers0 )n_ctx是上下文窗口端侧建议设 2048 到 4096。n_threads设成 CPU 核心数别设太大否则线程切换开销反而拖慢速度。4.3 ReAct 循环的实现核心的 ReAct 循环代码大概长这样def react_loop(query, max_steps5): context build_initial_context(query) for step in range(max_steps): output llm(context, max_tokens200, stop[Observation:]) thought, action parse_output(output) if action is None: return thought observation execute_tool(action) context f\nObservation: {observation}\n return 达到最大步数任务未完成parse_output负责从模型输出里提取 Thought 和 Action这里要用正则或者 JSON 解析容错性要做好。execute_tool根据 Action 调用对应工具返回结果。提示端侧模型输出格式不稳定parse_output一定要做多重兜底。我试过模型输出半截 JSON 的情况最后用正则提取关键字段才解决。4.4 记忆系统的落地工作记忆用一个列表维护超过窗口就压缩class WorkingMemory: def __init__(self, max_tokens3000): self.history [] self.max_tokens max_tokens def add(self, role, content): self.history.append({role: role, content: content}) self._compress_if_needed() def _compress_if_needed(self): if count_tokens(self.history) self.max_tokens: old self.history[:-3] summary llm_summarize(old) self.history [{role: system, content: summary}] self.history[-3:]长期记忆用 faiss 存写入时做向量化检索时取 top-3。这里的关键是写入时机——不是每轮对话都写而是检测到用户表达了偏好或重要事实时才写。5. 常见问题与排查技巧实录5.1 模型加载失败或内存溢出这是端侧最常见的问题。排查顺序先看模型文件大小和可用内存再看量化级别是否匹配设备。我遇到过 4GB 内存设备加载 Q4_K_M 的 7B 模型直接 OOM换成 1.5B 模型就好了。问题现象可能原因解决方案加载时 OOM模型太大或量化级别不够换更小模型或更低量化加载后推理卡顿线程数设置不当调整为 CPU 核心数的 1/2首次推理极慢模型未预热启动时跑一次空推理预热5.2 ReAct 循环死循环或跑偏小模型容易在 ReAct 循环里钻牛角尖反复调用同一个工具。我的解法是加步数上限 重复检测。如果连续两步调用同一个工具且参数相同直接中断并返回当前结果。还有一个坑是模型不按格式输出Thought 和 Action 混在一起。这时候要在 prompt 里加 few-shot 示例明确告诉模型输出格式。我试过加 3 个示例后格式正确率从 60% 提到 90% 以上。5.3 工具调用参数错误模型生成的 JSON 参数经常有类型错误比如该传数字传了字符串。我的做法是在execute_tool里做参数校验和类型转换用 Pydantic 定义参数模型自动校验和转换。这样即使模型输出有小错也能兜住。注意端侧 Agent 的工具调用一定要做超时控制。我见过一个案例Agent 调用了一个卡死的本地服务整个应用无响应。所有工具调用都要加 timeout默认 5 秒。5.4 中文场景下的精度问题小模型在中文任务上普遍比英文差尤其是量化和压缩后。我的经验是中文场景优先选中文语料训练充分的模型比如 Qwen 系列、ChatGLM 系列。量化级别至少 Q5_K_M别贪内存用 Q3。另外prompt 用中文写比英文写效果好因为小模型的中英对齐能力弱。我实测同一个任务中文 prompt 的准确率比英文 prompt 高 15% 左右。6. 一些实操心得和后续扩展方向搭完这套基础架构后我在实际项目里跑了几个月有几个体会值得分享。第一端侧 Agent 的性能瓶颈往往不在模型而在数据搬运。模型推理本身可能只要 200ms但向量检索、文件读写、内存拷贝加起来可能超过 1 秒。优化时要先做 profiling找到真正的瓶颈。第二别追求一步到位。我一开始想做一个全能 Agent结果什么都做不好。后来砍掉一半功能专注做本地文档问答体验反而上去了。端侧资源有限聚焦比全面重要。第三测试要覆盖极端场景。内存不足、网络断开、工具超时、模型输出异常这些在云端可能不常见在端侧是家常便饭。我写了一套混沌测试随机注入这些故障跑了一周才发现好几个隐藏 bug。后续扩展的话我打算往两个方向走一是多模态把图像理解加进来让 Agent 能处理截图和照片二是端云协同简单任务端侧做复杂任务走云端用一套路由策略动态切换。这两个方向都还在验证阶段等有成熟结果再分享。这套架构不是银弹但它给了我一个可复现的起点。端侧 Agent 这个领域变化很快新的量化方法、新的推理框架层出不穷保持学习和迭代比什么都重要。