构建智能 SQL 查询代理agent,把整个查询过程模块化,既能自动判断使用哪些表,又能自动生成 SQL 语句,最终返回查询结果

示例代码:

import os
import getpass
from dotenv import load_dotenv
from pyprojroot import here
from typing import List
from pprint import pprint
from pydantic import BaseModel
from langchain_core.tools import tool
from langchain_core.runnables import RunnablePassthrough
from langchain.chains import create_sql_query_chain
from operator import itemgetter
from langchain.chains.openai_tools import create_extraction_chain_pydantic
from langchain.chat_models import init_chat_model
from langchain_core.prompts import PromptTemplate
from langchain_community.utilities import SQLDatabase

# 定义用于提取表类别的 Pydantic 模型
class Table(BaseModel):
    name: str

# 定义一个映射函数,将类别名称转换为具体的 SQL 表名列表
def get_tables(categories: List[Table]) -> List[str]:
    """根据类别名称映射到对应的 SQL 表名列表."""
    tables = []
    for category in categories:
        if category.name == "Music":
            tables.extend([
                "Album",
                "Artist",
                "Genre",
                "MediaType",
                "Playlist",
                "PlaylistTrack",
                "Track",
            ])
        elif category.name == "Business":
            tables.extend(["Customer", "Employee", "Invoice", "InvoiceLine"])
    return tables

class ChinookSQLAgent:
    """
    一个专门用于 Chinook SQL 数据库查询的 agent,
    利用 LLM 解析用户的问题,自动判断与问题相关的表类别,
    并生成相应的 SQL 查询执行。
    
    属性:
        sql_agent_llm: 用于解析问题和生成 SQL 查询的 LLM 模型。
        db: Chinook 数据库的连接对象。
        full_chain: 一个链条,将用户问题转为 SQL 查询后执行。
    
    构造方法参数:
        sqldb_directory (str): Chinook SQLite 数据库文件所在的目录路径。
        llm (str): LLM 模型名称(例如 "gpt-3.5-turbo"),但内部使用 "llama3-70b-8192"。
        llm_temperature (float): LLM 的温度参数,用于控制生成结果的随机性。
    """
    def __init__(self, sqldb_directory: str, llm: str, llm_temperature: float) -> None:
        # 初始化 LLM 模型(此处使用 "llama3-70b-8192",由 groq 提供)
        self.sql_agent_llm = init_chat_model(llm, model_provider="groq", temperature=llm_temperature)
        
        # 建立到 Chinook SQLite 数据库的连接
        self.db = SQLDatabase.from_uri(f"sqlite:///{sqldb_directory}")
        print("可用表:", self.db.get_usable_table_names())
        
        # 定义系统提示,指导 LLM 根据用户问题返回相关的表类别
        category_chain_system = (
            "Return the names of the SQL tables that are relevant to the user question. "
            "The tables are:\n\nMusic\nBusiness"
        )
        # 创建提取链,从用户问题中提取表类别(使用 Pydantic 模型 Table)
        category_chain = create_extraction_chain_pydantic(Table, self.sql_agent_llm, system_message=category_chain_system)
        # 将提取到的类别转换为具体的 SQL 表名
        table_chain = category_chain | get_tables
        
        # 定义自定义 SQL 提示模板
        custom_prompt = PromptTemplate(
            input_variables=["dialect", "input", "table_info", "top_k"],
            template=(
                "You are a SQL expert using {dialect}.\n"
                "Given the following table schema:\n"
                "{table_info}\n"
                "Generate a syntactically correct SQL query to answer the question: \"{input}\".\n"
                "Don't limit the results to {top_k} rows.\n"
                "Ensure the query uses DISTINCT to avoid duplicate rows.\n"
                "Return only the SQL query without any additional commentary or Markdown formatting."
            )
        )
        # 利用自定义提示模板创建 SQL 查询链
        query_chain = create_sql_query_chain(self.sql_agent_llm, self.db, prompt=custom_prompt)
        
        # 将输入中的 "question" 键转换为 table_chain 所需的 "input" 键
        table_chain = {"input": itemgetter("question")} | table_chain
        
        # 利用 RunnablePassthrough.assign 将提取到的 table_names 注入上下文,然后通过管道传递给 SQL 查询链
        self.full_chain = RunnablePassthrough.assign(table_names_to_use=table_chain) | query_chain

    def run(self, query: str) -> str:
        """
        接收用户的查询,将问题转化为 SQL 查询语句,然后在 Chinook 数据库中执行。
        
        参数:
            query (str): 用户的自然语言查询,例如 "What are all the genres of Alanis Morisette songs? Do not repeat!"
        
        返回:
            str: 执行 SQL 查询后的结果
        """
        # 调用完整的链条生成 SQL 查询语句
        sql_query = self.full_chain.invoke({"question": query})
        # 执行生成的 SQL 查询并返回结果
        return self.db.run(sql_query)

