Post

AI能力集 -- 开发一个 Skill 工厂

把"用 Skill 产 Skill"这个折腾过程完整记下来,从需求拆解、方案设计,到 Go + SQLite的TDD落地

AI能力集 -- 开发一个 Skill 工厂

1. 引言

这阵子一直在做 Skill,做着做着发现一个问题:每开发一个 Skill 都是从零开始。经验散落在各种文档、对话记录里,没有统一标准,好不容易做完一个,下次遇到类似问题还是得重新来一遍。

与其每次都重新踩坑,不如干脆做一个自动化工厂出来 —— 用 Skill 生产 Skill,把开发、验证、沉淀、复用这条链路彻底打通。


2. 初始需求和加工

每次接到”做个新的定位 Skill”的需求,都得重复这些事:

  • 找历史经验:这个系统模块之前出过啥问题、有啥日志路径、哪些工具能查
  • 写 Skill 描述:基于经验,描述清楚要解决的问题
  • 开发实现:写代码、写文档
  • 验收:跑测试、看漏判误判
  • 归档:用完的脚本扔一边,下次想找都找不到

这一套跑下来,重复劳动占了 60% 以上。如何把这些事标准化,让 Agent 能按一个统一范式自动跑下去。

所以核心目标就一句话:搭建标准化、可复用、可沉淀、可自动迭代的业务问题定位 Skill 开发工厂

让豆包梳理后输出的初始PRD(Product Requirements Document,产品需求文档):

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
Skill自动化开发工厂
一、项目目标
搭建标准化、可复用、可沉淀、可自动迭代的业务问题定位 Skill 开发工厂,统一 Skill 定义、开发、验证、沉淀、复用全流程。实现用 Skill 生产 Skill的自动化闭环,所有开发经验、使用样例可入库、可评审、可团队复用。
二、核心统一规范(所有 Skill 通用标准框架)
所有业务定位 Skill 固定 4 项标准输入与验收闭环:
1. 前置经验库
沉淀各系统模块上下文、实时 / 归档日志路径、工具能力及用法、业务覆盖场景、标准日志样例、参考代码片段。统一遵循核心规则:关键字精准匹配优先级高于代码行号小片段匹配,支持版本管理,前期平台自建维护,后期可对接独立 MCP 公共知识库。
2. 待解决业务问题
清晰定义当前需要定位、排查的具体业务问题。
3. 关联资源上下文
明确问题涉及的系统模块、对应日志类型、可调用工具清单,供 AI 自动匹配开发资源。
4. 统一验收迭代标准
  - 规范性:基于 skill-creator 开发评审,迭代产物必须二次校验合规;
  - 准确性:人工标定标准输入输出样例,正向用例全部通过,附带基础反向脏数据用例校验,保障容错能力;
  - 迭代约束:优先满足「准确率达标、用例全覆盖、无误判漏判」收敛条件,达标即终止迭代;设置最大 20 轮迭代上限,杜绝无限循环,达标归档、不达标强制终止并标记异常。
所有合格 Skill 统一归档留存,留存完整输入输出示例,支持团队展示与复用。

