智能问数实现详解:从 SQL 工具链到 deepagents Skill
本文基于 mystu 项目中的 structured-query-pack,说明「结构化智能问数」的完整实现逻辑、Skill 如何被大模型发现与使用、如何封装为可复用 pack,以及在其他 Agent 项目中如何接入。
适用读者:需要在 deepagents / LangGraph Agent 上落地 Text-to-SQL、且希望工作流可拷贝复用的开发者。
一、什么是「智能问数」
智能问数指:用户用自然语言提问(如「青岛港 PB 粉最新库存和环比多少?」),由 LLM 驱动的 Agent 自动完成:
- 识别这是需要具体数字、聚合、排名的结构化问题;
- 查看数据库 schema;
- 生成并执行 只读 SQL;
- 把查询结果转成带口径说明的自然语言回答。
技术链路:LLM 写 SQL → 服务端 sqlglot 校验 → MySQL 只读执行 → LLM 解读结果。全程不使用向量 / Embedding。
二、整体架构
三层分工:
| 层级 | 职责 | 代码/文件位置 |
|---|---|---|
| Skill(工作流) | 告诉 LLM「何时用、按什么顺序调工具、如何解释结果」 | structured-query-pack/skills/structured-query/SKILL.md |
| Toolkit(能力) | 提供 LangChain 标准 SQL 四工具 + sqlglot 护栏 | structured-query-pack/structured_query_pack/ |
| Host 接入(装配) | CompositeBackend、工具注入、系统提示词补充 | mystu 的 deepagent.py |
三、大模型如何发现并使用 Skill(含 iron-ore.md 说明)
这是理解整个问数链路的关键:哪些文件会自动进上下文,哪些不会。
3.1 SkillsMiddleware:只扫描 SKILL.md 的 frontmatter
Agent 配置了 skills=["/skills/"] 后,SkillsMiddleware 在会话开始时:
- 通过
CompositeBackend列出/skills/下子目录(如structured-query/); - 仅读取 每个目录里的
SKILL.md; - 解析 YAML frontmatter(
name、description、allowed-tools); - 把 skill 摘要追加到系统提示词,例如:
- **structured-query**: 铁矿石结构化数据智能问数:使用 SQLDatabaseToolkit 查询数值指标...
-> Allowed tools: sql_db_list_tables, sql_db_schema, sql_db_query, sql_db_query_checker
-> Read `/skills/structured-query/SKILL.md` for full instructions
这叫做 渐进式披露(Progressive Disclosure):大模型先看到「有哪些 skill、干什么用」,不会在启动时把 SKILL.md 全文塞进上下文。
3.2 大模型何时读 SKILL.md 全文
deepagents 在系统提示词中指示大模型:
- 判断用户问题是否匹配某个 skill 的
description(如数值、环比类问题 →structured-query); - 需要时调用内置
read_file,路径为 skill 列表里给出的/skills/structured-query/SKILL.md; - 按 SKILL.md 中的工作流调用
sql_db_*工具。
3.3 iron-ore.md 不会自动加载
路径:
/skills/structured-query/references/iron-ore.md
磁盘对应:
structured-query-pack/skills/structured-query/references/iron-ore.md
重要事实:
| 文件 | 是否自动进上下文 | 说明 |
|---|---|---|
SKILL.md frontmatter 摘要 |
是 | SkillsMiddleware 启动时注入系统提示词 |
SKILL.md 全文 |
否 | 大模型按需 read_file |
references/iron-ore.md |
否 | SkillsMiddleware 不扫描 references/ 目录 |
iron-ore.md 是 mystu 的领域 overlay 文档,内容包括表名说明、字段口径、示例问句等。它存在于 skill 目录旁,供人和拷贝 pack 时参考;运行时不会自动推送给大模型。
当前 SKILL.md 正文仅泛化提及「若 host 提供 references/domain.md」,没有写死「请先读 iron-ore.md」,因此大模型默认不会去读它。
3.4 mystu 中铁矿石领域知识实际从哪来
在不读 iron-ore.md 的情况下,问数仍能工作,因为领域信息已通过其他途径注入:
| 来源 | 注入方式 | 内容 |
|---|---|---|
configure.py --preset iron-ore |
渲染进 SKILL.md 正文 |
view_port_inventory、青岛港示例 SQL、字段名 |
| 系统提示词 | 每条消息始终可见 | 何时走 structured-query、工具名列表(见 iron_ore_forecast.md 中结构化查数段落) |
iron-ore.md |
不自动 | 可选;需大模型主动 read_file 或你在 SKILL/提示词里显式引导 |
3.5 若希望大模型「必定使用」iron-ore.md
在 SKILL.md 的「领域补充」一节增加显式指令,例如:
## 领域补充
执行结构化查数前,请先阅读:
`/skills/structured-query/references/iron-ore.md`
其中包含白名单表说明、字段口径与示例问句。
或在 host 项目的系统提示词里增加同一路径。这样大模型在 read_file SKILL.md 后会被引导再读 overlay。
四、一次完整问数的执行逻辑
以用户问题「青岛港 PB 粉最新库存和环比?」为例:
4.1 阶段 0:Agent 启动时装配
Web 服务启动时(mystu/controller/__init__.py lifespan)会:
- 调用
build_agent()构建 deep agent; - 在
tools列表中注入get_sql_toolkit_tools(model)返回的四个 SQL 工具; - 设置
backend=CompositeBackend(...),把虚拟路径/skills/映射到 pack 磁盘目录; - 设置
skills=["/skills/"],启用 SkillsMiddleware。
相关代码(mystu 参考实现):
# mystu/buildagent/agent/deepagent.py(节选)
def _build_agent_backend() -> CompositeBackend:
return CompositeBackend(
default=StateBackend(),
routes={
"/skills/": FilesystemBackend(
root_dir=str(AGENT_SKILLS_DIR),
virtual_mode=True,
),
},
)
tools = [*_TOOLS, *MEMORY_TOOLS, *get_sql_toolkit_tools(model), ...]
return create_deep_agent(
model=model,
tools=tools,
system_prompt=load_iron_ore_forecast_prompt(),
backend=_build_agent_backend(),
skills=["/skills/"],
)
4.2 阶段 1:匹配 skill 并读取工作流
大模型根据 SkillsMiddleware 注入的摘要,判断问题属于数值/聚合类 → 匹配 structured-query → read_file 读取 SKILL.md 全文。
4.3 阶段 2:按 Skill 工作流调用工具
| 步骤 | 工具 | 作用 |
|---|---|---|
| 1 | sql_db_list_tables |
列出可查询对象(已被 include_tables 限制为白名单) |
| 2 | sql_db_schema |
查看 view_port_inventory 的列与样例行 |
| 3 | sql_db_query_checker |
(可选)LLM 检查 SQL 语法/逻辑 |
| 4 | sql_db_query |
执行 SELECT |
LLM 可能生成的 SQL 示例:
SELECT stat_date, port_name, ore_type,
inventory_wet_10k_tons, wow_change_10k_tons, data_source
FROM view_port_inventory
WHERE port_name = '青岛港' AND ore_type = 'PB粉'
ORDER BY stat_date DESC
LIMIT 1
4.4 阶段 3:sql_guard 服务端护栏
sql_db_query 在到达 MySQL 之前,会被 sql_guard 拦截校验(structured_query_pack/sql_guard.py):
- sqlglot 解析为 AST,只允许
SELECT/UNION; - 表白名单:SQL 中出现的表名必须在
SQL_ALLOWED_TABLES内; - 禁止 DML/DDL 关键字;
- 自动 LIMIT:若 SQL 无
LIMIT,追加LIMIT {SQL_MAX_ROWS}(默认 100)。
校验失败时,工具返回 Error: ... 字符串(不抛异常),LLM 可读错误信息改写 SQL 重试。
4.5 阶段 4:生成最终回答
LLM 根据 sql_db_query 返回的行数据,用自然语言回答,并注明统计日、单位(万吨湿吨)、数据来源。数值必须来自 SQL 结果,不得编造。
五、工具层实现细节(structured_query_pack)
pack 目录结构:
structured-query-pack/
├── structured_query_pack/ # Python 包
│ ├── config.py # SQL_READONLY_DSN 等 env
│ ├── sql_guard.py # sqlglot 护栏
│ └── sql_toolkit.py # SQLDatabaseToolkit 封装
├── skills/
│ └── structured-query/
│ ├── SKILL.md.template
│ ├── SKILL.md # configure 渲染产物
│ └── references/
│ ├── README.md # overlay 说明(不自动加载)
│ └── iron-ore.md # mystu 领域 overlay(不自动加载)
├── configure.py
├── INSTALL.md
├── CHECKLIST.md
└── wiring/deepagent_example.py
5.1 配置加载(config.py)
| 环境变量 | 说明 |
|---|---|
SQL_READONLY_DSN |
只读 MySQL 连接串,如 mysql://user:pass@host:3306/agent |
SQL_ALLOWED_TABLES |
逗号分隔白名单,如 view_port_inventory |
SQL_MAX_ROWS |
自动 LIMIT 上限,默认 100 |
未配置 DSN 或白名单为空时,get_sql_toolkit_tools() 返回 空列表,Agent 仍可启动(优雅降级)。
5.2 SQLDatabase 构建(sql_toolkit.py)
核心函数 get_sql_toolkit_tools(model):
- 读取配置,构建
SQLDatabase(LangChain); - 使用
SQLDatabaseToolkit(db, llm=model)生成四工具; - 仅对
sql_db_query外包一层 sql_guard; - 返回工具列表供 Agent 注入。
两个重要的兼容性处理:
| 问题 | 原因 | 处理 |
|---|---|---|
| 白名单是 VIEW 却报 not found | LangChain 默认 view_support=False |
设置 view_support=True |
MySQL 报 NotImplementedError |
view_support=True 时会调 get_materialized_view_names(),MySQL 方言未实现 |
临时包装 Inspector,捕获后返回 [] |
5.3 与 mystu 的集成方式
mystu 通过 editable 依赖 pack,不重复维护工具逻辑:
# pyproject.toml
dependencies = ["structured-query-pack", ...]
[tool.uv.sources]
structured-query-pack = { path = "structured-query-pack", editable = true }
mystu/buildagent/agent/sql_toolkit.py 仅为薄转发:
from structured_query_pack.sql_toolkit import (
AGENT_SKILLS_DIR,
SKILLS_VIRTUAL_PREFIX,
get_sql_toolkit_tools,
)
六、数据层:表、视图与样例数据
示例 DDL 见 scripts/sql/port_inventory.sql:
- 底层表:
port_inventory_stat(不在 Agent 白名单内,Agent 不可直接查); - 对外视图:
view_port_inventory(在白名单内,Agent 只能查视图); - 内置两周测试数据,便于验证环比。
.env 示例:
SQL_READONLY_DSN=mysql://industry_user:密码@192.168.169.14:3306/agent
SQL_ALLOWED_TABLES=view_port_inventory
SQL_MAX_ROWS=100
建议为 Agent 使用 只读 MySQL 账号,与业务写库账号分离。
七、为什么封装成 deepagents Skill
7.1 Skill 解决什么问题
| 若只有工具、没有 Skill | 加上 Skill 之后 |
|---|---|
| LLM 可能跳过 schema 直接写 SQL | Skill 强制「先 list/schema 再 query」 |
| 工作流散落在系统提示词里,难复用 | Skill 是独立 Markdown,可拷贝到其他项目 |
| 工具描述偏技术,缺领域示例 | 模板 + overlay 分离通用流程与业务表名 |
| 升级 SQL 工具不影响工作流文档 | Skill 与 Toolkit 解耦 |
7.2 Skill 与 Cursor Skill 的区别
| deepagents Skill(本项目) | Cursor Skill(.cursor/skills/) |
|
|---|---|---|
| 运行环境 | Agent 运行时(FastAPI / LangGraph) | Cursor IDE 里的 AI 助手 |
| 加载方式 | SkillsMiddleware + /skills/ 虚拟路径 |
用户手动 attach |
| 内容 | 工作流 + allowed-tools | IDE 编程指南 |
二者名称相似,不是同一套东西。
7.3 通用模板 + 领域 overlay
设计原则:
- pack 内
SKILL.md.template:占位符{{DOMAIN_NAME}}、{{EXAMPLE_TABLE}}等; references/iron-ore.md:mystu 领域 overlay,不自动加载;拷贝到其他项目时可删或改为domain.md;- 领域示例也可通过
configure.py --preset iron-ore直接渲染进SKILL.md。
渲染命令:
# 通用项目
python structured-query-pack/configure.py
# mystu 铁矿石预设(把表名、示例 SQL 写入 SKILL.md)
python structured-query-pack/configure.py --preset iron-ore
八、Skill 文件结构说明
8.1 Frontmatter
---
name: structured-query
description: 铁矿石结构化数据智能问数:...
allowed-tools:
- sql_db_list_tables
- sql_db_schema
- sql_db_query
- sql_db_query_checker
---
name:skill 目录名,对应/skills/structured-query/;allowed-tools:SkillsMiddleware 用于约束 LLM 在本 skill 上下文内可调用的工具;- 必须与
get_sql_toolkit_tools()注入的工具名 完全一致。
8.2 正文章节
| 章节 | 作用 |
|---|---|
| 何时使用 | 帮 LLM 识别数值/聚合类问题 |
| 可用工具 | 四工具职责表 |
| 推荐工作流 | 6 步标准流程 |
| 安全与约束 | 只读、白名单、不得编造数字 |
| 示例 | 占位符渲染后的领域问句 + SQL |
| 常见错误处理 | 工具 Error 字符串 → 下一步动作 |
| 领域补充 | 可指向 references/*.md(需显式 write_file 引导才生效) |
九、如何在其他项目中使用这个 Skill
9.1 拷贝 pack
将整个 structured-query-pack/ 复制到目标仓库根目录。
9.2 安装 Python 包
pip install -e ./structured-query-pack
9.3 配置环境变量
至少配置 SQL_READONLY_DSN 与 SQL_ALLOWED_TABLES(见第五节表格)。
9.4 渲染 SKILL.md
python structured-query-pack/configure.py
9.5 接入 deepagents(最小示例)
参考 structured-query-pack/wiring/deepagent_example.py:
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend
from deepagents.backends.filesystem import FilesystemBackend
from structured_query_pack import (
AGENT_SKILLS_DIR,
SKILLS_VIRTUAL_PREFIX,
get_sql_toolkit_tools,
)
def build_backend():
return CompositeBackend(
default=StateBackend(),
routes={
SKILLS_VIRTUAL_PREFIX: FilesystemBackend(
root_dir=str(AGENT_SKILLS_DIR),
virtual_mode=True,
),
},
)
model = your_chat_model()
tools = [*your_other_tools, *get_sql_toolkit_tools(model)]
agent = create_deep_agent(
model=model,
tools=tools,
system_prompt="你的系统提示词(说明何时走结构化查数)",
backend=build_backend(),
skills=[SKILLS_VIRTUAL_PREFIX],
)
9.6 编写领域 overlay(可选)
在 skills/structured-query/references/domain.md 写表白名单业务含义、字段口径。记住:该文件不会自动加载;若希望大模型使用,须在 SKILL.md 或系统提示词中写明 read_file 路径。
9.7 验收清单
见 structured-query-pack/CHECKLIST.md,至少确认:
-
configure.py渲染后无残留{{...}}; - 启动日志有
SQLDatabaseToolkit 已加载工具; - 未配 DSN 时 Agent 仍能启动;
- 对话中能完成 list → schema → query 链路。
十、安全模型
| 层级 | 机制 |
|---|---|
| 账号 | 只读 MySQL 用户,无 WRITE 权限 |
| 连接 | 独立 SQL_READONLY_DSN,可与业务库同实例不同账号 |
| 元数据 | include_tables 限制 SQLDatabase 可见对象 |
| 执行前 | sqlglot AST:仅 SELECT、表白名单、禁 DML/DDL |
| 执行后 | 自动 LIMIT,控制返回行数 |
| Agent 沙箱 | deepagents 默认 StateBackend,内置文件工具不触达宿主机;真实数据风险在本 SQL 工具链 |
SQL 查数为只读操作,未配置 HITL 人工审批。
十一、常见问题排查
11.1 启动日志:SQLDatabase 初始化失败
日志会打印:type、message、脱敏 dsn、allowed_tables、catalog(当前账号可见的表/视图列表)及完整堆栈。
| 现象 | 常见原因 |
|---|---|
include_tables ... not found 且 catalog 无该视图 |
DSN 连错库,或视图未建 |
| catalog 有视图但仍失败 | MySQL + view_support + materialized view 兼容问题(pack 已处理) |
catalog=探测失败 |
网络、账号密码、防火墙 |
11.2 有配置但日志写「跳过 SQLDatabaseToolkit」
说明 _build_sql_database() 返回了 None,SQL 四工具未注入。按 11.1 查 ERROR 日志。
11.3 Skill 未生效
检查:
skills=["/skills/"]是否传入create_deep_agent;CompositeBackend是否把/skills/路由到AGENT_SKILLS_DIR;skills/structured-query/SKILL.md是否存在(需先运行configure.py);- frontmatter
allowed-tools与注入工具名是否一致。
11.4 iron-ore.md 写了但模型好像没用
预期行为:overlay 不会自动加载。确认是否在 SKILL.md 或系统提示词里写了「请先 read_file …/iron-ore.md」。领域内容也可通过 configure.py --preset iron-ore 直接烘焙进 SKILL.md,不依赖 overlay 文件。
11.5 工具返回 Error: 访问未授权表
LLM 生成的 SQL 使用了白名单外的表名。应引导 LLM 先 sql_db_list_tables,且只查 SQL_ALLOWED_TABLES 内的视图。
十二、扩展与演进
| 方向 | 说明 |
|---|---|
| 多表白名单 | SQL_ALLOWED_TABLES=view_a,view_b |
| 新领域项目 | 拷贝 pack → 改 configure 占位符 → 可选写 overlay |
| 非 MySQL | 需调整 DSN 转换与 sqlglot read= 方言 |
| SQL 超时 | SQL_QUERY_TIMEOUT_SEC 已读取,执行层待后续接入 |
| 独立 PyPI 包 | 当前采用「复制文件夹」分发 |
十三、相关文档与代码索引
| 资源 | 路径 |
|---|---|
| 可拷贝 pack | structured-query-pack/ |
| 接入指南 | structured-query-pack/INSTALL.md |
| mystu Agent 装配 | mystu/buildagent/agent/deepagent.py |
| 领域 overlay(不自动加载) | structured-query-pack/skills/structured-query/references/iron-ore.md |
| 样例 DDL + 测试数据 | scripts/sql/port_inventory.sql |
| 需求与 pack 设计 | docs/brainstorms/2026-06-26-structured-query-skill-pack-requirements.md |
| 实现计划 | docs/plans/2026-06-26-001-feat-structured-query-skill-pack-plan.md |
十四、小结
智能问数的本质是 「LLM 编排 + 标准 SQL Toolkit + 服务端 sqlglot 护栏 + Skill 工作流文档」:
- Toolkit 提供 SQL 能力与安全边界;
- Skill 提供可拷贝的工作流;SkillsMiddleware 只注入摘要,全文靠
read_file按需加载; references/iron-ore.md等 overlay 不会自动加载,领域信息应写入渲染后的SKILL.md,或在 SKILL/提示词中显式引导read_file;- pack 把 Toolkit + Skill 打成可复制单元,host 项目负责 CompositeBackend 与工具注入。
按 structured-query-pack/INSTALL.md 接入后,任何 deepagents 项目均可获得同构的结构化问数能力。
更多推荐


所有评论(0)