什么?大模型删库跑路了?

删库跑路,一个所有程序员可能都幻想过的场景,不过想想后果还是很严重的,不过不要怕,现在我们可以利用大模型来删库,啊不,操作数据库,这就是传说中的Text2Sql。

什么是Text2Sql

Text2Sql就是将自然语言的问题转化为结构化的sql查询语句,可以让用户更直观的和数据库进行交互。其实Text2Sql不是什么特别新的东西,在大模型出来之前,也是有类似的尝试的

早期阶段主要依赖于人工编写的模板规则,用模板匹配自然语言和sql语句之间的关系,后面机器学习兴起之后,开始采用序列到序列模型这样的机器学习方法,来学习自然语言到SQL之间的映射关系。最后就是现在,能够借助大语言模型的强大语言理解和代码生成能力,再加上提示词工程、微调这样的方法,进一步提升了Text2Sql的性能。

LLM下的Text2Sql

在大语言模型的加持下,Text2Sql主要有以下几个步骤

  1. 自然语言理解:分析用户输入的自然语言问题,理解意图和语义
  2. 模式链接:将问题中的实体与数据库中的表和列进行链接
  3. SQL生成:根据语义和模式链接结果,生成对应的SQL查询语句
  4. SQL执行:在数据库上执行SQL语句,将结果返回给用户

前面几篇文章我们介绍了LangChain的使用,下面我们就用LangChain来简单实现一个Text2sql

Text2Sql的实现

SQLDatabaseTookit使用

SQLDatabaseTookit是LangChain提供的一个agent,可以轻松帮助我们实现Text2Sql

导入相关依赖

python 复制代码
from langchain_community.agent_toolkits.sql.base import create_sql_agent
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
from langchain_community.utilities import SQLDatabase
from dotenv import load_dotenv
load_dotenv('../.env')

配置数据库

python 复制代码
db_user = "user"
db_password = "pwd"
db_host = "localhost:3306"
db_name = "db_name"
db = SQLDatabase.from_uri(f"mysql+pymysql://{db_user}:{db_password}@{db_host}/{db_name}")

实例化agent

python 复制代码
api_key = os.environ.get('DASHSCOPE_API_KEY')
llm = ChatOpenAI(
    temperature=0.01,
    model="qwen-max",
    openai_api_base = "https://dashscope.aliyuncs.com/compatible-mode/v1",
    openai_api_key  = api_key
)

# 需要设置llm
toolkit = SQLDatabaseToolkit(db=db, llm=llm)

# SQL智能体:给它目标,它自己会进行规划,最终把结果给你
agent_executor = create_sql_agent(
    llm=llm,
    toolkit=toolkit,
    verbose=True
)

提问

python 复制代码
agent_executor.run("找出英雄攻击力最高的前5个英雄")

输出:

makefile 复制代码
> Entering new SQL Agent Executor chain...
Action: sql_db_list_tables
Action Input: address, asset_grades, bank, car_sales, customers, dept, employee, form, height_grades, hero_score, heros, orders, person, player, player_score, student, team, team_score, test_work, trips, user, users, using, weather从表名来看,`heros` 表可能包含了英雄的信息。我需要查看 `heros` 表的结构来确定是否有攻击力(attack)相关的字段。
Action: sql_db_schema
Action Input: heros
CREATE TABLE heros (
	id INTEGER NOT NULL AUTO_INCREMENT, 
	name VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, 
	hp_max FLOAT, 
	hp_growth FLOAT, 
	hp_start FLOAT, 
	mp_max FLOAT, 
	mp_growth FLOAT, 
	mp_start FLOAT, 
	attack_max FLOAT, 
	attack_growth FLOAT, 
	attack_start FLOAT, 
	defense_max FLOAT, 
	defense_growth FLOAT, 
	defense_start FLOAT, 
	hp_5s_max FLOAT, 
	hp_5s_growth FLOAT, 
	hp_5s_start FLOAT, 
	mp_5s_max FLOAT, 
	mp_5s_growth FLOAT, 
	mp_5s_start FLOAT, 
	attack_speed_max FLOAT, 
	attack_range VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci, 
	role_main VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci, 
	role_assist VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci, 
	birthdate DATE, 
	PRIMARY KEY (id)
)DEFAULT CHARSET=utf8mb3 ENGINE=InnoDB

