使用 smolagents 构建 Text-to-SQL 代码智能体:从单表查询到多表联表
【免费下载链接】smolagents🤗 smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents
导读
本文基于 smolagents 官方示例教程(对应仓库文档 docs/source/ko/examples/text_to_sql.md,并附可运行参考实现 examples/text_to_sql.py),带你用 smolagents 从零实现一个"以代码为思考方式"的 SQL 智能体:它能用自然语言提问、自动生成 SQL、执行查询、并根据执行结果反复自我修正。读完本文,你将掌握三条核心技能:用@tool装饰器封装自定义 SQL 工具、用CodeAgent+InferenceClientModel驱动推理循环、以及通过升级工具描述与模型来应对多表 JOIN 等复杂查询。
为什么不用传统 Text-to-SQL 流水线?
标准的 text-to-SQL 流水线(一条自然语言 → 一条 SQL 的直出映射)在实际使用中往往不够稳定:模型可能生成错误 SQL,更糟的是它可能"没有报错地"返回一个错误或无用的结果——因为流水线没有任何机制去检查输出是否合理。
而基于智能体(agent)的架构天然解决了这个问题:智能体可以批判性地检查执行结果,并自主决定是否需要重写查询、换一种写法再试一次。这正是 smolagents 的价值所在——它把"生成 → 执行 → 检查 → 修正"的闭环交给模型自己驱动,显著提升查询成功率。
环境准备与依赖安装
首先安装必要的依赖包:
!pip install smolagents python-dotenv sqlalchemy --upgrade -qsmolagents:本文主角,极简的代码思考式智能体库;python-dotenv:从.env文件加载环境变量;sqlalchemy:用于定义表结构、执行 SQL 查询(本文使用其 SQLite 内存引擎)。
调用推理服务需要有效的 Hugging Face 令牌,通过环境变量HF_TOKEN提供。用python-dotenv加载:
from dotenv import load_dotenv load_dotenv()从源码看,InferenceClientModel在未显式传入token时,会自动回退读取HF_TOKEN环境变量(见 src/smolagents/models.py),因此上面的加载步骤是让智能体"有模型可用"的前提。
用 SQLAlchemy 搭建内存版 SQL 环境
教程使用 SQLite 内存数据库(sqlite:///:memory:),这样无需外部数据库服务即可演示完整流程。先导入 SQLAlchemy 相关组件并创建引擎与元数据对象:
from sqlalchemy import ( create_engine, MetaData, Table, Column, String, Integer, Float, insert, inspect, text, ) engine = create_engine("sqlite:///:memory:") metadata_obj = MetaData() def insert_rows_into_table(rows, table, engine=engine): for row in rows: stmt = insert(table).values(**row) with engine.begin() as connection: connection.execute(stmt) table_name = "receipts" receipts = Table( table_name, metadata_obj, Column("receipt_id", Integer, primary_key=True), Column("customer_name", String(16), primary_key=True), Column("price", Float), Column("tip", Float), ) metadata_obj.create_all(engine) rows = [ {"receipt_id": 1, "customer_name": "Alan Payne", "price": 12.06, "tip": 1.20}, {"receipt_id": 2, "customer_name": "Alex Mason", "price": 23.86, "tip": 0.24}, {"receipt_id": 3, "customer_name": "Woodrow Wilson", "price": 53.43, "tip": 5.43}, {"receipt_id": 4, "customer_name": "Margaret James", "price": 21.11, "tip": 1.00}, ] insert_rows_into_table(rows, receipts)这里定义了一张名为receipts(小票)的表,包含四列:receipt_id(小票 ID,整数主键)、customer_name(顾客名,最长 16 字符的字符串主键)、price(消费金额)、tip(小费金额),并插入了 4 条样本数据。
核心技巧:把表结构"喂"给智能体
智能体的 LLM 并不了解你的表结构,所以必须把表的 schema 显式描述出来。教程通过 SQLAlchemy 的inspect接口自动提取列信息并格式化:
inspector = inspect(engine) columns_info = [(col["name"], col["type"]) for col in inspector.get_columns("receipts")] table_description = "Columns:\n" + "\n".join([f" - {name}: {col_type}" for name, col_type in columns_info]) print(table_description)输出如下:
Columns: - receipt_id: INTEGER - customer_name: VARCHAR(16) - price: FLOAT - tip: FLOAT这段自动生成的描述随后会被写进工具的 docstring,成为 LLM 提示词的一部分——这是整个 Text-to-SQL 智能体"知道表长什么样"的关键。
创建自定义工具:sql_engine
smolagents 中工具(Tool)是智能体与外部世界交互的接口。创建一个工具需要满足两个条件(详见 docs/source/ko/tutorials/tools.md):
- docstring 中包含参数列表(
Args:部分)与工具功能描述; - 输入和输出都有类型提示(type hints)。
使用@tool装饰器将普通函数转换为工具:
from smolagents import tool @tool def sql_engine(query: str) -> str: """ 테이블에 SQL 쿼리를 수행할 수 있습니다. 결과를 문자열로 반환합니다. 테이블 이름은 'receipts'이며, 설명은 다음과 같습니다: Columns: - receipt_id: INTEGER - customer_name: VARCHAR(16) - price: FLOAT - tip: FLOAT Args: query: 수행할 쿼리입니다. 올바른 SQL이어야 합니다. """ output = "" with engine.connect() as con: rows = con.execute(text(query)) for row in rows: output += "\n" + str(row) return output工具的行为要点:
- 函数体通过
engine.connect()建立连接,执行传入的 SQL(text(query)),把每一行结果追加到字符串中返回; - 返回类型是
str——这意味着智能体拿到的是查询结果的字符串表示,它会"读懂"这个字符串,判断结果是否符合预期; - 工具的
description属性会被系统注入到 LLM 的提示词中(这正是@tool底层实现所保证的:装饰器会解析函数的 docstring 与类型标注,动态构造Tool子类,见 src/smolagents/tools.py),LLM 据此学会"何时调用、如何调用"这个工具。
底层原理:@tool 做了什么?
从源码看,tool装饰器(src/smolagents/tools.py)会:
- 通过
get_json_schema解析函数签名,生成 JSON Schema,从中提取工具名、描述、输入参数与返回类型; - 动态创建一个
SimpleTool子类,把name、description、inputs、output_type等属性一一赋值; - 把原函数包装为
forward方法。
这意味着:工具的 docstring 质量直接决定 LLM 对工具的理解程度——这正是教程反复强调"在描述里写明表结构"的原因。
组装 CodeAgent 并跑通第一个查询
smolagents 的主智能体类是CodeAgent:它以代码的形式书写动作(而不是 JSON 工具调用),并遵循 ReAct 框架(行动-观察循环)反复迭代、根据之前的输出结果自我改进。模型(model)则是驱动整个智能体的 LLM。
使用InferenceClientModel可以经由 Hugging Face 的 Inference API 以 serverless(无服务器)或 Dedicated Endpoint 方式调用 LLM,也可以替换为其他私有 API:
from smolagents import CodeAgent, InferenceClientModel agent = CodeAgent( tools=[sql_engine], model=InferenceClientModel(model_id="meta-llama/Llama-3.1-8B-Instruct"), ) agent.run("Can you give me the name of the client who got the most expensive receipt?")agent.run(...)收到自然语言问题后,会循环执行:让 LLM 生成一段 Python 代码 → 代码调用sql_engine工具 → 观察执行结果 → 判断是否已得到答案 → 未完成则继续生成下一步代码,直到得出最终答案。
InferenceClientModel 关键参数
从源码文档字符串(src/smolagents/models.py)可以确认,InferenceClientModel支持以下常用参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
model_id | "Qwen/Qwen3-Next-80B-A3B-Thinking" | 使用的 Hugging Face 模型 ID,也可以是已部署 Inference Endpoint 的 URL |
provider | "auto" | 推理服务商名称(如hyperbolic、together等),设为 auto 时按用户设置自动选择;传入base_url时该参数失效 |
token | 无 | HF API 认证令牌;未传入时回退到HF_TOKEN环境变量或 HF CLI 配置 |
timeout | 120 | API 请求超时时间(秒) |
api_key | None | token的别名(与 OpenAI 客户端风格对齐),两者不能同时传入 |
bill_to | None | 计费账号,需为当前用户所属且已订阅 Enterprise Hub 的组织 |
base_url | None | 自定义推理服务的基础 URL |
例如,显式指定服务商与令牌的写法:
model = InferenceClientModel( model_id="Qwen/Qwen3-Next-80B-A3B-Thinking", provider="hyperbolic", token="your_hf_token_here", max_tokens=5000, )升级挑战:多表 JOIN 与动态工具描述
单表查询已经跑通,现在让智能体面对更复杂的问题——跨多张表的连接(JOIN)查询。
添加第二张表 waiters
为每个receipt_id记录对应的服务员姓名,创建waiters表:
table_name = "waiters" waiters = Table( table_name, metadata_obj, Column("receipt_id", Integer, primary_key=True), Column("waiter_name", String(16), primary_key=True), ) metadata_obj.create_all(engine) rows = [ {"receipt_id": 1, "waiter_name": "Corey Johnson"}, {"receipt_id": 2, "waiter_name": "Michael Watts"}, {"receipt_id": 3, "waiter_name": "Michael Watts"}, {"receipt_id": 4, "waiter_name": "Margaret James"}, ] insert_rows_into_table(rows, waiters)关键操作:更新工具的 description
表结构变了,工具的说明就必须跟着变,否则 LLM 依然以为只有一张receipts表。教程演示了如何用inspect动态生成多表描述,并直接赋值给工具:
updated_description = """Allows you to perform SQL queries on the table. Beware that this tool's output is a string representation of the execution output. It can use the following tables:""" inspector = inspect(engine) for table in ["receipts", "waiters"]: columns_info = [(col["name"], col["type"]) for col in inspector.get_columns(table)] table_description = f"Table '{table}':\n" table_description += "Columns:\n" + "\n".join([f" - {name}: {col_type}" for name, col_type in columns_info]) updated_description += "\n\n" + table_description print(updated_description) sql_engine.description = updated_description这里有两个值得注意的工程细节:
- 动态生成:用
inspect(engine)遍历所有表自动拼接描述,避免手写硬编码,表多了也易于维护; - 直接改属性:
sql_engine.description = updated_description覆盖默认描述——因为@tool生成的SimpleTool实例中description是普通类属性,运行时修改即刻生效。
换用更强的模型
新任务("哪位服务员收到的小费总额最高")需要 JOIN 与聚合推理,教程选择切换到更强大的Qwen/Qwen3-Next-80B-A3B-Thinking模型:
agent = CodeAgent( tools=[sql_engine], model=InferenceClientModel(model_id="Qwen/Qwen3-Next-80B-A3B-Thinking"), ) agent.run("Which waiter got more total money from tips?")执行后智能体应当能自动写出形如SELECT w.waiter_name, SUM(r.tip) FROM receipts r JOIN waiters w ON r.receipt_id = w.receipt_id GROUP BY w.waiter_name ...的 JOIN 查询,并基于返回结果给出答案。整个升级过程无需改动任何工具逻辑,只改了描述与模型——这正是智能体方案的灵活性所在。
配套可运行示例
仓库中提供了与教程对应的完整可运行脚本 examples/text_to_sql.py,其内容与本教程的单表查询部分一致(注意其中模型 ID 写作meta-llama/Meta-Llama-3.1-8B-Instruct,两种写法在 HF Hub 上指向同一模型,按实际可用 ID 填写即可)。可以直接在本地运行验证:
pip install smolagents python-dotenv sqlalchemy export HF_TOKEN=hf_xxx python examples/text_to_sql.py总结与进阶方向
通过本教程,你掌握了 smolagents 中 Text-to-SQL 智能体的完整构建链路:
- 创建新工具:用
@tool装饰器 + docstring + 类型标注封装sql_engine,把 SQL 执行能力暴露给智能体; - 更新工具描述:表结构变化时,用
inspect动态重写description,保持 LLM 对数据库认知的时效性; - 升级模型增强推理:面对 JOIN 等复杂任务,切换到更强 LLM(如
Qwen/Qwen3-Next-80B-A3B-Thinking)即可显著提升成功率。
更深一层看,这套模式可以直接迁移到真实生产场景:把 SQLite 内存引擎替换为 PostgreSQL/MySQL 连接串,把表描述生成逻辑接入真实的 schema 元数据,再配合 docs/source/ko/conceptual_guides/react.md 中介绍的 ReAct 循环原理、以及 docs/source/ko/tutorials/tools.md 中更丰富的工具编写技巧,即可构建属于你自己的企业级自然语言查询系统。
【免费下载链接】smolagents🤗 smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考