1. 从 Demo 到生产,中间隔着一道什么坎
做过 Agent 项目的人大概都有过这种体验:花一个周末用 Open WebUI 接上 DeepSeek 或者本地模型,配几个 Skill,跑通一个能查天气、能读文档、能调接口的智能体,截图发群里,大家一片叫好。然后老板说,行,下周给全公司用。这时候你才发现,Demo 和生产之间隔着的不是一步之遥,而是一整条鸿沟。
我自己踩过这个坑。去年帮一个团队把他们的 Agent 原型往生产环境推,原型阶段用的是 Open WebUI 加几个自定义 Skill,本地跑得好好的,一上服务器就各种问题:并发一上来 Runtime 直接崩,Skill 调用超时没有重试,会话状态丢了找不回来,日志散落在三个地方根本没法排查。折腾了将近两个月才勉强稳定下来。这段经历让我意识到,企业 Agent 平台真正缺的,从来不是模型能力,也不是 Skill 的数量,而是那些 Demo 阶段根本不会暴露出来的工程基础设施。
这篇文章想聊的就是这件事。我会从 Runtime 层、Skill 管理、会话与状态、可观测性、安全边界这几个维度,把 Demo 到生产之间那些“没人告诉你但一定会遇到”的问题拆开讲。适合正在做 Agent 项目、准备往生产推、或者已经在生产环境踩坑的开发者。不管你是用 Open WebUI、Hermes 还是自己搭的框架,这些问题的本质是一样的。
2. Runtime 层:Agent 平台最容易被低估的地基
2.1 为什么 Demo 阶段的 Runtime 看起来“够用”
Demo 阶段大家对 Runtime 的要求极低。你在本地跑一个 llama-server,或者用 Ollama 拉个模型,Open WebUI 连上去,请求量就是你自己一个人,偶尔几个同事试试。这时候 Runtime 只要能把模型加载起来、能响应请求就行。报错no lm runtime found for model format 'gguf'这种问题,搜一下换个加载方式就解决了。engine protocol runtime llama-server for这类配置,照着文档抄一遍也能跑。
但生产环境的 Runtime 要面对的东西完全不一样。我整理了一下 Demo 和生产两个阶段对 Runtime 的核心诉求差异:
| 维度 | Demo 阶段 | 生产阶段 |
|---|---|---|
| 并发量 | 1-5 个请求 | 几十到几百并发 |
| 模型加载 | 单模型,手动加载 | 多模型,动态调度 |
| 故障恢复 | 崩了重启就行 | 需要自动熔断、降级 |
| 资源管理 | 本机 GPU 随便用 | 多卡多机,需要调度 |
| 响应时间 | 几秒到几十秒都能接受 | 有 SLA 要求 |
| 版本管理 | 模型换了就换了 | 需要灰度、回滚 |
这个表格里每一行背后都是一堆工程问题。比如并发这一项,Demo 阶段你根本不会去想“AI Agent 怎么扛并发”这件事,因为压根没有并发。但生产环境一个部门几十号人同时用,每个请求可能触发多轮模型调用加 Skill 执行,Runtime 层的压力是指数级上升的。
2.2 Runtime 选型的几个关键决策点
Runtime 选型这件事,我的经验是不要一上来就追求“最强”,而是要看你的实际场景。几个核心决策点:
第一,推理引擎选什么。常见的有 llama.cpp 系列、vLLM、TGI 这几类。llama.cpp 胜在轻量和 GGUF 格式的广泛兼容,适合模型种类多、显存有限的场景。vLLM 的 PagedAttention 在并发场景下吞吐优势明显,但显存占用更高。我一般建议:如果并发是核心瓶颈,优先 vLLM;如果模型切换频繁、显存紧张,llama.cpp 更灵活。
第二,Runtime 和 Agent 逻辑的边界在哪。这是个架构问题。有些团队把 Skill 执行、会话管理全塞在 Runtime 里,结果 Runtime 变成一个巨型单体,改一处崩一片。我的做法是 Runtime 只管推理,Agent 编排、Skill 调度、状态管理全部上移一层。这样 Runtime 可以独立扩缩容,Agent 层也可以独立迭代。
第三,多模型怎么调度。生产环境很少只用一个模型。可能主对话用一个大模型,意图识别用一个小模型,代码生成用专门的模型。这时候需要一个模型路由层,根据请求类型分发到不同的 Runtime 实例。这个路由层要能感知每个实例的负载和健康状态,否则一个实例挂了整个链路就断了。
提示:Runtime 层的健康检查不要只做端口探测。我见过端口通着但模型已经 OOM 的情况,请求进去全部超时。健康检查要实际发一个轻量推理请求,确认模型真的能响应。
2.3 一个真实的 Runtime 故障排查记录
说个具体的。之前有个环境,Agent 跑着跑着就开始返回空结果,日志里没有任何明显报错。排查了半天,最后发现是 Runtime 的显存碎片化导致的。模型本身没崩,但连续运行几天后,显存被切得太碎,新的推理请求分配不到连续显存块,就静默失败了。
这个问题的解法有几个层次。短期可以在 Runtime 层加显存监控,超过阈值就主动重启实例。中期要优化请求的批处理策略,减少碎片产生。长期来看,如果你的场景对稳定性要求极高,考虑用支持显存池化的推理引擎,或者干脆做实例级别的轮换——定期把流量切到新实例,老实例排空后重启。
这类问题在 Demo 阶段永远不会遇到,因为 Demo 跑几个小时就关了。但生产环境是 7x24 跑的,任何资源泄漏、碎片化、缓慢退化的问题都会在某个时间点集中爆发。
3. Skill 体系:从“能跑”到“可管理”的跨越
3.1 Skill 不是插件,是契约
很多人把 Skill 理解成“给 Agent 加个功能”,写个函数注册进去就完事了。这在 Demo 阶段没问题,但生产环境里 Skill 的本质是一份契约——它定义了 Agent 能做什么、输入输出是什么格式、失败了怎么处理、超时了怎么办、权限边界在哪。
我见过太多项目,Skill 写了几十个,但没有一个统一的接口规范。有的 Skill 返回字符串,有的返回 JSON,有的抛异常,有的返回错误码。Agent 编排层要处理这些五花八门的返回,代码里全是 if-else。这种 Skill 体系在 Demo 阶段能跑,一旦 Skill 数量超过二十个,维护成本就失控了。
一个可管理的 Skill 体系,至少要包含这几个要素:
- 统一的输入输出 Schema:每个 Skill 声明自己的参数结构和返回结构,编排层可以自动校验和转换
- 超时和重试策略:每个 Skill 独立配置,而不是全局一刀切
- 权限声明:这个 Skill 能访问哪些资源,需要什么级别的授权
- 版本管理:Skill 升级后旧版本还能不能用,怎么灰度
- 依赖声明:这个 Skill 依赖哪些外部服务,那些服务挂了怎么降级
3.2 Skill 编码中的几个实操坑
热词里有个“skill编码247”,我理解可能是指某种 Skill 编码规范或者编号体系。不管具体指什么,Skill 编码这件事本身有几个坑值得说。
坑一:Skill 的幂等性。很多 Skill 涉及写操作,比如创建工单、发送消息、修改数据。如果 Agent 因为超时重试了一次,这个 Skill 就被执行了两次。生产环境里这种重复执行可能是灾难性的。解法是给每个 Skill 调用生成一个唯一的幂等键,Skill 内部根据这个键去重。
坑二:Skill 的副作用管理。有些 Skill 执行到一半失败了,但已经产生了部分副作用。比如一个“下单”Skill,扣了库存但创建订单失败了。这种需要 Skill 自己实现补偿逻辑,或者编排层支持事务回滚。大部分 Agent 框架对这块支持很弱,需要自己补。
坑三:Skill 的上下文传递。Agent 在多轮对话中调用 Skill,Skill 需要知道当前会话的上下文。但上下文怎么传?是通过参数显式传,还是通过某种隐式的会话状态?显式传更清晰但参数会膨胀,隐式传更方便但调试困难。我的建议是核心上下文显式传,辅助信息走会话状态。
坑四:Skill 的测试。Demo 阶段 Skill 基本靠手测,生产环境必须有自动化测试。每个 Skill 要有单元测试覆盖正常路径和异常路径,还要有集成测试验证 Skill 和 Agent 编排层的配合。这块工作量不小,但不做的话每次改 Skill 都是赌博。
3.3 Skill 的发现与编排机制
当 Skill 数量上去之后,Agent 怎么知道该调用哪个 Skill?Demo 阶段通常是硬编码,或者把所有 Skill 的描述塞进 prompt 让模型选。生产环境这两种都不行——硬编码不灵活,全塞 prompt 会撑爆上下文窗口。
我实践下来比较靠谱的方案是分层发现:
第一层是分类索引。把所有 Skill 按领域分类,比如“数据查询类”“消息通知类”“流程审批类”。Agent 先根据用户意图确定大类,再在大类里选具体 Skill。
第二层是语义检索。每个 Skill 有一个向量化的描述,Agent 根据当前任务做语义匹配,召回 Top-K 个候选 Skill。这样即使 Skill 有几百个,每次也只需要在少量候选里做选择。
第三层是动态加载。Skill 的实现不一定要全部常驻内存,可以按需加载。特别是那些依赖重、初始化慢的 Skill,用的时候再加载,用完可以卸载。
这套机制的核心思想是:不要让模型在几百个选项里做选择,而是先用工程手段把候选范围缩小,再让模型做最终决策。模型擅长的是在少量选项里做语义判断,不擅长在大规模选项里做精确检索。
4. 会话与状态:Agent 的“记忆”怎么管
4.1 会话状态为什么比想象中复杂
Demo 阶段会话状态基本就是对话历史,存在内存里,重启就没了。生产环境完全不是这么回事。
首先,会话状态不只是对话历史。它还包括:用户在当前会话中提供的所有信息(比如填了一半的表单)、Agent 已经执行的操作记录、当前任务的进度、临时产生的中间结果。这些东西如果丢了,用户体验是灾难性的——用户填了十分钟的表单,刷新一下全没了。
其次,会话状态需要跨实例共享。生产环境 Agent 服务是多实例部署的,用户的请求可能落到任意一个实例上。如果会话状态只存在本地内存,用户第二次请求落到另一个实例,就找不到之前的会话了。所以状态必须外置到共享存储,比如 Redis 或者数据库。
第三,会话状态有生命周期。不是所有会话都需要永久保存。有些会话几分钟就结束了,有些可能持续几天。需要根据会话类型设置不同的过期策略,否则存储会无限膨胀。
4.2 状态存储的选型与设计
状态存储这块,我踩过的坑主要是选型过于随意。一开始用内存,后来换 Redis,再后来发现有些状态需要持久化又加了数据库,最后变成三套存储并存,数据一致性成了大问题。
比较合理的设计是分层存储:
| 状态类型 | 存储方案 | 生命周期 | 一致性要求 |
|---|---|---|---|
| 对话历史 | Redis + 定期落库 | 天级 | 最终一致 |
| 任务进度 | Redis | 小时级 | 强一致 |
| 用户输入草稿 | Redis | 分钟级 | 强一致 |
| 操作审计日志 | 数据库 | 永久 | 强一致 |
| 中间计算结果 | 本地内存 + Redis 备份 | 秒级 | 弱一致 |
这个分层的逻辑是:越靠近用户交互的状态,一致性要求越高,生命周期越短;越靠近审计和合规的状态,持久化要求越高,生命周期越长。中间的计算结果可以容忍丢失,因为可以重算。
注意:会话状态的序列化格式要稳定。我见过用 Python pickle 序列化状态,后来代码重构改了类定义,反序列化全部失败。建议用 JSON 或者 Protobuf 这种跨版本兼容的格式。
4.3 多轮对话中的状态一致性
多轮对话有个隐蔽的问题:状态更新和消息发送的时序。用户发了一条消息,Agent 处理过程中更新了会话状态,然后返回响应。如果这个过程中用户又发了一条消息,两条消息的处理可能交叉,导致状态被覆盖。
这个问题的解法是给会话加锁。同一个会话的请求串行处理,不同会话并行。锁的粒度要控制好,太粗影响并发,太细容易死锁。我一般用会话 ID 作为锁的 key,加一个合理的超时时间,超时后自动释放并记录告警。
还有一种情况是 Agent 主动发起的操作,比如定时任务触发了某个 Skill,这个操作也要更新会话状态。这种异步操作和用户请求的并发需要特别小心,最好走同一个锁机制,或者设计成幂等的状态更新。
5. 可观测性:生产环境的“眼睛”
5.1 Agent 的可观测性和传统服务有什么不同
传统服务的可观测性主要是日志、指标、链路追踪三件套。Agent 服务这三样都需要,但还不够。
Agent 的调用链路比传统服务长得多。一个用户请求可能触发:意图识别模型调用、Skill 选择模型调用、Skill 执行、结果汇总模型调用、最终响应生成。这条链路上任何一环出问题,用户看到的都是“Agent 没反应”。如果没有细粒度的追踪,排查起来就是大海捞针。
而且 Agent 的行为有不确定性。同样的输入,模型可能给出不同的输出,调用不同的 Skill。这意味着你不能像传统服务那样用固定的断言做监控,需要更灵活的可观测性方案。
5.2 关键指标与埋点设计
我一般会在 Agent 平台里埋这几类指标:
性能类:端到端响应时间、各阶段耗时(模型推理、Skill 执行、状态读写)、首 token 时间。这些指标要按模型、按 Skill、按用户维度分别统计,才能定位瓶颈。
质量类:Skill 调用成功率、模型调用成功率、超时率、重试率、降级触发次数。这些指标反映系统的健康度。
业务类:会话数、活跃用户数、平均对话轮次、Skill 使用分布、用户满意度反馈。这些指标反映业务价值。
异常类:模型输出格式错误率、Skill 参数校验失败率、状态读写冲突次数、权限拒绝次数。这些指标帮助发现潜在问题。
埋点的关键是粒度要合适。太粗了定位不到问题,太细了数据量爆炸。我的经验是:每个模型调用、每个 Skill 执行、每次状态读写都埋一个点,但采样率可以调整。正常请求低采样,异常请求全采样。
5.3 链路追踪在 Agent 场景的落地
链路追踪这块,OpenTelemetry 是事实标准。但 Agent 场景有几个特殊的地方需要处理。
第一,模型调用要记录完整的 prompt 和 response。这对排查问题至关重要,但要注意脱敏和存储成本。我的做法是记录 prompt 的哈希和长度,response 记录完整内容但设置保留期限。
第二,Skill 调用要记录输入输出和耗时。Skill 是 Agent 和外部系统的边界,这里出问题最多。每个 Skill 调用都要有独立的 span,标注 Skill 名称、版本、参数摘要、返回状态。
第三,会话级别的追踪。一个会话可能包含多轮对话,每轮对话是一个独立的 trace。但排查问题时往往需要看整个会话的上下文,所以需要一个会话级别的 trace 把多轮对话串起来。
第四,异常要记录足够的上下文。Agent 的异常往往不是简单的报错,而是“结果不符合预期”。这种需要记录当时的完整状态:用户输入、会话历史、选中的 Skill、模型输出、最终响应。这些信息是复现问题的关键。
6. 安全边界:Agent 能做什么,不能做什么
6.1 Agent 安全为什么是个独立问题
传统应用的安全边界是清晰的:用户能访问哪些接口,接口能操作哪些数据,都是代码里写死的。Agent 的安全边界是模糊的:Agent 能调用哪些 Skill,Skill 能操作哪些资源,很大程度上取决于模型的决策。
这就带来一个根本性的矛盾:Agent 的灵活性来自模型的自主决策,但自主决策意味着你无法完全预测它会做什么。一个设计不当的 Agent,可能被用户诱导去调用不该调用的 Skill,或者把敏感信息泄露到不该泄露的地方。
热词里有个“agent安全”和“a-memguard: a proactive defense framework for llm-based agent memory”,说明这块已经有人在做专门的研究。核心思路是在 Agent 的记忆和决策链路上加防护层,而不是只依赖模型自身的对齐。
6.2 权限模型的设计
Agent 的权限模型我建议采用“最小权限 + 动态授权”的组合。
最小权限是指每个 Skill 只声明它真正需要的权限,不多要。比如一个“查询天气”的 Skill,只需要网络访问权限,不需要文件系统权限。这样即使这个 Skill 被恶意利用,影响范围也有限。
动态授权是指某些敏感操作需要额外的授权确认。比如 Agent 要执行“删除数据”的操作,不能直接执行,而是要先向用户确认,用户确认后才执行。这个确认过程要记录在审计日志里。
权限的粒度也很重要。我一般分三级:Skill 级别(这个 Skill 能不能被调用)、资源级别(这个 Skill 能访问哪些资源)、操作级别(这个 Skill 能执行哪些操作)。三级权限叠加,才能精确控制 Agent 的行为边界。
6.3 输入输出的安全过滤
Agent 的输入输出都需要过滤,但过滤的方式和传统应用不同。
输入过滤主要是防提示注入。用户可能在输入里嵌入指令,试图让 Agent 执行非预期的操作。传统的做法是在 prompt 里加防护指令,但这不够可靠。更稳的做法是在 Agent 编排层做输入解析,把用户输入和系统指令严格分离,用户输入永远不被当作指令执行。
输出过滤主要是防信息泄露。Agent 的输出可能包含敏感信息,比如其他用户的数据、系统内部配置、API 密钥等。需要在输出返回给用户之前做扫描和脱敏。这块可以用规则引擎加模型判断的组合,规则引擎处理明确的敏感模式,模型判断处理模糊的情况。
还有一个容易被忽略的点是 Skill 的返回内容。Skill 从外部系统拿到的数据可能包含敏感信息,这些数据进入 Agent 上下文后可能被模型引用到输出里。所以 Skill 的返回也要过滤,不能因为“是内部系统返回的”就信任。
7. 部署与运维:那些 Demo 阶段不会想的事
7.1 部署架构的演进路径
Agent 平台的部署架构,我观察下来大概有这么几个阶段:
单机阶段:所有东西跑在一台机器上,Open WebUI 加模型加 Skill 加存储。这是 Demo 阶段的典型形态,简单但不可靠。
分离阶段:把模型推理、Agent 服务、存储分离到不同机器。模型推理用 GPU 机器,Agent 服务用 CPU 机器,存储用独立的数据库和缓存。这个阶段解决了资源隔离问题,但部署和运维复杂度上升。
集群阶段:每个组件都是多实例,前面有负载均衡,后面有服务发现。这个阶段解决了可用性和扩展性问题,但引入了分布式系统的各种复杂性。
平台阶段:在集群基础上加统一的配置管理、发布管理、监控告警、权限管理。这个阶段 Agent 平台变成一个真正的内部产品,其他团队可以自助接入。
大部分团队卡在分离阶段到集群阶段的跨越上。这个跨越的核心难点不是技术,而是运维体系的建设。你需要有自动化部署、有健康检查、有滚动更新、有回滚机制。这些在 Demo 阶段完全不需要,但生产环境缺一不可。
7.2 配置管理的坑
Agent 平台的配置项特别多:模型配置、Skill 配置、权限配置、限流配置、超时配置。这些配置如果散落在各个地方,改一个配置要登录好几台机器,迟早出问题。
我的做法是统一配置中心,所有配置集中管理,支持热更新和版本回滚。配置的变更要有审计日志,谁在什么时候改了什么,都要记录。敏感配置比如 API 密钥要加密存储,访问要授权。
配置的灰度发布也很重要。改了一个 Skill 的超时时间,不要一次性全量生效,先在一个实例上生效观察,没问题再逐步扩大。这样即使配置有问题,影响范围也可控。
7.3 版本升级与回滚
Agent 平台的版本升级比传统服务复杂,因为涉及多个组件的协同。模型升级了,Agent 编排逻辑可能要跟着改;Skill 升级了,Agent 的 prompt 可能要调整。这些组件之间的版本兼容性需要管理。
我一般用“版本矩阵”的方式管理:明确哪个版本的 Agent 服务兼容哪个版本的模型、哪个版本的 Skill。升级时按矩阵来,不兼容的版本不能混用。这个矩阵要文档化,并且在新版本发布时更新。
回滚策略也要提前设计。不是所有升级都能平滑回滚,有些涉及数据格式变更的升级,回滚会导致数据不一致。这种升级要特别谨慎,最好先在测试环境完整验证回滚流程。
8. 常见问题速查与避坑清单
8.1 高频问题排查表
| 现象 | 可能原因 | 排查方向 | 解决方案 |
|---|---|---|---|
| Agent 无响应 | Runtime 挂了或 OOM | 检查 Runtime 健康状态和显存 | 重启实例,加显存监控 |
| Skill 调用超时 | 外部服务慢或网络问题 | 看 Skill 的 span 耗时 | 加超时和重试,考虑降级 |
| 会话状态丢失 | 存储连接断开或过期 | 检查 Redis 连接和 TTL | 修复连接,调整过期策略 |
| 响应格式错误 | 模型输出不稳定 | 看模型原始输出 | 加输出校验和重试 |
| 并发上不去 | Runtime 批处理配置不当 | 看 Runtime 的批处理参数 | 调优 batch size 和并发数 |
| 权限拒绝异常 | 权限配置错误 | 检查 Skill 权限声明 | 修正权限配置,加审计 |
| 内存持续增长 | 状态未释放或泄漏 | 看内存曲线和对象数 | 修复泄漏,加定期清理 |
| 模型加载失败 | 格式不兼容或文件损坏 | 看加载日志 | 检查模型格式,重新下载 |
8.2 几条血泪经验
经验一:不要在生产环境用 latest 标签。模型、Skill、依赖库,全部用固定版本。latest 意味着不可预测,今天能跑明天可能就崩了。
经验二:限流要从第一天就做。不要等到被打爆了才加限流。每个用户、每个会话、每个 Skill 都要有限流,防止单个异常请求拖垮整个系统。
经验三:日志要结构化。不要打纯文本日志,用 JSON 格式,每个字段有明确含义。这样排查问题时可以精确过滤,而不是 grep 半天。
经验四:压测要模拟真实场景。不要只压模型推理,要压完整的 Agent 链路。真实场景下模型调用、Skill 执行、状态读写是交织的,单独压某一环发现不了问题。
经验五:降级方案要提前设计。模型挂了怎么办,Skill 挂了怎么办,存储挂了怎么办。每个环节都要有降级方案,并且定期演练。降级方案不演练,真出事的时候大概率用不了。
8.3 从 Demo 到生产的检查清单
在把 Agent 项目往生产推之前,对照这个清单过一遍:
- Runtime 是否支持多实例部署和健康检查
- Skill 是否有统一的接口规范和版本管理
- 会话状态是否外置存储并支持跨实例共享
- 是否有完整的链路追踪和关键指标监控
- 权限模型是否覆盖 Skill、资源、操作三个级别
- 输入输出是否有安全过滤
- 配置是否集中管理并支持灰度
- 是否有降级方案和回滚流程
- 是否做过完整的压测和故障演练
- 是否有值班和告警机制
这个清单里的每一项,在 Demo 阶段都可以忽略,但在生产环境每一项都是必须的。我见过太多项目,Demo 惊艳,上线崩盘,问题就出在这些“看不见”的地方。
9. 我个人的一些体会
做 Agent 平台这件事,技术选型固然重要,但更关键的是心态的转变。Demo 阶段追求的是“能跑通”,生产阶段追求的是“跑不坏”。这两个目标的差异,决定了你在架构设计、代码质量、运维投入上的所有决策。
我自己的经验是,从 Demo 到生产的跨越,最难的往往不是某个具体的技术问题,而是团队对“生产级”的认知。很多人觉得加个监控、加个重试就叫生产级了,其实远远不够。生产级意味着你要为系统的每一个失败场景负责,意味着你要在凌晨三点被叫起来排查问题,意味着你要对用户的数据和体验有敬畏心。
Agent 这个领域还在快速演进,今天的最佳实践明天可能就过时了。但有些东西是不变的:对可靠性的追求、对边界的敬畏、对细节的关注。这些才是从 Demo 走到生产的真正通行证。
最后分享一个小技巧:如果你正在做 Agent 平台,建议从第一天就按生产标准来搭架子,哪怕当前只有你一个人用。因为架子搭好了,后面加功能是顺水推舟;架子没搭好,后面每加一个功能都是在还技术债。这个债,迟早要还的。