# 使用 @tool 装饰器将查询功能暴露为一个工具
@tool
def query_chinook_sqldb(query: str) -> str:
    """
    查询 Chinook SQL 数据库的工具函数,输入为用户的查询语句。
    
    该函数会实例化一个 ChinookSQLAgent,然后调用其 run 方法来处理查询。
    """
    # 注意:sqldb_directory 应该是数据库文件所在路径,这里假设它是全局变量或配置项
    sqldb_directory = here("data/Chinook.db")
    agent = ChinookSQLAgent(
        sqldb_directory=sqldb_directory,  # 如 TOOLS_CFG.chinook_sqldb_directory
        llm="llama3-70b-8192",              # 如 TOOLS_CFG.chinook_sqlagent_llm
        llm_temperature=0
    )
    return agent.run(query)


query_chinook_sqldb('What are all the genres of Alanis Morisette songs')

输出结果:
在这里插入图片描述
下面我来用通俗易懂的话解释这段代码的整体作用和每个部分的含义,同时举例说明输入输出情况。


1. 引入依赖和定义工具

代码最开始导入了各种模块和工具,这里主要用到的是 LangChain 相关的模块,比如:

  • Pydantic:用于定义数据模型(这里定义了一个 Table 模型,它只有一个字段 name,用来存储表类别名称)。
  • langchain_core 和 langchain.chains:提供了链(Chain)相关的操作,让我们可以将多个步骤拼接起来,比如从问题中提取信息、生成 SQL 查询等。
  • pyprojroot.here:用于定位当前项目目录下的文件路径,方便定位数据库文件。

2. 定义数据模型和映射函数

数据模型 Table

class Table(BaseModel):
    name: str
  • 作用
    定义了一个 Pydantic 模型 Table,用来描述从用户问题中提取出来的表类别。这个模型只有一个字段 name,比如可能返回 "Music""Business"

映射函数 get_tables

def get_tables(categories: List[Table]) -> List[str]:
    """根据类别名称映射到对应的 SQL 表名列表."""
    tables = []
    for category in categories:
        if category.name == "Music":
            tables.extend([
                "Album",
                "Artist",
                "Genre",
                "MediaType",
                "Playlist",
                "PlaylistTrack",
                "Track",
            ])
        elif category.name == "Business":
            tables.extend(["Customer", "Employee", "Invoice", "InvoiceLine"])
    return tables
  • 作用
    接收一个 Table 对象列表,然后根据类别名称返回实际在 Chinook 数据库中使用的表名列表。
  • 举例说明
    • 如果提取结果是 [Table(name="Music")],那么函数返回的表名列表就是
      ["Album", "Artist", "Genre", "MediaType", "Playlist", "PlaylistTrack", "Track"]
    • 如果类别是 "Business",则返回商业相关的表名列表。

3. 定义 ChinookSQLAgent 类

