news 2026/10/14 11:22:15

Agent 不稳定的根源与可靠性工程:从 SKILL.md 到状态快照的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent 不稳定的根源与可靠性工程:从 SKILL.md 到状态快照的实战指南

说实话,我本地跑过的 Agent 项目不算少了,但真正让我下决心做一套“给 Agent 用的网站”的,是某个周五晚上的翻车现场。那是一个自动化发布任务,Agent 前面十几步都干得漂亮,结果在最后一步把预发环境的地址当成了正式环境,而且因为日志里恰好出现了一行production字段,它甚至“自我确认”了这个错误,直接往下执行。等人工发现的时候,发布流程已经跑完一大半。

从那之后我意识到一件事:Agent 不可靠,很多时候不是模型智力不够,而是整条运行链路缺少结构。没人告诉它“边界在哪”,没人为它保存关键状态,没有机制在错误发生后拦住它并让它回退。我做的这个网站,本质就是给 Agent 补上这些结构——工具入口、流程模板、状态管理、失败恢复、过程回放,外加一套 skills 机制,把已经验证过的任务路径固化下来,让 Agent 不再每次都“临场发挥”。这篇文章把设计思路、Skills 的落地方式、可靠性机制和部署踩坑都拆开讲清楚,如果你也在被 Agent 的“不稳定”折磨,应该能省不少时间。

1. Agent 为什么总是不靠谱?先把问题拆清楚

1.1 目标漂移:上下文一长就忘本

Agent 干活最典型的翻车模式,就是做着做着忘了自己最初要什么。对话历史一旦积累到上万 token,模型对“当前目标”的注意力会被大量中间过程稀释掉。尤其是有日志、报错、工具返回的混合上下文时,它很容易把“上一轮的操作”当成“本轮的指令”,把“日志里的描述”当成“实际发生的事实”。

我见过最离谱的一次:任务要求从订单列表里提取异常订单并汇总原因,Agent 在第三步拉取数据时返回了一个字段缺失的结果,它不但没去检查缺失,反而在后续推理里反复引用这个不存在的字段,最终生成了一份结构完整但内容全错的报告。从外面看,流程每一步都执行了,中间结果却早就跑偏了。

所以目标漂移的本质是:模型没有内建“任务边界意识”,它既不维护一份“当前目标清单”,也不会主动核对“这一步结果是否符合预期”。如果外部不给它提供这些约束,它就会在一堆 token 里失去方向。

1.2 工具幻觉:把不存在的函数说得像真的

第二类问题是工具调用层面的。LLM 在生成工具调用时,偶尔会“发明”一些不存在的函数名或参数,这在工具数量多、参数结构复杂的系统里尤其明显。

举个例子,系统里明明只注册了search_orders(user_id, page, page_size),模型可能因为指令里出现过“查一下用户订单”就直接生成query_order(user_id)甚至get_user_orders_by_name(name)。参数值也经常是编出来的:它会把示例数据里的用户 ID 当成真实 ID 传进去,然后把返回结果当成真实数据继续加工。

这类问题靠“提示词里写清楚工具列表”只能缓解,不能根治。因为模型生成 token 是概率性的,工具名称越相似、参数越多,它混淆的概率就越大。真正可靠的做法是:在它发起调用时用程序拦住它,参数不符合注册表的 schema 就直接拒绝,而不是把错误传递下去。

1.3 错误放大:小失误滚成大雪球

如果说前两类问题还可以分别防御,那第三类才是最可怕的——错误在步骤间被级联放大。

Agent 是多步执行的,每一步的输出是下一步的输入。如果中间某一步产生了错误结果,而下游步骤没有校验能力,那么这个错误会像滚雪球一样越滚越大。更糟的是,模型有极强的“自洽倾向”:一旦它自己生成了一个结论,后面它会倾向于寻找支持这个结论的证据,忽略相反的信号。这就是为什么很多 Agent 项目在跑长任务时,错误率不是线性增长而是指数增长。

要打破这种放大效应,必须在关键节点插入“确定性校验器”——用代码判断结果对不对,而不是让模型自己判断。这是我后来设计整个网站时最重要的原则。

2. 给 Agent 用的“网站”到底长什么样?设计与架构

2.1 为什么不能只靠封装一组 API

很多人觉得,给 Agent 加能力就是多写几个 API 让它调。这话对了一半。API 解决的是“能不能调”,但 Agent 实际需要的是“能不能被正确地发现、安全地调用、有效地校验、失败后能恢复”。

