LangChain教程 - 基于SQL数据的问答系统教程

前提条件

本文假定你已经熟悉以下概念:

  • Chaining runnables(可运行的链)
  • Chat models(聊天模型)
  • Tools(工具)
  • Agents(代理)

在本教程中,我们将使用Python来创建一个可以对SQL数据库中的表格数据进行自然语言问答的系统。我们将通过链(Chain)和代理(Agent)来实现这个系统。

数据库设置

首先,我们需要设置一个SQLite数据库来存储数据,并创建表和插入一些初始数据。

python 复制代码
from langchain_community.utilities import SQLDatabase

db = SQLDatabase.from_uri("sqlite:///Chinook.db")

# 执行建表语句
create_table_sql = '''
CREATE TABLE Employee (
    EmployeeId INTEGER NOT NULL,
    LastName NVARCHAR(20) NOT NULL,
    FirstName NVARCHAR(20) NOT NULL,
    Title NVARCHAR(30),
    ReportsTo INTEGER,
    BirthDate DATETIME,
    HireDate DATETIME,
    Address NVARCHAR(70),
    City NVARCHAR(40),
    State NVARCHAR(40),
    Country NVARCHAR(40),
    PostalCode NVARCHAR(10),
    Phone NVARCHAR(24),
    Fax NVARCHAR(24),
    Email NVARCHAR(60),
    CONSTRAINT PK_Employee PRIMARY KEY (EmployeeId),
    FOREIGN KEY (ReportsTo) REFERENCES Employee (EmployeeId) 
        ON DELETE NO ACTION ON UPDATE NO ACTION
);
'''

db.run(create_table_sql)

# 插入初始化数据
insert_data_sql = '''
INSERT INTO [Employee] ([EmployeeId], [LastName], [FirstName], [Title], [ReportsTo], [BirthDate], [HireDate], [Address], [City], [State], [Country], [PostalCode], [Phone], [Fax], [Email]) VALUES
    (1, 'Adams', 'Andrew', 'General Manager', NULL, '1962-02-18', '2002-08-14', '11120 Jasper Ave NW', 'Edmonton', 'AB', 'Canada', 'T5K 2N1', '+1 (780) 428-9482', '+1 (780) 428-3457', 'andrew@chinookcorp.com'),
    (2, 'Edwards', 'Nancy', 'Sales Manager', 1, '1958-12-08', '2002-05-01', '825 8 Ave SW', 'Calgary', 'AB', 'Canada', 'T2P 2T3', '+1 (403) 262-3443', '+1 (403) 262-3322', 'nancy@chinookcorp.com'),
    (3, 'Peacock', 'Jane', 'Sales Support Agent', 2, '1973-08-29', '2002-04-01', '1111 6 Ave SW', 'Calgary', 'AB', 'Canada', 'T2P 5M5', '+1 (403) 262-3443', '+1 (403) 262-6712', 'jane@chinookcorp.com'),
    (4, 'Park', 'Margaret', 'Sales Support Agent', 2, '1947-09-19', '2003-05-03', '683 10 Street SW', 'Calgary', 'AB', 'Canada', 'T2P 5G3', '+1 (403) 263-4423', '+1 (403) 263-4289', 'margaret@chinookcorp.com'),
    (5, 'Johnson', 'Steve', 'Sales Support Agent', 2, '1965-03-03', '2003-10-17', '7727B 41 Ave', 'Calgary', 'AB', 'Canada', 'T3B 1Y7', '1 (780) 836-9987', '1 (780) 836-9543', 'steve@chinookcorp.com'),
    (6, 'Mitchell', 'Michael', 'IT Manager', 1, '1973-07-01', '2003-10-17', '5827 Bowness Road NW', 'Calgary', 'AB', 'Canada', 'T3B 0C5', '+1 (403) 246-9887', '+1 (403) 246-9899', 'michael@chinookcorp.com'),
    (7, 'King', 'Robert', 'IT Staff', 6, '1970-05-29', '2004-01-02', '590 Columbia Boulevard West', 'Lethbridge', 'AB', 'Canada', 'T1K 5N8', '+1 (403) 456-9986', '+1 (403) 456-8485', 'robert@chinookcorp.com'),
    (8, 'Callahan', 'Laura', 'IT Staff', 6, '1968-01-09', '2004-03-04', '923 7 ST NW', 'Lethbridge', 'AB', 'Canada', 'T1H 1Y8', '+1 (403) 467-3351', '+1 (403) 467-8772', 'laura@chinookcorp.com');
'''

