首页 › tutorials › 自建Skill:中文问数据库

自建 Skill:中文问数据库——从零打造一个只读数据问答技能

概述

本教程带你在 30 分钟内从零创建一个完整的 Claude Code 自定义 Skill——ask-db:用户用一句中文提问,Claude 自动生成 SQL、在只读安全沙箱里执行、再用自然语言回答。你将掌握 2026 版 Skills 的全部核心能力:frontmatter 触发、动态上下文注入(!command`)、scripts/工程化与${CLAUDE_SKILL_DIR}、渐进式披露的参考文件、参数传递与allowed-tools`。适合已用过 Claude Code、想把"重复粘贴的查库流程"固化成可复用能力的开发者与数据分析师。

前置条件


第一步:先决定"要不要做成 Skill"

很多人上手就想造 Skill,但 Skills 官方文档给了一个清晰的判据——当你在对话里反复粘贴同一段指令、检查清单或多步流程时,或者当 CLAUDE.md 里某一节从"事实陈述"长成了一段"操作步骤"时,就该抽成 Skill。区别在于生命周期:CLAUDE.md 的内容常驻上下文,而 Skill 的正文只在被触发时才加载,长篇参考材料平时几乎零成本。

把"用中文查数据库"这件事和另外两种扩展方式做个对照,你能清楚为什么它最适合做成 Skill:

维度 CLAUDE.md Skill(本教程) MCP 服务器
加载时机 常驻 触发/手动调用时 工具被调用时
适合内容 事实、约定 多步流程 + 脚本 + 参考材料 高频原子工具、跨会话状态
改造成本 改文件即生效 改文件即生效(live change detection) 写服务进程、定协议
查库场景 太重,浪费 token ✅ 恰好 适合"查库存"这类高频原子调用

我们的场景是"理解一句中文 → 编排多步(生成 SQL、安全执行、组织回答)",这正是 Skill 的甜区。高频的"查某个 SKU 库存"那种点查,才更值得做成 MCP 工具。理解这条边界,是写好 Skill 的前提。

第二步:规划目录结构

Skills 遵循开放的 Agent Skills 标准,每个 Skill 就是一个目录,入口是 SKILL.md。我们把它放在个人级目录,这样所有项目都能用:

mkdir -p ~/.claude/skills/ask-db/scripts
mkdir -p ~/.claude/skills/ask-db/reference

最终结构长这样:

ask-db/
├── SKILL.md              # 入口:frontmatter + 编排指令(必须,<500 行)
├── scripts/
│   └── run_query.py      # 只读查询执行器(被运行,不进上下文)
└── reference/
    ├── schema.md         # 完整表结构说明(按需加载)
    └── safety_rules.md   # SQL 安全红线(按需加载)

预期结果:目录创建成功。Claude Code 会实时监听 ~/.claude/skills/ 的文件变化——新建目录、改 SKILL.md 在当前会话内立即生效,无需重启。注意:若创建的是会话启动时不存在的顶层 skills 目录,才需要重启一次让它被纳入监听。

常见问题:报错"No such file"——多半是 ~ 没展开,用 echo $HOME 确认绝对路径。Windows/PowerShell 环境把内联 shell 默认从 bash 换成 powershell 需设 CLAUDE_CODE_USE_POWERSHELL_TOOL=1。

第三步:写 frontmatter——把触发做精准

SKILL.md 顶部是 YAML frontmatter(--- 之间),告诉 Claude 何时用这个 Skill;下面是 markdown 正文,告诉它怎么做。先写 frontmatter:

---
description: 用中文自然语言查询 SQLite 数据库并给出人类可读的回答。当用户想"查一下/统计/算一下"数据库里的数据、问销售/库存/用户等问题、或要求生成 SQL 时使用。不用于修改数据。
when_to_use: 触发词包括"查一下""帮我统计""有多少""按…分组""上个月/最近"等数据提问,以及"对 sales.db 跑个查询"。
argument-hint: "[数据问题]"
allowed-tools: Bash(python3 *) Bash(sqlite3 *)
---

这里有几条 2026 版的关键规则,踩中才能精准触发:

  1. description 把核心用例放第一句。description 和 when_to_use 拼接后在技能列表里会被截断到 1,536 个字符,而且 Claude 是靠它做语义匹配决定是否自动加载的——第一句写清"做什么 + 何时用",比堆关键词有效。
  2. 用 when_to_use 补充触发短语,而不是往 description 里塞。两者分工不同:前者是"能力 + 场景",后者是"例子请求"。本教程末尾"已覆盖主题"里那些泛泛而谈的标题,多半就是 description 写得太宽。
  3. 不设 disable-model-invocation: true。这个字段会让 Claude 永不自动触发、只能你手动 /ask-db。我们希望用户说"查一下上月销量"就自动用,所以保留默认。
  4. allowed-tools 预批准权限:列出 Bash(python3 *) 后,Claude 跑查询脚本时不再每次弹窗问你要不要批准。注意,它只是"免询问",并不限制可用工具范围。

预期结果:保存后在会话里输入 / 应能看到 ask-db 出现在补全列表;说"帮我查一下数据"应能被自动触发。

常见问题一:Skill 不触发——八成是 description 太抽象。把"数据库助手"改成"用中文查 SQLite 并回答数据问题"这种带动词和场景的句子。 常见问题二:触发太频繁——description 写了"回答任何问题",Claude 就会在无关对话里也加载它。把范围收窄到"数据/统计/SQL"。 常见问题三:description 被截断——精简到 1500 字符内,把长例子挪进 reference/。

第四步:动态注入实时表结构

NL-to-SQL 最大的难点是 Claude 得先知道库里有哪些表、哪些列。动态上下文注入(!command`)正好解决:在SKILL.md` 正文里写一行 shell,Claude Code 会先跑这条命令、把输出原地替换进正文,Claude 看到的就是真实 schema 而非命令本身。

在 SKILL.md 正文区写下骨架(完整版见第六步):

## 当前数据库结构

!`sqlite3 sales.db ".schema" 2>/dev/null || echo "(未找到 sales.db,请向用户确认数据库路径)"`

> 上面是实时拉取的表结构。若为空,先问用户数据库文件在哪,或参考 `reference/schema.md` 的约定。

语法要点:

预期结果:触发 Skill 时,Claude 的 prompt 里直接是 CREATE TABLE customers (...),而不是一条 sqlite3 命令。

常见问题:schema 很大、几十张表——别全塞进 SKILL.md,那会让 Skill 正文每轮都占上下文。改为只注入 .tables 列表,把详细结构放进 reference/schema.md(下一步),让 Claude 按需读。

第五步:写安全的查询脚本(scripts 工程化)

Skill 正文应该简洁(官方建议 <500 行),重活交给 scripts/ 里的脚本——脚本是被执行的,不进上下文,零 token 成本。我们写一个只读查询执行器,这是整个 Skill 的安全基石。保存到 ~/.claude/skills/ask-db/scripts/run_query.py:

#!/usr/bin/env python3
"""只读 SQLite 查询执行器:供 ask-db skill 调用。

安全设计:
1. 以 mode=ro 只读方式打开数据库,物理上无法写入。
2. 只允许 SELECT/WITH/EXPLAIN/PRAGMA 开头的查询,拒绝一切 DDL/DML。
3. 只执行单条语句(sqlite3 遇到多余语句会抛错),防止语句堆叠。
4. 强制行数上限与执行时限,防止全表扫描拖垮机器。
"""
import sqlite3, sys, time, re, json

MAX_ROWS = 500
TIMEOUT_MS = 5000
ALLOW_PREFIX = ("select", "with", "explain", "pragma")
FORBIDDEN = re.compile(
    r"\b(insert|update|delete|drop|alter|create|truncate|attach|detach|"
    r"replace|merge|vacuum|reindex)\b", re.IGNORECASE)

def run(db_path: str, sql: str) -> None:
    sql = sql.strip().rstrip(";")
    if not sql:
        print(json.dumps({"error": "空查询"}, ensure_ascii=False)); return
    first = sql.split(None, 1)[0].lower()
    if not sql.lower().startswith(ALLOW_PREFIX) or FORBIDDEN.search(sql):
        print(json.dumps({"error": "仅允许只读 SELECT 查询"}, ensure_ascii=False)); return

    # 以只读 URI 打开;找不到库会直接报错
    con = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True, timeout=1)
    con.row_factory = sqlite3.Row

    deadline = [time.monotonic()]
    def progress():
        if (time.monotonic() - deadline[0]) * 1000 > TIMEOUT_MS:
            return 1  # 中止查询
        return 0
    con.set_progress_handler(progress, 1000)  # 每处理 1000 个虚拟机指令检查一次

    wrapped = f"SELECT * FROM ({sql}) AS _q LIMIT {MAX_ROWS}"
    try:
        cur = con.execute(wrapped)  # execute 只跑首条语句,多语句会抛错
        rows = cur.fetchall()
        out = {"columns": [d[0] for d in cur.description],
               "rows": [list(r) for r in rows],
               "rowcount": len(rows),
               "truncated": len(rows) >= MAX_ROWS}
        print(json.dumps(out, ensure_ascii=False, default=str))
    except sqlite3.Error as e:
        print(json.dumps({"error": f"SQL 执行失败: {e}"}, ensure_ascii=False))
    finally:
        con.close()

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(json.dumps({"error": "用法: run_query.py <db> <sql>"}, ensure_ascii=False))
        sys.exit(2)
    run(sys.argv[1], sys.argv[2])

四个安全层次是刻意的:只读连接是物理兜底;前缀白名单 + 关键词黑名单是语义校验;单语句执行防堆叠注入;行数/时限防资源耗尽。即使 Claude 生成了 DROP TABLE,这里也会被拦下。

路径用 ${CLAUDE_SKILL_DIR} 引用,无论 Skill 装在个人、项目还是插件级都正确:

python3 ${CLAUDE_SKILL_DIR}/scripts/run_query.py sales.db "SELECT ...;"

预期结果:手动跑 python3 scripts/run_query.py sales.db "SELECT 1" 返回 {"columns":["1"],"rows":[[1]],...};跑 DELETE FROM t 返回 {"error":"仅允许只读 SELECT 查询"}。

常见问题一:${CLAUDE_SKILL_DIR} 没展开——它是 Skills 提供的字符串替换变量,只在 Skill 正文/动态注入里有效,别在普通对话或子代理里用。 常见问题二:子查询包裹 SELECT * FROM (...) LIMIT N 报错——极少数含特定窗口函数的语句会有歧义,让 Claude 换写法即可,安全优先。 常见问题三:查询慢——set_progress_handler 会在超时后中止并报错,提示 Claude 加 WHERE 或建索引。

第六步:编排完整工作流

现在把 frontmatter、动态注入、脚本调用串成一条清晰的工作流。这是完整 SKILL.md 正文:

# ask-db:中文问数据库

你是只读数据分析师。面对用户的数据问题,按以下步骤工作:

## 1. 确认数据源
默认库为 `sales.db`(当前工作目录)。schema 见上方注入块;若为空,先问用户库文件路径。
详细字段含义见 reference/schema.md(仅当注入的 schema 不够清晰时再读)。

## 2. 把问题翻译成只读 SQL
- 只生成 SELECT/WITH 开头的查询;禁止任何写操作。
- 列名用双引号、字符串用单引号;日期用 `DATE('now','-1 month')` 等。
- 复杂查询先在脑中验证聚合粒度,避免笛卡尔积。

## 3. 执行(必须走安全脚本,禁止直接 sqlite3 写命令)
用 ${CLAUDE_SKILL_DIR}/scripts/run_query.py 跑你的 SQL:

python3 ${CLAUDE_SKILL_DIR}/scripts/run_query.py "<你的SQL>"

脚本会返回 JSON:`columns`/`rows`/`rowcount`/`truncated` 或 `error`。
若 `error`,修正 SQL 重跑;若 `truncated:true`,告知用户结果被截断到 500 行并建议加条件。

## 4. 用自然语言回答
不要把原始 JSON 丢给用户。用表格/要点呈现结论,先给答案再给关键数字,必要时附上你用的 SQL。

## 红线
- 永不绕过 run_query.py 直接执行写语句。
- 安全规则的细节见 reference/safety_rules.md(有争议时再查)。

注意第 4 步那句话——"skill 看起来在影响行为"和"它真的影响了"是两回事。Skill 正文被调用后作为一条消息留在会话里整个任务期间有效,Claude 不会每轮重读文件。所以把"贯穿任务的红线"写成常驻指令(如"必须走脚本"),把一次性步骤写清楚,行为才会稳定。

补两个轻量参考文件。reference/schema.md:

# sales.db 表结构约定
- customers(id, name, city, signup_date)
- products(id, name, category, price)
- orders(id, customer_id, order_date, total) — total 为订单金额
- order_items(order_id, product_id, qty, unit_price) — 明细
时间字段统一存为 ISO 字符串 'YYYY-MM-DD'。

reference/safety_rules.md:把脚本里那四层防护用中文复述一遍,供 Claude 在用户质疑"为什么不让我 UPDATE"时引用。这两个文件默认不加载,只有 SKILL.md 里引用它们、且 Claude 判断需要时才被读入——这就是渐进式披露,长参考材料平时零成本。

第七步:测试、评测与迭代

先做两轮人工冒烟(务必在新会话里测,老会话有编写时的残留上下文会掩盖缺陷):

手动验证技巧:分别"开 Skill 跑一次"和"关掉 Skill 跑一次"对比结果——这是官方推荐的基线对照法。

接着上自动化评测。skill-creator 插件把"开/关对照"循环自动化了:

/plugin install skill-creator@claude-plugins-official
# 若提示找不到,先:
/plugin marketplace update claude-plugins-official
/reload-plugins

装好后对 Claude 说"用 skill-creator 评测我的 ask-db skill"。它会:

  1. 在 evals/evals.json 里存测试用例(prompt + 输入文件 + 期望行为);
  2. 每个用例开一个隔离子代理跑(干净上下文),记录 token 与耗时;
  3. 按断言打分写入 grading.json;
  4. 把"开/关 Skill"的通过率、耗时、token 汇总进 benchmark.json,让你看到加这个 Skill 到底提升了多少通过率、付出了多少 token;
  5. 还能对两个版本做盲测 A/B,确认改动是改进再提交,并自动生成"该触发/不该触发"样例、测算命中率、建议 description 调整。

预期结果:benchmark.json 里"有 Skill"通过率显著高于"无 Skill",且 token 开销可接受。

常见问题:评测显示"命中率低、该触发没触发"——回到第三步,把 description/when_to_use 里缺的触发短语补上重测。

实战案例:30 秒跑通 sales.db

从零建一个库并提问。先造数据:

sqlite3 sales.db <<'SQL'
CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT, city TEXT, signup_date TEXT);
CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, category TEXT, price REAL);
CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INT, order_date TEXT, total REAL);
CREATE TABLE order_items(order_id INT, product_id INT, qty INT, unit_price REAL);

