Agent Skill 自我进化:轨迹归纳与文本空间优化的完整指南
2026/9/12 10:41:07 网站建设 项目流程

这两年只要聊 AI Agent,几乎绕不开一个词:Skill。从 Claude Code 的 skill 目录,到 Codex 的 skill 插件,再到各类 agent 框架里的 skill 模板,大家都在折腾“怎么让模型更听话地完成复杂任务”。但你有没有想过一个问题——Skill 本身能不能自己变强?它不是一段写死的提示词,而是可以像人一样,从干过的活里总结经验、优化自己的行为。这篇博文我想从轨迹归纳和文本空间优化这两个角度,把 Skill 的进化路径拆开聊聊。

先说清楚这篇文章是给谁看的:你如果是刚开始接触 agent skill,想知道 SKILL.md 到底是什么、怎么写出一个能用的技能包,前面两节可以帮你建好基础认知;如果你已经在写 skill,但总感觉越用越不稳定、输出忽好忽坏,那后面关于轨迹归纳、评测闭环和常见坑的部分,应该能直接帮到你。我会结合我实际开发 skill 的经验,把思路和踩过的坑一起讲透。

1. Skill 是什么:它凭什么能“进化”

1.1 一个 Skill 的最小构成

Skill 在概念上并不神秘,它就是把“完成某类任务的一整套方法”封装成一个可复用的单元。一个标准的 skill 包,通常包含 SKILL.md 作为入口文件,里面写清楚这个技能解决什么问题、适用什么场景、调用时需要注意什么;然后在同一目录下放若干辅助文件,比如脚本、模板、示例数据、依赖声明。模型在运行时会把这些文件加载进上下文,相当于给模型发了一份“岗位说明书”。

有个容易忽视的点:skill 并不只是“提示词”。提示词只是入口,真正让 skill 变强的,是整个目录文件组成的“知识包”。你可以把脚本放进去做计算,可以把优秀案例放进去做 few-shot 参考,可以把历史失败记录放进去做规避提醒。也就是说,skill 的本质是一个“可挂载的局部知识库 + 行为规范”,它不是一个字符串,而是一个目录。

1.2 为什么 Skill 能被迭代出“智能”

传统提示词最大的问题,是你每次想改行为,都要手改提示词,然后祈祷这次改动不会破坏别的地方。Skill 的设计天然更适合迭代,因为它有文件结构、有版本、有依赖声明,你可以像管理代码一样管理它。

我自己在维护 skill 包时,会把它放进 Git 仓库,每次改完行为都会提交一次 commit。这样一来,某个改动导致模型表现变差,我可以直接用 git diff 看改了什么,快速回滚;模型表现变好,我也知道是哪一次改动带来的提升,后续可以继续往这个方向压。这种“可回滚、可对比、可复用”的属性,是 skill 能持续变强的基础设施前提。

1.3 Skill 和 Agent 的分工

热词里经常把 skill 和 agent 放在一起比较,大家很容易混淆。我的理解是:agent 负责决策和执行,比如读完用户的需求、拆分步骤、调用工具;skill 负责提供“怎么把这一类事做好”的领域方法论。你可以把 agent 想成一个全能实习生,skill 就是他在做某类项目前临时翻的专业手册。没有手册,实习生也能干活,但有了手册,干活的套路会更稳、质量上限更高。

因为分工如此,skill 的进化路径也就清晰了:agent 负责在真实任务里产生轨迹数据,skill 负责从这些轨迹数据中提取“哪些做法有效、哪些做法无效”,然后把有效做法固化下来。接下来的两个核心机制,轨迹归纳和文本空间优化,正好对应了这个过程的两端。

2. 轨迹归纳:Skill 的记忆从哪来

2.1 轨道归纳入门:把干过的活变成经验

“轨迹归纳”这个词听起来玄乎,拆开看就是一点:把 agent 一次真实任务中的完整行为链——用户需求、中间思考、工具调用、输出结果——记录下来,然后从中提炼出稳定的模式。比如你让 agent 用 skill 写一份数学建模论文,它会经历“读题、查文献、搭模型、写代码、跑结果、排版”一系列动作。轨迹记录会把每一步的输入输出都存下来,尤其是哪些操作成功了、哪些失败了。

