在构建基于 SQL 的问答系统时,代理(Agent)的动态决策能力至关重要。它通过与工具的多轮交互,逐步拼凑出答案所需的信息。本文将基于实际代码和执行日志,逐字节拆解代理执行过程中三类核心消息的交互细节,让每个工具调用、参数生成和结果处理的逻辑都清晰可溯。
代理执行流程本质是消息驱动的状态机,包含三种消息类型,通过stream
方法按时间顺序输出,形成完整的 “决策链条”:
plaintext
========================[1m Human Message [0m========================
Which country's customers spent the most?
plaintext
=======================[1m Ai Message [0m=======================
Tool Calls:
sql_db_schema (call_KJZ1Jx6JazyDdJa0uH1UeiOz) # 工具名 + 唯一调用ID
Call ID: call_KJZ1Jx6JazyDdJa0uH1UeiOz # 用于追踪全流程的标识符
Args: # 工具入参(严格遵循工具定义格式)
table_names: Customer, Invoice # 目标表名(逗号分隔字符串)
sql
========================[1m Tool Message [0m========================
Name: sql_db_schema # 工具名(与调用时一致)
Result:
CREATE TABLE "Customer" (
"CustomerId" INTEGER NOT NULL,
"Country" NVARCHAR(40), # 关键业务字段(用于国家筛选)
FOREIGN KEY("SupportRepId") REFERENCES "Employee" ("EmployeeId")
)
/* 示例数据(帮助代理理解字段含义)
CustomerId Country
1 Brazil
2 Germany
*/
Human Message首次出现,代理此时对数据库结构一无所知:
plaintext
====================[1m Human Message [0m====================
Which country's customers spent the most? # 核心需求:按国家统计消费总额
Ai Message表明代理缺乏表信息,选择sql_db_list_tables
工具(无参数,默认返回所有表名):
plaintext
=====================[1m Ai Message [0m======================
Tool Calls:
sql_db_list_tables (call_tFp7HYD6sAAmCShgeqkVZH6Q)
Args: (无,工具自动获取全表)
Tool Message返回表列表,代理从中识别出关键表Customer
(客户)和Invoice
(发票):
plaintext
====================[1m Tool Message [0m====================
Name: sql_db_list_tables
Result: Album, Artist, Customer, Employee, Genre, Invoice, ... # 省略无关表
Ai Message基于上一步结果,调用sql_db_schema
工具,传入目标表名参数:
plaintext
=====================[1m Ai Message [0m======================
Tool Calls:
sql_db_schema (call_KJZ1Jx6JazyDdJa0uH1UeiOz)
Args:
table_names: Customer, Invoice # 明确需要这两张表的结构(来自表列表结果)
Tool Message返回详细 DDL 和示例数据,代理从中获取关键信息:
Customer.Country
存储国家名称Invoice.Total
存储消费金额,CustomerId
作为外键关联客户表CustomerId
建立JOIN
关系Ai Message根据表结构生成 SQL 查询,并调用sql_db_query_checker
工具验证语法:
plaintext
=====================[1m Ai Message [0m======================
Tool Calls:
sql_db_query_checker (call_AQuTGbgH63u4gPgyV723yrjX)
Args:
query: SELECT c.Country, SUM(i.Total) as TotalSpent # 关联查询语句
FROM Customer c JOIN Invoice i
ON c.CustomerId = i.CustomerId
GROUP BY c.Country ORDER BY TotalSpent DESC LIMIT 1;
Tool Message确认查询合法(无语法错误提示),代理获得执行许可。
Ai Message调用sql_db_query
工具,传入已验证的查询:
plaintext
=====================[1m Ai Message [0m======================
Tool Calls:
sql_db_query (call_B88EwU44nwwpQL5M9nlcemSU)
Args: (同上查询语句)
Tool Message返回结果[('USA', 523.06)]
,代理确认数据足够回答问题。
代理不再调用工具,直接基于查询结果生成回答,消息链终止:
plaintext
=====================[1m Ai Message [0m======================
The country whose customers spent the most is the USA, with a total spending of 523.06.
代理不会凭空生成参数,而是严格依赖历史消息:
sql_db_schema
的table_names
参数值,来自sql_db_list_tables
的返回结果sql_db_query
的query
参数,基于sql_db_schema
返回的表结构和字段名生成toolkit.get_tools()
获取的工具,其参数校验逻辑与消息链强绑定,确保入参合法。每次Tool Message
的结果都会作为新输入传递给 LLM,形成 “决策 - 反馈 - 修正” 闭环:
这种机制让代理能逐步缩小知识缺口,例如:在不知道Invoice
表有Total
字段时,绝不会生成聚合查询。
当代理生成错误查询(如拼写错误的表名Customr
),消息链会出现关键转折:
sqlite3.OperationalError: no such table: Customr
Customer
,再次调用sql_db_query
sql_db_list_tables
)→ 表结构(sql_db_schema
)→ 查询执行(sql_db_query
)sql_db_schema
的table_names
必须是逗号分隔的字符串(如"Customer, Invoice"
),而非列表["Customer", "Invoice"]
Tool Message
返回Invalid input format
,需在Ai Message
的Args
中检查格式。sql_db_query
的query
中是否包含sql_db_schema
返回的字段名(如Country
、Total
)通过解析消息链,我们看到代理的每个决策都是数据驱动的:
掌握这套消息体系,就能:
下次开发 SQL 问答系统时,记得用stream
方法打印完整消息链 —— 这是破解代理逻辑最直接的方式。觉得有帮助的话,欢迎收藏本文,后续我们将深入探讨如何通过消息分析提升代理的复杂查询能力!