Text2SQL圣经:从0到1精通Text2Sql(Chat2Sql)的原理,以及Text2Sql开源项目的使用

本文的 原始地址 ,传送门

下面的正文,如果 出现图片 缺少, 请去原文查找:

本文的 原始地址 ,传送门

尼恩:LLM大模型学习圣经PDF的起源

在40岁老架构师 尼恩的读者交流群(50+)中,经常性的指导小伙伴们改造简历。

经过尼恩的改造之后,很多小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试机会,拿到了大厂机会。

然而,其中一个成功案例,是一个9年经验 网易的小伙伴,当时拿到了一个年薪近80W的大模型架构offer,逆涨50%,那是在去年2023年的 5月。

不到1年,小伙伴也在团队站稳了脚跟,成为了名副其实的大模型 应用 架构师。接下来,尼恩架构团队,通过 梳理一个《LLM大模型学习圣经》 帮助更多的人做LLM架构,拿到年薪100W, 这个内容体系包括下面的内容:

以上学习圣经 的 配套视频, 2025年 5月份之前发布。

什么是 Text2SQL

Text2SQL 是一种将自然语言文本转换为 SQL 查询语句的技术。

简单的来说,就是你说一句需求,Text2SQL 帮你写SQL,并且得到你想要的答案。

它旨在让用户能够使用自然语言来与数据库进行交互,而无需具备专业的 SQL 知识。

例如, “查找年龄大于 30 岁的用户” 这样的自然语言描述, 通过 Text2SQL 转换为 “SELECT * FROM users WHERE age > 30” 这样的 SQL 查询。

Text2SQL 能基本 干掉 我们 sql boy 的工作。

