[控场AI]
教程攻略· 8 分钟阅读· 4,031 字

Spring AI实战:手撸Text-to-SQL助手Agent工具链开发全解析

Spring AI实战:手撸Text-to-SQL助手Agent工具链开发全解析

基于Spring AI Alibaba Agent Framework实现自然语言转SQL助手及Tool上下文传递技巧

本文基于Spring AI Alibaba Agent Framework,详解如何通过四步Tool工具链(ListTables、GetSchema、VerifyQuery、ExecuteQuery)构建自然语言转SQL助手。重点剖析了Agent开发中Tool上下文传递的核心问题:Stream模式下ThreadLocal失效的根因及正确方案——使用RunnableConfig+ToolContext通过Metadata传递用户信息,避免跨线程数据丢失。

前言

把用户说的大白话自动转成SQL查询语句,这件事在企业级AI应用中需求量极大。本文基于Spring AI Alibaba Agent Framework,手把手拆解如何通过Tool工具链搭建一个自然语言转SQL的小助手,同时深入聊聊Agent开发中Tool上下文传递的核心技巧——这个坑踩过的人都懂。

Tool上下文传递:ThreadLocal的陷阱与正确方案

问题场景

在Agent应用中有一个非常普遍的需求:用户登录后与大模型对话,Tool被调用时需要拿到当前登录用户的信息(比如UserID、UserName)。在传统Spring AI里,我们习惯用InheritableThreadLocal来传递上下文,但在Alibaba Agent Framework中情况完全不同。

结论先行: 同步调用(call方法)中ThreadLocal能正常工作,但在流式请求(stream)中会彻底失效。

Tool上下文传递在Stream模式下的问题

为什么Stream模式下ThreadLocal会失效?

要理解这个问题,需要先了解ThreadLocal的本质:它是Java中一种线程局部变量机制,每个线程拥有独立的变量副本,天然实现线程隔离。InheritableThreadLocal是其子类,允许子线程继承父线程的变量值。在传统Servlet同步模型中,一次HTTP请求从头到尾由同一线程处理,ThreadLocal因此成为传递用户上下文的标准手段。然而在响应式/异步编程模型中,一次请求可能跨越多个线程甚至线程池,ThreadLocal的线程绑定特性反而成为陷阱——Spring WebFlux、Project Reactor等框架均有类似问题,通常通过专用Context对象来替代ThreadLocal完成跨异步边界的上下文传递。

根本原因在于线程模型的差异:

  • Call模式:大模型调用和Tool执行跑在同一个线程里,ThreadLocal自然能正常传递
  • Stream模式:大模型调用和Tool执行分属不同线程,无论ThreadLocal还是InheritableThreadLocal都无法跨线程传递数据

还有一个容易忽略的细节:千万不要在ReactAgent的@Bean配置方法中设置ThreadLocal。原因很简单——Bean初始化发生在Spring Boot启动阶段,等用户真正发起请求时早已切换到另一个线程了。

正确方案:RunnableConfig + ToolContext

Alibaba Agent Framework提供了专门的RunnableConfig机制来解决上下文传递问题,核心分两步。

第一步:构建RunnableConfig,在调用Agent时传入

RunnableConfig config = RunnableConfig.builder()
    .threadId("sessionId-xxx")  // 用于聊天记录隔离
    .addMetadata("userId", "xushu")  // 自定义元数据
    .build();
agent.call(message, config);

第二步:将Tool的Function接口改为BiFunction,接收ToolContext

BiFunction相比Function多接收一个ToolContext参数,这是Alibaba Agent Framework的扩展设计,使工具函数能感知Agent运行时上下文,而不仅仅处理输入参数——这正是解决上下文传递问题的关键接口设计。

BiFunction<InputType, ToolContext, OutputType> toolFunction = (input, toolContext) -> {
    RunnableConfig runnableConfig = (RunnableConfig) toolContext.getContext().get(AGENT_CONFIG);
    String userId = runnableConfig.getMetadata().get("userId");
    // 业务逻辑...
};