一个给人用的 API 文档,写清楚路径、参数、返回值就够了;但一个给 Agent 用的工具平台,还要回答几个程序调用从来不会问的问题:这个工具应该在任务哪个阶段被调用?如果参数格式和模型生成的有偏差,谁负责修正?如果工具调用失败了,是重试还是换方案?调用结果要不要作为事实写入下一步的上下文?这些决策如果都丢给模型现场判断,就等于让列车在没有轨道的情况下自己找路。

所以我做的这个网站,本质是一个中间层。它对外暴露给 Agent 的是一整套“可发现、可校验、可观测、可恢复”的数字工作台,而不是一堆裸接口。

2.2 核心部件:注册中心、运行时网关、状态服务、回放中心、Skills 库

整个系统我拆成了五个部件,各自职责很清晰:

部件职责核心存储/技术
工具注册中心维护所有 Agent 可调用的能力清单、参数 Schema、触发场景PostgreSQL + 索引
运行时网关统一接收 tool_call,做鉴权、参数校验、结果结构化、重试与熔断Python 异步服务
任务状态服务保存会话快照、幂等键、断点信息,支持回退恢复PostgreSQL + Redis
观测回放中心记录每次请求、响应、上下文,支持事后还原执行过程对象存储 + 检索服务
Skills 库存储 SKILL.md 与配套脚本,按任务场景自动装配Git 仓库 + 文件系统

工具注册中心和运行时网关是 Agent 的“手”,决定它能不能正确操作;任务状态服务和回放中心是 Agent 的“记忆”,决定它会不会失忆、犯错后能不能复盘;Skills 库是 Agent 的“经验”,决定它能不能按已验证的路径执行而非每次碰运气。

2.3 一次工具调用怎么穿过整条链路

我把一次完整的 tool call 拆成六个步骤,任何一个环节不过就直接中止或降级:

  1. LLM 生成一个结构化的工具调用请求,比如请求获取服务器状态;
  2. 运行时网关接收请求,去注册中心核验工具是否存在、参数类型是否合法;
  3. 网关检查该请求是否携带有效的调用 ID 和会话上下文快照,没有就直接拒绝;
  4. 网关通过适配层真正执行底层能力——可能是 HTTP 调用、Shell 命令或内部服务;
  5. 执行完成后,网关把结果转成统一格式,做字段完整性校验,生成结果指纹;
  6. 结果返回给 LLM,同时完整写入回放中心。
{ "tool_call_id": "tc_7f3a2d9e", "tool_name": "get_server_status", "arguments": { "server_id": "web-prod-01" }, "execution_id": "exe_20240119_001", "session_id": "sess_20240119_abc" }

一次看似简单的调用,背后至少做了三类校验:工具是否存在、参数是否符合 schema、会话状态是否有效。这些校验极大减少了前面说的“工具幻觉”和“错误放大”。

3. Skills 才是这个项目的灵魂:把经验固化成可复用流程

3.1 为什么要用 SKILL.md 而不是“靠模型自己摸索”

长任务里最让我头疼的是,同一个任务,Agent 每次的走法都不一样。有时候它先做 A 再 B,有时候先 B 再 A,有时候干脆乱序。结果是:同样的需求,今天成功、明天失败,完全不可控。

Skills 机制就是来治这个病的。把一个已经被验证过的高质量任务路径,固化成一个资产:包含触发条件、执行步骤、校验规则和兜底策略。以后 Agent 遇到同类任务,不再现场推理“该怎么做”,而是加载这个 Skill,按里面的流程填参数、执行、验证。

我自己的体会是:Skill 的价值不亚于一个资深工程师把一个复杂流程写成标准作业指导书,只是这份指导书是给模型看的。它把公司业务里那些“老员工知道但新人不知道”的流程知识,变成了 Agent 可以直接使用的确定性资产。

3.2 SKILL.md 的核心字段设计

我设计的 SKILL.md 用 YAML front-matter 加正文描述组成,核心字段就几个:

--- name: code-review-guard description: 在合并请求上进行代码审查,重点检查密钥泄漏、危险函数和 SQL 注入 trigger: scene: code_review when: 用户要求审查代码或检测到 merge request 事件 inputs: - name: branch required: true description: 待审查的分支名 - name: base required: false default: main description: 对比基线分支 workflow: - step: fetch_files command: git diff --name-only {{base}}...{{branch}} - step: secret_scan tool: secret-scanner target: ${fetch_files.output} - step: llm_review tool: llm-reviewer target: ${fetch_files.output} verification: - assert: scan_result contains "scan_summary" - assert: risk_list is not empty fallback: - condition: secret_scan failed action: 降级为只返回变更文件列表并说明扫描失败原因 ---