/*
3 rows from heros table:
id	name	hp_max	hp_growth	hp_start	mp_max	mp_growth	mp_start	attack_max	attack_growth	attack_start	defense_max	defense_growth	defense_start	hp_5s_max	hp_5s_growth	hp_5s_start	mp_5s_max	mp_5s_growth	mp_5s_start	attack_speed_max	attack_range	role_main	role_assist	birthdate
10000	夏侯惇	7350.0	288.8	3307.0	1746.0	94.0	430.0	321.0	11.57	159.0	397.0	21.14	101.0	98.0	3.357	51.0	37.0	1.571	15.0	0.0	近战	坦克	战士	2016-07-19
10001	钟无艳	7000.0	275.0	3150.0	1760.0	95.0	430.0	318.0	11.0	164.0	409.0	22.07	100.0	92.0	3.143	48.0	37.0	1.571	15.0	0.0	近战	战士	坦克	2021-05-01
10002	钟	8341.0	329.4	3450.0	100.0	0.0	100.0	301.0	10.57	153.0	504.0	27.07	125.0	115.0	4.143	57.0	5.0	0.0	5.0	0.0	近战	坦克	辅助	2012-05-04
*/`heros` 表中确实有 `attack_max` 字段,这应该代表英雄的最大攻击力。为了找出攻击力最高的前5个英雄,我需要根据 `attack_max` 字段降序排列,并限制结果为前5条记录。同时,我还应该选择 `name` 和 `attack_max` 两个字段来展示英雄的名字和他们的最大攻击力。

Action: sql_db_query_checker
Action Input: SELECT name, attack_max FROM heros ORDER BY attack_max DESC LIMIT 5```sql
SELECT name, attack_max FROM heros ORDER BY attack_max DESC LIMIT 5
```查询语句看起来是正确的,接下来我将执行这个查询。

Action: sql_db_query
Action Input: SELECT name, attack_max FROM heros ORDER BY attack_max DESC LIMIT 5[('阿轲', 427.0), ('孙尚香', 411.0), ('百里守约', 410.0), ('虞姬', 407.0), ('黄忠', 403.0)]I now know the final answer.

Final Answer: 攻击力最高的前5个英雄及其攻击力如下:
1. 阿轲 - 427.0
2. 孙尚香 - 411.0
3. 百里守约 - 410.0
4. 虞姬 - 407.0
5. 黄忠 - 403.0

> Finished chain.

根据以上的输出,我们可以看到agent一共做了这几步操作:

  1. 查询有哪些表
sql 复制代码
Action: sql_db_list_tables
Action Input: address, asset_grades, bank, car_sales, customers, dept, employee, form, height_grades, hero_score, heros, orders, person, player, player_score, student, team, team_score, test_work, trips, user, users, using, weather
  1. 分析哪张表式需要查询的表
sql 复制代码
从表名来看,`heros` 表可能包含了英雄的信息。我需要查看 `heros` 表的结构来确定是否有攻击力(attack)相关的字段。
Action: sql_db_schema
Action Input: heros
CREATE TABLE heros (
	id INTEGER NOT NULL AUTO_INCREMENT, 
	name VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, 
	hp_max FLOAT, 
	hp_growth FLOAT, 
	hp_start FLOAT, 
	mp_max FLOAT, 
	mp_growth FLOAT, 
	mp_start FLOAT, 
	attack_max FLOAT, 
	attack_growth FLOAT, 
	attack_start FLOAT, 
	defense_max FLOAT, 
	defense_growth FLOAT, 
	defense_start FLOAT, 
	hp_5s_max FLOAT, 
	hp_5s_growth FLOAT, 
	hp_5s_start FLOAT, 
	mp_5s_max FLOAT, 
	mp_5s_growth FLOAT, 
	mp_5s_start FLOAT, 
	attack_speed_max FLOAT, 
	attack_range VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci, 
	role_main VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci, 
	role_assist VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci, 
	birthdate DATE, 
	PRIMARY KEY (id)
)DEFAULT CHARSET=utf8mb3 ENGINE=InnoDB
  1. 查看表里的数据,并分析需要用的字段