这个类封装了整个从自然语言问题到生成并执行 SQL 查询的流程。

3.1 初始化方法 init

def __init__(self, sqldb_directory: str, llm: str, llm_temperature: float) -> None:
    # 1. 初始化 LLM 模型
    self.sql_agent_llm = init_chat_model(llm, model_provider="groq", temperature=llm_temperature)
    
    # 2. 连接到 Chinook 数据库(SQLite)
    self.db = SQLDatabase.from_uri(f"sqlite:///{sqldb_directory}")
    print("可用表:", self.db.get_usable_table_names())
  • 作用
    • 利用 init_chat_model 初始化语言模型(例如这里传入 "llama3-70b-8192"),用来解析用户问题和生成 SQL 查询。
    • 通过 SQLDatabase.from_uri 连接到 Chinook 数据库,并打印出数据库中可用的表(例如:Album、Artist、Customer、Employee 等)。

3.2 创建提取表类别的链

# 定义系统提示,告诉 LLM 只返回 "Music" 或 "Business" 两类
category_chain_system = (
    "Return the names of the SQL tables that are relevant to the user question. "
    "The tables are:\n\nMusic\nBusiness"
)
# 创建提取链(利用 Pydantic 模型 Table),从用户问题中提取出相关的表类别
category_chain = create_extraction_chain_pydantic(Table, self.sql_agent_llm, system_message=category_chain_system)
# 将提取出的类别映射为具体的 SQL 表名
table_chain = category_chain | get_tables
  • 作用

    • 定义一个系统提示(system message),指导 LLM 只考虑 “Music” 和 “Business” 两个类别。
    • 通过 create_extraction_chain_pydantic 创建一个链,自动从用户问题中提取出一个或多个 Table 对象。
    • 利用管道操作符 | 把提取出的结果传递给 get_tables 函数,得到实际的 SQL 表名列表。
  • 举例

    • 用户问题“哪些表中存储了 Alanis Morisette 歌曲信息?”会被 LLM 分析后返回 [Table(name="Music")],进而映射为音乐相关的所有表名。

3.3 定义自定义 SQL 提示模板

custom_prompt = PromptTemplate(
    input_variables=["dialect", "input", "table_info", "top_k"],
    template=(
        "You are a SQL expert using {dialect}.\n"
        "Given the following table schema:\n"
        "{table_info}\n"
        "Generate a syntactically correct SQL query to answer the question: \"{input}\".\n"
        "Don't limit the results to {top_k} rows.\n"
        "Ensure the query uses DISTINCT to avoid duplicate rows.\n"
        "Return only the SQL query without any additional commentary or Markdown formatting."
    )
)
  • 作用
    • 定义了一个提示模板,让 LLM 生成 SQL 查询时遵循固定的格式。
    • 模板中说明:
      • 你是一个使用特定 SQL 方言({dialect})的 SQL 专家。
      • 根据给定的数据库表结构({table_info})和用户问题({input}),生成一条正确的 SQL 查询。
      • 不要限制返回行数({top_k}仅作为参考),并且必须使用 DISTINCT 去除重复行。
    • 这样就能让自然语言生成的 SQL 语句在逻辑上避免重复数据的问题,而无需后期修改生成的 SQL。

3.4 创建 SQL 查询链和组合完整链条

# 利用自定义提示模板创建 SQL 查询链
query_chain = create_sql_query_chain(self.sql_agent_llm, self.db, prompt=custom_prompt)

# 将输入中的 "question" 键转换为 table_chain 需要的 "input" 键
table_chain = {"input": itemgetter("question")} | table_chain