有几个设计细节值得说。

第一,trigger字段必须写得“可判定”,不能用模糊场景词。比如“当用户需要代码质量分析时”就很模糊,应该写成“用户要求审查代码或检测到 merge request 事件”,这样自动装配器才能稳定匹配。

第二,workflow里每个步骤尽量引用上一步的输出,用${step_name.output}这种显式引用。这能降低模型“自行决策”的空间,流程路径越确定,结果越稳定。

第三,verification字段是关键中的关键。它定义了步骤执行完以后,系统要用代码校验哪些条件,比如“结果里必须包含scan_summary字段”。如果校验不过,整个 Skill 直接进入fallback。这个设计让“检查”从模型的任务里剥离出来,交给了确定性的程序。

3.3 自动装配:Agent 怎么找到并加载合适的 Skill

Skills 库里的技能越来越多之后,另一个问题来了:怎么让 Agent 在正确的时机选择正确的 Skill?

我的做法是两层匹配。第一层,把每个 SKILL.md 的description和trigger.when做向量化,任务进来时先用语义相似度筛出多个候选 Skill。第二层,把候选 Skill 的trigger条件交给一个轻量分类器做判定,选出最匹配的一个或几个,然后把它们的执行模板注入到当前任务的上下文里。

def assemble_skills(task_description: str, agent_context: dict): candidates = vector_search( index=skill_index, query=f"{task_description} {agent_context.get('scene', '')}", top_k=5 ) matched = [ skill for skill in candidates if rule_match(skill.trigger.when, agent_context) ] if not matched: return None # 注入执行模板,而不是让模型自由发挥 return render_skill_template(matched[0], agent_context)

注入的时候有个小技巧:不要一次性把所有 Skill 内容都塞给模型,只要把匹配到的那个 Skill 的 workflow 和 verification 渲染成一段结构化指令喂进去,让模型“照着填参数”,而不是“重新发明流程”。实测下来,任务执行的路径稳定性有明显提升。

3.4 版本管理与回归测试:技能更新不能靠肉眼

Skill 不是写一次就完事的。业务逻辑会变、工具接口会变,Skill 也必须持续迭代。但我见过太多团队改完 Skill 不验证,结果新 Skill 上线后任务失败率反而更高。

我给每个 Skill 配了一个测试集,里面的用例都是历史上真实任务改造而来的:输入固定、期望输出固定。每次 Skill 更新,自动跑一遍回归测试,看每个 step 是否按预期执行、verification 是否通过。只有通过率达标才允许发布新版本。

skill-cli test code-review-guard --version 2.3.0 # 用例: 6/6 通过, 平均执行时间下降 18%

这套机制还带来一个额外好处:新人接手业务时,不需要翻各种文档,只要看 Skills 库的测试用例,就知道这个技能在什么场景下应该产出什么结果。这本身也是组织经验沉淀的过程。

4. 可靠性不能靠提示词,要靠机制

4.1 结构化输出:把模型的自由发挥关进笼子里

大模型的自由文本输出是 Agent 不稳定的根源之一。同一个意思,它可以表达成十种不同的格式;哪怕你在提示词里要求“必须返回 JSON”,它也可能多包一层嵌套、把数字写成字符串、或者漏掉字段。

我的做法是:对每个工具的执行结果,定义一份 JSON Schema,网关强制校验。校验不过,结果不会被当作事实进入下一步。

{ "type": "object", "required": ["order_id", "status", "amount"], "properties": { "order_id": { "type": "string", "pattern": "^ORD-[0-9]{6}$" }, "status": { "type": "string", "enum": ["pending", "paid", "failed", "refunded"] }, "amount": { "type": "number", "minimum": 0 } } }

Schema 校验之外,还要加一层语义校验。比如amount是数字、非负,status必须是枚举值之一,日期字段必须能被解析为真正的日期。模型可能结构上完全满足 Schema,但语义是错的(比如金额为负数但类型是数字),这一层就是最后的防线。

4.2 幂等、重试与熔断:不能让错误被重复执行

Agent 的另一个常见问题是重试导致的副作用放大。模型发现工具调用失败后,很可能以同样的参数重发同一个请求。如果这个请求有副作用——比如扣费、发通知、创建订单——那一次错误就是多次损失。

