跳至主要内容

在 SQL 数据上构建问答系统

先决条件

本指南假定您熟悉以下概念

在本指南中,我们将介绍在 SQL 数据库上创建问答链和代理的基本方法。这些系统将允许我们询问有关 SQL 数据库中数据的疑问,并获得自然语言答案。两者之间的主要区别在于,我们的代理可以循环查询数据库,以回答问题所需的次数。

⚠️ 安全提示 ⚠️

构建 SQL 数据库的问答系统可能需要执行模型生成的 SQL 查询。这样做存在固有的风险。请确保数据库连接权限始终尽可能严格地限定在链/代理的需求范围内。这将减轻构建模型驱动系统的风险,但不能完全消除这些风险。有关一般安全最佳实践的更多信息,请参见 此处

架构

从高层次上讲,大多数 SQL 链和代理的步骤如下

  1. 将问题转换为 SQL 查询:模型将用户输入转换为 SQL 查询。
  2. 执行 SQL 查询:执行 SQL 查询
  3. 回答问题:模型使用查询结果对用户输入进行响应。

SQL Use Case Diagram

设置

首先,获取所需的软件包并设置环境变量

提示

有关安装集成软件包的一般说明,请参见 本节

npm i langchain @langchain/community @langchain/openai @langchain/core

在本指南中,我们默认使用 OpenAI 模型。

export OPENAI_API_KEY=<your key>

# Uncomment the below to use LangSmith. Not required, but recommended for debugging and observability.
# export LANGCHAIN_API_KEY=<your key>
# export LANGCHAIN_TRACING_V2=true

# Reduce tracing latency if you are not in a serverless environment
# export LANGCHAIN_CALLBACKS_BACKGROUND=true
import { SqlDatabase } from "langchain/sql_db";
import { DataSource } from "typeorm";

const datasource = new DataSource({
type: "sqlite",
database: "../../../../Chinook.db",
});
const db = await SqlDatabase.fromDataSourceParams({
appDataSource: datasource,
});
console.log(db.allTables.map((t) => t.tableName));
/**
[
'Album', 'Artist',
'Customer', 'Employee',
'Genre', 'Invoice',
'InvoiceLine', 'MediaType',
'Playlist', 'PlaylistTrack',
'Track'
]
*/

API 参考

太棒了!我们现在有了可以查询的 SQL 数据库。现在让我们尝试将其连接到 LLM。

让我们创建一个简单的链,该链接受一个问题,将其转换为 SQL 查询,执行查询,然后使用结果来回答最初的问题。

将问题转换为 SQL 查询

SQL 链或代理的第一步是获取用户输入并将其转换为 SQL 查询。LangChain 附带了一个内置链来执行此操作:createSqlQueryChain

import { ChatOpenAI } from "@langchain/openai";
import { createSqlQueryChain } from "langchain/chains/sql_db";
import { SqlDatabase } from "langchain/sql_db";
import { DataSource } from "typeorm";

const datasource = new DataSource({
type: "sqlite",
database: "../../../../Chinook.db",
});
const db = await SqlDatabase.fromDataSourceParams({
appDataSource: datasource,
});

const llm = new ChatOpenAI({ model: "gpt-4", temperature: 0 });
const chain = await createSqlQueryChain({
llm,
db,
dialect: "sqlite",
});

const response = await chain.invoke({
question: "How many employees are there?",
});
console.log("response", response);
/**
response SELECT COUNT(*) FROM "Employee"
*/
console.log("db run result", await db.run(response));
/**
db run result [{"COUNT(*)":8}]
*/

API 参考

我们可以查看 LangSmith 跟踪 以更好地了解此链的作用。我们还可以直接检查链以获取其提示。查看提示(如下),我们可以看到它

  • 特定于方言。在本例中,它明确引用了 SQLite。
  • 包含所有可用表的定义。
  • 每个表都有三个示例行。

此技术受到 这篇论文 等的启发,该论文表明,显示示例行并明确说明表格可以提高性能。我们也可以通过 LangSmith 跟踪检查完整的提示。