INSERT INTO customers VALUES (1,'张三','上海','2024-01-15'),(2,'李四','北京','2025-05-20');
INSERT INTO products VALUES (1,'无线鼠标','外设',99),(2,'机械键盘','外设',399),(3,'显示器','显示',1299);
INSERT INTO orders VALUES (1,1,'2026-05-28',498),(2,2,'2026-05-30',399),(3,1,'2026-06-02',1299);
INSERT INTO order_items VALUES (1,1,1,99),(1,2,1,399),(2,2,1,399),(3,3,1,1299);
SQL

在 sales.db 所在目录启动 claude,直接问:

上个月(2026-05)销量金额最高的三个商品是哪些?

Claude 会自动触发 ask-db,注入 sales.db 的 schema,生成形如:

SELECT p.name, SUM(oi.qty*oi.unit_price) AS revenue
FROM order_items oi JOIN products p ON p.id=oi.product_id
JOIN orders o ON o.id=oi.order_id
WHERE o.order_date LIKE '2026-05%'
GROUP BY p.name ORDER BY revenue DESC LIMIT 3;

经 run_query.py 执行后,你会得到一段自然语言结论(例如"机械键盘 798 元居首,其次……"),而非原始 JSON。

整个过程的工作流:

flowchart TD
    A[用户中文提问] --> B{Claude 语义匹配<br/>description/when_to_use}
    B -->|命中| C[加载 ask-db Skill 正文]
    C --> D["动态注入 !sqlite3 .schema<br/>拿到真实表结构"]
    D --> E[Claude 生成只读 SELECT SQL]
    E --> F["运行 scripts/run_query.py<br/>只读+白名单+行数时限"]
    F --> G{执行结果}
    G -->|error| E
    G -->|JSON rows| H[Claude 组织自然语言回答]
    H --> I[表格/要点交付给用户]
    G -->|truncated| J[提示加 WHERE 重试]
    J --> E