三、系统整体架构(双端协同闭环)
1. 后台管理系统(Go+SQLite + 内嵌 Web 页面)
作为 Skill 任务和经验资产的统一底座,提供基础治理能力:
- 任务管理:支持任务创建、状态流转(待认领 / 进行中 / 已归档/异常),记录创建时间、认领时间、维护人、插件仓库地址、归档位置,支持条件查询。任务统一按标准框架结构化录入。
- 经验库管理:支持模块化维护、模糊查询、一键导出 MD 文件,供 AI/CLI 离线参考复用,预留后续独立知识库接口同步能力。
- 结构化能力:支持单任务一键复制完整标准配置文本。
- 开放 API 能力:对外输出标准化任务数据接口,供自动化开发引擎拉取任务。
2. 自动化工厂 Skill(核心闭环能力)
将原有 CLI「拉取任务、开发、TDD 迭代、结果回写」全流程,封装为标准化 Skill,纳入统一 Skill 体系管控:
- 自动调用平台 API 拉取任务信息、关联经验库数据;
- 基于 Goal 目标拆解 + TDD 模式,自主完成 Skill 开发、自测、迭代优化,版本号v0.0.1自增;
- 用于验证skill功能的环境进行人为确认;
- 严格遵守评审、准确性验收规则;准确率达标、用例全覆盖、无误判漏判,满足条件则终止迭代,最大不超过20轮迭代;
- 开发完成后自动上传产物、迭代记录、测试结果,更新任务状态并归档。
四、完整运行链路
1. 平台创建标准化 Skill 开发任务,完善经验库、问题描述、关联资源、验收样例;
2. 工厂 Skill 自动拉取结构化任务与经验资产;
3. 自主完成环境适配、TDD 开发、多轮迭代验收;
4. 结果自动回写平台,任务归档,合格 Skill 入库先上传平台,后续再考虑插件仓库;
5. 形成任务产出→自动开发→验证迭代→资产沉淀→复用的完整工厂闭环。
五、核心价值
1. 统一所有 Skill 开发范式,规范统一、可落地、可评审;
2. 经验资产可沉淀、可导出、可团队复用;
3. 自动化开发流程标准化、可管控,实现 Skill 工业化自迭代生产;
4. 极简入参框架,剥离冗余环境配置,职责边界更清晰。

3. 代码实现

3.1. 整体架构

经过多轮推敲,最终选定的结构就两层:一个后台管理 + 一个 Factory Skill

1
2
3
4
5
6
7
8
9
10
11
skill-factory/
  cmd/server/          # HTTP 服务入口 + 内嵌 Web UI
  internal/
    backend/           # 数据模型 + SQLite Repo 层
    task/              # TaskRepo TDD 测试
    experience/        # ExperienceRepo TDD 测试
  go.mod

skills/
  redis-cluster-troubleshoot/  # 示例产出物(Redis 集群故障定位)
  skill-factory/               # Factory Skill(自动化闭环)
  • 后台管理主要管两类东西:任务(Task)经验(Experience)
  • Factory Skill 负责拉任务、调用 TDD 流程、结果回写
  • Web UI 内嵌到 Go 二进制里,单文件就能跑,部署零依赖

3.2. 数据模型

数据库用 SQLite(纯 Go 实现,无 CGO,后面会讲为啥这么选)。两张表就够了:

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
-- 任务表:每个待开发的 Skill 是一行
CREATE TABLE tasks (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    description TEXT,
    status TEXT DEFAULT 'pending',     -- pending / in_progress / archived / exception
    experience_id TEXT,                -- 关联到经验库
    resources TEXT,                    -- 关联资源上下文
    acceptance TEXT,                   -- 验收标准
    version TEXT DEFAULT 'v0.0.1',
    created_at DATETIME,
    claimed_at DATETIME,
    maintainer TEXT,
    repo_address TEXT,
    archived_at DATETIME,
    result TEXT                        -- 最终产物地址
);

-- 经验表:沉淀的系统模块上下文
CREATE TABLE experiences (
    id TEXT PRIMARY KEY,
    module TEXT NOT NULL,              -- 哪个系统模块
    keywords TEXT,                     -- 关键字/错误码
    log_paths TEXT,                    -- 日志路径
    tool_usage TEXT,                   -- 排查工具
    scene TEXT,                        -- 适用场景
    log_samples TEXT,                  -- 日志样例
    code_snippets TEXT,                -- 代码片段
    version TEXT,
    created_at DATETIME,
    updated_at DATETIME
);

SQLite 单文件、零部署,跟整个项目的”轻量”定位一致。

3.3. API 设计

API 走标准 RESTful,接口精简到最少:

方法路径说明
GET/api/tasks查任务列表,支持 status 过滤
POST/api/tasks创建任务
GET/api/tasks/:id拿任务详情
PUT/api/tasks/:id/status改状态
GET/api/experiences查经验库,支持模糊搜
POST/api/experiences录入新经验

没有复杂的多用户、权限、审计之类的东西 —— 那些是后面再说的事,先把核心闭环跑通。

3.4. TDD 循环

