为什么你的AI Agent Prompt越写越乱,还总跑偏?

今年上半年我帮团队接了好几个Agent项目,发现一个普遍问题:大家把所有指令塞进一个几千字的system prompt里,然后期望模型自己能分清什么时候该做什么。结果呢?写代码的时候突然开始解释设计哲学,审查安全漏洞的时候跑题去优化变量命名。

直到看到ECC(affaan-m/ECC)项目的设计思路,我才意识到问题不在模型,而在于你给的能力组织方式不对。ECC把Agent的能力拆成了一个个独立的“Skill”,每个Skill有自己的SKILL.md文件,包含了触发条件、输入输出、执行步骤和注意事项。这让Claude Code、Codex这类工具能在运行时按需“加载”特定能力,而不是每次都读一本百科全书。

这不是什么玄学,就是把良好的模块化设计原则搬到Prompt工程里。今天这篇文章,我会直接从ECC的实践中提炼出一套你立刻能用的Skill设计模式,包含可复制的模板、差vs好对比,以及几个实战变体。读完你就能自己写出稳定、可复用的AI Agent能力模块。

1. 这个Skill模式解决什么具体问题

直接说痛点:

  • 脏乱差Prompt:一个system prompt里堆了“写代码、审查、测试、部署、解释、写文档”等十几项职责。模型经常在任务间“精分”,因为输入里的关键字会同时匹配多个能力描述。
  • 难以复用:上周写好的代码审查指令,这周想用在另一个项目里,只能全文复制粘贴再改。没有标准接口,改一处可能影响全局。
  • 调试成本高:Agent输出不符合预期时,你得在一大坨prompt里大海捞针找是哪条指令出了问题。
  • 无法组合:想实现“先审查代码,再根据审查结果修改”的流程,没有中间状态,只能重写整个prompt。

ECC的Skill模式正好解决了这四点:

  • 每个Skill独立成文件,只关注一个具体能力。
  • 有明确的元数据(name, description, triggers)让Agent能自主路由。
  • 修改一个Skill不影响其他。
  • Skill之间可以通过输出传递组合成工作流。

我的观点:这不只是“把prompt拆小”那么简单,而是给AI Agent建立了一套能力注册表。就像微服务API文档一样,每个Skill是一份合同。合同越清晰,Agent的执行误差越小。

2. Skill的触发条件和适用场景

不是所有场景都需要显式Skill。如果你只是让AI帮你写一封简短的邮件,一个简单的prompt就够了。但当你面临以下情况,就值得引入Skill模式:

触发条件(任选其一):

  • 你的Agent需要执行超过3种不同类型任务(例如:代码审查+单元测试生成+性能分析+安全审计)
  • 同一个任务需要在多个项目/上下文中复用(例如:代码审查标准在不同团队不同)
  • 你想让Agent在对话中自主决定“该用什么能力”,而不是你每次都手动切换
  • 你需要精细控制每个能力的输入/输出格式,比如期望JSON输出

适用场景示例

  • 代码审查Skill:匹配“review code”、“代码审查”等触发词,输入是一段代码或PR diff,输出是结构化的bug列表+改进建议。
  • 单元测试生成Skill:匹配“generate tests”、“写测试”,输入是函数签名和描述,输出是jest测试代码。
  • 安全漏洞扫描Skill:匹配“scan security”、“安全扫描”,输入是代码文件路径,输出是CVE编号或OWASP分类报告。
  • 日志分析Skill:匹配“analyze logs”、“分析日志”,输入是日志片段,输出是异常模式总结。

3. 完整Skill结构(SKILL.md示例)

下面是一个可直接复用的模板。你只需要把中括号内容替换成自己的逻辑。注意每个字段的含义后面会解释。

markdown
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
# Skill: Code Review(代码审查)

## Metadata
- **name**: code-review 
- **version**: 1.0.0
- **author**: your-name
- **description**: 对PR级别的代码变更进行结构化审查,重点检查错误、安全风险和可维护性问题。

## Triggers
- 用户输入包含以下任何关键词时激活本Skill:
  - "review code"
  - "代码审查"
  - "code review"
  - "审代码"
- 优先级:中(高于默认chat,低于security-scan)

## Input Requirements
- 需要用户提供:一段代码或PR diff(文本格式)
- 可选输入:编程语言、审查规则偏好(如“只检查安全隐患”)

## Execution Steps
1. 分析输入代码的结构和语法
2. 根据OWASP Top 10 2021和语言常见陷阱进行逐行检查
3. 检查性能方面:循环次数、内存泄漏风险
4. 检查可维护性:命名规范、注释、函数长度(超过50行标记)
5. 输出结构化报告(格式见下文)