db.run(insert_data_sql)

通过上面的代码,我们创建了Employee表并插入了一些初始员工数据。通过下面代码测试:

python 复制代码
from langchain_community.utilities import SQLDatabase

db = SQLDatabase.from_uri("sqlite:///Chinook.db")
print(db.dialect)
print(db.get_usable_table_names())
db.run("SELECT * FROM Artist LIMIT 10;")

使用链来实现

链支持那些步骤明确且可预测的应用场景。在这个场景下,我们可以创建一个简单的链,来执行以下操作:

  1. 将问题转换为SQL查询 :我们可以利用LangChain的create_sql_query_chain方法将用户问题转换为SQL查询。
  2. 执行查询:通过SQL查询数据库获取结果。
  3. 根据结果回答问题:生成用户问题的最终回答。

将问题转换为SQL查询

首先,我们导入OpenAI模型并创建SQL查询链:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.chains import create_sql_query_chain

llm = ChatOpenAI(model="gpt-4o-mini")
chain = create_sql_query_chain(llm, db)

response = chain.invoke({"question": "How many employees are there"})
print(response)

在这个步骤中,用户问题被转换成了SQL查询:

sql 复制代码
SQLQuery: SELECT COUNT("EmployeeId") AS "TotalEmployees" FROM "Employee";

执行SQL查询

生成SQL查询后,我们需要执行它,并返回结果:

python 复制代码
db.run(response[9:])

使用链(Chains)实现问答系统

接下来我们将构建一个链来处理SQL查询并返回结果。该链的步骤如下:

  1. 将用户问题转换为SQL查询
  2. 执行查询
  3. 使用查询结果回答问题

创建并执行SQL查询链

我们使用LangChain提供的create_sql_query_chain来将用户问题转换为SQL查询。

python 复制代码
from operator import itemgetter
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool

# 定义用于回答问题的模板
answer_prompt = PromptTemplate.from_template(
    """Given the following user question, corresponding SQL query, and SQL result, answer the user question.

Question: {question}
SQL Query: {query}
SQL Result: {result}
Answer: """
)

# 将问题转换为SQL查询
write_query = create_sql_query_chain(llm, db)
execute_query = QuerySQLDataBaseTool(db=db)

# 清理查询语句结果
def clean_query(query_result):
    return query_result.get("query", "").replace("SQLQuery:", "").strip()

# 使用 assign + itemgetter 进行数据流转
chain = (
    RunnablePassthrough.assign(query=write_query).assign(
        result=lambda x: execute_query.invoke({"query": clean_query(x)})
    )
    | answer_prompt
    | llm
    | StrOutputParser()
)

# 执行链并回答问题
chain.invoke({"question": "How many employees are there"})

在上面的代码中,我们首先定义了问题模板answer_prompt,并通过create_sql_query_chain将用户的问题转换为SQL查询。然后我们使用QuerySQLDataBaseTool执行查询并通过链(Chain)将查询结果返回给用户。

使用代理(Agents)实现问答系统

代理提供了比链更灵活的方式,允许多次查询数据库,并根据需要生成答案。

初始化SQL代理

我们可以通过SQLDatabaseToolkit来创建工具集,并结合LangChain的create_react_agent代理系统来处理SQL查询。

python 复制代码
from langchain_community.agent_toolkits import SQLDatabaseToolkit
from langchain_core.messages import SystemMessage

toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()

# 定义代理系统提示信息
SQL_PREFIX = """You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct SQLite query to run, then look at the results of the query and return the answer."""

system_message = SystemMessage(content=SQL_PREFIX)

from langgraph.prebuilt import create_react_agent
agent_executor = create_react_agent(llm, tools, state_modifier=system_message)

使用代理执行查询

接下来我们使用代理来执行更复杂的SQL查询并返回结果:

python 复制代码
from langchain_core.messages import HumanMessage

for s in agent_executor.stream(
    {"messages": [HumanMessage(content="Who is the oldest employee?")]}
):
    print(s)