yaml 复制代码
/*
3 rows from heros table:
id	name	hp_max	hp_growth	hp_start	mp_max	mp_growth	mp_start	attack_max	attack_growth	attack_start	defense_max	defense_growth	defense_start	hp_5s_max	hp_5s_growth	hp_5s_start	mp_5s_max	mp_5s_growth	mp_5s_start	attack_speed_max	attack_range	role_main	role_assist	birthdate
10000	夏侯惇	7350.0	288.8	3307.0	1746.0	94.0	430.0	321.0	11.57	159.0	397.0	21.14	101.0	98.0	3.357	51.0	37.0	1.571	15.0	0.0	近战	坦克	战士	2016-07-19
10001	钟无艳	7000.0	275.0	3150.0	1760.0	95.0	430.0	318.0	11.0	164.0	409.0	22.07	100.0	92.0	3.143	48.0	37.0	1.571	15.0	0.0	近战	战士	坦克	2021-05-01
10002	钟	8341.0	329.4	3450.0	100.0	0.0	100.0	301.0	10.57	153.0	504.0	27.07	125.0	115.0	4.143	57.0	5.0	0.0	5.0	0.0	近战	坦克	辅助	2012-05-04
*/`heros` 表中确实有 `attack_max` 字段,这应该代表英雄的最大攻击力。为了找出攻击力最高的前5个英雄,我需要根据 `attack_max` 字段降序排列,并限制结果为前5条记录。同时,我还应该选择 `name` 和 `attack_max` 两个字段来展示英雄的名字和他们的最大攻击力。
  1. 根据提问编写sql并执行查询,最终返回结果
sql 复制代码
Action: sql_db_query_checker
Action Input: SELECT name, attack_max FROM heros ORDER BY attack_max DESC LIMIT 5```sql
SELECT name, attack_max FROM heros ORDER BY attack_max DESC LIMIT 5
```查询语句看起来是正确的,接下来我将执行这个查询。

Action: sql_db_query
Action Input: SELECT name, attack_max FROM heros ORDER BY attack_max DESC LIMIT 5[('阿轲', 427.0), ('孙尚香', 411.0), ('百里守约', 410.0), ('虞姬', 407.0), ('黄忠', 403.0)]I now know the final answer.

Final Answer: 攻击力最高的前5个英雄及其攻击力如下:
1. 阿轲 - 427.0
2. 孙尚香 - 411.0
3. 百里守约 - 410.0
4. 虞姬 - 407.0
5. 黄忠 - 403.0

如果是一个程序员从一个陌生的数据库里查询数据,基本上也就是这个流程了,虽然这个sql不是很复杂,但是可以看到agent已经具备通过自然语言来查询数据的能力了。

下面我们通过保险场景,来看一下,Text2Sql的实战能力如何

保险场景实战

数据库信息