Metadata与ThreadId的使用建议

threadId的职责是聊天记录隔离(可以是会话ID或用户ID),而业务数据(用户ID、权限信息等)建议统一走addMetadata传递。这样做更灵活,也避免了与聊天记录隔离维度产生冲突。

ToolContext的隐藏能力

ToolContext不仅能获取自定义元数据,还能访问当前的聊天记录State——这是Alibaba Graph中的核心概念,包含了用户提示词、大模型响应、工具调用参数等完整的对话上下文信息。

ToolContext中State数据结构

Text-to-SQL助手:核心架构设计

Text-to-SQL技术背景

Text-to-SQL是NLP领域的经典任务,目标是将自然语言问题自动转换为结构化查询语言。早期方案依赖规则引擎和语义解析,准确率受限于规则覆盖度。深度学习时代,基于Seq2Seq和Transformer的模型(如IRNet、RATSQL)在Spider等基准数据集上取得突破。大模型时代,GPT-4等模型凭借强大的上下文理解能力,在零样本(zero-shot)条件下即可生成高质量SQL,但仍面临幻觉问题——模型可能生成语法正确但表名/列名错误的SQL。本文的四步工具链正是针对这一痛点设计:通过动态获取真实Schema和样本数据,将数据库结构信息注入模型上下文,从根本上减少幻觉发生。

多路召回的背景知识

在AI应用中,不同数据源各有所长:

检索类型最佳数据源
聚合查询(总数、平均值、最大值)关系型数据库
语义检索向量数据库
图关系查询图数据库
关键字检索Elasticsearch / Markdown+Grep

本文聚焦关系型数据库场景,通过Tool工具链将自然语言转换为SQL并执行MySQL查询。

四步Tool工具链设计

整个Text-to-SQL助手的核心是四个自定义Tool,它们组成一条严谨的ReAct迭代链路。ReAct(Reasoning + Acting)是2022年由Google提出的Agent范式,核心思想是让大模型在推理(Thought)和行动(Action)之间交替迭代,每次行动后观察(Observation)结果再决定下一步。与Chain-of-Thought(CoT)纯推理不同,ReAct允许模型调用外部工具,使其能处理需要实时信息的复杂任务。在Text-to-SQL场景中,ReAct的迭代特性尤为关键——模型不需要一次性生成完美SQL,而是通过多轮工具调用逐步收集信息、验证、修正,最终得到可执行的查询语句:

  1. ListTables Tool —— 获取数据库中所有表名
  2. GetSchema Tool —— 获取目标表的结构定义和样本数据
  3. VerifyQuery Tool —— 借助大模型检验生成的SQL语法是否合法
  4. ExecuteQuery Tool —— 执行最终的SQL查询并返回结果

Text-to-SQL助手的Tool工具链架构

实战代码解析

ReactAgent配置详解

通过系统提示词明确指导大模型按顺序调用四个Tool,并使用FunctionToolCallback方式绑定工具:

@Bean
public ReactAgent textToSqlAgent() {
    return ReactAgent.builder()
        .systemPrompt("你是一个Text-to-SQL助手,请按以下步骤执行...")
        .tools(listTablesTool, getSchemaTool, verifyQueryTool, executeQueryTool)
        .build();
}

Spring AI中Tool的注册方式主要有两种:动态绑定(运行时根据请求动态加载工具)和静态绑定(编译期确定、随Bean初始化注册)。选择FunctionToolCallback静态绑定方式的理由是:它将Java函数(Function/BiFunction)包装为大模型可调用的工具描述,包含工具名称、功能描述、输入参数的JSON Schema等元信息,大模型通过这些描述决定何时调用哪个工具。这些Tool数量固定、编译期就能确定逻辑、且是应用运行的基础组件——完全符合静态绑定的适用场景。

各Tool核心逻辑拆解

ListTables Tool: 通过JdbcTemplate执行SHOW TABLES获取所有表名,返回给大模型做推理定位。

**Get

分享:

相关推荐