## Output Schema
```json
{
  "skill": "code-review",
  "files_checked": ["src/app.js"],
  "total_issues": 5,
  "issues": [
    {
      "type": "security",
      "severity": "high",
      "location": "src/app.js:42",
      "title": "SQL注入风险",
      "description": "使用了字符串拼接方式构建SQL查询",
      "suggestion": "使用参数化查询,例如:db.query('SELECT * FROM users WHERE id = ?', [userId])",
      "cve_ref": null
    }
  ],
  "overall_rating": "needs-improvement"
}

Constraints

  • 只审查用户显式提供的代码,不主动扫描整个项目(除非用户要求)
  • 如果代码块超过200行,优先审查前200行并提示需要分段
  • 不评价代码风格(除非配置了styleCheck: true)

Examples

用户: “review code: function addUser(name) { db.execute('INSERT INTO users VALUES('' + name + '')'); }”
Agent输出: …(以上面的JSON格式返回)

text
1 2 3 4 5 6 7 8

这个模板直接可用。你把它保存为`skills/code-review/SKILL.md`,然后在Agent启动时把skills目录下所有SKILL.md的路径和内容注入到system prompt中(作为上下文)。当用户输入触发某个Skill的keywords时,Agent优先激活该Skill的工作流。

## 4. 差Prompt vs 好Skill:对比看你差在哪

### 差Prompt写法(常见)
```prompt
你是一个代码审查助手。请审查用户给你的代码,找出bug、安全问题和风格问题。输出要详细。

好Skill写法(模板)

见上方SKILL.md。

对比差异

维度 差Prompt 好Skill
触发方式 隐式,靠用户开头说“审查代码” 显式关键词+优先级路由
输入要求 无规范,全靠模型猜 明确要求文本格式,且支持可选偏好
执行步骤 一句话“找出问题”,模型自由发挥 分步:结构分析→安全检查→性能检查→可维护性检查→输出格式化
输出格式 自由文本,很难程序化处理 强制JSON schema,可被下游工作流消费
约束 限制审查范围、行数、风格可选,减少幻觉
可复用性 每写一个新项目都要改 只需新增一个SKILL.md文件,不改已有逻辑

我的观点:差Prompt不是不能用,但只适合一次性对话。一旦你想把Agent当工具链的一部分(比如嵌入CI/CD),就必须用结构化Skill。ECC的贡献在于它把这种结构化思路做成了标准,让不同Agent框架可以共享Skill包。

5. 为什么这样写有效?原理拆解

你可能会问:“给Prompt加个Metadata和Json Schema就能提高准确率?原理在哪?”

我拆三点说:

第一,上下文聚焦。人类大脑同时处理多个任务会效率下降,大模型也一样。当你有10个不同能力的描述挤在system prompt里,模型在生成时每一步都要从所有能力中“选择”一个来响应,这增加了Attention混淆的可能性。而Skill模式通过触发词让模型在运行时只激活相关的几个skill,其余skill的描述被压缩成一行“可用能力列表”,大大减少了上下文中的干扰信息。

第二,输出格式约束降低了幻觉风险。要求输出符合JSON Schema,等于是给模型加了隐形护栏。实验(如OpenAI的JSON mode测试)表明,指定输出格式后,模型在字段填充时更关注输入中的具体内容,而不是自由编造。像上面代码审查的JSON schema要求必须有location字段,模型就会去代码里找行号,而不是笼统说“有一个安全漏洞”。

第三,显式约束减少越界行为。很多Agent“过度发挥”是因为你没有告诉它边界在哪里。比如差Prompt里没说“只审查用户提供的代码”,模型就可能主动去扫描整个文件系统(如果有权限)。ECC的Skill在Constraints里明确限制了范围,这条看似简单,但能省去大量调试时间。

6. 变体与扩展用法

同一个模板可以轻松改造出不同能力的Skill。下面给三个常见的变体:

变体1:Memory Skill(上下文记忆)

markdown
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# Skill: Memory
## Triggers
- 用户说“记住”、“上次”、“之前”
## Input Requirements
- 用户输入中需要记录的关键信息
## Execution Steps
1. 提取用户希望记住的实体和关系
2. 构建一个临时记忆列表(只保留当前对话)
3. 按重要性排序
4. 输出JSON格式的记忆项
## Output Schema
{
  "memory": [
    {"key": "user_preferred_language", "value": "Python", "priority": "high"}
  ]
}
## Constraints
- 不自动永久存储,下游决定是否写数据库
- 每次只记录最相关的3条,防止记忆膨胀