通过上述代码,代理会自动分析数据库中的表结构,生成正确的SQL查询,最终回答问题。比如在这个例子中,代理会根据Employee表中的出生日期找到年龄最大的员工。

完整代码实例

python 复制代码
# 安装必要的库
# %pip install langchain langchain-community langchain-openai faiss-cpu sqlite3

import os
import getpass
from langchain_community.utilities import SQLDatabase
from langchain_openai import ChatOpenAI
from langchain.chains import create_sql_query_chain
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool
from langchain_community.agent_toolkits import SQLDatabaseToolkit
from langchain_core.messages import SystemMessage
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import HumanMessage

# 设置 API 密钥
os.environ["OPENAI_API_KEY"] = getpass.getpass("请输入您的 OpenAI API 密钥: ")

# 初始化数据库并插入数据
db = SQLDatabase.from_uri("sqlite:///Chinook.db")

# 检查表是否已经存在
table_check_sql = '''
SELECT name FROM sqlite_master WHERE type='table' AND name='Employee';
'''
table_exists = db.run(table_check_sql)

# 如果表不存在,则创建表并插入数据
if not table_exists:

    # 执行建表语句
    create_table_sql = '''
    CREATE TABLE Employee (
        EmployeeId INTEGER NOT NULL,
        LastName NVARCHAR(20) NOT NULL,
        FirstName NVARCHAR(20) NOT NULL,
        Title NVARCHAR(30),
        ReportsTo INTEGER,
        BirthDate DATETIME,
        HireDate DATETIME,
        Address NVARCHAR(70),
        City NVARCHAR(40),
        State NVARCHAR(40),
        Country NVARCHAR(40),
        PostalCode NVARCHAR(10),
        Phone NVARCHAR(24),
        Fax NVARCHAR(24),
        Email NVARCHAR(60),
        CONSTRAINT PK_Employee PRIMARY KEY (EmployeeId),
        FOREIGN KEY (ReportsTo) REFERENCES Employee (EmployeeId) 
            ON DELETE NO ACTION ON UPDATE NO ACTION
    );
    '''

    db.run(create_table_sql)

    # 插入初始化数据
    insert_data_sql = '''
    INSERT INTO [Employee] ([EmployeeId], [LastName], [FirstName], [Title], [ReportsTo], [BirthDate], [HireDate], [Address], [City], [State], [Country], [PostalCode], [Phone], [Fax], [Email]) VALUES
        (1, 'Adams', 'Andrew', 'General Manager', NULL, '1962-02-18', '2002-08-14', '11120 Jasper Ave NW', 'Edmonton', 'AB', 'Canada', 'T5K 2N1', '+1 (780) 428-9482', '+1 (780) 428-3457', 'andrew@chinookcorp.com'),
        (2, 'Edwards', 'Nancy', 'Sales Manager', 1, '1958-12-08', '2002-05-01', '825 8 Ave SW', 'Calgary', 'AB', 'Canada', 'T2P 2T3', '+1 (403) 262-3443', '+1 (403) 262-3322', 'nancy@chinookcorp.com'),
        (3, 'Peacock', 'Jane', 'Sales Support Agent', 2, '1973-08-29', '2002-04-01', '1111 6 Ave SW', 'Calgary', 'AB', 'Canada', 'T2P 5M5', '+1 (403) 262-3443', '+1 (403) 262-6712', 'jane@chinookcorp.com'),
        (4, 'Park', 'Margaret', 'Sales Support Agent', 2, '1947-09-19', '2003-05-03', '683 10 Street SW', 'Calgary', 'AB', 'Canada', 'T2P 5G3', '+1 (403) 263-4423', '+1 (403) 263-4289', 'margaret@chinookcorp.com'),
        (5, 'Johnson', 'Steve', 'Sales Support Agent', 2, '1965-03-03', '2003-10-17', '7727B 41 Ave', 'Calgary', 'AB', 'Canada', 'T3B 1Y7', '1 (780) 836-9987', '1 (780) 836-9543', 'steve@chinookcorp.com'),
        (6, 'Mitchell', 'Michael', 'IT Manager', 1, '1973-07-01', '2003-10-17', '5827 Bowness Road NW', 'Calgary', 'AB', 'Canada', 'T3B 0C5', '+1 (403) 246-9887', '+1 (403) 246-9899', 'michael@chinookcorp.com'),
        (7, 'King', 'Robert', 'IT Staff', 6, '1970-05-29', '2004-01-02', '590 Columbia Boulevard West', 'Lethbridge', 'AB', 'Canada', 'T1K 5N8', '+1 (403) 456-9986', '+1 (403) 456-8485', 'robert@chinookcorp.com'),
        (8, 'Callahan', 'Laura', 'IT Staff', 6, '1968-01-09', '2004-03-04', '923 7 ST NW', 'Lethbridge', 'AB', 'Canada', 'T1H 1Y8', '+1 (403) 467-3351', '+1 (403) 467-8772', 'laura@chinookcorp.com');
    '''

    db.run(insert_data_sql)