我常拿程序员带新人的场景来类比。老带新的时候,不会一上来就讲架构,而是先带着新人做几个需求,做完一起复盘:这个任务应该先看什么、遇到报错先查哪里、哪些坑是这类型项目独有的。轨迹归纳本质就是把这套“复盘”自动化,把一次项目的操作过程变成可以被后续任务复用的经验。

2.2 轨迹归纳的常见做法:从日志到行为模板

实现轨迹归纳有三种常见路线,难度递增:

  • 最简单的做法是日志回溯。直接解析 agent 运行日志,把工具调用序列抽取出来,找高频出现的操作组合。比如 20 次任务里有 15 次都用到了“先读取文件目录 → 再定位相关代码 → 最后写测试”这个顺序,就可以把它做成一个推荐流程。
  • 进阶一点是行为聚类。把轨迹向量化,然后用聚类算法把相似轨迹归成一组。每组代表一种“常见的干活姿势”,再为每组生成一个代表性模板。这个方案比日志回溯更灵活,能发现你没想到的模式。
  • 再进一步是规则抽取。把“条件-动作”对提取出来,比如“当模型输出的 JSON 解析失败时,下一步应该先检查是否包含 Markdown 代码块标记”。这种规则可以直接写进 SKILL.md 的规避清单里。

我实际做的时候,通常先用日志回溯快速出一版粗模板,立刻改善当前任务的表现;等积累了足够多数据,再做行为聚类和规则抽取,把 skill 打磨到更通用的状态。不要一上来就追求复杂算法,先跑通闭环最重要。

2.3 一个可落地的轨迹归纳 Demo

我分享一下最简的实现思路。假设你的 agent 是 Python 写的,项目日志里已经记录了每一次调用 LLM 的 prompt 和 response,以及 tool_call 的参数。你可以写一个脚本,把同一类任务的工具调用序列做统计:

from collections import Counter, defaultdict import json def extract_tool_sequences(log_path, task_type=None): sequences = defaultdict(list) with open(log_path, "r", encoding="utf-8") as f: for line in f: record = json.loads(line) if task_type and record.get("task_type") != task_type: continue ops = tuple( step["tool_name"] for step in record["steps"] if step.get("tool_name") ) sequences[record["task_id"]].append(ops) counter = Counter() for ops_list in sequences.values(): for ops in ops_list: counter[ops] += 1 return counter # 示例:统计"数学建模"类任务里出现最多的工具调用组合 stats = extract_tool_sequences("agent_logs.jsonl", task_type="math_modeling") for ops, count in stats.most_common(5): print(count, "->", ops)

拿到统计结果后,你可以人工确认高频序列是否合理,再把它加工成 SKILL.md 里的推荐步骤。比如“查文献 → 写代码 → 跑结果 → 写摘要”这条序列高频出现,就把它写进 skill 的“建议执行顺序”小节。这一步其实就是“轨迹归纳”的 MVP,不需要重型算法,也能明显提升 skill 的稳定性。

2.4 轨迹归纳的坑:你归纳的不一定是金矿,也可能是坏习惯

轨迹归纳最需要警惕的,是“把偶然的成功当成了必然”。agent 某次任务成功,可能只是因为那次运气好、模型状态在最佳区间,而不是操作序列本身有多优秀。如果你只收集成功轨迹,很容易把没必要的复杂步骤也固化下来;如果只收集失败轨迹,又容易把模型搞成惊弓之鸟,啥都不敢干。

我见过一个例子:某 skill 的轨迹日志显示,凡是任务成功,agent 都会在最后多调用一次“重新整理输出格式”。开发者以为是这个步骤提升了质量,就把它写进了 skill 的强制流程。结果后续任务全都变成了“先干活再从头格式化一遍”,浪费了大量 token,输出质量并没有提升。原因很简单,那次多调用一次格式化只是因为前一步输出被截断了,跟成功率没有因果关系。归纳轨迹时,最好带上“因果判断”,至少多问一句:这个步骤真的对结果有贡献,还是只是伴随出现?

