在 SQL 数据上构建问答系统
本指南假设您熟悉以下概念
在本指南中,我们将介绍在 SQL 数据库上创建问答链和代理的基本方法。这些系统将允许我们询问 SQL 数据库中的数据,并获得自然语言的答案。两者之间的主要区别在于,我们的代理可以根据需要多次查询数据库以回答问题。
⚠️ 安全提示 ⚠️
构建 SQL 数据库的问答系统可能需要执行模型生成的 SQL 查询。这样做存在固有的风险。请确保您的数据库连接权限始终尽可能狭窄地限定在链/代理的需求范围内。这将减轻但不消除构建模型驱动系统的风险。有关一般安全最佳实践的更多信息,请参阅这里。
架构
从高层面上讲,大多数 SQL 链和代理的步骤如下
- 将问题转换为 SQL 查询:模型将用户输入转换为 SQL 查询。
- 执行 SQL 查询:执行 SQL 查询
- 回答问题:模型使用查询结果来响应用户输入。
设置
首先,获取所需的包并设置环境变量
- npm
- Yarn
- pnpm
npm i langchain @langchain/community @langchain/openai
yarn add langchain @langchain/community @langchain/openai
pnpm add langchain @langchain/community @langchain/openai
在本指南中,我们默认使用 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。
- 为所有可用表定义了定义。
- 每个表有三个示例行。
这种技术灵感来自像 this 这样的论文,这些论文建议显示示例行并明确说明表格会提高性能。我们还可以通过 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 跟踪 here.
回答问题
现在我们有了自动生成和执行查询的方法,我们只需要将原始问题和 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 跟踪 here.
下一步
对于更复杂的查询生成,我们可能需要创建少量示例提示或添加查询检查步骤。有关这些高级技术以及更多内容,请查看
代理
LangChain 提供了许多工具和功能,使您可以创建 SQL 代理,这提供了一种更灵活的方式与 SQL 数据库进行交互。使用 SQL 代理的主要优点是
- 它可以根据数据库的模式和数据库的内容(如描述特定表)来回答问题。
- 它可以通过运行生成的查询、捕获回溯并正确重新生成来从错误中恢复。
- 它可以回答需要多个依赖查询的问题。
- 它将通过仅考虑相关表的模式来节省令牌。
- 要初始化代理,我们使用
createOpenAIToolsAgent
函数。此代理包含SqlToolkit
,其中包含用于 - 创建和执行查询
- 检查查询语法
- 检索表描述
- … 以及更多