首先来看一下我们要用的数据表

  • 客户信息表(CustomerInfo):客户ID(CustomerID)、客户姓名(Name)、性别(Gender)、出生日期(DateOfBirth)、身份证号码(IDNumber)、联系地址(Address)、联系电话(PhoneNumber)、电子邮件地址(EmailAddress)、婚姻状况(MaritalStatus)、职业(Occupation)、健康状况(HealthStatus)、客户注册日期(RegistrationDate)、客户类型(CustomerType)、客户来源(SourceOfCustomer)、客户状态(CustomerStatus)

  • 保单信息表(PolicyInfo):保单号(PolicyNumber)、客户ID(CustomerID)、保险产品ID(ProductID)、保单状态(PolicyStatus)、受益人(Beneficiary)、受益人关系(Relationship)、投保日期(PolicyStartDate)、终止日期(PolicyEndDate)、保费支付状态(PremiumPaymentStatus)、保费支付日期(PaymentDate)、保费支付方式(PaymentMethod)、代理人ID(AgentID)

  • 理赔信息表(ClaimInfo):理赔号(ClaimNumber)、保单号(PolicyNumber)、理赔日期(ClaimDate)、理赔类型(ClaimType)、理赔金额(ClaimAmount)、理赔状态(ClaimStatus)、理赔描述(ClaimDescription)、受益人ID(BeneficiaryID)、医疗记录(MedicalRecords)、事故报告(AccidentReport)、审核人(ClaimHandler)、审核日期(ReviewDate)、支付方式(PaymentMethod)、支付日期(PaymentDate)、拒赔原因(DenialReason)

  • 受益人信息表(BeneficiaryInfo):受益人ID(BeneficiaryID)、姓名(Name)、性别(Gender)、出生日期(DateOfBirth)、国籍(Nationality)、联系地址(Address)、电话号码(PhoneNumber)、电子邮件(EmailAddress)

  • 代理人信息表(AgentInfo):代理人ID(AgentID)、姓名(Name)、性别(Gender)、出生日期(DateOfBirth)、联系地址(Address)、电话号码(PhoneNumber)、电子邮件(EmailAddress)、证书号码(CertificateNumber)、执照发放日期(LicenseIssueDate)、执照到期日期(LicenseExpirationDate)、佣金结构(CommissionStructure)

  • 保险产品信息表(ProductInfo):产品ID(ProductID)、产品名称(ProductName)、产品类型(ProductType)、保险金额范围(CoverageRange)、保险期限(CoverageTerm)、保费(Premium)、缴费频率(PaymentFrequency)、产品特性(ProductFeatures)、投保年龄限制(AgeLimit)、保费计算方式(PremiumCalculation)、理赔流程(ClaimsProcess)、投保要求(UnderwritingRequirements)、销售区域(SalesRegion)、产品状态(ProductStatus)

  • 保险公司内部员工表(EmployeeInfo):员工ID(EmployeeID)、姓名(Name)、性别(Gender)、出生日期(DateOfBirth)、联系地址(Address)、电话号码(PhoneNumber)、电子邮件(EmailAddress)、入职日期(HireDate)、职位(Position)、部门(Department)、工资(Salary)、工作地点(Location)、上级主管(Supervisor)、员工类型(EmployeeType)、员工状态(EmployeeStatus)

编写agent

python 复制代码
import os
from dotenv import load_dotenv
load_dotenv('.env')
from langchain_community.agent_toolkits.sql.base import create_sql_agent
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
from langchain_community.utilities import SQLDatabase
from langchain_community.chat_models import ChatOpenAI

db_user = "user"
db_password = "pwd"
db_host = "localhost:3306"
db_name = "db_name"
db = SQLDatabase.from_uri(f"mysql+pymysql://{db_user}:{db_password}@{db_host}/{db_name}")

# 从环境变量获取 dashscope 的 API Key
api_key = os.environ.get('DASHSCOPE_API_KEY')

llm = ChatOpenAI(
    temperature=0.01,
    model="deepseek-v3",  
    openai_api_base = "https://dashscope.aliyuncs.com/compatible-mode/v1",
    openai_api_key  = api_key
)
# 需要设置llm
toolkit = SQLDatabaseToolkit(db=db, llm=llm)

agent_executor = create_sql_agent(
    llm=llm,
    toolkit=toolkit,
    verbose=True
)

要查询的信息