3. 文本空间优化:Skill 变强的真正战场

3.1 什么是文本空间优化

如果说轨迹归纳是从“行为数据”中找模式,那文本空间优化就是直接作用在“模型看得见的文字”上。大模型的一切行为都由输入文本决定,所以 skill 变强的过程,本质上就是不断调整输入文本的质量。这个“文本空间”包括你的 SKILL.md 措辞、示例排列顺序、few-shot 的选择、上下文的裁剪策略,甚至包括系统提示和用户消息的组织方式。

为什么说它是“空间”?因为同一段信息,用不同语言组织、放在不同位置、给不同模型看,效果天差地别。同一个项目背景,写在 SKILL.md 的最前面和最后面,模型遵循的优先级都不一样。同一个示例,放两个还是放五个,效果也会出现边际递减。你需要在这个高维文本空间里,找到当前任务当前模型下的“最优落点”。

3.2 提示词层面的优化手段

我把文本空间优化拆成三类手段:

  • 措辞级优化。把模糊的描述改成具体可执行的指令。比如 SKILL.md 里写“注意代码质量”就是模糊的,改成“每个函数必须包含 docstring,且所有临时文件必须放在 output/ 目录下”就是可执行的。用指令式动词开头,能显著提升模型的遵循率。
  • 结构级优化。调整 SKILL.md 的章节顺序、段落长度、标题层级。模型对“先定义目标,再给步骤,最后给规避清单”这种结构,理解成本最低。把最核心的规则放在文档前三分之一处,通常比埋在末尾效果好。
  • 上下文级优化。主要在运行时做,比如动态裁剪 skill 里用不到的章节、把示例从三个压缩成一个、根据当前任务关键词只加载相关片段。

这三种手段可以同时使用。我维护的测试用例生成 skill,早期只有 3000 多字的描述,效果一般;后来我把描述拆成“核心必读”和“按需加载”两部分,核心部分只有 800 字,其余细节放辅助文件,模型既不容易漏掉重点,也不会被长文档冲淡注意力。

3.3 如何用“评测驱动”让模型自己优化文本空间

想实现 Skill 自动变强,完全靠人肉调提示词是不够的,你还需要建立一个反馈回路:先定义一组评测用例和打分标准,然后让 skill 在这些用例上跑出得分,再把得分低的部分交给模型去反思和重写。这个思路和强化学习里的 reward modeling 类似,但做起来可以很轻量。

就拿我自己写的“代码评审 skill”举例。我准备了 10 个历史 commit 作为评测集,每个 commit 都有一段已知的潜在缺陷。skill 的任务是找出这些缺陷并给出修复建议。我写了一个评分脚本,用人工标注结果比对输出,得到 F1 分数。每周跑一次评测,把得分最低的 2 个用例连同错误输出喂回给模型,让它对着 SKILL.md 提出修改建议,我再人工审核后合入。持续三轮之后,F1 从 0.52 涨到了 0.81。

这里有个关键:不要直接让模型“优化你自己”,而是在小样本上给出具体的“错误输出-期望输出”对比,让模型提出针对性的修改点。这样它改的不是泛泛的措辞,而是真正和失败用例相关的规则。

3.4 一个自动化优化脚本的设计参考

如果你想做一个不完全依赖人工的闭环,可以参考我下面的脚本骨架。核心流程是:用旧 skill 跑评测集 → 收集低分输出 → 拼装反思 prompt → 调用大模型生成新 skill 版本 → 人工确认后再合入。

import subprocess import json from openai import OpenAI client = OpenAI() def evaluate_skill(skill_dir, cases): """跑一遍评测集,返回每个 case 的得分和输出""" results = [] for case in cases: output = run_agent_with_skill(skill_dir, case["input"]) score = grade(case, output) # 基于人工标注比对 results.append({"case": case, "output": output, "score": score}) return results def propose_improvements(low_score_results, skill_content): prompt = build_feedback_prompt(low_score_results, skill_content) resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": prompt}], temperature=0.2, ) return resp.choices[0].message.content cases = load_eval_cases("eval_cases.json") results = evaluate_skill("skills/code_review", cases) failed = [r for r in results if r["score"] < 0.5] if failed: proposal = propose_improvements(failed, read_skill_file()) save_proposal("proposals/latest.md", proposal) print("已生成优化建议,人工审核后合入") else: print("当前 skill 达到阈值,不需要更新")