Text2SQL 属于 智能 BI(Business Intelligence,即 商业智能 是一种将数据转化为有价值的信息和见解,以支持企业决策和业务发展的技术和应用。

从这个角度讲 ,BI boy 的工作 ,也快没了

智能 BI 是在传统 BI 基础上,融合了 人工智能 、机器学习、自然语言处理等先进技术,从而具备更强大的数据处理、分析和洞察能力的系统。

它能够自动发现数据中的模式、趋势和关联,为企业提供更精准、深入的业务分析和预测,帮助企业管理者做出更明智的决策。

Text2SQL 关键功能
  • 数据整合与管理:、

能从多个不同的数据源,如数据库、文件系统、云服务等,采集和整合数据,并进行清洗、转换和加载,以确保数据的一致性和准确性。

  • 可视化分析

通过直观的图表、图形和仪表板等可视化方式展示数据,使用户能够快速理解数据的含义和趋势。用户还可以通过交互操作,如钻取、筛选、排序等,深入分析数据。

  • 自助式分析

赋予业务用户自行探索和分析数据的能力,无需依赖专业的技术人员。用户可以通过简单的拖拽、选择等操作,快速创建自己所需的分析报表和图表。

  • 智能洞察与预测

利用机器学习和数据挖掘算法,自动发现数据中的隐藏信息和规律,进行趋势预测和异常检测。例如,预测销售额、客户流失率等,帮助企业提前制定应对策略。

  • 自然语言处理与对话式分析

允许用户通过自然语言与智能 BI 系统进行交互,提出问题并获得即时回答。用户可以用日常语言询问关于数据的问题,系统会理解并返回相应的分析结果,使数据分析更加便捷和高效。

从关键功能我们可以得到一个可落地的场景 Text2SQL

Text2SQL 工作原理

Text2SQL 系统通常基于深度学习模型,如循环神经网络(RNN)、长短时记忆网络(LSTM)或 Transformer 等。

这些模型在大量的自然语言文本和对应的 SQL 查询语句对上进行训练,学习如何将自然语言中的语义信息映射到 SQL 的语法结构和语义上。

Text2SQL 关键技术
  • 语义理解

需要准确理解自然语言文本中的语义信息,包括实体、属性、关系等。

例如,对于 “查询上海的员工信息”,要能识别出 “上海” 是地点实体,“员工信息” 是要查询的内容,以及它们之间的关系是查询特定地点的员工信息。

  • 语法生成

根据语义理解的结果,生成符合 SQL 语法的查询语句。这涉及到将自然语言中的逻辑和意图转换为正确的 SQL 关键字、运算符和语句结构。

比如,将 “找出销售额最高的产品” 转换为 “SELECT product_name FROM sales WHERE sales_amount = (SELECT MAX (sales_amount) FROM sales)”。

Text2SQL 应用场景
  • 数据库查询

普通用户可以通过自然语言输入查询需求,方便快捷地从数据库中获取所需信息,无需编写复杂的 SQL 语句。

例如,业务人员可以直接问 “查询上个月销售业绩超过 100 万的销售人员名单”,而不必了解 SQL 的具体语法。

  • 数据可视化

与数据可视化工具结合,用户通过自然语言描述数据需求,Text2SQL 将其转换为 SQL 查询,然后将查询结果可视化展示,帮助用户更直观地理解数据。

  • 智能客服

在企业的客服系统中,客服人员可以使用自然语言通过 Text2SQL 查询数据库,快速获取客户信息、订单状态等相关数据,提高服务效率和质量。

Text2SQL 难点
  • 语义模糊性

自然语言往往存在模糊性和多义性,不同的人可能对同一句话有不同的理解,这给准确的语义理解和 SQL 转换带来困难。

例如,“查找红色和蓝色的汽车”,可能被理解为查找同时具有红色和蓝色的汽车,或者是查找红色的汽车和蓝色的汽车两类

  • 复杂查询处理

对于复杂的业务逻辑和嵌套查询,Text2SQL 需要准确解析自然语言中的多层语义和逻辑关系,并生成正确的 SQL 语句。

例如,“查询每个部门中工资高于该部门平均工资的员工信息”,这需要模型理解部门、员工、工资之间的关系,并进行相应的分组和比较操作。

  • 跨领域适应性

不同领域的数据库具有不同的表结构、字段名称和业务规则,Text2SQL 模型需要具备较强的跨领域适应性,才能在各种不同的数据库环境中准确工作。

Text2SQL 实现的 3大架构

如果我们需要自己实现一个 Text2SQL,应该考虑什么

Text2SQL的实现路径一般有如下 3大架构 方案:

  • **基于PE 工程 实操 的Text2SQL **

  • 基于SQLDatabaseChain的方法

  • 基于Agent的方法

架构一:基于PE 工程实操的实操Text2SQL

基于prompt template提示词工程实操的实操Text2SQL

NL2SQL核心在于如何把自然语言组装成Prompt,并交给LLM转化成SQL。

知乎 官网上一个标准自然语言转SQL的例子。

首先 准备一堆提示词:

接下来就直接 在 AI界面 进行 提问,让ai 编写sql:

可以对比一下OpenAI的结果。

同样的 系统提示词。

接下来就直接 在 OpenAI 界面 进行 提问,让ai 编写sql:

两者对比起来,差距好像有点明显啊, OpenAI 还是牛逼多了

NL2SQL的Prompt 提示词工程

对于 NL2SQL 来说,NL2SQL的Prompt基本上就是几个部分组成:

  • 指令(Instruction)

比如,“你是一个SQL生成专家。请参考如下的表格结构,直接输出SQL语句,不要多余的解释。”

  • 数据结构(Table Schema)

类似于语言翻译中的“词汇表”。

即需要使用的数据库表结构,由于大模型无法直接访问数据库,需要把元数据的结构组装进入Prompt,通常包括表名、列名、列的类型、列的含义、主外键信息。

  • 用户问题(Questions)

自然语言表达的问题,比如,“统计上个月的平均订单额”。

  • 参考样例(Few-shot)

这是一个可选项,当然也是提示工程的常见技巧。

即指导大模型生成本次SQL的参考样例。

  • 其他提示(Tips):

其他认为有必要的指示。比如要求生成的SQL中不允许出现的表达式,或者要求列名必须用“table.column"的形式等

架构二:基于SQLDatabase实操Text2SQL

LangChain 提供基于LLM的SQLDatabaseChain 数据库操作 链。

关于 LangChain 的系统化学习,请参考 尼恩团队的 LangChain 学习圣经。

LangChain学习圣经:从0到1,精通LLM大模型开发框架

SQLDatabaseChain 可以利用LLM的能力将自然语言表述的query转化为SQL,连接DB进行查询,并利用LLM组装润色结果,返回最终 answer。

实操第一步骤:本地使用Mysql创建表


CREATE TABLE `orders` (
  `OrderID` int NOT NULL,
  `CustomerID` int DEFAULT NULL,
  `OrderDate` datetime DEFAULT NULL,
  `OrderTime` varchar(8) DEFAULT NULL,
  PRIMARY KEY (`OrderID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


实操第2步骤:插入25条数据

INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (1, 101, '2024-01-01 10:30:00', '10:30:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (2, 102, '2024-02-15 14:45:00', '14:45:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (3, 103, '2024-03-20 09:15:00', '09:15:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (4, 104, '2024-04-05 16:20:00', '16:20:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (5, 105, '2024-05-12 11:50:00', '11:50:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (6, 106, '2024-06-25 13:30:00', '13:30:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (7, 107, '2024-07-08 15:40:00', '15:40:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (8, 108, '2024-08-18 17:10:00', '17:10:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (9, 109, '2024-09-03 08:25:00', '08:25:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (10, 110, '2024-10-10 12:00:00', '12:00:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (11, 111, '2024-11-22 14:15:00', '14:15:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (12, 112, '2024-12-07 16:30:00', '16:30:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (13, 113, '2025-01-14 09:45:00', '09:45:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (14, 114, '2025-02-28 13:00:00', '13:00:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (15, 115, '2025-03-11 15:20:00', '15:20:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (16, 116, '2025-04-23 17:40:00', '17:40:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (17, 117, '2025-05-06 10:10:00', '10:10:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (18, 118, '2025-06-19 12:30:00', '12:30:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (19, 119, '2025-07-31 14:50:00', '14:50:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (20, 120, '2025-08-13 16:05:00', '16:05:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (21, 121, '2025-09-26 08:40:00', '08:40:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (22, 122, '2025-10-09 11:15:00', '11:15:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (23, 123, '2025-11-21 13:35:00', '13:35:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (24, 124, '2025-12-04 15:55:00', '15:55:00');
INSERT INTO `test_db`.`orders` (`OrderID`, `CustomerID`, `OrderDate`, `OrderTime`) VALUES (25, 125, '2026-01-17 09:20:00', '09:20:00');

实操第3步骤:撸铁

写一个 python 代码, 调用openai接口作为llm,连接数据库, 然后 得到查询的结果

当然,这个代码也是用ai写的, 一句话就生成了。

现在 ai 已经 把 crud代码 一下写完了, crud boy 的活(牛马活儿),已经快被 ai干没了。

pip install --upgrade --quiet  langchain langchain-community langchain-openai
pip install langchain-experimental
pip install mysql-connector-python
pip install psycopg2-binary
 
from langchain.llms import OpenAI
from langchain.utilities import SQLDatabase
from langchain_experimental.sql import SQLDatabaseChain
import os

// OPENAI_API_KEY 需要填入 openai的key,魔法节点选择漂亮国,防止被封
os.environ["OPENAI_API_KEY"] = "your open ai key"

// db = SQLDatabase.from_uri("sqlite:///..../Chinook.db")
db = SQLDatabase.from_uri("mysql+mysqlconnector://root:123456@localhost:3306/test_db")

llm = OpenAI(temperature=0, verbose=True)
db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True)
db_chain.run("总共有多少订单?")

跑代码之前,通过pip 安装一些需要的python库。

调用结果如下:

G:\lession\AI2\demo\demo_16\example\text2sql01.py:13: LangChainDeprecationWarning: The method `Chain.run` was deprecated in langchain 0.1.0 and will be removed in 1.0. Use :meth:`~invoke` instead.
  db_chain.run("总共有多少订单?")


> Entering new SQLDatabaseChain chain...
总共有多少订单?
SQLQuery:SELECT COUNT(*) AS total_orders FROM orders
SQLResult: [(25,)]
Answer:25
> Finished chain.

基于SQLDatabaseChain实现的Text2SQL是最基础的实践方式。

简单,而且粗暴。

但,SQLDatabaseChain 对于逻辑复杂的查询在稳定性、可靠性、安全性方面可能无法达到预期,比如输出幻觉问题、数据安全问题。

架构三:基于Agent 智能体实操Text2SQL

LangChain的SQL Agent提供一种比Chain更灵活的与SQL数据库交互的方式。

这里又用到 LangChain ,关于 LangChain 的系统化学习,请参考 尼恩团队的 LangChain 学习圣经。

LangChain学习圣经:从0到1,精通LLM大模型开发框架

LangChain的SQL Agent介绍

LangChain 的SQL Agent主要有以下优点:

  • 可以根据Database Schema和数据库的内容(如描述特定的表)回答问题
  • 可以通过运行生成的查询、捕获回溯信息并正确地重新生成,以此来纠错
  • 可以根据需要多次查询数据库以回答用户问题
  • 仅检索相关表格的schema节省token

使用create_sql_agent构造器来初始化SQL Agent,Agent使用的SQLDatabaseToolkit包含用于执行以下操作的工具:

  • 创建和执行查询
  • 检查查询语法
  • 检索表格的描述
  • ……等等

实操第一步骤:创建数据库文件 chinook 库

来自 https://github.com/lerocha/chinook-database/blob/master/ChinookDatabase/DataSources/Chinook_MySql.sql

直接把文件内容拷贝到navicate执行,可以得到chinook库和对应的表格

通过navicate逆向数据库到模型可以得到 表结构和关系,这个关系可以作为后续的Promot内容基础

使用如下代码

from langchain.llms import OpenAI
from langchain.utilities import SQLDatabase
from langchain_experimental.sql import SQLDatabaseChain
import os

os.environ["OPENAI_API_KEY"] = "sk-proj-your  open ai key"
//连接本地数据库
db = SQLDatabase.from_uri("mysql+mysqlconnector://root:123456@localhost:3306/chinook")
// 打印数据库类型和表格
print(db.dialect)
print(db.get_usable_table_names())
db.run("SELECT * FROM Artist LIMIT 10;")

运行结果如下:

mysql
['album', 'artist', 'customer', 'employee', 'genre', 'invoice', 'invoiceline', 'mediatype', 'playlist', 'playlisttrack', 'track']

实操第2步骤:使用 agent_executor 实现Text2SQL

我们将使用OpenAI聊天模型和"openai-tools" agent,该 agent 将使用OpenAI的 function-calling API来驱动agent 的工具选择和调用。

代码如下:

agent_executor = create_sql_agent(llm, db=db, agent_type="openai-tools", verbose=True)

正如我们所看到的,agent 将首先选择哪些表是相关的,然后将这些表的schema和一些示例行添加到prompt中。


from langchain.utilities import SQLDatabase
from langchain_community.agent_toolkits import create_sql_agent
from langchain_openai import ChatOpenAI
import os

os.environ["OPENAI_API_KEY"] = "sk-proj-your open ai key"
db = SQLDatabase.from_uri("mysql+mysqlconnector://root:123456@localhost:3306/chinook")

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
agent_executor = create_sql_agent(llm, db=db, agent_type="openai-tools", verbose=True)

agent_executor.invoke(
    "List the total sales per country. Which country's customers spent the most?"
)

运行结果:

)G:\lession\AI2\demo\demo_16\example\text2sql03.py:1: LangChainDeprecationWarning: Importing SQLDatabase from langchain.utilities is deprecated. Please replace deprecated imports:

>> from langchain.utilities import SQLDatabase

with new imports of:

>> from langchain_community.utilities import SQLDatabase
You can use the langchain cli to **automatically** upgrade many imports. Please see documentation here <https://python.langchain.com/docs/versions/v0_2/>
  from langchain.utilities import SQLDatabase


> Entering new SQL Agent Executor chain...

Invoking: `sql_db_list_tables` with `{}`


album, artist, customer, employee, genre, invoice, invoiceline, mediatype, playlist, playlisttrack, track


Invoking: `sql_db_schema` with `{'table_names': 'customer, invoice, invoiceline'}`



CREATE TABLE customer (
	`CustomerId` INTEGER NOT NULL, 
	`FirstName` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 
	`LastName` VARCHAR(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 
	`Company` VARCHAR(80) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Address` VARCHAR(70) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`City` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`State` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Country` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`PostalCode` VARCHAR(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Phone` VARCHAR(24) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Fax` VARCHAR(24) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Email` VARCHAR(60) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 
	`SupportRepId` INTEGER, 
	PRIMARY KEY (`CustomerId`), 
	CONSTRAINT `FK_CustomerSupportRepId` FOREIGN KEY(`SupportRepId`) REFERENCES employee (`EmployeeId`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_0900_ai_ci

/*
3 rows from customer table:
CustomerId	FirstName	LastName	Company	Address	City	State	Country	PostalCode	Phone	Fax	Email	SupportRepId
1	Luís	Gonçalves	Embraer - Empresa Brasileira de Aeronáutica S.A.	Av. Brigadeiro Faria Lima, 2170	São José dos Campos	SP	Brazil	12227-000	+55 (12) 3923-5555	+55 (12) 3923-5566	luisg@embraer.com.br	3
2	Leonie	Köhler	None	Theodor-Heuss-Straße 34	Stuttgart	None	Germany	70174	+49 0711 2842222	None	leonekohler@surfeu.de	5
3	François	Tremblay	None	1498 rue Bélanger	Montréal	QC	Canada	H2G 1A7	+1 (514) 721-4711	None	ftremblay@gmail.com	3
*/


CREATE TABLE invoice (
	`InvoiceId` INTEGER NOT NULL, 
	`CustomerId` INTEGER NOT NULL, 
	`InvoiceDate` DATETIME NOT NULL, 
	`BillingAddress` VARCHAR(70) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`BillingCity` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`BillingState` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`BillingCountry` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`BillingPostalCode` VARCHAR(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Total` DECIMAL(10, 2) NOT NULL, 
	PRIMARY KEY (`InvoiceId`), 
	CONSTRAINT `FK_InvoiceCustomerId` FOREIGN KEY(`CustomerId`) REFERENCES customer (`CustomerId`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_0900_ai_ci

/*
3 rows from invoice table:
InvoiceId	CustomerId	InvoiceDate	BillingAddress	BillingCity	BillingState	BillingCountry	BillingPostalCode	Total
1	2	2021-01-01 00:00:00	Theodor-Heuss-Straße 34	Stuttgart	None	Germany	70174	1.98
2	4	2021-01-02 00:00:00	Ullevålsveien 14	Oslo	None	Norway	0171	3.96
3	8	2021-01-03 00:00:00	Grétrystraat 63	Brussels	None	Belgium	1000	5.94
*/


CREATE TABLE invoiceline (
	`InvoiceLineId` INTEGER NOT NULL, 
	`InvoiceId` INTEGER NOT NULL, 
	`TrackId` INTEGER NOT NULL, 
	`UnitPrice` DECIMAL(10, 2) NOT NULL, 
	`Quantity` INTEGER NOT NULL, 
	PRIMARY KEY (`InvoiceLineId`), 
	CONSTRAINT `FK_InvoiceLineInvoiceId` FOREIGN KEY(`InvoiceId`) REFERENCES invoice (`InvoiceId`), 
	CONSTRAINT `FK_InvoiceLineTrackId` FOREIGN KEY(`TrackId`) REFERENCES track (`TrackId`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_0900_ai_ci

/*
3 rows from invoiceline table:
InvoiceLineId	InvoiceId	TrackId	UnitPrice	Quantity
1	1	2	0.99	1
2	1	4	0.99	1
3	2	6	0.99	1
*/


Invoking: `sql_db_query` with `{'query': 'SELECT c.Country, SUM(i.Total) AS TotalSales\nFROM customer c\nJOIN invoice i ON c.CustomerId = i.CustomerId\nGROUP BY c.Country\nORDER BY TotalSales DESC'}`


[('USA', Decimal('523.06')), ('Canada', Decimal('303.96')), ('France', Decimal('195.10')), ('Brazil', Decimal('190.10')), ('Germany', Decimal('156.48')), ('United Kingdom', Decimal('112.86')), ('Czech Republic', Decimal('90.24')), ('Portugal', Decimal('77.24')), ('India', Decimal('75.26')), ('Chile', Decimal('46.62')), ('Hungary', Decimal('45.62')), ('Ireland', Decimal('45.62')), ('Austria', Decimal('42.62')), ('Finland', Decimal('41.62')), ('Netherlands', Decimal('40.62')), ('Norway', Decimal('39.62')), ('Sweden', Decimal('38.62')), ('Belgium', Decimal('37.62')), ('Denmark', Decimal('37.62')), ('Italy', Decimal('37.62')), ('Poland', Decimal('37.62')), ('Spain', Decimal('37.62')), ('Australia', Decimal('37.62')), ('Argentina', Decimal('37.62'))]The total sales per country are as follows:
1. USA: $523.06
2. Canada: $303.96
3. France: $195.10
4. Brazil: $190.10
5. Germany: $156.48

The country whose customers spent the most is the USA with a total sales amount of $523.06.

> Finished chain.

这里打印了 agent_executor 智能体的 思考过程。

思考过程,包括 推理出了可以执行的mysql,执行mysql,并得出了最后的结论

从这里,我们得到了可执行的mysql,以及问题的答案,那么接下来应该思考的是性能的优化,那么,优化谁?

又该优化什么地方?

这里我们需要优化agent的性能, 需要使用 few-shot 能力

实操第3步骤:RAG:使用动态few-shot prompt

为了优化 agent 性能,我们可以提供具有特定领域知识的自定义prompt。

在这种情况下,我们将使用example selector 创建few shot prompt,该example selector根据用户输入动态构建few shot prompt。

通过在 prompt 中插入相关的query作为参考,可以帮助模型进行更好的查询。

个人的理解,可以认为是属于提示词类型的RAG

from langchain.utilities import SQLDatabase
from langchain_community.agent_toolkits import create_sql_agent
from langchain_openai import ChatOpenAI
import os
// 我们需要一些用户输入SQL查询示例:
examples = [
    {"input": "List all artists.", "query": "SELECT * FROM Artist;"},
    {
        "input": "Find all albums for the artist 'AC/DC'.",
        "query": "SELECT * FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'AC/DC');",
    },
    {
        "input": "List all tracks in the 'Rock' genre.",
        "query": "SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');",
    },
    {
        "input": "Find the total duration of all tracks.",
        "query": "SELECT SUM(Milliseconds) FROM Track;",
    },
    {
        "input": "List all customers from Canada.",
        "query": "SELECT * FROM Customer WHERE Country = 'Canada';",
    },
    {
        "input": "How many tracks are there in the album with ID 5?",
        "query": "SELECT COUNT(*) FROM Track WHERE AlbumId = 5;",
    },
    {
        "input": "Find the total number of invoices.",
        "query": "SELECT COUNT(*) FROM Invoice;",
    },
    {
        "input": "List all tracks that are longer than 5 minutes.",
        "query": "SELECT * FROM Track WHERE Milliseconds > 300000;",
    },
    {
        "input": "Who are the top 5 customers by total purchase?",
        "query": "SELECT CustomerId, SUM(Total) AS TotalPurchase FROM Invoice GROUP BY CustomerId ORDER BY TotalPurchase DESC LIMIT 5;",
    },
    {
        "input": "Which albums are from the year 2000?",
        "query": "SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';",
    },
    {
        "input": "How many employees are there",
        "query": 'SELECT COUNT(*) FROM "Employee"',
    },
]


os.environ["OPENAI_API_KEY"] = "sk-your open ai key"
db = SQLDatabase.from_uri("mysql+mysqlconnector://root:123456@localhost:3306/chinook")

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

from langchain_community.vectorstores import FAISS
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings

// 我们可以创建example selector。这里将采用实际的用户输入,并选择一些示例添加到few-shot prompt中。使用SemanticSimilarityExampleSelector基于配置的embedding和向量存储执行语义搜索,以找到与输入最相似的示例:
example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples,
    OpenAIEmbeddings(),
    FAISS,
    k=5,
    input_keys=["input"],
)

// 创建FewShotPromptTemplate,它的参数包括example selector、用于格式化每个示例的example prompt,以及放在格式化示例前后的字符串前缀和后缀
from langchain_core.prompts import (
    ChatPromptTemplate,
    FewShotPromptTemplate,
    MessagesPlaceholder,
    PromptTemplate,
    SystemMessagePromptTemplate,
)

// Prompt的内容如下,由系统指令、few-shot示例和用户query组成:
system_prefix = """You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.
Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most {top_k} results.
You can order the results by a relevant column to return the most interesting examples in the database.
Never query for all the columns from a specific table, only ask for the relevant columns given the question.
You have access to tools for interacting with the database.
Only use the given tools. Only use the information returned by the tools to construct your final answer.
You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.

DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.

If the question does not seem related to the database, just return "I don't know" as the answer.

Here are some examples of user inputs and their corresponding SQL queries:"""

few_shot_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=PromptTemplate.from_template(
        "User input: {input}\nSQL query: {query}"
    ),
    input_variables=["input", "dialect", "top_k"],
    prefix=system_prefix,
    suffix="",
)
// 底层代理是OpenAI tools agent,它使用OpenAI function calling,因此完整的prompt应该是带有human message template 和 agent_scratchpad MessagesPlaceholder的 chat prompt。few-shot prompt将用于system message
full_prompt = ChatPromptTemplate.from_messages(
    [
        SystemMessagePromptTemplate(prompt=few_shot_prompt),
        ("human", "{input}"),
        MessagesPlaceholder("agent_scratchpad"),
    ]
)

prompt_val = full_prompt.invoke(
    {
        "input": "How many arists are there",
        "top_k": 5,
        "dialect": "SQLite",
        "agent_scratchpad": [],
    }
)



// print(prompt_val.to_string())
agent = create_sql_agent(
    llm=llm,
    db=db,
    prompt=full_prompt,
    verbose=True,
    agent_type="openai-tools",
)

agent.invoke({"input": "How many artists are there?"})

运行结果

G:\lession\AI2\demo\demo_16\example\text2sql03.py:1: LangChainDeprecationWarning: Importing SQLDatabase from langchain.utilities is deprecated. Please replace deprecated imports:

>> from langchain.utilities import SQLDatabase

with new imports of:

>> from langchain_community.utilities import SQLDatabase
You can use the langchain cli to **automatically** upgrade many imports. Please see documentation here <https://python.langchain.com/docs/versions/v0_2/>
  from langchain.utilities import SQLDatabase


> Entering new SQL Agent Executor chain...

Invoking: `sql_db_list_tables` with `{}`


album, artist, customer, employee, genre, invoice, invoiceline, mediatype, playlist, playlisttrack, track
Invoking: `sql_db_schema` with `{'table_names': 'customer, invoice, invoiceline'}`



CREATE TABLE customer (
	`CustomerId` INTEGER NOT NULL, 
	`FirstName` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 
	`LastName` VARCHAR(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 
	`Company` VARCHAR(80) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Address` VARCHAR(70) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`City` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`State` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Country` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`PostalCode` VARCHAR(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Phone` VARCHAR(24) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Fax` VARCHAR(24) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Email` VARCHAR(60) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, 
	`SupportRepId` INTEGER, 
	PRIMARY KEY (`CustomerId`), 
	CONSTRAINT `FK_CustomerSupportRepId` FOREIGN KEY(`SupportRepId`) REFERENCES employee (`EmployeeId`)
)COLLATE utf8mb4_0900_ai_ci ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

/*
3 rows from customer table:
CustomerId	FirstName	LastName	Company	Address	City	State	Country	PostalCode	Phone	Fax	Email	SupportRepId
1	Luís	Gonçalves	Embraer - Empresa Brasileira de Aeronáutica S.A.	Av. Brigadeiro Faria Lima, 2170	São José dos Campos	SP	Brazil	12227-000	+55 (12) 3923-5555	+55 (12) 3923-5566	luisg@embraer.com.br	3
2	Leonie	Köhler	None	Theodor-Heuss-Straße 34	Stuttgart	None	Germany	70174	+49 0711 2842222	None	leonekohler@surfeu.de	5
3	François	Tremblay	None	1498 rue Bélanger	Montréal	QC	Canada	H2G 1A7	+1 (514) 721-4711	None	ftremblay@gmail.com	3
*/


CREATE TABLE invoice (
	`InvoiceId` INTEGER NOT NULL, 
	`CustomerId` INTEGER NOT NULL, 
	`InvoiceDate` DATETIME NOT NULL, 
	`BillingAddress` VARCHAR(70) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`BillingCity` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`BillingState` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`BillingCountry` VARCHAR(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`BillingPostalCode` VARCHAR(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci, 
	`Total` DECIMAL(10, 2) NOT NULL, 
	PRIMARY KEY (`InvoiceId`), 
	CONSTRAINT `FK_InvoiceCustomerId` FOREIGN KEY(`CustomerId`) REFERENCES customer (`CustomerId`)
)COLLATE utf8mb4_0900_ai_ci ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

/*
3 rows from invoice table:
InvoiceId	CustomerId	InvoiceDate	BillingAddress	BillingCity	BillingState	BillingCountry	BillingPostalCode	Total
1	2	2021-01-01 00:00:00	Theodor-Heuss-Straße 34	Stuttgart	None	Germany	70174	1.98
2	4	2021-01-02 00:00:00	Ullevålsveien 14	Oslo	None	Norway	0171	3.96
3	8	2021-01-03 00:00:00	Grétrystraat 63	Brussels	None	Belgium	1000	5.94
*/


CREATE TABLE invoiceline (
	`InvoiceLineId` INTEGER NOT NULL, 
	`InvoiceId` INTEGER NOT NULL, 
	`TrackId` INTEGER NOT NULL, 
	`UnitPrice` DECIMAL(10, 2) NOT NULL, 
	`Quantity` INTEGER NOT NULL, 
	PRIMARY KEY (`InvoiceLineId`), 
	CONSTRAINT `FK_InvoiceLineInvoiceId` FOREIGN KEY(`InvoiceId`) REFERENCES invoice (`InvoiceId`), 
	CONSTRAINT `FK_InvoiceLineTrackId` FOREIGN KEY(`TrackId`) REFERENCES track (`TrackId`)
)COLLATE utf8mb4_0900_ai_ci ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

/*
3 rows from invoiceline table:
InvoiceLineId	InvoiceId	TrackId	UnitPrice	Quantity
1	1	2	0.99	1
2	1	4	0.99	1
3	2	6	0.99	1
*/
Invoking: `sql_db_query` with `{'query': 'SELECT c.Country, SUM(i.Total) AS TotalSales\nFROM customer c\nJOIN invoice i ON c.CustomerId = i.CustomerId\nGROUP BY c.Country\nORDER BY TotalSales DESC'}`


[('USA', Decimal('523.06')), ('Canada', Decimal('303.96')), ('France', Decimal('195.10')), ('Brazil', Decimal('190.10')), ('Germany', Decimal('156.48')), ('United Kingdom', Decimal('112.86')), ('Czech Republic', Decimal('90.24')), ('Portugal', Decimal('77.24')), ('India', Decimal('75.26')), ('Chile', Decimal('46.62')), ('Hungary', Decimal('45.62')), ('Ireland', Decimal('45.62')), ('Austria', Decimal('42.62')), ('Finland', Decimal('41.62')), ('Netherlands', Decimal('40.62')), ('Norway', Decimal('39.62')), ('Sweden', Decimal('38.62')), ('Belgium', Decimal('37.62')), ('Denmark', Decimal('37.62')), ('Italy', Decimal('37.62')), ('Poland', Decimal('37.62')), ('Spain', Decimal('37.62')), ('Australia', Decimal('37.62')), ('Argentina', Decimal('37.62'))]The total sales per country are as follows:
1. USA: $523.06
2. Canada: $303.96
3. France: $195.10
4. Brazil: $190.10
5. Germany: $156.48

The country whose customers spent the most is the USA with a total sales amount of $523.06.

> Finished chain.


通过RGA 处理高维列 、高维查询?

什么是高维列?

在数据处理和分析中,“高维列” 是一个相对的概念,通常是指数据集中具有较多列(特征)的情况。

当数据集的列数较多,比如几十列、上百列甚至更多时,就可以将其称为具有高维列

这里的 “高维” 是相对于一些常见的、列数较少的简单数据集而言的。

例如

  • 低维列:一个只包含姓名、年龄、性别三列的数据集是低维的

  • 高维列:而一个包含了用户的各种行为数据、属性数据等几十列的数据集则可被认为是高维列数据集。

对于当前的例子来说, 为了过滤包含专有名词(如地址、歌曲名称或艺术家)的列,我们首先需要仔细检查拼写以便正确过滤数据。

什么是高维查询?

其实就是 类似语义搜索,把 关键词转换成 的高维向量, 用 向量的相似距离计算 去查询。

撸代码:通过RGA 处理高维列 、高维查询

如何通过RGA 处理高维列 、高维查询?

  • 用数据库中所有不同的专用名词创建一个向量存储
  • 每当用户在问题中包含专有名词时让agent查询向量存储,以找到该词的正确拼写。

通过这种方式,agent可以确保在构建目标查询之前了解用户所指的实体。

// 先用cpu版本 的  faiss 向量库,简单点

pip install faiss-cpu

代码如下

from langchain.utilities import SQLDatabase
from langchain_community.agent_toolkits import create_sql_agent
from langchain_openai import ChatOpenAI
import os
// 我们需要一些用户输入SQL查询示例:
examples = [
    {"input": "List all artists.", "query": "SELECT * FROM Artist;"},
    {
        "input": "Find all albums for the artist 'AC/DC'.",
        "query": "SELECT * FROM Album WHERE ArtistId = (SELECT ArtistId FROM Artist WHERE Name = 'AC/DC');",
    },
    {
        "input": "List all tracks in the 'Rock' genre.",
        "query": "SELECT * FROM Track WHERE GenreId = (SELECT GenreId FROM Genre WHERE Name = 'Rock');",
    },
    {
        "input": "Find the total duration of all tracks.",
        "query": "SELECT SUM(Milliseconds) FROM Track;",
    },
    {
        "input": "List all customers from Canada.",
        "query": "SELECT * FROM Customer WHERE Country = 'Canada';",
    },
    {
        "input": "How many tracks are there in the album with ID 5?",
        "query": "SELECT COUNT(*) FROM Track WHERE AlbumId = 5;",
    },
    {
        "input": "Find the total number of invoices.",
        "query": "SELECT COUNT(*) FROM Invoice;",
    },
    {
        "input": "List all tracks that are longer than 5 minutes.",
        "query": "SELECT * FROM Track WHERE Milliseconds > 300000;",
    },
    {
        "input": "Who are the top 5 customers by total purchase?",
        "query": "SELECT CustomerId, SUM(Total) AS TotalPurchase FROM Invoice GROUP BY CustomerId ORDER BY TotalPurchase DESC LIMIT 5;",
    },
    {
        "input": "Which albums are from the year 2000?",
        "query": "SELECT * FROM Album WHERE strftime('%Y', ReleaseDate) = '2000';",
    },
    {
        "input": "How many employees are there",
        "query": 'SELECT COUNT(*) FROM "Employee"',
    },
]


os.environ["OPENAI_API_KEY"] = "sk-your open ai key"
db = SQLDatabase.from_uri("mysql+mysqlconnector://root:123456@localhost:3306/chinook")

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

// 需要每个实体的唯一值,为此我们定义一个函数将结果解析为元素列表
import ast
import re

// 定义数据库查询结果处理函数
def query_as_list(db, query):
    res = db.run(query)
    res = [el for sub in ast.literal_eval(res) for el in sub if el]
    res = [re.sub(r"\b\d+\b", "", string).strip() for string in res]
    return list(set(res))

artists = query_as_list(db, "SELECT Name FROM Artist")
albums = query_as_list(db, "SELECT Title FROM Album")
albums[:5]

from langchain_community.vectorstores import FAISS
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings

//我们可以创建example selector。
//这里将采用实际的用户输入,并选择一些示例添加到few-shot prompt中。
//使用SemanticSimilarityExampleSelector基于配置的embedding和向量存储执行语义搜索,以找到与输入最相似的示例:


// ==================== 语义相似性示例选择器配置 ====================
// 创建基于语义的示例选择器,用于动态选择与用户问题最相关的few-shot示例
example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples,  // 预定义的示例数据集,格式应为[{"input": "问题1", "query": "SQL1"}, ...]
    OpenAIEmbeddings(),  // 使用OpenAI的文本嵌入模型计算语义相似度
    FAISS,  // 采用Meta的FAISS向量数据库进行高效相似性搜索
    k=5,  // 每次选择与用户输入最相似的前5个示例
    input_keys=["input"],  // 指定使用示例中的input字段进行相似度匹配
)        
        

//   导入提示模板组件
from langchain_core.prompts import (
    ChatPromptTemplate,
    FewShotPromptTemplate,
    MessagesPlaceholder,
    PromptTemplate,
    SystemMessagePromptTemplate,
)
        
//后面  创建FewShotPromptTemplate,它的参数包括example selector、用于格式化每个示例的example prompt,以及放在格式化示例前后的字符串前缀和后缀      

// Prompt的内容如下,由系统指令、few-shot示例和用户query组成:
        
system_prefix = """You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.
Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most {top_k} results.
You can order the results by a relevant column to return the most interesting examples in the database.
Never query for all the columns from a specific table, only ask for the relevant columns given the question.
You have access to tools for interacting with the database.
Only use the given tools. Only use the information returned by the tools to construct your final answer.
You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.

DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.

If the question does not seem related to the database, just return "I don't know" as the answer.

Here are some examples of user inputs and their corresponding SQL queries:"""

system = """You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.
Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most {top_k} results.
You can order the results by a relevant column to return the most interesting examples in the database.
Never query for all the columns from a specific table, only ask for the relevant columns given the question.
You have access to tools for interacting with the database.
Only use the given tools. Only use the information returned by the tools to construct your final answer.
You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.

DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.

If you need to filter on a proper noun, you must ALWAYS first look up the filter value using the "search_proper_nouns" tool! 

You have access to the following tables: {table_names}

If the question does not seem related to the database, just return "I don't know" as the answer."""
// 构建few-shot提示模板

few_shot_prompt = FewShotPromptTemplate(
    example_selector=example_selector,  // 连接示例选择器
    example_prompt=PromptTemplate.from_template(
        "User input: {input}\nSQL query: {query}"  // 单个示例的展示格式
    ),
    input_variables=["input", "dialect", "top_k"],  // 动态变量:用户输入、数据库方言、结果数量限制
    prefix=system_prefix,  // 系统级指令前缀,包含基本行为准则
    suffix="",  // 后缀留空,保持提示结构简洁
)    
    
    
// ==================== 完整对话提示整合 ====================
    
// 底层代理是OpenAI tools agent,
// 它使用OpenAI function calling
// 因此完整的prompt应该是带有human message template 和 agent_scratchpad MessagesPlaceholder的 chat prompt。
//  few-shot prompt将用于system message

// 构建符合OpenAI工具代理要求的对话提示模板
full_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate(prompt=few_shot_prompt),  // 将few-shot提示包装为系统消息
    ("human", "{input}"),  // 用户原始输入占位符
    MessagesPlaceholder("agent_scratchpad"),  // 保留代理思考过程的暂存空间
])
    
    

// 测试提示模板生成效果
prompt_val = full_prompt.invoke({
    "input": "How many arists are there",  // 包含拼写错误的测试问题(artists拼错为arists)
    "top_k": 5,  // 结果数量上限
    "dialect": "SQLite",  // 指定数据库方言
    "agent_scratchpad": [],  // 初始化时思考记录为空
})    
    


// ==================== 自定义实体检索工具 ====================
// 创建自定义检索器工具和最终agent:
from langchain.agents.agent_toolkits import create_retriever_tool

// 创建实体验证检索器
vector_db = FAISS.from_texts(
    artists + albums,  // 合并艺术家和专辑名称作为检索范围
    OpenAIEmbeddings()  // 使用相同嵌入模型保证语义一致性
)
retriever = vector_db.as_retriever(
    search_kwargs={"k": 5}  // 返回最相似的5个候选实体
)
    
    
// 工具描述(关键功能说明)
retriever_description = """
实体验证工具:用于确认用户提到的专有名词是否存在于数据库中
输入:可能包含拼写错误的实体名称(如"alis chain")
输出:最接近的有效实体列表(如匹配到"Alice In Chains")
使用场景:在生成SQL查询前验证过滤条件中的实体有效性
"""

// 将检索器封装为Agent可用工具
retriever_tool = create_retriever_tool(
    retriever,
    name="search_proper_nouns",  // 工具名称(需与代理提示中的工具调用匹配)
    description=retriever_description  // 自然语言描述,供LLM理解工具用途
)    

// ==================== 最终代理创建 ====================
// 配置完整的对话提示模板(集成系统指令和工具调用)
final_prompt = ChatPromptTemplate.from_messages([
    ("system", system_prefix),  // 系统级行为准则
    ("human", "{input}"),  // 用户问题输入
    MessagesPlaceholder("agent_scratchpad")  // 工具调用记录暂存区
])

// 创建支持工具调用的SQL代理
sql_agent = create_sql_agent(
    llm=llm,  // 预初始化的ChatGPT-3.5模型
    db=db,  // SQLDatabase数据库连接对象
    extra_tools=[retriever_tool],  // 注入实体验证工具
    prompt=final_prompt,  // 集成了few-shot示例的提示模板
    agent_type="openai-tools",  // 使用OpenAI函数调用格式
    verbose=True,  // 启用详细日志输出(显示思考过程)
)
    
// ==================== 代理执行示例 ====================
// 典型错误查询场景测试(包含拼写错误alis和错误分词)
agent_response = sql_agent.invoke({
    "input": "How many albums does alis in chain have?"  // 正确应为"Alice In Chains"
}) 
    
 

运行结果:

G:\lession\AI2\demo\demo_16\example\text2sql05.py:1: LangChainDeprecationWarning: Importing SQLDatabase from langchain.utilities is deprecated. Please replace deprecated imports:

>> from langchain.utilities import SQLDatabase

with new imports of:

>> from langchain_community.utilities import SQLDatabase
You can use the langchain cli to **automatically** upgrade many imports. Please see documentation here <https://python.langchain.com/docs/versions/v0_2/>
  from langchain.utilities import SQLDatabase


> Entering new SQL Agent Executor chain...

Invoking: `search_proper_nouns` with `{'query': 'alis in chain'}`


Alice In Chains

Aisha Duo

Xis

Da Lama Ao Caos

A-Sides

Invoking: `sql_db_query` with `{'query': "SELECT COUNT(*) AS album_count FROM album WHERE artistid = (SELECT artistid FROM artist WHERE name = 'Alice In Chains')"}`


[(1,)]Alice In Chains has 1 album.


看起来结果不错,但是真的就那么简单么?

query_as_list 函数介绍:

执行数据库查询并处理结果,返回去重后的清理数据列表

def query_as_list(db, query):
    """执行数据库查询并处理结果,返回去重后的清理数据列表
    
    参数:
        db: 数据库连接对象,需具有run()方法执行查询
        query: 要执行的SQL查询语句
    
    返回:
        list: 去重后的非空字符串列表,已移除纯数字内容
    
    处理流程:
        1. 执行查询获取原始结果
        2. 将字符串结果转换为二维列表结构
        3. 扁平化处理并过滤空值
        4. 清理字符串中的数字内容
        5. 去重后返回最终结果
    """
    
    // 执行数据库查询,获取原始结果(通常为字符串格式)
    // 假设返回格式为字符串表示的二维列表,例如:"[[值1, 值2], [值3], ...]"
    res = db.run(query)
    
    // 将字符串解析为Python列表结构(安全解析)
    // 示例转换:"[[A, 1], [B], []]" → [['A', 1], ['B'], []]
    // 使用嵌套列表推导式展开为扁平列表,同时过滤空元素
    // 转换后示例:['A', 1, 'B']
    res = [el for sub in ast.literal_eval(res) for el in sub if el]
    
    // 清理每个字符串:
    // 1. 使用正则替换移除纯数字(\b表示单词边界,匹配单独的数字)
    // 2. 移除清理后可能产生的空字符串并去除两端空白
    // 示例转换:'A1' → 'A', '123' → '', ' B ' → 'B'
    res = [re.sub(r"\b\d+\b", "", string).strip() for string in res]
    
    // 使用集合去重后转回列表(会丢失原始顺序)
    // 示例最终结果:['A', 'B']
    return list(set(res))

Text2SQL 难点与挑战

挑战1:当前AI模型输出SQL的准确性

当前AI模型输出SQL的准确性还远无法达到人类工程师的输出精度。

挑战2:自然语言表达本身的歧义性

自然语言表达本身的歧义性,而SQL是一种精确编程语言。

因此在实际应用中,可能会出现无法理解,或者错误理解的情况。

比如,“谁是这个月最厉害的销售”,那么AI是理解成订单数量最多,还是订单金额最大?

挑战3:缺乏外部行业知识导致错误

尽管可以通过Prompt输入数据结构信息帮助AI模型来理解,但有时候AI可能会由于缺乏外部行业知识导致错误。

比如,“分析去年的整体客户流失率?”,如果AI缺乏对“客户流失率”的理解,自然就会出错或者编造。

NL2SQL的方案在企业应用中还会面临两个严重的挑战:

1、可能会出现正常运行的“假象”

即正常完成了任务,但实际结果是错误的。
由于NL2SQL是直接输出用于数据库访问的语句,理论上只要不存在基本的语法错误,就可以执行成功,即使转换的SQL在语义上是错误的!

NL2SQL输出语义准确性衡量的复杂性本质上来自于这样一个事实:判断AI输出的一段代码是否正确,要比判断一个选择题答案是否正确,或者一段字符串的相似度要复杂的多。

评估NL2SQL模型输出正确性的复杂所在:你既不能用输出SQL的执行结果来判断,也不能简单的把输出SQL与标准答案对比来判断。


简单来说,就是你平时自己写的代码,看起来能跑,但是实际上和需求不一致

2、 企业应用的特点会加大错误输出的概率

数据量更大
语义复杂,容易理解错误

Text2SQL 未来的优化方向

在传统的text2SQL的提示中通过注入一些相似的样例,利用LLM的上下文学习能力,以提高输出SQL的精度

  • Clear Prompting

通过对Prompt提供更清晰的层次,并只嵌入必要的数据结构信息,来优化提示。

一个重要的思想是,在构建Prompt之前,先通过大语言模型来分析本次输入最可能相关的数据实体(table)及其列(column)信息,即仅召回本次最可能用到的table和column,然后组装到Prompt,而不是把整个数据库的结构全部组装进入。

  • Claibration Bias Prompting

通过在上下文信息中嵌入一些偏差提示,可以简单的理解为指示大模型在一些场景下需要遵循的一些规则或者“注意点”。

  • Consistent Output:

这是一种输出处理方式,也是解决大模型输出不确定性的一种方案。

可以解释为:让大模型输出多次SQL,然后根据输出的SQL执行结果进行“投票

比如让LLM输出四次SQL,其中三个的执行结果都是一致的,另一个结果不一致,那么就认为这三次的输出是正确的,类似Agent的做法。

目前可选的text2sql开源框架

市面上开原框架比较多,但是看起来鱼龙混杂,要么文档烂,要么社区支持更新的慢,

这里 推荐 vanna

https://github.com/vanna-ai/vanna?tab=readme-ov-file

目前14.4k star,数量比较大,readme写的比较有高级感,原理图示清晰,介绍视频一目了然

vanna可以结合多种关系型数据库,非关系型数据库、向量数据库,支持多种多模型和前端

vanna工作示意图也是一目了然

整个流程,和上边我们手撸的感觉是不是比较类似

Vanna 通过两个简单的步骤工作 —— 在数据上训练一个 RAG “模型”,然后提出问题,这些问题将返回 SQL 查询,这些查询可以设置为在您的数据库上自动运行。

如果不知道RAG是什么,也不需要担心 。

只需要有这样的思路:“训练”了一个模型,该模型存储了一些元数据,然后使用它来“问”问题。

剩下的细节vanna都屏蔽掉了。

vanna编程接口

这里看到可以使用 jupyter notebook,那么也是挺轻量级了,存在 vanna-streamlit 就可以容易搭建web ui

vanna-flask 方便做后端应用开发,可以打包成微服务。。。

vanna支持的大模型

官网给出支持的大模型比较丰富

居然连Zhipu也支持。。。 https://zhipuai.cn/

vanna支持的数据库

相当丰富,关系型非关系型大数据都支持了

vanna安装

pip install vanna


vanna简单的例子

对于数据来说,我们仍旧使用 chinook

对于vanna来说,我们需要创建一个账号(其实这一步你甚至也可以跳过,但是申请了之后可以得到一个api key,有备无患)


搞AI,魔法和google账号是必需品,可以直接sign in,注册就不说了,该填啥就填啥

保存好这个key,因为只会显示一次,如果实在忘记了,就只才能重新创建一个

可以先来一个简单例子


https://vanna.ai/docs/mysql-openai-standard-chromadb/

点点点就可以step by step的进行操作,最后得到一个可执行的代码,具体代码含义请参考 注释

from vanna.openai import OpenAI_Chat
from vanna.chromadb import ChromaDB_VectorStore
import os

class MyVanna(ChromaDB_VectorStore, OpenAI_Chat):
    def __init__(self, config=None):
        ChromaDB_VectorStore.__init__(self, config=config)
        OpenAI_Chat.__init__(self, config=config)

os.environ["OPENAI_API_KEY"] = "sk-proj-your open ai key"
vn = MyVanna(config={'api_key': 'sk-proj-your open ai key', 'model': 'gpt-3.5-turbo'})
// 连接本地数据库
vn.connect_to_mysql(host='localhost', dbname='chinook', user='root', password='123456', port=3306)


// 把数据库的 FROM INFORMATION_SCHEMA.COLUMNS 告诉vanna
df_information_schema = vn.run_sql("SELECT * FROM INFORMATION_SCHEMA.COLUMNS")

// 这将把信息模式分解成便于处理的小部分,这些小部分可供大语言模型(LLM)引用。
plan = vn.get_training_plan_generic(df_information_schema)
plan

// 告诉vn这些作为训练内容,这里的粗浅理解为RAG,train得越多,结果一般就约接近你的要求
vn.train(plan=plan)

// The following are methods for adding training data. Make sure you modify the examples to match your database.

// 可以使用sql得到数据,再给到vanna
vn.train(ddl="""
    SELECT * FROM customer
""")

// 描述数据库的业务逻辑, 这里的提示词可以通过上边数据库翻转模型的描述,将中文描述扔到豆包里边,让豆包帮你生成提示词
vn.train(documentation="The Chinook database is a sample database of great significance as an alternative, which can serve as a substitute for the NorthWind database. It is mainly applied in scenarios related to representing digital media stores. This database encompasses a wealth of content, including 11 tables. These tables record relevant data such as artists, albums, media tracks, invoices, and customers. Based on this information, think deeply about the application and value of the Chinook database in the field of digital media stores.")



// 获取数据
training_data = vn.get_training_data()
training_data

// You can remove training data if there's obsolete/incorrect information.
vn.remove_training_data(id='1-ddl')

// 使用 flask  得到一个网页
from vanna.flask import VannaFlaskApp
app = VannaFlaskApp(vn)
app.run()

总结一下 text2SQL

所需要的能力与技术和RAG,Promot这块关系比较紧密

用到的核心是 zero-shot ,属于零样本学习,零样本学习旨在让模型在没有见过特定任务的训练样本的情况下,能够完成对该任务的学习和推理。

也就是说,模型需要利用已有的知识和经验,来理解和处理从未遇到过的新情况或新类别,它也面临着一些挑战,

比如对新类别信息的理解和表示可能不够准确,容易受到数据偏差和噪声的影响,以及在复杂任务中性能可能不如有监督学习等。

遇到问题,找老架构师取经

借助此文,尼恩给解密了一个高薪的 秘诀,大家可以 放手一试。保证 屡试不爽,涨薪 100%-200%。

后面,尼恩java面试宝典回录成视频, 给大家打造一套进大厂的塔尖视频。

通过这个问题的深度回答,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。

很多小伙伴刷完后, 吊打面试官, 大厂横着走。

在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。

遇到职业难题,找老架构取经, 可以省去太多的折腾,省去太多的弯路。

尼恩指导了大量的小伙伴上岸,前段时间,刚指导一个40岁+被裁小伙伴,拿到了一个年薪100W的offer。

狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。

<think>好的,我现在需要帮助用户了解或实现得物签名的相关技术,特别是签名算法的实现方式。首先,我需要收集已有的信息。根据用户提供的引用,特别是引用[4]和[5],得物的签名算法可能涉及逆向工程分析,使用了JavaScript中的XMLHttpRequest重写来拦截请求,并在请求中包含特定的sign参数。这可能意味着签名生成是在客户端进行的,通常在前端JavaScript代码中完成。 接下来,我应该考虑常见的签名生成方法。常见的Web应用签名算法通常使用HMAC、AES、RSA或自定义的哈希组合。引用[1]提到了RSA算法,但得物的签名可能更倾向于使用HMAC-SHA256之类的哈希算法,结合时间戳、随机数和请求参数进行签名,以防止篡改和重放攻击。 用户可能需要具体的步骤,比如逆向分析得物的JavaScript代码,找到生成sign的函数,提取关键参数和加密逻辑。引用[5]中的代码片段展示了如何重写XMLHttpRequest的open方法来拦截请求,这可能用于调试和定位签名生成的代码位置。 此外,引用[4]提到使用Python进行算法还原,所以可能需要在Python中复现JavaScript的签名生成逻辑。这通常涉及分析请求参数如何排序、拼接,以及如何与密钥结合进行哈希或加密。需要注意反爬机制,如obfuscation(代码混淆)或动态密钥,这可能需要使用AST(抽象语法树)解析或动态执行来还原算法。 用户可能还需要了解如何应对反调试措施,比如在Chrome开发者工具中使用断点调试,或者使用工具如Frida进行Hook。同时,引用[3]和[4]强调了法律和合规问题,确保研究仅用于学习目的,不用于非法用途。 最后,应该提醒用户注意法律风险,遵循网站的使用条款,避免侵犯知识产权或违反计算机安全法律。总结起来,实现得物签名需要逆向工程分析前端代码,提取关键算法,处理可能的混淆和反调试,最后用目标语言如Python复现算法。</think>### 得物签名算法实现方式分析 得物签名算法主要用于接口请求的身份验证和防篡改保护,常见实现方式涉及以下技术点: #### 1. **签名参数组成** 签名通常由以下参数动态生成: - 请求参数(经排序、过滤空值后拼接) - 时间戳(如:$timestamp=1620000000$) - 随机数(如:$nonce=5a8s3d$) - 设备指纹(如:$device\_id=abcdef$) - 应用密钥(加密盐值,可能动态获取)[^4] 示例参数拼接逻辑: $$ \text{sign\_str} = \text{path} + \text{sorted\_params} + \text{timestamp} + \text{nonce} $$ #### 2. **加密算法类型** 根据逆向分析,得物可能采用以下组合: - **HMAC-SHA256**:对拼接字符串进行哈希运算 - **AES/Base64编码**:对结果二次处理 - **自定义位移/异或操作**:增加逆向难度[^5] #### 3. **JavaScript代码混淆** 关键函数可能被混淆,例如: ```javascript function _0x12ab5(a, b) { return a ^ b << 3; } // 需要AST解析还原控制流 ``` #### 4. **Python算法还原示例** ```python import hmac import hashlib def generate_sign(params, secret_key): # 1. 参数排序并拼接 sorted_str = '&'.join([f"{k}={v}" for k,v in sorted(params.items())]) # 2. HMAC-SHA256加密 sign = hmac.new(secret_key.encode(), sorted_str.encode(), hashlib.sha256).hexdigest() # 3. 自定义处理(示例) return sign.upper() + str(int(time.time())) ``` #### 5. **反爬对抗措施** - 动态密钥:通过接口定期更新加密盐值 - 环境检测:验证是否在真机环境运行 - 请求频率限制:异常高频触发验证码[^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值