news 2026/9/19 23:17:53

使用 smolagents 构建 Text-to-SQL 代码智能体:从单表查询到多表联表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 smolagents 构建 Text-to-SQL 代码智能体:从单表查询到多表联表

使用 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 -q
  • smolagents:本文主角,极简的代码思考式智能体库;
  • 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):

  1. docstring 中包含参数列表(Args:部分)与工具功能描述;
  2. 输入和输出都有类型提示(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子类,把namedescriptioninputsoutput_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"推理服务商名称(如hyperbolictogether等),设为 auto 时按用户设置自动选择;传入base_url时该参数失效
tokenHF API 认证令牌;未传入时回退到HF_TOKEN环境变量或 HF CLI 配置
timeout120API 请求超时时间(秒)
api_keyNonetoken的别名(与 OpenAI 客户端风格对齐),两者不能同时传入
bill_toNone计费账号,需为当前用户所属且已订阅 Enterprise Hub 的组织
base_urlNone自定义推理服务的基础 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

这里有两个值得注意的工程细节:

  1. 动态生成:用inspect(engine)遍历所有表自动拼接描述,避免手写硬编码,表多了也易于维护;
  2. 直接改属性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 智能体的完整构建链路:

  1. 创建新工具:用@tool装饰器 + docstring + 类型标注封装sql_engine,把 SQL 执行能力暴露给智能体;
  2. 更新工具描述:表结构变化时,用inspect动态重写description,保持 LLM 对数据库认知的时效性;
  3. 升级模型增强推理:面对 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 23:17:37

包更新失败与相关性冲突验证的排查思路全解析

包更新失败、相关性或冲突验证,到底在验证什么?这套排查思路能帮你省下半天时间做开发这些年,几乎每个项目都会遇到同一个让人头疼的场景:高高兴兴执行一条更新命令,结果屏幕上弹出一串依赖错误、相关性验证失败、包冲…

作者头像 李华
网站建设 2026/9/19 23:17:14

BiliBiliToolPro 批量取关配置全解:从部署到定时调度

BiliBiliToolPro 批量取关配置全解:从部署到定时调度 【免费下载链接】BiliBiliToolPro B 站(bilibili)自动任务工具,支持docker、青龙、k8s等多种部署方式。全面拥抱AI。敏感肌也能用。 项目地址: https://gitcode.com/GitHub_…

作者头像 李华
网站建设 2026/9/19 23:17:07

Node.js卸载不干净怎么办?全平台深度清理指南

卸载Node.js这件事,按理说不难,但你真去搜一圈,会发现几乎全是“删这里、删那里”的零散回答,照着操作下来经常遗漏关键路径,尤其是Windows环境下,注册表、符号链接、用户目录下的全局安装包,哪…

作者头像 李华
网站建设 2026/9/19 23:16:14

DualSpeechLM: Towards Unified Speech Understanding and Generation via Dual Speech Token Modeling ...

DualSpeechLM论文总结与关键部分翻译 一、文章主要内容 本文聚焦于解决现有语音大语言模型(Speech LLMs)在统一语音理解与生成任务中面临的两大核心挑战:一是语音令牌与文本令牌间存在巨大模态鸿沟,需大规模配对数据进行微调;二是理解任务依赖高层语义信息,生成任务依赖…

作者头像 李华
网站建设 2026/9/19 23:14:00

六种跨平台桌面方案对比:Electron、Tauri、Neutralino等选型实战指南

1. 这不是“换框架”的故事,是桌面应用交付逻辑的彻底重写 你有没有试过双击一个桌面软件安装包,然后盯着进度条等三分钟?或者打开任务管理器,发现那个标着“XX工具”的进程,内存占用比 Chrome 开五个标签页还高&…

作者头像 李华
网站建设 2026/9/19 23:11:06

BrewUI:Homebrew图形化管理,让macOS包管理告别命令行

1. BrewUI 是什么,它到底解决了什么问题先交代一下背景。用过 macOS 做开发的朋友,几乎绕不开 Homebrew 这个包管理器。装个 nginx、redis、ffmpeg,或者管理 Node.js、Python 的小版本,基本都是brew install一把梭。但 Homebrew 好…

作者头像 李华