注意这段代码里的run_agent_with_skillgrade都需要你自己实现,前者是 agent 运行时,后者是评测脚本。我强烈建议你在项目一开始就建立起这层评测脚本,哪怕只是最简单的关键词匹配,也比“凭感觉判断 skill 有没有变好”强得多。

4. 实操:从零构建一个能自我迭代的 Skill

4.1 设计 Skill 的目录结构

一个可迭代的 skill,目录结构一定要清晰。我常用的模板是这样:

my-skill/ ├── SKILL.md ├── requirements.txt ├── scripts/ │ ├── preprocess.py │ └── report.py ├── templates/ │ └── report_template.md ├── examples/ │ ├── good_case_01.md │ ├── good_case_02.md │ └── bad_case_01.md └── tests/ └── eval_cases.json

SKILL.md是入口,scripts放可执行脚本,templates放输出模板,examples放 few-shot 素材,tests放评测数据。很多人一开始只写一个 SKILL.md 就结束了,后面想加东西发现全堆在一起,文档越来越长,模型越来越抓不住重点。目录结构不是形式主义,它决定了你能不能在后续版本里快速增删规则。

4.2 写一个高质量的 SKILL.md

SKILL.md 不需要写成论文,但一定要让模型“扫两眼就知道怎么干活”。我总结的写作顺序是:先一句话说明技能目标,再列前置条件,接着是完整步骤,然后是质量标准和规避项,最后放一个最简示例。我给大家一个通用模板参考:

# 技能名称:测试用例生成 ## 目标 为给定函数生成覆盖正常路径、边界条件和异常路径的 pytest 用例。 ## 适用场景 - 输入是 Python 函数源代码 - 输出是可直接运行的 pytest 文件 ## 执行步骤 1. 分析函数签名,识别所有输入参数和返回值。 2. 列出至少 3 个典型场景:正常输入、空输入、非法类型。 3. 为每个场景编写独立 test 函数。 4. 运行 `pytest -q` 验证,修复失败用例。 ## 质量标准 - 所有 test 函数必须以 `test_` 开头。 - 不允许修改被测试函数本身的代码。 - 断言必须包含实际输出与期望输出的对比。 ## 规避项 - 不要在用例里使用 `input()` 等待用户输入。 - 不要只覆盖 happy path,至少包含一个异常分支。 - 如果被测试函数依赖外部服务,使用 mock 而非真实调用。 ## 示例 (此处放一个输入源码和对应测试文件的短示例)

这个模板最关键的是“质量标准”和“规避项”两节,它们是 skill 比普通提示词更可靠的核心原因。模型看到明确的“一定要”和“一定不要”,执行起来才会有边界感。

4.3 设计评测闭环:让 Skill 的进化有据可依

没有评测闭环,skill 的“进化”就无从谈起。我建议哪怕是临时项目,也至少要维护一个 20 条左右的评测集。每一条包含:输入、期望输出、打分标准。打分标准不一定要用 LLM judge,有时候就是几个关键词匹配,比如输出里必须包含某个函数名、必须没有某个禁用词。

我第一次给 skill 加评测时,踩了一个大坑:评测集全部来自开发时用过的用例,结果 skill 在评测集上分数很高,一到真实场景就打回原形。后来我学乖了,评测集要定期补充“真实用户问题”,尤其是那些导致过失败输出的问题。真正的进化不是把已知问题都修好,而是把未知问题慢慢变少。

4.4 版本管理:进化的安全网

Skill 的版本管理比很多人以为的更重要。你永远不知道哪一次改动会让模型“突然变笨”,所以必须保证可以随时回到上一个可用版本。我现在每个 skill 目录都会用 Git 管理,并且在每次评测分数达到新高时打一个 tag。发布前还会在另一个分支上跑一次完整评测,确认分数没有回退。