Chain Prompt

执行 SQL 查询

现在我们已经生成了 SQL 查询,我们需要执行它。这是创建 SQL 链中最危险的部分。仔细考虑是否可以对您的数据运行自动查询。尽可能减少数据库连接权限。考虑在查询执行之前在您的链中添加人工审批步骤(见下文)。

我们可以使用 QuerySqlTool 轻松将查询执行添加到我们的链中

import { ChatOpenAI } from "@langchain/openai";
import { createSqlQueryChain } from "langchain/chains/sql_db";
import { SqlDatabase } from "langchain/sql_db";
import { DataSource } from "typeorm";
import { QuerySqlTool } from "langchain/tools/sql";

const datasource = new DataSource({
type: "sqlite",
database: "../../../../Chinook.db",
});
const db = await SqlDatabase.fromDataSourceParams({
appDataSource: datasource,
});
const llm = new ChatOpenAI({ model: "gpt-4", temperature: 0 });

const executeQuery = new QuerySqlTool(db);
const writeQuery = await createSqlQueryChain({
llm,
db,
dialect: "sqlite",
});

const chain = writeQuery.pipe(executeQuery);
console.log(await chain.invoke({ question: "How many employees are there" }));
/**
[{"COUNT(*)":8}]
*/

API 参考

提示

查看上面链条的 LangSmith 跟踪 此处

回答问题

现在我们有了一种自动生成和执行查询的方法,我们只需要将原始问题和 SQL 查询结果结合起来生成最终答案。我们可以通过将问题和结果再次传递给 LLM 来实现。

import { ChatOpenAI } from "@langchain/openai";
import { createSqlQueryChain } from "langchain/chains/sql_db";
import { SqlDatabase } from "langchain/sql_db";
import { DataSource } from "typeorm";
import { QuerySqlTool } from "langchain/tools/sql";
import { PromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import {
RunnablePassthrough,
RunnableSequence,
} from "@langchain/core/runnables";

const datasource = new DataSource({
type: "sqlite",
database: "../../../../Chinook.db",
});
const db = await SqlDatabase.fromDataSourceParams({
appDataSource: datasource,
});
const llm = new ChatOpenAI({ model: "gpt-4", temperature: 0 });

const executeQuery = new QuerySqlTool(db);
const writeQuery = await createSqlQueryChain({
llm,
db,
dialect: "sqlite",
});

const answerPrompt =
PromptTemplate.fromTemplate(`Given the following user question, corresponding SQL query, and SQL result, answer the user question.

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

const answerChain = answerPrompt.pipe(llm).pipe(new StringOutputParser());

const chain = RunnableSequence.from([
RunnablePassthrough.assign({ query: writeQuery }).assign({
result: (i: { query: string }) => executeQuery.invoke(i.query),
}),
answerChain,
]);
console.log(await chain.invoke({ question: "How many employees are there" }));
/**
There are 8 employees.
*/

API 参考

提示

查看上面链条的 LangSmith 跟踪 此处

下一步

对于更复杂的查询生成,我们可能需要创建少量示例提示或添加查询检查步骤。有关此类高级技术以及更多内容,请查看

代理

LangChain 提供了许多工具和函数,可让您创建 SQL 代理,它提供了一种更灵活的方式与 SQL 数据库交互。使用 SQL 代理的主要优势在于

  • 它可以根据数据库的模式以及数据库的内容(如描述特定表)回答问题。
  • 它可以通过运行生成的查询,捕获跟踪并正确重新生成它来从错误中恢复。
  • 它可以回答需要多个依赖查询的问题。
  • 它将通过只考虑来自相关表的模式来节省令牌。
  • 要初始化代理,我们使用 createOpenAIToolsAgent 函数。该代理包含 SqlToolkit,其中包含用于以下操作的工具:
  • 创建和执行查询
  • 检查查询语法
  • 检索表描述
  • … 等等

此页面对您有帮助吗?


您也可以在 GitHub 上留下详细的反馈 on GitHub.