# 利用 RunnablePassthrough.assign 将提取到的 table_names 注入上下文,接着传递给 SQL 查询链
self.full_chain = RunnablePassthrough.assign(table_names_to_use=table_chain) | query_chain
  • 作用
    • 用刚才定义的 custom_prompt 和数据库信息,创建一个 SQL 查询链,用于将自然语言问题转换成 SQL 查询语句。
    • 为了保证输入格式一致,将用户输入中的 question 键转换为 input 键(因为之前的链条是根据 input 来工作的)。
    • 最后利用 RunnablePassthrough.assign 将前面提取到的表名列表(table_names_to_use)注入到上下文中,并与 SQL 查询链拼接起来,构成一个完整的处理链。这条链会先从问题中提取出使用哪些表,然后再生成 SQL 语句。

3.5 run 方法

def run(self, query: str) -> str:
    """
    接收用户的查询,将问题转化为 SQL 查询语句,然后在 Chinook 数据库中执行。
    """
    # 调用完整链条生成 SQL 查询语句
    sql_query = self.full_chain.invoke({"question": query})
    # 执行生成的 SQL 查询并返回结果
    return self.db.run(sql_query)
  • 作用
    • 接受用户传入的自然语言查询(例如“What are all the genres of Alanis Morisette songs”)。
    • 通过调用完整链条(self.full_chain)将该查询转成 SQL 查询语句。
    • 最后在数据库中执行该 SQL 查询,并返回结果。

4. 将 agent 以工具形式暴露

@tool
def query_chinook_sqldb(query: str) -> str:
    """
    查询 Chinook SQL 数据库的工具函数,输入为用户的查询语句。
    """
    # 定位数据库文件,使用 pyprojroot 的 here 函数查找路径
    sqldb_directory = here("data/Chinook.db")
    # 实例化一个 ChinookSQLAgent
    agent = ChinookSQLAgent(
        sqldb_directory=sqldb_directory,  
        llm="llama3-70b-8192",
        llm_temperature=0
    )
    # 调用 agent 的 run 方法执行查询,并返回结果
    return agent.run(query)
  • 作用
    • 使用 @tool 装饰器将函数暴露为一个工具(比如在其他系统中可以直接调用)。
    • 函数内部通过 here("data/Chinook.db") 定位数据库文件路径,然后创建一个 ChinookSQLAgent 实例。
    • 最后调用 agent.run 方法来处理用户的查询并返回最终的查询结果。

5. 举例说明

假设用户调用如下命令:

query_chinook_sqldb('What are all the genres of Alanis Morisette songs')

整个流程如下:

  1. 输入:用户输入问题“What are all the genres of Alanis Morisette songs”。

  2. 提取表类别

    • LLM 根据预定义的系统提示分析问题,判断这个问题涉及音乐数据,所以会提取出类别 "Music"
    • 映射函数 get_tables"Music" 映射为相关的表名列表:["Album", "Artist", "Genre", "MediaType", "Playlist", "PlaylistTrack", "Track"]
  3. 生成 SQL 查询

    • 根据数据库的 schema(表结构信息)和自定义的 SQL 提示模板,LLM 会生成一条 SQL 查询语句。
    • 提示模板中要求生成的查询语句必须使用 DISTINCT 去除重复记录,因此生成的 SQL 可能类似下面这样:
      SELECT DISTINCT Genre.Name
      FROM Track
      JOIN Genre ON Track.GenreId = Genre.GenreId
      JOIN Artist ON Track.ArtistId = Artist.ArtistId
      WHERE Artist.Name LIKE '%Alanis Morisette%'
      
      (实际生成的语句可能会根据数据库结构略有不同)
  4. 执行查询

    • 生成的 SQL 查询语句通过 self.db.run(sql_query) 在 Chinook 数据库中执行,并返回查询结果。
  5. 输出

    • 最终,用户获得查询结果,比如数据库中所有不重复的音乐流派名称。

总结

这段代码的整体流程就是:

  • 利用 LLM 根据自然语言问题判断需要查询的表类别,
  • 将类别映射成 Chinook 数据库中实际的表名,
  • 利用自定义提示模板(要求生成的查询中使用 DISTINCT 去重)生成 SQL 查询语句,
  • 执行 SQL 查询并返回结果。