所以网关层实现了三个机制:

  • 幂等键:每个工具调用请求必须携带唯一的execution_id,网关在同一键下只执行一次,后续重复请求直接返回第一次的结果;
  • 指数退避重试:对网络类错误按 1s、2s、4s、8s 的间隔重试,最多 4 次,且只对安全错误类型重试;
  • 熔断器:某个工具连续失败超过阈值后,网关直接标记该工具不健康,后续请求不再调用它,而是返回“工具不可用,请切换方案并通知人工”。

这三个机制的组合,本质上就是把支付系统里那套“不可能让用户被重复扣款”的思路搬到了 Agent 世界里。

async def execute_with_protection(call: ToolCall): cached = await load_execution_result(call.execution_id) if cached: return cached for attempt in range(4): try: result = await adapter.execute(call) await save_execution_result(call.execution_id, result) return result except RetryableError as e: await asyncio.sleep(2 ** attempt) except FatalError: circuit_breaker.mark(call.tool_name) raise circuit_breaker.mark(call.tool_name) raise ToolExecutionError("重试次数耗尽,已熔断")

4.3 状态快照与回放:出错以后能“倒带”复盘

我以前最痛恨的就是 Agent 出错后,不知道它当时看到了什么、想了什么、做了什么。上下文被新的对话覆盖,日志里只有零散的 tool call,根本拼不出完整过程。

状态快照机制解决了这个问题。系统在每个关键节点,保存一份结构化的session_snapshot:

{ "session_id": "sess_20240119_abc", "step": 12, "current_goal": "发布构建产物到预发环境", "last_result": { "tool": "deploy_release", "status": "failed", "error": "target_env is not allowed" }, "context_summary": "已完成代码拉取、构建、权限校验;尚未执行发布", "pending_actions": ["确认目标环境后重试发布"] }

这份快照有两个用途。第一是回放定位:出问题后,运维同学可以查看每个步骤的快照,知道 Agent 是何时偏离目标的。第二是恢复执行:发现 Agent 跑偏后,让它“回到第 12 步的状态重新来”,而不是从头执行整个任务。前者解决“可追溯”,后者解决“可恢复”。

4.4 人工介入通道:机器兜不住时,人能接管

最后一个可靠性机制,听起来最不“智能”,但实战中可能最有用——给 Agent 留一个人工接管的口子。

我的做法是:任务状态服务里为每个会话保留“暂停/接管”状态。当校验器连续失败、熔断触发、或者模型自己标记“不确定”时,任务不再继续死循环,而是转入人工处理队列。人工可以在工作台上直接查看快照、修改当前步骤状态、甚至绕过模型直接执行某个工具,然后再把控制权交还给 Agent。

这个设计让“可靠性”从模型单点承担责任,变成了“模型+机制+人工”三层兜底。真正可靠的系统不是不出错的系统,而是出了错能被人发现、被人接住、可以回退的系统。

5. 部署、压测与踩坑实录

5.1 部署形态与资源预估

这个网站最初我用 Docker Compose 部署在一台 8C16G 的服务器上,服务内部团队几十个 Agent 任务完全够用。核心组件就四个容器:网关服务、PostgreSQL、Redis、对象存储网关。等日志量上来了,再考虑迁移到 K8s 做自动扩容。

资源预估方面,我自己的经验是:20 个并发 Agent、每次任务调用链平均 30 秒、每秒约 5 个 tool call 的场景下,单机部署几乎没有压力。真正的瓶颈反而在日志存储和数据库大小,而不是 CPU。

5.2 五个让我印象最深的坑

坑一:SQLite 并发写锁导致接口间歇性失败。

最开始为了省事,我用 SQLite 当主库,结果并发一上来,频繁出现database is locked。这不是 Agent 的问题,是我基础选型太轻率。后来换到 PostgreSQL,配合连接池,问题彻底消失。只要是并发场景,别用 SQLite 做主存储。

坑二:完整日志导致磁盘爆炸。

我把每次请求的完整上下文都存下来,以为这样最保险。结果运行一个月,单日日志量轻松到几十 GB。后来改成分级存储:请求摘要、工具名、执行状态、耗时这些结构化字段永久保存;完整的输入输出原文只保留 7 天;再老的数据做抽样留存。回放排查基本用不上太久远的原文。

坑三:工具清单太长,把 token 预算吃光了。

工具注册中心里的能力越来越多,每次请求前我习惯性把所有工具描述都注入给模型。然后发现模型还没开始干活,prompt 就已经三四千 token,遇到长任务直接在上下文窗口边缘崩溃。解决办法是:按任务场景做工具预筛,只注入与当前任务可能相关的工具描述,其他工具留在注册中心供动态查询。

