Skip to content

第 17 章 · 实战一:GitHub 自动分诊 Agent

本章目标:以官方 README 的 Triage 示例为蓝本,从零实现一个完整的 bug 自动分诊 agent,串联 skills、tools、sandbox 三大要素。

17.1 目标与蓝图

我们要实现的 agent 收到一条 bug 报告后,自主完成端到端分诊

text
bug 报告 → 复现问题 → 定位根因 → 判断是否为预期行为 → 尝试修复 → 开 issue 汇报

官方示例的骨架(本章的起点):

typescript
// agents/triage.ts — 官方 README 原始示例
'use agent';
import { useModel, useSandbox, useSkill, useTool } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import triage from '../skills/triage/SKILL.md';
import verify from '../skills/verify/SKILL.md';
import { openIssue, searchCode } from '../tools/github.ts';

export function Triage() {
  useModel('anthropic/claude-sonnet-4-6');
  useSandbox(local());
  useSkill(triage);
  useSkill(verify);
  useTool(openIssue);
  useTool(searchCode);
  return `Triage a bug report end-to-end: reproduce the bug,
diagnose the root cause, verify whether the behavior is
intentional, and attempt a fix.`;
}

17.2 第一步:工具层(tools/github.ts)

工具是类型化的外部动作。先实现两个最小工具:

typescript
// tools/github.ts
'use flue-tool';
import { defineTool } from '@flue/runtime';

// 在仓库中搜索代码片段(只读,安全)
export const searchCode = defineTool({
  name: 'search_code',
  description: '在 GitHub 仓库中搜索代码,返回匹配文件与行',
  inputSchema: {
    type: 'object',
    properties: {
      repo: { type: 'string', description: 'owner/name 形式的仓库' },
      query: { type: 'string', description: '搜索关键词' },
    },
    required: ['repo', 'query'],
  },
  async execute({ repo, query }: { repo: string; query: string }) {
    // 调用 GitHub Code Search API(只发 GET,无副作用)
    const url = `https://api.github.com/search/code?q=${encodeURIComponent(query)}+repo:${repo}`;
    const res = await fetch(url, {
      headers: {
        Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
        Accept: 'application/vnd.github+json',
      },
    });
    const data = (await res.json()) as { items?: Array<{ path: string }> };
    return (data.items ?? []).slice(0, 10).map((i) => i.path); // 只回路径,省 token
  },
});
typescript
// tools/github.ts(续)— 开 issue(有副作用,注意幂等)
let reported = new Set<string>(); // 生产应换持久化去重(第 12 章)

export const openIssue = defineTool({
  name: 'open_issue',
  description: '在仓库创建分诊结论 issue',
  durable: true, // 声明持久化:恢复时不会重复建 issue
  inputSchema: {
    type: 'object',
    properties: {
      title: { type: 'string' },
      body: { type: 'string', description: '包含复现步骤、根因、修复建议' },
      labels: { type: 'array', items: { type: 'string' } },
    },
    required: ['title', 'body'],
  },
  async execute(args: { title: string; body: string; labels?: string[] }) {
    const key = args.title;
    if (reported.has(key)) return { duplicated: true }; // 幂等保护
    reported.add(key);

    const res = await fetch('https://api.github.com/repos/your-org/your-repo/issues', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
        Accept: 'application/vnd.github+json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ ...args, labels: [...(args.labels ?? []), 'triaged'] }),
    });
    return { url: (await res.json()).html_url as string };
  },
});

17.3 第二步:技能层(SKILL.md)

技能是可复用的专业知识包,agent 在需要时加载:

markdown
<!-- skills/triage/SKILL.md -->
# Bug 分诊方法论

1. **复现优先**:先写最小复现脚本再谈修复;
2. **二分定位**:用 git bisect 或注释代码段缩小范围;
3. **区分三类结论**
   - 真实缺陷(label: bug)
   - 预期行为(label: wontfix,附文档链接)
   - 信息不足(label: needs-more-info,列出缺失信息)
4. **修复纪律**:改动最小化,必须附带测试。

<!-- skills/verify/SKILL.md -->
# 修复验证清单

- [ ] 复现脚本现在通过
- [ ] 相关测试套件全绿(npm test)
- [ ] 未引入新的 lint 错误
- [ ] 变更描述包含根因一句话总结

17.4 第三步:组装并强化指令

把官方英文指令扩展为更可控的中文版本,加入输出约束:

typescript
// agents/triage.ts — 强化后的最终版
'use agent';
import { useModel, useSandbox, useSkill, useTool } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import triage from '../skills/triage/SKILL.md';
import verify from '../skills/verify/SKILL.md';
import { openIssue, searchCode } from '../tools/github.ts';

export function Triage() {
  useModel('anthropic/claude-sonnet-4-6');
  useSandbox(local());
  useSkill(triage);
  useSkill(verify);
  useTool(openIssue);
  useTool(searchCode);

  return `
对给定的 bug 报告执行端到端分诊:

1. 用 search_code 了解相关代码位置;
2. 在本地沙箱中编写并运行最小复现脚本;
3. 按 SKILL.md 方法论得出结论:真实缺陷 / 预期行为 / 信息不足;
4. 若是缺陷且可修,实施最小修复并按 verify 清单验证;
5. 调用 open_issue 提交结构化报告:
   【结论】【复现步骤】【根因】【修复建议】四段式,正文中文。

约束:
- 不修改 src/ 以外的目录;
- 无法在 30 分钟内定位时,直接标记 needs-more-info 并停止。
`;
}

17.5 第四步:接入事件源并运行

bash
# 手动触发一次完整分诊
flue run agents/triage.ts "用户反馈:列表页在 Safari 下滚动白屏"

# 接入 GitHub channel 自动触发(见第 10 章)
flue dev --port 3000   # 本地起服务接收 webhook

验证产出:agent 应该自动完成"搜索 → 复现 → 修复 → 验证 → 开 issue"闭环,issue 正文呈四段式结构。

17.6 本章小结

  • 工具层:defineTool + inputSchema;只读工具随便加,副作用工具要 durable: true + 幂等;
  • 技能层:SKILL.md 承载方法论与检查清单,让行为可审计;
  • 指令层:明确步骤顺序、输出格式与停止条件,比"自由发挥"稳定得多;
  • 全链路:channel 触发 → sandbox 执行 → skill 指导 → tool 落地。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. Triage agent 的 open_issue 工具为什么声明 durable: true?

2. SKILL.md 在本实战中的作用是?

3. 强化版指令中加入"无法在 30 分钟内定位时标记 needs-more-info 并停止",目的是?

4. 关于本实战的完整链路,正确的顺序是?

🛠️ 动手实践

  1. 给 Triage 增加 add_label 工具,要求它根据结论自动打标签,并处理标签已存在的冲突。
  2. 把 verify 清单扩展为 SKILL.md 中可勾选的表格,对比 agent 遵守程度的变化。
  3. 将沙箱从 local() 切换为远程容器,观察复现脚本执行环境的差异并记录遇到的问题。