
提到端侧 Agent很多人第一反应是“在手机上跑个大模型然后在上面叠 prompt 和 tool call”。但真正把一个 Agent 从 demo 推到工程化尤其是端侧场景下的工程化你会发现事情完全不是这么简单。这个系列前面聊完概念和架构这一篇上我打算专门讲工程化落地时的那些硬骨头工具链怎么设计、技能怎么封装、资源受限下怎么做工程质量控制、以及调试和可观测性怎么搞。内容比较多所以拆成上下两篇这半篇聚焦在“投产之前”要做的事。写这篇的动机是我最近在项目里帮团队把一个端侧 Agent 从“能跑”打磨到“能扛”踩了不少坑也总结出一套相对可复用的方法论。如果你正准备把端侧 Agent 推向真实环境或者正在纠结几个框架和方案怎么选这篇文章应该能给你一些参考。我尽量把原理讲透也尽量把可直接照做的步骤拿出来晒。1. 先搞清楚端侧 Agent 的工程化和云端有什么本质区别1.1 端侧 Agent 不是云端 Agent 的“缩小版”先放下一个常见的误解很多人以为端侧 Agent 就是在本地跑一个轻量模型然后把云端的 Agent 设计拿过来换一下底座。但实际上端侧和云端的 Agent 工程化差异从最底层的假设就分道扬镳了。云端 Agent 的本质假设是“资源近乎无限”模型足够大、推理足够强上下文窗口可以做得很大工具调用失败了大不了重试或者是让模型重新规划。整套工程设计围绕的是“怎么让模型做得更好”甚至可以用更重的推理策略比如多次采样取优、自我反思循环。端侧 Agent 的假设是“资源是硬约束”芯片的算力有限内存带宽有限功耗受电池制约模型本身能力也比云端大模型弱一个等级。这意味着我们做工程化的时候首要目标不是“让模型变得更强”而是“让有限能力的模型在受限资源里稳定地产出正确结果”。举一个很实际的例子。云端 Agent 的工具调用失败模型可以再来一轮函数调用成本无非是多花几百个 token。但端侧一旦推理一次就占用了设备的内存带宽和功耗预算。如果一次任务里反复出现工具调用失败、重试、再失败用户感受到的不只是卡顿还有设备的发热和掉电。这种体验劣化在手机这种贴身设备上会被无限放大。所以端侧 Agent 工程化的第一步就是重新定义成功的标准不是“最终结果对不对”而是“用最少次数、最省的资源把结果做对”。这直接影响了后续所有设计决策。比如工具数量的选择、技能拆分的粒度、上下文压缩的策略甚至模型量化位数的取舍全都围绕这个核心目标展开。1.2 资源约束工程化设计的隐形天花板说到资源约束具体拆开看端侧 Agent 要跟四样东西抢资源CPU、内存/显存、存储和功耗。这四样不是独立的它们是互相耦合的。拿最常见的内存来说端侧 Agent 的内存占用不仅仅是模型权重的大小还包括模型的 KV cache、上下文窗口内原始文本的 token 表示、Agent 调用的工具描述、运行时状态、以及中间日志。我见过很多失败的端侧 Agent 项目都是在原型的阶段只看了模型权重大小算一下“2GB 内存够跑 4bit 量化模型”就以为稳了。结果一跑起来上下文稍微长一点KV cache 直接吃掉几百 MB工具描述写得冗长prompt 动不动三四千 token再加上推理框架本身的基础内存占用总共算下来远超预算。最后只能砍上下文窗口砍完窗口模型能力又下降落到一个两难的窘境。所以工程化的第一步不是选模型而是做一份“资源预算表”。我把自己的实操方式说一下。先确定目标设备的硬件基线比如某款手机的内存是 8GB系统占用大概 3GB应用进程可用 2GB那你给 Agent 的硬预算是 1.5GB。然后反向推模型权重量化后占多少、推理框架运行需要多少、预留多少给 KV cache 和喷发运行时临时分配、多少给日志和工具内存。把这笔账算完你才知道手上的设备能跑多大的模型、多大的上下文。端侧 Agent 工程化本质是在这笔预算内做“可行性设计”而不是先定模型再碰运气。还有个容易被忽略的热管理。端侧推理是密集型计算如果设计不合理让芯片长时间满载跑高负载设备会触发降频推理速度反而下降造成“越跑越慢”的恶性循环。工程上要主动做功耗调度比如在低电量模式下自动降低 Agent 的响应频率或者对推理任务做排队避免并发推理把整机功耗顶上去。这些细节在云端的服务器上完全不用考虑但在端侧就是不得不做的功课。2. 工具链设计端侧 Agent 学会“干活”的根基2.1 先给 Agent 配一套“小而准”的工具集Agent 的工程化本质上是把“模型能理解工具”这件事变成可靠的产品能力。端侧 Agent 能做的事很大程度取决于它能调用哪些工具。但端侧不是工具越多越好工具多意味着 prompt 里的工具描述变长token 消耗上升还会让模型的工具选择准确率下降。我实测过工具数量从 5 个增加到 15 个工具选择的准确率能掉好几个百分点尤其是在 7B 级别的端侧模型上效果很明显。所以我给项目定了一个原则工具集宁缺毋滥每个工具都要回答“用户任务里真的有这个高频场景吗”。核心的日常场景工具先上那些“偶尔能用上”的放到二级工具集只在特定意图命中时才注入到上下文中。这样有两个好处一是常驻工具的 token 开销可控二是二级工具集按需加载既保证覆盖面又不无脑增加每轮决策的负担。工具描述本身也要精修。很多人写工具描述喜欢堆功能什么“获取天气信息”“返回温度湿度风向”但对端侧模型来说描述越短越明确越好最好一句话说清“这个工具能做什么、什么时候用”。我建议把工具描述写成“触发条件 动作”的句式比如“当用户需要查询未来天气时调用此工具获取气象预报数据”。这种描述方式比功能罗列更贴合模型的指令遵循习惯工具选择的准确性会有明显改善。2.2 技能Skill是工具之上的封装单元这一两年“技能”Skill的概念越来越流行OpenAI 那边有 Skills社区里也有一堆 Agent 框架在推类似的概念。实际做下来我特别认可技能这种封装方式因为它解决的是“工具太细碎、Agent 规划负担重”的问题。举个例子一个工具层面的设计是“打开备忘录”“新建笔记”“添加待办”“设置提醒时间”这四五个工具让 Agent 自己去协调容易出错尤其在端侧模型能力有限的情况下多步规划经常在中途跑偏。而按技能来封装就是做一个“创建待办”技能内部把“打开备忘录、新建笔记、解析文本中的时间、添加提醒”这些工具调用串成固定的工作流。Agent 只需要决策“要不要执行这条技能”剩下的是流程化执行稳很多。工程化上的做法是建立一套技能描述规范我用的是三段式输入参数、触发场景、执行步骤。输入参数要声明类型和必填项触发场景写清楚什么情况下该调用执行步骤则描述内部会依次进行哪些操作。这套规范看起来简单但对最后的效果影响极大。因为技能里的执行步骤是给 Agent 看的“剧本”剧本写得清晰Agent 就不容易自由发挥。技能还有一个容易被忽略的工程价值可复用。同一个技能可以在不同的 Agent 场景里复用比如“智能家居控制”技能既能用在通勤助手里也能用在居家场景的 Agent 里。做好技能仓库管理把技能的版本、依赖、测试记录都管理起来端侧 Agent 的迭代效率会高很多。2.3 别急着接 API先把工具协议定下来端侧 Agent 的工具调用工程上有两种主流协议一种是基于 JSON Schema 的 function calling另一种是让模型输出结构化文本再由运行时解析。前者是主流模型厂商基本都支持后者是为了兼容一些不支持 function calling 的端侧模型。我强烈建议优先用 function calling 协议。原因很简单它会强制你定义严格的参数结构对入参做类型校验模型输出只要不符合结构就会被框架拦住而不是等到真正调用工具时报错。这相当于在模型和真实世界之间建了一个校验层工程可靠性大幅提升。真正做协议设计的时候有几个细节值得注意。第一个是参数命名不要用缩写要用全称因为参数名本身也是给模型看的语义信息比如start_date就比sd更容易让模型填对。第二个是必填与可选能标必填就标必填可选项别太多可选项多了模型填参时的拿不准会导致反复试错。第三是枚举值能限制枚举就限制枚举比如天气工具的温度单位直接给celsius和fahrenheit两个枚举值别让模型自由生成。我用过的最有效的一个改进是把参数默认值直接写进 Schema 的描述字段里比如“温度单位默认摄氏度”。这样模型在缺省情况下会直接填默认值减少了不必要的不确定性。这个细节听着小在端侧模型的指令遵循能力偏弱时对结果稳定性有实打实的帮助。3. 工程质量从“能跑”到“能扛”的关键一跃3.1 类型系统和运行时契约端侧 Agent 的项目里大量 bug 其实不是模型的问题而是模型输出和真实代码之间的隐式约定没有建立起来。所谓隐式约定就是模型以为工具会接受某种格式的参数但代码实现根本没这回事。最典型的就是时间格式模型可能按自己的习惯输出“下午三点”而工具端要的是 ISO 8601 的15:00:00。如果没有一个强制的契约层这类分布在每一次调用里的格式错乱会在运行时像地雷一样炸开。工程化的做法是建立严格的运行时契约也就是在 Schema 层做强制校验在代码侧做值归一化。校验是把模型输出卡在边界内归一化是把可接受的不同表示统一成一种标准格式。我自己的经验是任何工具的参数校验都不能只靠模型自觉必须代码兜底。模型输出一个时间范围代码先做解析解析失败就返回标准错误信息而不是抛一个 Python 异常给上层框架那样调试起来会非常难定位。这个契约层还有一个作用把“模型的行为”和“系统的行为”解耦。模型变了、prompt 改了只要契约层的接口不变下游代码就不用动。我在项目里做过一次模型切换从 7B 模型换到更小的 3B 模型契约层几乎没改只是调整了少部分工具描述。这种解耦带来的维护收益做几次模型迭代之后你就能深刻体会到。3.2 状态管理与记忆机制的工程化设计任何有实际价值的 Agent都需要某种跨会话的记忆能力。但端侧场景里记忆工程化有两个很现实的问题一是记忆的存储位置和格式二是记忆的读写频率直接关系着设备存储的损耗和性能。先讲记忆的持久化设计。我的做法是分层存储核心的用户偏好和长期事实存结构化键值中短期的对话摘要存文本记录完整的原始对话记录只保留最近几次其余做摘要压缩后丢弃。分层的目的是控制 token 占用同时保证关键信息不丢。具体来说每次对话结束后我会运行一个“摘要 Agent”来生成本轮对话的结构化摘要存入记忆库。下次用户再次交互时只加载摘要而不是把整个历史对话重新塞进上下文。这里有个工程上的痛点是摘要本身的可靠性。端侧模型的摘要能力弱于云端大模型生成的摘要往往会丢掉关键细节。我的应对方案是“半结构化摘要”不只是自由文本而是要求摘要按固定模板输出比如用户意图、执行结果、未完成事项、用户偏好每项单独一行。模板化的摘要能让记忆信息更完整后续加载时也能按字段提取效果比自由摘要稳定得多。记忆写入的频率也要控制。我是按“有实质状态变化才写”的原则来做而不是每轮对话都写。一次任务执行完后如果只是普通的闲聊或者查询不产生状态变化那就不写记忆只有当用户明确表达了偏好、任务状态发生变化、或者有需要跨会话记住的信息时才执行写入。这样既节约了设备存储端的写入次数也能避免记忆库被大量无用信息污染。3.3 安全边界本地权限与最小化原则端侧 Agent 的安全问题工程化上常常被当作“上线前再想想”的事但真出事的时候往往就是数据隐私和权限失控的大事。拿一个场景举例Agent 要访问用户的日历、通讯录、位置信息如果权限模型不清模型在错误时机调用了这些工具轻则骚扰用户重则隐私泄露。这不是危言耸听一个会写日历任务的 Agent完全可能在用户不知情的情况下因为一次错误的意图识别就创建一堆无用的日程。我的安全设计原则是“最小权限 显式确认”。具体分几层第一层是工具级别的权限控制高敏工具默认不可直接调用必须经过用户确认第二层是数据的最小化访问工具只接收完成当前任务所需的最小参数比如查询天气只需要城市 ID不需要向工具暴露用户完整位置坐标第三层是运行时沙箱所有外部工具调用都走一个代理层由代理层做鉴权和审计。审计日志也需要。我常用的做法是记一条“工具调用审计链”时间、触发了什么意图、调用了哪个工具、传了什么参数、工具返回了什么。这条审计链一方面可以做安全审查另一方面调试的时候也极其有用——你回看 Agent 为什么做出某个错误操作审计链直接把当时的决策现场还原出来。我在做安全复盘时经常能从审计链里发现模型调用了不该调用的工具然后反过来调整 prompt 或权限策略。4. 性能优化在算力天花板下跑出能用的效果4.1 一笔算清延迟预算一次完整决策的账端侧 Agent 的用户体验很大程度上取决于延迟。但“延迟”这个词太笼统我建议把一次完整的 Agent 决策拆成四段音频/文本输入处理、意图理解与上下文构建、模型推理可能多轮、工具调用与结果返回。每段的耗时都要单独量不能只盯着模型推理那一段。实际上上下文构建这一段的耗时往往被严重低估尤其当你要注入大量工具描述和历史记忆时prompt 的序列化、token 化也是要花时间的。我讲过一句话端侧 Agent 优化延迟第一刀永远砍在 token 数量上。token 少了prompt 序列化快了KV cache 占用小了推理也加速了一鱼多吃。所以每一版迭代我都会跑一个“token 审计”统计平均每轮请求的 prompt tokens 里系统指令、工具描述、历史记忆各占多少。然后逐个压缩工具描述能精简就精简历史记忆能多摘要就不塞原文。这一轮优化做完往往延迟能降 20% 到 30%比换模型还划算。第二刀是算好 KV cache 的复用。很多端侧推理框架支持前缀缓存也就是多轮对话中系统指令和工具描述这些不变的前缀可以缓存复用不需要每轮重新计算。在工程上要主动利用这个能力把系统指令和工具描述放在 prompt 的最前面保证前缀一致性让框架能命中缓存。这个改动几乎零成本但对多轮对话场景的延迟和功耗改善非常显著。4.2 并发不止是“同时跑几个 Agent”热搜词里有个“AI Agent 怎么扛并发”在端侧语境下这个问题的答案跟云端完全不同。云端扛并发是加机器、上负载均衡端侧扛并发是在一个设备上让多个任务交替进行不能互相饿死也不能把设备资源打满。我实际处理过的一个场景是一个 Agent 正在做耗时的本地知识库检索另一个 Agent 响应用户的语音指令还有后台在跑模型预加载。如果不做调度三个任务同时抢 CPU推理速度全部烂掉。工程上的解法是做一个轻量的任务调度层给推理任务分配优先级用户主动交互最高优先级后台预处理中等优先级日志和检索等 IO 密集任务异步执行不要阻塞推理。这个调度层不需要很复杂一个优先级队列加一个执行器就能撑住大部分场景。还有一个点是模型推理的串行化。端侧模型推理通常是高算力占用任务多个推理并发实际上很少能并行加速反而会互相拖累。所以我的做法是全局只保留一个推理执行器所有 Agent 的推理请求都进队列排队。表面上看起来“并发变低了”实际上因为避免了资源争抢吞吐反而更高。这种“反直觉”的设计真正做过后才理解。4.3 量化、内存和“模型缩水”的三角博弈端侧部署少不了一件事模型量化。但量化的位数和精度选择经常被人为推向极端。我的观察是很多团队一味追求 4bit、甚至 3bit 量化就为了把模型塞进内存预算结果模型能力损失太大Agent 的工具调用准确率跌破及格线最后只能靠堆 prompt 来挽救又把上下文撑大了内存收益反而被吃掉。量化到底怎么选我的经验是做“能力基线测试”拿一套 Agent 专用评测集分别跑 fp16、8bit、4bit对比工具选择准确率、指令遵循率、输出稳定性。如果 8bit 和 fp16 差距不大但 4bit 掉了 5 个百分点以上那就老老实实上 8bit别为了压缩内存而压缩能力。我见过一个很有意思的案例某团队用 3bit 量化把模型内存压到 800MB但每次工具调用准确率只有六成用户反复重试体验极差后来换成 6bit内存多了 400MB准确率回到九成用户的满意度反而上来了。因为“一次做对”比“省内存但反复试错”更重要这是端侧 Agent 的体验铁律。内存方面还有一个容易踩的坑是 KV cache 的峰值分配。有些推理框架默认按最大的上下文长度来预分配 KV cache即使实际只用了很少的 token内存也全被占了。工程上要按实际需求配置缓存大小或者开启 KV cache 的动态分配否则你模型权重省下来的内存又被这段缓存悄悄吃光了。5. 可观测性端侧 Agent 排障的“最后一公里”5.1 轻量日志体系别漏掉决策现场端侧 Agent 的调试难度比云端高一个量级因为你在电脑上看到的日志和用户设备上真正发生的行为往往隔着好几层。为了让排障不是靠猜我把日志体系拆成三层请求层、决策层、工具层。请求层记录用户输入、上下文构建耗时决策层记录模型输出、意图判断、选中的技能或工具工具层记录工具调用参数、返回结果、耗时和错误。这三层日志有各自的价值但决策层是最容易被忽视也最值钱的。因为 Agent 出了问题大多数根源都出在“模型当时到底怎么想的”。虽然模型没法告诉你它的推理过程但模型输出的原始内容、选择的工具、给定的参数就是它的“行为痕迹”。把模型输出原文完整记录下来不经过任何加工是后期复盘最重要的原材料。端侧日志还有一个工程约束不能全量漫无目的地打。每一条日志都会消耗存储和 IO尤其设备存储都是闪存高频写入还会加速损耗。所以我的做法是分层采样正常请求只记录摘要出现错误或者低置信度时才记录完整现场。这个“按需全量”的策略能很好地在调试能力和存储寿命之间取得平衡。5.2 决策路径回放从“报错日志”到“事故现场”光有分散的日志还不够你需要一条决策路径回放的能力。所谓回放就是能把一次完整的用户请求从上到下串起来用户在什么时间、什么场景下说了什么Agent 构建了怎样的上下文模型第一次输出了什么后续采取了几步行动每一步又触发了什么工具最终结果是成功还是失败。有了回放你才能回答“这个 agent 为什么做成这个样子”这个终极问题。实现回放的核心是“trace_id”贯穿机制。从用户请求进入系统的那一刻就生成一个唯一 ID之后所有日志、工具调用、计费记录、推理记录都挂上这个 ID。排查问题时拿 trace_id 一查整条链路就出来了。这个机制在云端是标配但端侧因为要节省日志量经常被砍掉这是个错误。trace_id 只是一串字符串成本极低带来的排障效率提升却是几何级的。有了回放能力之后我建议做一个定期的“事故复盘流程”每周挑几个失败案例回放整个决策路径弄清楚是模型意图识别错了、工具描述不清晰、还是上下文信息不足然后针对性地改。这样迭代几轮之后Agent 在真实场景里的表现会肉眼可见地变好。这也是端侧 Agent 工程化“持续优化”的核心循环。5.3 评测集没有它迭代就是瞎撞最后聊一个可能不受重视但极其关键的事评测集。我在做 Agent 工程化时最怕的一件事就是改了一个 prompt本地测几次没问题上线后一个隐藏场景直接崩掉。为了拦住这类回归我建了一套 Agent 专属评测集里面的用例全部来自真实用户数据覆盖核心场景和各种边界情况。评测集里的用例要分级。P0 是高频且必须成功的核心场景比如“设置提醒”“查天气”“打开应用”P1 是常见但允许偶发失败的场景P2 是长尾和边角场景。每次做模型升级、prompt 调整、工具逻辑改动都跑一遍评测集对比通过率变化。通过率掉了就说明你的改动引入了回归要及时调整。评测集还有一个重要用途量化不同方案之间的对比。比如前面提到量化位数的选择不是凭感觉拍脑袋而是跑评测集看数据。比如工具数量从 10 个增到 15 个准确率到底降了几个点也靠评测集说话。做端侧 Agent 工程化没有评测集的迭代就是瞎撞。这句话听着不新鲜但我见过太多项目在“快速迭代”的幻觉里把自己迭代进坑里。理论上评测集要跟线上行为持续对齐。用户行为是会漂移的评测集里的用例也要定期回流更新把最近新的失败案例补充进去。这相当于给 Agent 的工程质量上一道日常保险长期坚持下来系统的稳定性会有质的提升。6. 常见问题与避坑实录6.1 工具调用失败不是简单抛个错就完事工具调用失败是端侧 Agent 最高频的问题但处理手法的差距直接体现在体验上。最简单的做法是失败后把错误抛回给模型让模型自己决定怎么办。听起来合理实际上一旦模型陷入“反复尝试同一个错误方式”的循环后果就是无限的延迟和功耗浪费。我的做法是给工具调用失败设计三级响应机制。第一级是自动纠错如果失败原因是参数格式问题代码先做归一化修正再重新调用一次。第二级是降级处理修正后仍失败则给出一个“可用的替代结果”比如查询天气失败但缓存里有昨天的数据先给用户一个参考值。第三级才是真正的失败返回把明确的错误原因和可能的解决方案一起返回给模型让它决定是否换工具或通知用户。这个三级机制能避免大部分“模型原地打转”的问题。还有一件事要注意错误信息本身必须是结构化的。不要给模型返回一个“无法解析”这种模糊描述要给它“参数 start_date 格式错误应为 YYYY-MM-DD实际收到 2024/1/1”这种带具体细节的错误。端侧模型的推理能力本来就偏弱错误信息越具体它采取正确下一步的概率越高。6.2 上下文窗口溢出与裁剪策略端侧模型的上下文窗口往往比云端小很多常见的 4K、8K大一点的 32K。但真正工程化时会发现系统指令、工具描述、历史记忆七七八八加起来可用空间就没多少了。上下文溢出的表现不只是报错更隐蔽的问题是“模型开始忽略上下文深处的信息”因为太多内容超出了它的有效注意力范围。我的裁剪策略是“固定前缀 动态中间 最近优先”。固定前缀是系统指令和工具描述永远保留且精简动态中间是历史摘要可以被压缩或丢弃最近优先是保证最近几轮对话原文完整。每次构建上下文时先算固定前缀的 token 数减去总预算剩下的空间给历史历史太长就压缩摘要摘要还超就丢更早的只保留最近的关键信息。选择裁剪时机也很重要。不要等到下一次请求才来裁剪而是每次请求结束后就做一次“上下文瘦身”判断当前 token 占用如果超过预设阈值立即生成摘要并丢弃旧内容。这样下一次请求时上下文都是紧凑的不会出现“突然超限导致故障”的情况。这个“预裁剪”的策略帮我解决了很多次线上上下文爆炸的问题。6.3 端侧模型的“幻觉”工程化的防御姿势端侧模型因为参数量小幻觉或者说“编造信息”的问题比云端大模型严重得多。尤其是 Agent 场景下幻觉会直接导致工具参数填错、信息凭空捏造这是工程化绕不开的坎。我的防御策略是三层信息源优先、调用前校验、结果后置过滤。信息源优先的意思是Agent 回答用户问题时凡是涉及事实性信息必须以工具返回的数据为准禁止模型凭记忆作答。这在 prompt 里要写得非常强硬比如“回答事实性问题前必须调用知识库工具不得直接回答”。调用前校验就是前面说的参数 Schema 校验把模型填错参数的幻觉拦在工具调用之前。结果后置过滤则是检查最终输出内容的可信度比如发现模型输出的时间是“周四”但工具返回的数据明确是“周三”宁可返回错误提示也不能让幻觉污染用户。这三层不能单独用必须同时上。因为端侧模型幻觉的随机性太强单靠某一层堵不住多层叠加才能把幻觉导致的问题压到可接受范围。工程上还有一个辅助手段是把模型输出的关键实体抽出来跟工具返回的数据做交叉一致性检查不一致就标记为低置信度触发系统的修正机制。这套流程做下来端侧 Agent 的可靠性会有一个肉眼可见的提升。最后说一句我个人的体会。端侧 Agent 工程化这件事最磨人的不是哪一项技术难点而是你得时刻记着“资源是有限的、模型是弱的、用户是没耐心的”这三条铁律然后把每一条工程决策都放在这三条铁律里去权衡。工具集宁可少而精技能要封装成可复用的积木内存和功耗要像过日子一样精打细算日志和评测不是上线后的事而是开发期就要配套。前前后后搞了这些项目才真正从“演示很酷”走到“日常能用”。系列的下半篇我会重点拆解端侧 Agent 上线后的持续迭代问题包括线上数据的回流、模型热更新的策略、以及怎么处理多设备碎片化的适配难题。如果你也在做端侧 Agent 的工程化欢迎把遇到的问题丢出来一起讨论说不定你踩的坑就是下一篇的素材。