# 初始化OpenAI LLM模型
llm = ChatOpenAI(model="gpt-4o-mini")

# ------------------------
# 使用链(Chains)实现问答系统
# ------------------------

# 定义用于回答问题的模板
answer_prompt = PromptTemplate.from_template(
    """Given the following user question, corresponding SQL query, and SQL result, answer the user question.

Question: {question}
SQL Query: {query}
SQL Result: {result}
Answer: """
)

# 将问题转换为SQL查询
write_query = create_sql_query_chain(llm, db)
execute_query = QuerySQLDataBaseTool(db=db)

# 清理查询语句结果
def clean_query(query_result):
    return query_result.get("query", "").replace("SQLQuery:", "").strip()

# 使用 assign + itemgetter 进行数据流转
chain = (
    RunnablePassthrough.assign(query=write_query).assign(
        result=lambda x: execute_query.invoke({"query": clean_query(x)})
    )
    | answer_prompt
    | llm
    | StrOutputParser()
)

# 执行链并回答问题
print(chain.invoke({"question": "How many employees are there"}))

# ------------------------
# 使用代理(Agents)实现问答系统
# ------------------------

# 初始化SQL代理工具包
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()

# 定义代理系统提示信息
SQL_PREFIX = """You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct SQLite query to run, then look at the results of the query and return the answer."""
system_message = SystemMessage(content=SQL_PREFIX)

# 创建代理
agent_executor = create_react_agent(llm, tools, state_modifier=system_message)

# 使用代理执行查询并回答问题
for s in agent_executor.stream(
    {"messages": [HumanMessage(content="Who is the oldest employee?")]}
):
    print(s)

总结

在本教程中,我们演示了如何通过链(Chains)和代理(Agents)实现基于SQL数据的问答系统。链适用于步骤明确、可预测的场景,而代理适合处理更加复杂和灵活的查询需求。在实际使用中,务必保证数据库的安全性,避免潜在的风险。

系列文章索引
LangChain教程 - 系列文章

相关推荐
Mephisto.java5 小时前
【大数据学习 | HBASE高级】storeFile文件的合并
大数据·sql·oracle·json·hbase·database
不二人生5 小时前
SQL面试题——连续出现次数
hive·sql·面试
奔跑的花短裤5 小时前
少儿编程启蒙学习
学习·青少年编程·机器人·ai编程
邹阿涛涛涛涛涛涛6 小时前
月之暗面招 Android 开发,大家快来投简历呀
android·人工智能·aigc
神秘的土鸡7 小时前
丹摩征文活动 | SD3+ComfyUI的图像部署实践
人工智能·云计算·aigc·ai绘画·gpu算力·安全架构
python零基础入门小白7 小时前
如何通过AI绘画技术轻松变现?教你三种方法赚取高额利润!
ai作画·stable diffusion·aigc
在下方方8 小时前
【快捷入门笔记】mysql基本操作大全-SQL表
笔记·sql·mysql
Ttang238 小时前
数据库&SQL——嵌套子查询(IN/NOT IN/SOME/ALL/EXIST/NOT EXIST)
数据库·sql
敲上瘾8 小时前
C++11新特性(二)
java·c语言·开发语言·数据结构·c++·python·aigc
量子位10 小时前
o1 不是唯一路径!MIT 新研究:在测试时训练,模型推理能力最高升至 5.8 倍
人工智能·aigc