整个 TDD 流程,核心就是一个 shell 脚本级别的循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for iter in $(seq 1 20); do
  # 4a. 解析验收样例,生成测试用例
  test_cases=$(parse_acceptance "$task.acceptance")

  # 4b. 基于问题描述 + 经验上下文,实现 Skill
  skill_impl=$(develop_skill "$task.description" "$experience_ctx" "$test_cases")

  # 4c. 执行测试
  result=$(run_tests "$skill_impl" "$test_cases")

  # 4d. 验收判断
  pass_rate=$(echo "$result" | jq '.pass_rate')
  false_pos=$(echo "$result" | jq '.false_pos_count')
  false_neg=$(echo "$result" | jq '.false_neg_count')

  if [ "$pass_rate" = "1.0" ] && [ "$false_pos" = "0" ] && [ "$false_neg" = "0" ]; then
    break  # 验收通过
  fi

  # 4e. 迭代优化(把失败信息注入下一轮)
  task.description="$task.description [ITER $iter FAILED: $result]"
done

20 轮上限是经验值,再多就是无底洞了。false_pos(误判)和 false_neg(漏判)都必须为 0,这个是硬指标,不能”大部分通过就行”。


4. 为啥选 Go + SQLite

这一节单独写一下选型。

考量维度最终选择真实原因
部署简单SQLite没有独立 DB 进程,单文件,跟整个项目”轻量”定位一致
交叉编译modernc.org/sqlite这才是选 Go 的关键 —— 纯 Go 驱动,无 CGO,Linux/Mac/Windows 都能编译
Web 层标准库 net/http + embed用不上 Gin/Echo 那种,内嵌 HTML 模板够用,少一个依赖
测试Go testing内置,表格驱动测试写起来很顺

最关键的是 modernc.org/sqlite 这个库。普通的 mattn/go-sqlite3 是 CGO 实现的,意味着你交叉编译的时候得在目标机器上装 C 编译器 —— 这点无法接受。modernc 这个库是纯 Go 翻译的 SQLite,代价是包大一点、性能差一点,但能换来”一处编译,到处运行”,这个 trade-off 是值得的。


5. 落地实现的关键代码

这块不打算把所有代码贴出来(完整版在仓库里),只挑几个有代表性的。

5.1. 数据结构定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Task struct {
    ID           string     `json:"id"`
    Title        string     `json:"title"`
    Description  string     `json:"description,omitempty"`
    Status       string     `json:"status"`
    ExperienceID string     `json:"experience_id,omitempty"`
    Resources    string     `json:"resources,omitempty"`
    Acceptance   string     `json:"acceptance,omitempty"`
    Version      string     `json:"version"`
    CreatedAt    time.Time  `json:"created_at"`
    ClaimedAt    *time.Time `json:"claimed_at,omitempty"`
    Maintainer   string     `json:"maintainer,omitempty"`
    ArchivedAt   *time.Time `json:"archived_at,omitempty"`
    Result       string     `json:"result,omitempty"`
}

const (
    TaskStatusPending    = "pending"
    TaskStatusInProgress = "in_progress"
    TaskStatusArchived   = "archived"
    TaskStatusException  = "exception"
)

time.Time 用指针是踩过的坑:零值时间在 JSON 里会序列化成 "0001-01-01T00:00:00Z",前端解析会出问题。用指针 + omitempty 才干净。

5.2. TaskRepo 测试

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
func TestTaskCreateAndGet(t *testing.T) {
    db, _, err := backend.TestDB()
    if err != nil {
        t.Fatalf("TestDB: %v", err)
    }
    repo := backend.NewTaskRepo(db)

    task := &backend.Task{
        ID:          "test-task-001",
        Title:       "Redis 集群节点失联定位",
        Description: "实现 Redis 集群节点失联场景的问题定位 Skill",
        Status:      backend.TaskStatusPending,
        Version:     "v0.0.1",
        CreatedAt:   time.Now(),
    }

    if err := repo.Create(task); err != nil {
        t.Fatalf("Create: %v", err)
    }

    got, err := repo.Get("test-task-001")
    if err != nil {
        t.Fatalf("Get: %v", err)
    }
    if got.Title != task.Title {
        t.Errorf("Title = %q, want %q", got.Title, task.Title)
    }
}