一个很实用的习惯:每次改动都记录“实验笔记”,写清楚这一版改了什么、评测分数变化如何、在哪些用例上变好、在哪些用例上变差。过几周再回来看,你会发现这些笔记比代码本身更有价值。

5. 常见问题与排查实录

5.1 requirements.txt 缺失导致的依赖问题

热词里出现了“缺少 requirements.txt 或依赖声明”,这是 skill 开发里非常常见的问题。Skill 包只要用到额外 Python 库,就必须在 requirements.txt 里声明,否则换一台机器加载 skill 时,脚本会直接跑崩。我遇到过最气人的一次:本地跑得好好的,打包发给同事后,报了一个ModuleNotFoundError: numpy,排查了半天才发现是依赖没带上。

排查技巧:在 CI 或者本地用一个干净的 venv 环境做一次全量加载测试,确保 requirements.txt 能覆盖所有 import。另外,尽量把第三方依赖控制在最小范围,能用标准库解决的就别引包,一方面降低安装失败概率,另一方面也能减少 skill 的启动时间。

5.2 轨迹归纳过拟合:skill 变得只适合旧任务

过拟合是 Skill 进化中最隐蔽的拦路虎。当你不断用同一批历史轨迹优化 skill,它很可能会把某些旧项目的特殊习惯当成普适规则。比如某个项目里所有文件都放在 src/ 目录下,skill 就可能默认“所有项目的源码都在 src/”,遇到其他结构就懵了。

我给的思路是:归纳出的规则必须能够映射回通用场景,至少要留一条“当项目结构不符合默认假设时,先探查再行动”的兜底规则。同时,每隔一段时间就要拿全新的任务类型去测试 skill,如果新的任务类型完全无法执行,说明它已经过拟合了,需要做一次“反归纳”。

5.3 上下文污染:SKILL.md 越长,效果反而越差

很多人在 skill 不好用的时候,第一反应是往 SKILL.md 里加内容。但模型对上下文的关注度是有限的,文档越长,关键内容的权重往往越低。我实测过一个技能包,3500 字的时候评测分数最高,改到 6000 字后分数反而掉了。原因是额外增加的注意事项分散了模型对核心步骤的注意力。

解决方式有两个:一是把长文档拆成“核心文件+附录文件”,核心文件只保留必读内容;二是在运行时按需加载,只有当前任务命中某个专题时,才把对应的附录内容加入上下文。

5.4 评测设计常见的三个误区

  • 只测成功路径。评测集里全是正常输入,skill 当然表现好,但真实世界的输入永远是千奇百怪的。至少要有 30% 的异常用例。
  • 打分标准过于主观。如果评分标准无法量化为“输出里是否包含某个结构”“是否命中禁用词”,评测结果就没有可重复性。每次跑完分数忽高忽低,很难判断改动是否有价值。
  • 忽略 token 成本。有些 skill 通过堆 prompt、多次调用模型来提升效果,评测时只看准确率不看成本,迭代方向会被带偏。我在评测脚本里会同时记录 token 消耗,准确率提升幅度小于成本增长幅度时,会优先考虑精简 prompt。

最后分享一点我的实际体会

Skill 能不能自己变强?我现在可以给你一个肯定的答案:能,但不是靠“模型自动进化”这种玄学,而是靠你把它当成一个真正的系统去设计。轨迹归纳负责从真实的执行历史中沉淀经验,文本空间优化负责把这些经验变成模型愿意遵循的表达。两条腿缺一条,skill 都会在原地打转。

我最大的感受是,不要一开始就想做一个一步到位的复杂进化系统。先用日志回溯做一个粗略的 skill 更新,再搭一套最简单的评测脚本,哪怕评分标准只是几个关键词匹配也好。跑通一轮“运行→收集→归纳→优化→评测”的闭环之后,你自然知道下一步该往哪里用劲。踩过几次坑、迭代过几轮版本之后,你会发现 skill 真的像活物一样,会慢慢长出属于自己的“手感”,那种体验挺上瘾的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询