进阶:把 Skill 跑在隔离子代理里

当库很大、查询很多时,主会话上下文会被 schema 和中间结果撑爆。给 frontmatter 加两行,让整个 Skill 在隔离子代理里跑、只把结论回传:

context: fork
agent: Explore

context: fork 让 SKILL.md 正文成为驱动子代理的 prompt;agent: Explore 用内置的只读探索代理(它跳过 CLAUDE.md 和 git status,上下文极小)。注意:fork 只适合"有明确任务"的 Skill;若是纯约定类("用这些 SQL 规范")放进 fork,子代理拿到规范却没有可执行任务,会空手返回。

分发与共享

延伸阅读

训练营适用性评估

参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方权威:frontmatter 字段表、动态注入 !command`、${CLAUDE_SKILL_DIR}、context: fork`、skill-creator 评测流程
  2. Steering Claude Code: skills, hooks, subagents and more — Claude Blog — CLAUDE.md / Skill / Subagent / MCP 的官方决策框架与扩展心智模型
  3. anthropics/skills — GitHub — Agent Skills 开放标准官方仓库,含可复用示例 Skill 与目录约定
  4. Claude Code Skills: The Complete Guide (2026) — Duet — 第三方完整指南,覆盖 SKILL.md/MCP/子代理/团队协作
  5. Claude Code Customization Guide (2026) — alexop.dev — CLAUDE.md vs Skills vs Subagents 的对比决策表

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入