在本教程中,我们将构建一个自定义 agent,该 agent 可以使用 LangGraph 回答有关 SQL 数据库的问题。
LangChain 提供内置 agent 实现,使用 LangGraph 原语。如果需要更深入的定制,可以直接在 LangGraph 中实现 agent。本指南演示了 SQL agent 的示例实现。关于实际介绍,请参阅 使用更高级别的 LangChain 抽象构建 SQL agent.
该 预构建 agent 让我们快速入门,但我们依赖系统提示来约束其行为——例如,我们指示 agent 始终以"list tables"工具开始,并在执行查询之前始终运行查询检查工具。
通过自定义 agent,我们可以在 LangGraph 中实施更高程度的控制。在这里,我们实现了一个简单的 ReAct-agent 设置,具有用于特定工具调用的专用节点。我们将使用与预构建 agent 相同的 [state]。
概念
我们将涵盖以下概念:
- - 工具 用于从 SQL 数据库读取
- - LangGraph 图形 API,包括状态、节点、边和条件边。
- - Human-in-the-loop 流程
设置
安装
npm i langchain @langchain/core @langchain/langgraph @langchain/openai sqlite3 zod
yarn add langchain @langchain/core @langchain/langgraph @langchain/openai sqlite3 zod
pnpm add langchain @langchain/core @langchain/langgraph @langchain/openai sqlite3 zod
### LangSmith 设置 LangSmith 以检查链或 agent 内部发生的情况。然后设置以下环境变量:
1. 选择一个 LLM
选择一个支持 tool-calling:
下面示例中显示的输出使用了 OpenAI。
2. 配置数据库
您将创建一个 SQLite 数据库 用于本教程。SQLite 是一个轻量级数据库,易于设置和使用。我们将加载 chinook 数据库,这是一个代表数字媒体商店的示例数据库。
为方便起见,我们将数据库(Chinook.db)托管在一个公共 GCS 存储桶上。
我们将使用 sqlite3 库与数据库交互:
Dialect: sqlite
Available tables: Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track
Sample output: [{"ArtistId":1,"Name":"AC/DC"},{"ArtistId":2,"Name":"Accept"},{"ArtistId":3,"Name":"Aerosmith"},{"ArtistId":4,"Name":"Alanis Morissette"},{"ArtistId":5,"Name":"Alice In Chains"}]
3. 添加用于数据库交互的工具
我们将创建自定义工具来与数据库交互:
sql_db_list_tables: Input is an empty string, output is a comma-separated list of tables in the database.
sql_db_schema: Input to this tool is a comma-separated list of tables, output is the schema and sample rows for those tables. Be sure that the tables actually exist by calling sql_db_list_tables first! Example Input: table1, table2, table3
sql_db_query: Input to this tool is a detailed and correct SQL query, output is a result from the database. If the query is not correct, an error message will be returned. If an error is returned, rewrite the query, check the query, and try again.
4. 定义应用步骤
我们为以下步骤构建专用节点:
- - 列出数据库表
- - 调用"获取模式"工具
- - 生成查询
- - 检查查询
将这些步骤放在专用节点中使我们能够 (1) 在需要时强制工具调用,以及 (2) 自定义与每个步骤关联的提示。
5. 实现代理
我们现在可以使用 Graph API将这些步骤组装成工作流程。 条件边 在查询生成步骤处定义,如果生成了查询则路由到查询检查器,如果没有工具调用则结束,此时 LLM 已将响应返回给用户。
我们在下方可视化该应用:
我们现在可以调用该图:
================================ Human Message =================================
Which genre on average has the longest tracks?
================================== Ai Message ==================================
Available tables: Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track
================================== Ai Message ==================================
Tool Calls:
sql_db_schema (call_yzje0tj7JK3TEzDx4QnRR3lL)
Call ID: call_yzje0tj7JK3TEzDx4QnRR3lL
Args:
table_names: Genre, Track
================================= Tool Message =================================
Name: sql_db_schema
CREATE TABLE "Genre" (
"GenreId" INTEGER NOT NULL,
"Name" NVARCHAR(120),
PRIMARY KEY ("GenreId")
)
/*
3 rows from Genre table:
GenreId Name
1 Rock
2 Jazz
3 Metal
*/
CREATE TABLE "Track" (
"TrackId" INTEGER NOT NULL,
"Name" NVARCHAR(200) NOT NULL,
"AlbumId" INTEGER,
"MediaTypeId" INTEGER NOT NULL,
"GenreId" INTEGER,
"Composer" NVARCHAR(220),
"Milliseconds" INTEGER NOT NULL,
"Bytes" INTEGER,
"UnitPrice" NUMERIC(10, 2) NOT NULL,
PRIMARY KEY ("TrackId"),
FOREIGN KEY("MediaTypeId") REFERENCES "MediaType" ("MediaTypeId"),
FOREIGN KEY("GenreId") REFERENCES "Genre" ("GenreId"),
FOREIGN KEY("AlbumId") REFERENCES "Album" ("AlbumId")
)
/*
3 rows from Track table:
TrackId Name AlbumId MediaTypeId GenreId Composer Milliseconds Bytes UnitPrice
1 For Those About To Rock (We Salute You) 1 1 1 Angus Young, Malcolm Young, Brian Johnson 343719 11170334 0.99
2 Balls to the Wall 2 2 1 U. Dirkschneider, W. Hoffmann, H. Frank, P. Baltes, S. Kaufmann, G. Hoffmann 342562 5510424 0.99
3 Fast As a Shark 3 2 1 F. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman 230619 3990994 0.99
*/
================================== Ai Message ==================================
Tool Calls:
sql_db_query (call_cb9ApLfZLSq7CWg6jd0im90b)
Call ID: call_cb9ApLfZLSq7CWg6jd0im90b
Args:
query: SELECT Genre.Name, AVG(Track.Milliseconds) AS AvgMilliseconds FROM Track JOIN Genre ON Track.GenreId = Genre.GenreId GROUP BY Genre.GenreId ORDER BY AvgMilliseconds DESC LIMIT 5;
================================== Ai Message ==================================
Tool Calls:
sql_db_query (call_DMVALfnQ4kJsuF3Yl6jxbeAU)
Call ID: call_DMVALfnQ4kJsuF3Yl6jxbeAU
Args:
query: SELECT Genre.Name, AVG(Track.Milliseconds) AS AvgMilliseconds FROM Track JOIN Genre ON Track.GenreId = Genre.GenreId GROUP BY Genre.GenreId ORDER BY AvgMilliseconds DESC LIMIT 5;
================================= Tool Message =================================
Name: sql_db_query
[('Sci Fi & Fantasy', 2911783.0384615385), ('Science Fiction', 2625549.076923077), ('Drama', 2575283.78125), ('TV Shows', 2145041.0215053763), ('Comedy', 1585263.705882353)]
================================== Ai Message ==================================
The genre with the longest tracks on average is "Sci Fi & Fantasy," with an average track length of approximately 2,911,783 milliseconds. Other genres with relatively long tracks include "Science Fiction," "Drama," "TV Shows," and "Comedy."
6. 实现人工介入审查
在执行 SQL 查询之前检查代理的查询,以防止任何意外操作或低效情况,这是明智的做法。
在这里我们利用 LangGraph 的 human-in-the-loop 功能,在执行 SQL 查询之前暂停运行并等待人工审查。使用 LangGraph 的 持久化层,我们可以无限期暂停运行(或者至少在持久化层存活期间)。
让我们在接收人工输入的节点中包装 sql_db_query 工具。我们可以使用 interrupt 函数来实现这一点。在下方,我们允许输入以批准工具调用、编辑其参数或提供用户反馈。
现在让我们重新组装图。我们将用人工审查替换程序化检查。请注意,我们现在包含了一个 checkpointer;这是暂停和恢复运行所必需的。
我们可以像之前一样调用图。这次,执行会被中断:
...
INTERRUPTED:
{
"action": "sql_db_query",
"args": {
"query": "SELECT Genre.Name, AVG(Track.Milliseconds) AS AvgLength FROM Track JOIN Genre ON Track.GenreId = Genre.GenreId GROUP BY Genre.Name ORDER BY AvgLength DESC LIMIT 5;"
},
"description": "Please review the tool call"
}
我们可以使用 Command:
================================== Ai Message ==================================
Tool Calls:
sql_db_query (call_t4yXkD6shwdTPuelXEmY3sAY)
Call ID: call_t4yXkD6shwdTPuelXEmY3sAY
Args:
query: SELECT Genre.Name, AVG(Track.Milliseconds) AS AvgLength FROM Track JOIN Genre ON Track.GenreId = Genre.GenreId GROUP BY Genre.Name ORDER BY AvgLength DESC LIMIT 5;
================================= Tool Message =================================
Name: sql_db_query
[('Sci Fi & Fantasy', 2911783.0384615385), ('Science Fiction', 2625549.076923077), ('Drama', 2575283.78125), ('TV Shows', 2145041.0215053763), ('Comedy', 1585263.705882353)]
================================== Ai Message ==================================
The genre with the longest average track length is "Sci Fi & Fantasy" with an average length of about 2,911,783 milliseconds. Other genres with long average track lengths include "Science Fiction," "Drama," "TV Shows," and "Comedy."
接受或编辑工具调用 人在回路指南 详情请参阅。
## 下一步 请参阅 评估图 了解如何使用 LangSmith 评估 LangGraph 应用程序(包括此类 SQL 代理)的指南。