这种设计把整个查询过程模块化,既能自动判断使用哪些表,又能自动生成 SQL 语句,适合构建智能 SQL 查询代理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/980198.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

fastapi中的patch请求

目录 示例测试使用 curl 访问:使用 requests 访问:预期返回: 浏览器访问 示例 下面是一个使用 app.patch("") 的 FastAPI 示例,该示例实现了一个简单的用户信息更新 API。我们使用 pydantic 定义数据模型,并…

【文献阅读】Collective Decision for Open Set Recognition

基本信息 文献名称:Collective Decision for Open Set Recognition 出版期刊:IEEE TRANSACTIONS ON KNOWLEDGE AND DATA ENGINEERING 发表日期:04 March 2020 作者:Chuanxing Geng and Songcan Chen 摘要 在开集识别&#xff0…

Hadoop之02:MR-图解

1、不是所有的MR都适合combine 1.1、map端统计出了不同班级的每个学生的年龄 如:(class1, 14)表示class1班的一个学生的年龄是14岁。 第一个map任务: class1 14 class1 15 class1 16 class2 10第二个map任务: class1 16 class2 10 class…

代码随想录Day23 | 39.组合总和、40.组合总和II、131.分割回文串

39.组合总和 自己写的代码&#xff1a; class Solution { public:vector<int> path;vector<vector<int>> res;int sum0;void backtracking(vector<int>& candidates,int target,int startIndex){if(sum>target) return;if(sumtarget){res.pus…

【MySQL】索引(页目录、B+树)

文章目录 1. 引入索引2. MySQL与磁盘交互的基本单位3. 索引的理解3.1 页目录3.2 B树 4. 聚簇索引、非聚簇索引5. 索引的操作5.1 索引的创建5.1.1 创建主键索引5.1.2 创建唯一索引5.1.3 普通索引的创建5.1.4 全文索引的创建 5.2 索引的查询5.3 删除索引 1. 引入索引 索引&#…

132. 分割回文串 II

简单分析 输入的参数是字符串s&#xff0c;返回值是最小的切割次数。那这个问题的典型解法应该是动态规划&#xff0c;因为我们需要找最优解&#xff0c;而每一步的选择可能会影响后面的结果&#xff0c;但可以通过子问题的最优解来构建整体最优解。 那么动态规划的状态如何定…

CSS定位详解

1. 相对定位 1.1 如何设置相对定位&#xff1f; 给元素设置 position:relative 即可实现相对定位。 可以使用 left 、 right 、 top 、 bottom 四个属性调整位置。 1.2 相对定位的参考点在哪里&#xff1f; 相对自己原来的位置 1.3 相对定位的特点&#xff1…

NLP11-命名实体识别(NER)概述

目录 一、序列标注任务 常见子任务 二、 命名实体识别&#xff08;NER&#xff09; &#xff08;一&#xff09;简介 &#xff08;二&#xff09;目标 &#xff08;三&#xff09;应用场景 &#xff08;四&#xff09;基本方法 &#xff08;五&#xff09;工具与资源 一…

基于SQL数据库的酒店管理系统

一、数据库设计 1&#xff0e;需求分析 客房的预定&#xff1a;可以通过网络进行预定&#xff0c;预定修改&#xff0c;取消预订。 客房管理&#xff1a;预定管理、客房查询、设置房态、开房、换房、续住、退房等管理。 员工管理: 员工修改信息、人员调配。 账务管理&…

2024年中国城市统计年鉴(PDF+excel)

2024年中国城市统计年鉴&#xff08;PDFexcel&#xff09; 说明&#xff1a;包括地级县级市 格式&#xff1a;PDFEXCEL 《中国城市统计年鉴》是一部全面反映中国城市发展状况的官方统计出版物&#xff0c;包括各级城市的详细统计数据。这部年鉴自1985年开始出版&#xff0c;…

1.C语言初识