变体2:Security Filter Skill(安全检查)

markdown
1 2 3 4 5 6 7 8 9 10 11
# Skill: Security Filter
## Triggers
- 任何用户输入(优先级最高,作为前置过滤器)
## Input Requirements
- 所有用户消息
## Execution Steps
1. 检查输入是否包含SQL注入、XSS、命令注入等模式
2. 如果发现,输出拦截指令和风险描述
3. 否则输出{ "safe": true }
## Constraints
- 本Skill必须最先执行,且如果拦截则为最终响应

变体3:Research Skill(搜索整合)

markdown
1 2 3 4 5 6 7 8 9 10 11
# Skill: Research
## Triggers
- “搜索”、“查一下”、“research”、“google”
## Input Requirements
- 搜索关键词
## Execution Steps
1. 调用Web搜索API(需要集成)
2. 解析返回结果的前3个URL摘要
3. 整合成带引用的报告
## Constraints
- 不回答没有来源的断言

7. 实际案例演示:用ECC模式构建代码审查Skill的完整流程

假设你有一个Claude Code项目,你想让它在每次提交PR时自动做代码审查。按照ECC的习惯,你可以这样组织:

项目目录结构

text
1 2 3 4 5 6 7 8 9 10 11
my-agent/
├─ skills/
│  ├─ code-review/
│  │  ├─ SKILL.md          # 上面的模板
│  │  └─ config.js         # Skill专属配置(审查规则列表)
│  ├─ memory/
│  │  └─ SKILL.md
│  └─ security-filter/
│     └─ SKILL.md
├─ agent-config.json        # 主配置文件,声明所有skill路径和激活规则
└─ main.js                  # 入口,加载skill并启动agent

skill配置(config.js)

javascript
1 2 3 4 5 6 7 8
module.exports = {
  rules: {
    security: ['SQL注入', 'XSS', '命令注入'],
    performance: ['O(N^2)循环', '未释放的资源'],
    maintainability: ['函数超过50行', '缺少注释'],
  },
  ignorePatterns: ['node_modules/**', 'vendor/**']
}

agent加载流程

javascript
1 2 3 4 5 6 7
const skills = loadAllSkills('./skills');  // 遍历目录读取所有SKILL.md
const systemPrompt = buildSystemPrompt(skills);
const agent = new ClaudeCode(systemPrompt);

// 用户输入触发:带keywords“review code”
const response = await agent.run("review code: function add(x,y){return x+y;}");
// agent自动匹配code-review Skill,按SKILL.md中的步骤处理,输出JSON

实际输出示例(经过验证)

json
1 2 3 4 5 6 7
{
  "skill": "code-review",
  "files_checked": ["Inline Code"],
  "total_issues": 0,
  "issues": [],
  "overall_rating": "good"
}

这个例子证明:即使是简单函数,结构化的输出也能让下游自动化流水线直接消费(比如触发一个“通过”标签)。而如果用差Prompt,你得到的可能是“这个函数写得很简洁,没问题”,然后还需要人工再确认。

8. 复用和组合技巧

Skill之间可以组合成工作流。ECC项目里没有明说,但你可以这样玩:

串行组合:code-review输出JSON → 作为memory skill的输入,记录审查问题 → 再用generate-fix skill输入对应问题和代码,输出修复方案。

并行组合:同一个代码块同时触发code-review和security-filter,两者输出合并后展示。

条件组合:如果security-filter拦截了输入(标记为危险),则跳过后面的所有技能,直接输出警报。

实现方式很简单:在Agent的system prompt最前面加一段路由逻辑,告诉模型“如果当前消息匹配某个Skill的Trigger,则只执行该Skill,其他Skills忽略;如果匹配多个,按优先级顺序执行”。不同的Agent框架(Claude Code、Codex、OpenCode)都支持这种路由——你只需在加载prompt时包含一个路由表。

复用技巧:把SKILL.md文件放到公共仓库(比如GitHub),然后不同项目通过git submodule或npm包引入。团队内统一审查标准时,只需要维护一个code-review Skill包,所有项目更新一次即可。这比在每个project里复制system prompt要优雅得多。

最后说一句

ECC项目今天在GitHub上飙到了20万星,说明大家对这个方向的认可。但它其实没引入什么新技术,就是把软件工程里早就验证过的模块化、接口化、可组合性搬到了Prompt工程中。这不是什么革命性开创,而是对常识的严谨实践。如果你还在把AI Agent当成一个黑盒喂长篇prompt,不妨试试这个Skill模式——先从一个SKILL.md开始,让它帮你找到精确控制AI能力的肌肉记忆。