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教程 - 系列文章

相关推荐
爱编程的小庄4 小时前
web网络安全:SQL 注入攻击
前端·sql·web安全
banjin5 小时前
免费体验,在阿里云平台零门槛调用满血版DeepSeek-R1模型
阿里云·自然语言处理·云计算·ai编程·ai写作·deepseek
史迪仔01125 小时前
【SQL】SQL多表查询
数据库·sql
※※冰馨※※6 小时前
【AI】VS Code中使用GitHub Copilot
ai编程
天天向上杰7 小时前
通义灵码AI程序员
人工智能·aigc·ai编程
焱焱枫7 小时前
自适应SQL计划管理(Adaptive SQL Plan Management)在Oracle 12c中的应用
数据库·sql·oracle
2301_793069827 小时前
Spring Boot +SQL项目优化策略,GraphQL和SQL 区别,Spring JDBC 等原理辨析(万字长文+代码)
java·数据库·spring boot·sql·jdbc·orm
偏右右9 小时前
PL/SQL 异常处理
数据库·sql·oracle
朱剑君10 小时前
Python——生成AIGC图像
人工智能·python·aigc
Neo很努力13 小时前
【deepseek】本地部署+RAG知识库挂载+对话测试
自然语言处理·chatgpt·langchain·aigc·llama