在 SQL 数据上构建问答系统
在本指南中,我们将介绍在 SQL 数据库上创建问答链和代理的基本方法。这些系统将允许我们询问有关 SQL 数据库中数据的疑问,并获得自然语言答案。两者之间的主要区别在于,我们的代理可以循环查询数据库,以回答问题所需的次数。
⚠️ 安全提示 ⚠️
构建 SQL 数据库的问答系统可能需要执行模型生成的 SQL 查询。这样做存在固有的风险。请确保数据库连接权限始终尽可能严格地限定在链/代理的需求范围内。这将减轻构建模型驱动系统的风险,但不能完全消除这些风险。有关一般安全最佳实践的更多信息,请参见 此处。
架构
从高层次上讲,大多数 SQL 链和代理的步骤如下
- 将问题转换为 SQL 查询:模型将用户输入转换为 SQL 查询。
- 执行 SQL 查询:执行 SQL 查询
- 回答问题:模型使用查询结果对用户输入进行响应。
设置
首先,获取所需的软件包并设置环境变量
有关安装集成软件包的一般说明,请参见 本节。
- npm
- Yarn
- pnpm
npm i langchain @langchain/community @langchain/openai @langchain/core
yarn add langchain @langchain/community @langchain/openai @langchain/core
pnpm add 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 参考
- SqlDatabase 来自
langchain/sql_db
太棒了!我们现在有了可以查询的 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 参考
- ChatOpenAI 来自
@langchain/openai
- createSqlQueryChain 来自
langchain/chains/sql_db
- SqlDatabase 来自
langchain/sql_db
我们可以查看 LangSmith 跟踪 以更好地了解此链的作用。我们还可以直接检查链以获取其提示。查看提示(如下),我们可以看到它
- 特定于方言。在本例中,它明确引用了 SQLite。
- 包含所有可用表的定义。
- 每个表都有三个示例行。
此技术受到 这篇论文 等的启发,该论文表明,显示示例行并明确说明表格可以提高性能。我们也可以通过 LangSmith 跟踪检查完整的提示。
执行 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 参考
- ChatOpenAI 来自
@langchain/openai
- createSqlQueryChain 来自
langchain/chains/sql_db
- SqlDatabase 来自
langchain/sql_db
- QuerySqlTool 来自
langchain/tools/sql
查看上面链条的 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 参考
- ChatOpenAI 来自
@langchain/openai
- createSqlQueryChain 来自
langchain/chains/sql_db
- SqlDatabase 来自
langchain/sql_db
- QuerySqlTool 来自
langchain/tools/sql
- PromptTemplate 来自
@langchain/core/prompts
- StringOutputParser 来自
@langchain/core/output_parsers
- RunnablePassthrough 来自
@langchain/core/runnables
- RunnableSequence 来自
@langchain/core/runnables
查看上面链条的 LangSmith 跟踪 此处。
下一步
对于更复杂的查询生成,我们可能需要创建少量示例提示或添加查询检查步骤。有关此类高级技术以及更多内容,请查看
代理
LangChain 提供了许多工具和函数,可让您创建 SQL 代理,它提供了一种更灵活的方式与 SQL 数据库交互。使用 SQL 代理的主要优势在于
- 它可以根据数据库的模式以及数据库的内容(如描述特定表)回答问题。
- 它可以通过运行生成的查询,捕获跟踪并正确重新生成它来从错误中恢复。
- 它可以回答需要多个依赖查询的问题。
- 它将通过只考虑来自相关表的模式来节省令牌。
- 要初始化代理,我们使用
createOpenAIToolsAgent
函数。该代理包含SqlToolkit
,其中包含用于以下操作的工具: - 创建和执行查询
- 检查查询语法
- 检索表描述
- … 等等