坑四:切换模型后 Schema 不兼容。

同一个工具返回结果,不同模型对格式的处理有细微差别,有的模型会把数字字段自动转成字符串,有的会把嵌套对象拍平。我一开始在网关里就固定了一套解析逻辑,结果换模型后大量解析失败。后来把 Schema 校验独立成层,并在网关做类型强转,才稳定下来。

坑五:Agent 循环重试打爆下游服务。

这个坑最隐蔽。Agent 遇到工具失败后会自己重试,而且重试时可能带相同的参数。如果没有幂等层,一个下游系统可能在几秒内被同一个失败请求打几十次。加上执行 ID 和结果缓存之后才算真正兜住。重试保护不是写给模型看的,是写给下游系统看的。

5.3 实测效果

在我自己的环境里跑了一个多月的对比(样本不算大,只代表我这边的工作负载):引入 Skills 机制之后,计划类任务的一次性成功率大约从 58% 提升到了 87%;加了幂等和熔断之后,任务重试次数下降了六成左右;而状态快照配合回放中心,让我定位一个 Agent 失败问题的平均时间,从原来的小时级降到了分钟级。

这几个数字不算惊艳,但对我来说已经足够说明问题:Agent 的可靠性从来不是模型单点决定的,而是被工程结构一层层托起来的。

最后说一点个人体会。这个项目做下来,我最大的收获不是“我会写代码了”“我会设计架构了”,而是理解了可靠性的本质——它不是一个功能,是一整套护栏。模型是概率系统,它一定会犯错;我们能做的不是求它别犯错,而是设计一个系统:让它犯错时能被发现,被发现时能被控制,被控制时还能回退。这个思路,适用在 AI Agent 上,其实也适用在所有复杂软件工程里。项目本身还能往很多方向扩展,比如把 Skills 开放成团队共享库、接入更多验证器、把回放中心做成自动化调试工作台,我后面还会继续折腾。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 11:21:59

船舶智能制造整体规划:从制造模式演进到中外差距的教学骨架

简介:这是一份关于船舶智能制造整体规划的PPT教学课件,面向船舶工程、智能制造相关专业师生及行业从业者,旨在帮助读者理解船舶智能制造的顶层设计、整体规划与关键技术。资源包共1个文件,为pptx格式,压缩后体积约25.1…

作者头像 李华
网站建设 2026/10/14 11:21:33

Hugging Face Transformers实战:NLP应用构建全流程指南

Transformers 自然语言处理这几年热度一直不减,但很多朋友拿到 Hugging Face 的库之后,第一反应往往是“例子能跑通,换到自己数据上就抓瞎”。这篇指南我尽量不讲大道理,直接围绕最常用的语言应用构建流程来拆解,从环境…

作者头像 李华
网站建设 2026/10/14 11:21:21

FPGA生理刺激反应时间测试仪:数电课设从分频到状态机实战

简介:这份资源是东华大学数字电子技术课程设计的「生理刺激反应时间测试仪」完整工程包,面向电子信息、自动化等专业学习FPGA与数字逻辑设计的学生及自学者。项目以FPGA为核心,配合Quartus II完成刺激信号生成、反应时间记录与结果显示三大模…

作者头像 李华
网站建设 2026/10/14 11:20:50

生产级Agent调优实战:性能、成本与稳定性全攻略

2. 生产级Agent调优,先承认三件事:快、省、稳,一个都不能少先泼一盆冷水:你本地跑得飞起的Agent Demo,扔到生产环境大概率会变成又慢又贵又不听话的定时炸弹。我见过太多团队死在从原型到上生产的最后一公里&#xff0…

作者头像 李华
网站建设 2026/10/14 11:20:32

药品管理系统数据库设计全流程:从ER模型到SQL优化与答辩指南

简介:这份数据库课程设计报告以药品管理系统为实战项目,完整记录了从需求分析、概念设计、逻辑设计到物理设计、实施与测试的数据库开发全过程,适合计算机相关专业学生作为课程设计参考,也可供初学者学习SQL Server关系数据库的设…

作者头像 李华
网站建设 2026/10/14 11:20:18

模式识别入门:从特征到决策的完整链路与实战要点

先说个我自己的体验。我学模式识别第一遍的时候,最大的误区是以为它只跟算法有关,觉得把某个分类器调好就完事了。后来做完第一个实际项目才明白,模式识别是一个从数据到决策的完整链路,算法只是其中最显眼的一环。所以这次笔记的…

作者头像 李华