
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 在多轮对话中调用 SkillSkill 需要知道当前会话的上下文。但上下文怎么传是通过参数显式传还是通过某种隐式的会话状态显式传更清晰但参数会膨胀隐式传更方便但调试困难。我的建议是核心上下文显式传辅助信息走会话状态。坑四Skill 的测试。Demo 阶段 Skill 基本靠手测生产环境必须有自动化测试。每个 Skill 要有单元测试覆盖正常路径和异常路径还要有集成测试验证 Skill 和 Agent 编排层的配合。这块工作量不小但不做的话每次改 Skill 都是赌博。3.3 Skill 的发现与编排机制当 Skill 数量上去之后Agent 怎么知道该调用哪个 SkillDemo 阶段通常是硬编码或者把所有 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 能调用哪些 SkillSkill 能操作哪些资源很大程度上取决于模型的决策。这就带来一个根本性的矛盾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 平台建议从第一天就按生产标准来搭架子哪怕当前只有你一个人用。因为架子搭好了后面加功能是顺水推舟架子没搭好后面每加一个功能都是在还技术债。这个债迟早要还的。