以下是我们的agent要完成的任务,让我们看看执行效果如何:

  1. 获取所有客户的姓名和联系电话。

    • 生成的sql:

      sql 复制代码
      SELECT Name, PhoneNumber FROM customerinfo LIMIT 10;
    • 查询结果 1. 欧颖 - 14708198484 2. 李辉 - 15182875235 3. 李璐 - 18604878643 4. 张玉 - 18266615576 5. 张云 - 15783496041 6. 周玉兰 - 18944485428 7. 徐波 - 18537535161 8. 季丹丹 - 18556735783 9. 张杰 - 13648915552 10. 张杰 - 13302970653

    • 验证

  2. 找出所有已婚客户的保单。

    • 生成的sql:
    sql 复制代码
     SELECT p.PolicyNumber, c.Name, c.MaritalStatus FROM policyinfo p JOIN customerinfo c ON CAST(p.CustomerID AS UNSIGNED) = c.CustomerID WHERE c.MaritalStatus = '已婚' LIMIT 10
    • 查询结果

      复制代码
      目前数据库中没有已婚客户的保单记录。
    • 验证

      这个结果对吗?我们去数据库里做一下验证,agent用了连表查询,我们换一种方式,用子查询来做:

      sql 复制代码
      select PolicyNumber from policyinfo where CustomerID IN (select CustomerID from customerinfo where MaritalStatus='已婚');

      可以看到,结果是对的

  3. 查询所有未支付保费的保单号和客户姓名。

    • 生成的sql:

      sql 复制代码
      SELECT p.PolicyNumber, c.Name FROM policyinfo p JOIN customerinfo c ON p.CustomerID = c.CustomerID WHERE p.PremiumPaymentStatus = '未支付' LIMIT 10

      sql看起来没有什么问题

    • 查询结果

      目前数据库中没有未支付的保单号和客户姓名

      这个结果好像不太对,数据库里明明是有大批量的未支付的保单

      让我们来查找一下原因: 查看数据库发现policyinfo表的CustomerID字段和customerinfo表的CustomerID字段是对不上的

      所以是这里的信息没有关联上导致没有查询到数据,而不是sql的问题

根据上面这三个任务,我们可以看到,agent生成sql的能力还是可以的,但是如果数据库的数据有问题就无能为力了,不过这就不是代码层面的问题了,所以综合效果还是可以。

但是,还存在一个问题,就是上面的代码都是依靠框架完成的,如果我需要更灵活的配置,该怎么办?这个我们可以借助RAG来完成,后面的文章会详细介绍。就这样,bye!!!

相关推荐
DeepSeek忠实粉丝1 小时前
Deepseek篇--阿里QwQ-325b性能比肩Deepseek满血版
人工智能·程序员·llm
硬核隔壁老王1 小时前
AI Agent从概念到实战全面解析(三):AI Agent 运作机制与大模型协同
人工智能·程序员·llm
LLM大模型1 小时前
LangChain篇-自定义RAG加载器
人工智能·程序员·llm
LLM大模型1 小时前
LangChain篇-自定义会话管理和Retriever
人工智能·程序员·llm
元元的飞2 小时前
Ollama vs. vLLM:Java工程师的AI部署血泪史
llm
6confim3 小时前
超越代码的未来:对话Cursor CEO,AI时代工程师的终极进化
llm·cursor·trae
阿里云大数据AI技术4 小时前
DistilQwen-ThoughtX蒸馏模型在PAI-ModelGallery的训练、评测、压缩及部署实践
大数据·开源·llm
Python测试之道7 小时前
RAG实战:基于LangChain的《肖申克的救赎》知识问答系统构建指南
langchain·embedding·rag·deepseek
Smaller、FL7 小时前
介绍MCP的背景及流程
人工智能·自然语言处理·llm·deepseek·mcp
玩转AGI8 小时前
面试篇-一文搞定 Agent
人工智能·程序员·llm