跳到主要内容

在 SQL 数据上构建问答系统

先决条件

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

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

⚠️ 安全提示 ⚠️

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

架构

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

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

SQL Use Case Diagram

设置

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

npm i 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 参考

太棒了!我们现在拥有一个可以查询的 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。
  • 为所有可用表定义了定义。
  • 每个表有三个示例行。

这种技术灵感来自像 this 这样的论文,这些论文建议显示示例行并明确说明表格会提高性能。我们还可以通过 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 跟踪 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 参考

提示

查看上面链的 LangSmith 跟踪 here.

下一步

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

代理

LangChain 提供了许多工具和功能,使您可以创建 SQL 代理,这提供了一种更灵活的方式与 SQL 数据库进行交互。使用 SQL 代理的主要优点是

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

此页面是否有帮助?


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