测试用的是 SQLite 内存模式(:memory:),跑完一个测试 case 就销毁,完全无副作用。这才是单元测试该有的样子 —— 不要为了测个 CRUD 去启动一个 test DB 起来。

5.3. ExperienceRepo 测试

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
func TestExperienceCreateAndSearch(t *testing.T) {
    db, _, err := backend.TestDB()
    if err != nil {
        t.Fatalf("TestDB: %v", err)
    }
    repo := backend.NewExperienceRepo(db)

    exp := &backend.Experience{
        ID:        "exp-redis-cluster",
        Module:    "redis-cluster",
        Keywords:  "CLUSTERDOWN,MOVED,ASK,READONLY",
        LogPaths:  "/var/log/redis/redis-server.log",
        ToolUsage: "redis-cli cluster nodes, redis-cli slowlog get 10",
        Scene:     "集群节点失联定位",
        Version:   "v1.0.0",
        CreatedAt: time.Now(),
    }

    if err := repo.Create(exp); err != nil {
        t.Fatalf("Create: %v", err)
    }

    results, err := repo.Search("redis-cluster")
    if err != nil {
        t.Fatalf("Search: %v", err)
    }
    if len(results) != 1 {
        t.Errorf("search count = %d, want 1", len(results))
    }
}

模糊搜索用的是 SQLite 原生的 LIKE,没引第三方搜索引擎 —— 这种小规模数据量(经验库也就几百条),LIKE 完全够用,别过度工程化

5.4. 最终测试结果

1
2
3
4
5
6
$ go test ./...
?       skill-factory/cmd/server  [no test files]
?       skill-factory/internal/backend  [no test files]
ok      skill-factory/internal/experience  0.004s
ok      skill-factory/internal/task       0.005s
ALL OK

干净利落,4 个测试文件全部通过。


6. 示例产出物:Redis 集群故障定位 Skill

光有工厂本身缺乏说服力,还需要一个真实业务 Skill 来验证 —— 用这个工厂产出一个 Redis 集群故障定位 Skill。

6.1. 它能覆盖啥

7 个核心场景,都是线上真出过问题的:

场景关键字/触发
集群节点失联CLUSTERDOWN
内存碎片化mem_fragmentation_ratio > 1.4
主从复制中断replication backlog 不足
慢查询根因slowlog > 1s
Big Key 查询redis-cli --bigkeys
热 Key 探测QPS 集中在少数 key
连接数打满maxmemory + OOM

6.2. 验收用例

正向用例(必须识别正确):

输入预期输出
CLUSTERDOWN The cluster is gone定位到 cluster-node-timeout,给 redis.conf 调优建议
READONLY You can't write识别只读场景,输出 replica 配置检查步骤
slowlog 显示 KEYS > 5s给出 SCAN 替换方案
mem_fragmentation_ratio > 1.5给出 MEMORY PURGE + activedefrag 配置

反向用例(脏数据)(必须明确拒绝):

输入预期行为
空 slowlog返回”无慢查询记录”,跳过分析
MySQL 日志明确拒绝,输出”非 Redis 日志格式”
二进制数据提示”请提供文本日志”

反向用例这块特意强化过 —— 最容易出事故的就是把 MySQL 日志当 Redis 解析,所以”非 Redis 日志格式”这种识别必须做到位。


7. 仓库地址

仓库在 xiaodongQ/ai-playground (后续再此基础加功能后重命名了)

关键目录:

路径说明
skill-factory/后台管理系统完整代码
skills/redis-cluster-troubleshoot/Redis 集群故障定位 Skill(示例产出物)
skills/skill-factory/Factory Skill(全流程自动化闭环)

快速启动:

1
2
3
4
5
6
7
8
9
10
cd skill-factory
go mod tidy
go build ./cmd/server/...
./server  # 默认监听 :8080

# 测试 API
curl -s http://localhost:8080/api/tasks
curl -s -X POST http://localhost:8080/api/tasks \
  -H "Content: application/json" \
  -d '{"id":"test-001","title":"测试任务","status":"pending"}'

一行 go mod tidy + 一个二进制,部署完成。

This post is licensed under CC BY 4.0 by the author.