第 7 章 · Skills 与能力模块化

03 · 技能匹配机制

两种触发方式:显式前缀(/debug)和关键词自动匹配。显式优先,自动兜底。

两种触发方式

用户怎么告诉 agent "我要用哪个 skill"?两种方式:

显式选择: 用户在输入前加 skill 名称作为前缀。例如 /debug 为什么测试失败了

自动匹配: 系统分析用户输入中的关键词,自动选择最匹配的 skill。例如用户输入"帮我修复这个 bug",系统自动匹配到 debug skill。

匹配优先级:显式 > 自动 > 无 skill

SkillRegistry 实现

src/skills/registry.ts 中实现注册和匹配:

export class SkillRegistry {
  private skills = new Map<string, Skill>();

  /** 根据用户输入匹配 skill */
  match(input: string): SkillMatch | null {
    const trimmed = input.trim();

    /** 1. 检查显式前缀 */
    if (trimmed.startsWith("/")) {
      const spaceIndex = trimmed.indexOf(" ");
      const skillName = spaceIndex === -1
        ? trimmed.slice(1)
        : trimmed.slice(1, spaceIndex);
      const skill = this.skills.get(skillName);
      if (skill) {
        return { skill, source: "explicit" };
      }
    }

    /** 2. 关键词匹配:统计每个 skill 的匹配数,选最多的 */
    const lowerInput = trimmed.toLowerCase();
    let bestMatch: { skill: Skill; count: number } | null = null;

    for (const skill of this.skills.values()) {
      let count = 0;
      for (const keyword of skill.keywords) {
        if (lowerInput.includes(keyword.toLowerCase())) {
          count++;
        }
      }
      if (count > 0 && (!bestMatch || count > bestMatch.count)) {
        bestMatch = { skill, count };
      }
    }

    if (bestMatch) {
      return { skill: bestMatch.skill, source: "auto" };
    }

    return null;
  }
}

显式匹配

用户输入 /debug 测试失败了,解析过程:

  1. 检测到以 / 开头
  2. 提取 skill 名称:debug
  3. 在 registry 中查找 → 找到 debug skill
  4. 返回 { skill: debugSkill, source: "explicit" }

显式匹配的优势是零歧义——用户明确说了要哪个 skill,系统不需要猜测。

如果用户输入 /unknown test,registry 中没有 "unknown" 这个 skill,显式匹配失败。此时不会回退到自动匹配——用户说了用哪个,如果不存在就直接告知,不要自作主张换一个。

自动匹配

用户输入 帮我修复这个 bug,解析过程:

  1. 不以 / 开头,跳过显式匹配
  2. 遍历所有 skill,检查关键词匹配:
    • debug skill: "bug" 匹配 → count = 1
    • review skill: 无匹配 → count = 0
    • refactor skill: 无匹配 → count = 0
  3. debug skill 匹配数最多 → 返回 { skill: debugSkill, source: "auto" }

多个 skill 都匹配时,选关键词匹配数最多的。 如果用户输入"帮我修复这个 bug 并做 code review",debug 匹配 "bug"(1 个),review 匹配 "review"(1 个),两者平局。这种情况下返回第一个达到最高匹配数的 skill(Map 的迭代顺序即注册顺序)。

剥离 skill 前缀

显式选择 skill 后,需要把前缀从用户输入中剥离,只保留纯任务描述传给模型:

stripSkillPrefix(input: string): string {
  const trimmed = input.trim();
  if (trimmed.startsWith("/")) {
    const spaceIndex = trimmed.indexOf(" ");
    if (spaceIndex !== -1) {
      const skillName = trimmed.slice(1, spaceIndex);
      if (this.skills.has(skillName)) {
        return trimmed.slice(spaceIndex + 1).trim();
      }
    }
  }
  return input;
}

/debug 为什么测试失败了为什么测试失败了。剥离后的纯任务描述作为 state.task 传给 agent。模型不需要看到 /debug 前缀——它已经通过 skill 的 instructions 知道自己在 debug 模式。

在 main.ts 中的集成

main.ts 中,每一轮用户输入都会尝试匹配 skill:

const skillMatch = skillRegistry.match(input);
const taskInput = skillMatch
  ? skillRegistry.stripSkillPrefix(input)
  : input;

if (skillMatch) {
  console.log(`使用技能: ${skillMatch.skill.name} (${skillMatch.source === "explicit" ? "显式选择" : "自动匹配"})`);
}

const result = await runAgent(state, model, tools, {
  // ...
  skill: skillMatch?.skill,
});

用户还输入 /skills 可以查看所有可用技能:

if (input.trim() === "/skills") {
  console.log(skillRegistry.formatHelpText() + "\n");
  continue;
}

formatHelpText() 输出类似:

可用技能:
  /debug     - 定位和修复 bug
  /review    - 代码审查
  /refactor  - 代码重构

用法: /技能名 任务描述
示例: /debug 为什么测试失败了

下一节讲解 skill 注入到 agent 循环的方式。

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page