第 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. 关于本实战的完整链路,正确的顺序是?
🛠️ 动手实践
- 给 Triage 增加
add_label工具,要求它根据结论自动打标签,并处理标签已存在的冲突。 - 把 verify 清单扩展为 SKILL.md 中可勾选的表格,对比 agent 遵守程度的变化。
- 将沙箱从
local()切换为远程容器,观察复现脚本执行环境的差异并记录遇到的问题。