C语言初识 C语言初识基础知识hello world数据类型变量、常量变量命名变量分类变量的使用变量的作用域 常量字符字符串转义字符 选择语句循环语句 函数&#xff1b;数组函数数组数组下标 操作符操作符算术操作符移位操作符、位操作符赋值操作符单目操作符关系操作符逻辑操作符条…

LINUX基础 - 网络基础 [一]

前言 在当今的数字化世界中&#xff0c;网络已成为计算机系统和应用的核心组成部分。Linux&#xff0c;作为一个开放源代码的操作系统&#xff0c;在服务器、嵌入式设备、以及开发环境中被广泛使用&#xff0c;而其强大的网络能力使其在网络管理和网络编程领域占据了重要地位。…

苹果廉价机型 iPhone 16e 影像系统深度解析

【人像拍摄差异】 尽管iPhone 16e支持后期焦点调整功能&#xff0c;但用户无法像iPhone 16系列那样通过点击屏幕实时切换拍摄主体。前置摄像头同样缺失人像深度控制功能&#xff0c;不过TrueTone原彩闪光灯系统在前后摄均有保留。 很多人都高估了 iPhone 的安全性&#xff0c;查…

游戏引擎学习第128天

开始 然而&#xff0c;我们仍然有一些工作要做&#xff0c;渲染部分并没有完全完成。虽然现在已经能够运行游戏&#xff0c;而且帧率已经可以接受&#xff0c;但仍然有一些东西需要进一步完善。正在使用调试构建编译版本&#xff0c;虽然调试版本的性能不如优化版本&#xff0…

几个api

几个api 原型链 可以阅读此文 Function instanceof Object // true Object instanceof Function // true Object.prototype.isPrototypeOf(Function) // true Function.prototype.isPrototypeOf(Object) // true Object.__proto__ Function.prototype // true Function.pro…

用DeepSeeker + AI app工具自动生成 APP代码

作为上海嘉冰信息技术有限公司创始人&#xff0c;我想做一个AI美食点评类APP&#xff0c;用户可以上传自己的美食图片并生成相应的AI美食点评&#xff0c;可以帮我详细描述一下这个APP&#xff0c;用于方便我的企业B端客户开拓本地生活的内容市场。 AI美食点评APP&#xff1a;开…

布署elfk-准备工作

建议申请5台机器部署elfk&#xff1a; filebeat(每台app)--> logstash(2台keepalived)--> elasticsearch(3台)--> kibana(部署es上)采集输出 处理转发 分布式存储 展示 ELK中文社区: 搜索客&#xff0c;搜索人自己的社区 官方…

利用PyQt简单的实现一个机器人的关节JOG界面

在上一篇文章中如何在Python用Plot画出一个简单的机器人模型&#xff0c;我们介绍了如何在Python中画出一个简单的机器人3D模型&#xff0c;但是有的时候我们需要通过界面去控制机器人每一个轴的转动&#xff0c;并实时的显示出当前机器人的关节位置和末端笛卡尔位姿。 那么要实…

制造业中的“大数据”:如何实现精准决策?

在当今全球经济竞争日趋激烈、技术变革周期不断缩短的环境下&#xff0c;制造业面临着全新的挑战和机遇。随着信息技术的飞速发展&#xff0c;“大数据”正以前所未有的速度渗透到制造业的各个环节&#xff0c;帮助企业实现更精准的决策、更灵活的生产组织以及更敏捷的市场响应…

【沙漠之心:揭秘尘封奇迹的终极之旅】

在地球的边缘,横亘着一片浩瀚无垠的沙漠,它既是生命的绝域,亦是奇迹孕育的秘境。这片广袤的沙漠,以其神秘莫测的面貌,自古以来便吸引着无数探险家、旅行者和梦想家的目光。它既是生命的禁区,让无数生命在这片不毛之地中消逝;同时,它也是奇迹的摇篮,孕育着无数未被发现…