本地大模型编程实战(23)用智能体(Agent)实现基于SQL数据构建问答系统(2)

news/2025/2/26 15:55:23

本文将用 智能体(Agent) 实现对 SQLite 数据库的查询:用户用自然语言提出问题,智能体也用自然语言根据数据库的查询结果回答问题。

本次将分别在英文、中文环境下,使用 qwen2.5MFDoom/deepseek-r1-tool-calling:7b 以及 llama3.1 做实验。

准备

您可以按下面的步骤准备本地编程环境。

  1. 计算机
    本文涉及的代码可以在没有显存的环境中执行。建议最低配置为:
  • CPU: Intel i5-8400 2.80GHz
  • 内存: 16GB
  1. Visual Studio Code 和 venv
    Visual Studio Code 是很受欢迎的开发工具,建议用 venv 创建虚拟环境, 详见:
    在Visual Studio Code中配置venv。

  2. Ollama
    基于 Ollama 平台,我们可以在本地方便的使用 llama3.1qwen2.5deepseek 等各种 LLM(大语言模型)。详见:
    在langchian中使用本地部署的llama3.1大模型 。

创建 SQLite

我们直接使用之前创建好的 SQLite 数据库:

# 获取当前执行的程序文件的文件夹路径
current_folder = os.path.dirname(os.path.abspath(__file__))

db_file_path = os.path.join(current_folder, 'assert/Chinook.db')

from langchain_community.utilities import SQLDatabase

db = SQLDatabase.from_uri(f"sqlite:///{db_file_path}")

智能体(Agent)

LangChain 有一个 SQL智能体,它提供了一种比链更灵活的与 SQL 数据库交互的方式。使用 SQL智能体 的主要优点是:

  • 它可以根据数据库的架构以及数据库的内容(如描述特定表)回答问题
  • 它可以通过运行生成的查询、捕获执行栈并正确地重新生成它来从错误中恢复
  • 它可以根据需要多次查询数据库以回答用户问题
    … 等等

创建 SQLite 工具

为了初始化智能体,我们将使用 SQLDatabaseToolkit 创建一组工具:

  • 创建和执行查询
  • 检查查询语法
  • 检索表描述
    … 等等
def create_tools(llm_model_name):
    """创建工具"""

    llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True)
    toolkit = SQLDatabaseToolkit(db=db, llm=llm)

    tools = toolkit.get_tools()
    print(tools)

    return tools

系统提示词

我们来创建指导智能体的中英文提示词。

  • 英文版
system = """You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct SQLite query to run, then look at the results of the query and return the answer.
Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 5 results.
You can order the results by a relevant column to return the most interesting examples in the database.
Never query for all the columns from a specific table, only ask for the relevant columns given the question.
You have access to tools for interacting with the database.
Only use the given tools. Only use the information returned by the tools to construct your final answer.
You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.

DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.

You have access to the following tables: {table_names}
""".format(
    table_names=db.get_usable_table_names()
)

system_message = SystemMessage(content=system)
  • 中文版
system = """您是设计用于与 SQL 数据库交互的代理。用中文回答问题。
给定一个输入问题,创建一个语法正确的 SQLite 查询来运行,然后查看查询结果并返回答案。
除非用户指定他们希望获得的特定数量的示例,否则请始终将查询限制为最多 5 个结果。
您可以按相关列对结果进行排序,以返回数据库中最有趣的示例。
切勿查询特定表中的所有列,仅询问给定问题的相关列。
您可以使用与数据库交互的工具。
仅使用给定的工具。仅使用工具返回的信息来构建最终答案。
在执行查询之前,您必须仔细检查查询。如果在执行查询时出现错误,请重写查询并重试。

请勿对数据库执行任何 DML 语句(INSERT、UPDATE、DELETE、DROP 等)。

您有权访问以下数据库表: {table_names}
""".format(
    table_names=db.get_usable_table_names()
)

system_message = SystemMessage(content=system)

上述提示词对大模型生成SQL语句的行为做了比较严格的限制,以防止生成错误的SQL破坏数据库。

初始化智能体

使用 create_react_agent 方法初始化智能体,定义问答方法。

def ask(llm_model_name,question):
    """询问智能体"""

    tools = create_tools(llm_model_name)
    llm = ChatOllama(model=llm_model_name,temperature=0, verbose=True)
    agent_executor = create_react_agent(llm, tools, state_modifier=system_message)

    for s in agent_executor.stream(
        {"messages": [HumanMessage(content=question)]}
    ):
        print(s)
        print("----")

React智能体React Agent)指的是一种能自主决策和执行任务的AI智能体,它结合了大语言模型(LLM)和工具调用,可以根据环境和任务需求动态调整自己的行为。
简单理解:

  1. React = 解释 + 计算(Reason + Act)
    • 先分析当前的任务和数据(Reason)
    • 然后做出相应的行动(Act)
  2. 如何工作?
    • 先阅读输入信息
    • 决定是否调用某个工具(如数据库查询、API 调用)
    • 处理返回的结果,再次分析,继续执行任务
  3. 示例:
    • 您输入:“明天的天气如何?”
    • 智能体会先思考:“这个问题需要调用天气 API。”
    • 然后它调用天气 API,获取数据后再回复你:“明天是晴天,气温 20°C。”

见证效果

下面我们出3个同样的中文、英文问题,看看这三款大模型的表现如何。

当用中文提问时,系统提示词也用中文版;反之亦然。

英文问题

    questions = [
        "How many Employees are there?",
        "Which country's customers spent the most?",
        "Describe the PlaylistTrack table"
    ]

我们先看看 qwen2.5 是如何思考回答 “How many Employees are there?” 的:

[QuerySQLDatabaseTool(description="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. If you encounter an issue with Unknown column 'xxxx' in 'field list', use sql_db_schema to query the correct table fields.", ...)]
{'agent': {'messages': [AIMessage(content='',..., tool_calls=[{'name': 'sql_db_schema', 'args': {'table_names': 'Employee'}, ..., 'type': 'tool_call'}], ...)]}}
----
{'tools': {'messages': [ToolMessage(content='\nCREATE TABLE "Employee" (\n\t"EmployeeId" INTEGER NOT NULL, \n\t"LastName" NVARCHAR(20) NOT NULL, \n\t"FirstName" NVARCHAR(20) NOT NULL, \n\t"Title" NVARCHAR(30), \n\t"ReportsTo" INTEGER, \n\t"BirthDate" DATETIME, \n\t"HireDate" DATETIME, \n\t"Address" NVARCHAR(70), \n\t"City" NVARCHAR(40), \n\t"State" NVARCHAR(40), \n\t"Country" NVARCHAR(40), \n\t"PostalCode" NVARCHAR(10), \n\t"Phone" NVARCHAR(24), \n\t"Fax" NVARCHAR(24), \n\t"Email" NVARCHAR(60), \n\tPRIMARY KEY ("EmployeeId"), \n\tFOREIGN KEY("ReportsTo") REFERENCES "Employee" ("EmployeeId")\n)\n\n/*\n3 rows from Employee table:\nEmployeeId\tLastName\tFirstName\tTitle\tReportsTo\tBirthDate\tHireDate\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\n1\tAdams\tAndrew\tGeneral Manager\tNone\t1962-02-18 00:00:00\t2002-08-14 00:00:00\t11120 Jasper Ave NW\tEdmonton\tAB\tCanada\tT5K 2N1\t+1 (780) 428-9482\t+1 (780) 428-3457\tandrew@chinookcorp.com\n2\tEdwards\tNancy\tSales Manager\t1\t1958-12-08 00:00:00\t2002-05-01 00:00:00\t825 8 Ave SW\tCalgary\tAB\tCanada\tT2P 2T3\t+1 (403) 262-3443\t+1 (403) 262-3322\tnancy@chinookcorp.com\n3\tPeacock\tJane\tSales Support Agent\t2\t1973-08-29 00:00:00\t2002-04-01 00:00:00\t1111 6 Ave SW\tCalgary\tAB\tCanada\tT2P 5M5\t+1 (403) 262-3443\t+1 (403) 262-6712\tjane@chinookcorp.com\n*/', name='sql_db_schema',...)]}}
----
{'agent': {'messages': [AIMessage(content='', ..., tool_calls=[{'name': 'sql_db_query_checker', 'args': {'query': 'SELECT COUNT(*) FROM Employee'}, ..., 'type': 'tool_call'}], ...)]}}
----
{'tools': {'messages': [ToolMessage(content='The provided SQL query is:\n\n```sql\nSELECT COUNT(*) FROM Employee\n```\n\nThis query appears to be correct and does not contain any of the common mistakes listed. It simply counts all rows in the `Employee` table.\n\nTherefore, the final SQL query remains:\n\n```sql\nSELECT COUNT(*) FROM Employee\n```', name='sql_db_query_checker', ...)]}}
----
{'agent': {'messages': [AIMessage(content='', ..., tool_calls=[{'name': 'sql_db_query', 'args': {'query': 'SELECT COUNT(*) FROM Employee'}, ..., 'type': 'tool_call'}], ...)]}}
----
{'tools': {'messages': [ToolMessage(content='[(8,)]', name='sql_db_query', ...)]}}
----
{'agent': {'messages': [AIMessage(content='There are 8 employees in the database.', ...)]}}
----

从上面的输出来看,智能体是一步一步推理出最终的答案的:

  • QuerySQLDatabaseTool
    给出提示:此工具的输入是详细且正确的 SQL 查询,输出是数据库的结果。如果查询不正确,将返回错误消息。如果返回错误,请重写查询,检查查询,然后重试。如果您遇到“字段列表”中未知列“xxxx”的问题,请使用 sql_db_schema 查询正确的表字段。
  • agent
    生成名为 sql_db_schematool_call ,确定表名为 Employee
  • tools
    执行前面tool_call,生成名为 sql_db_schemaToolMessage ,找出了表 EmployeeDML ,并成功查询出3条信息。
  • agent
    生成名为 sql_db_query_checkertool_call,其中包含 SQL语句。
  • tools
    验证前面 tool_call 中的SQL是否正确。生成名为 sql_db_query_checkerToolMessage,其内容显示最终确定了正确的SQL语句。
  • agent
    生成名为 sql_db_querytool_call,其中包含SQL语句:SELECT COUNT(*) FROM Employee
  • tools
    执行前面的 tool_call ,生成名为 sql_db_queryToolMessage ,其中已经包含了 SQLite的返回值。
  • agent
    基于前面的结果生成了最终回答。

通过上述的推理过程显示:智能体与工具进行了多轮交互后才给出最终回答。

下表直接列出各个大模型针对这3个问题的处理结果:

How many Employees are there?Which country’s customers spent the most?Describe the PlaylistTrack table
qwen2.5There are 8 employees in the database.The country with the highest spending by customers is USA, followed by Canada, France, Brazil, and Germany.\n\nHere are the top 5 countries based on total invoice amount:\n\n1. USA - $523.06\n…The PlaylistTrack table has two columns: PlaylistId and TrackId. It is a junction table used to establish a many-to-many relationship between the Playlist and Track tables…
llama3.1There are 8 Employees.The country’s customers that spent the most are from the USA, with a total spend of $523.06.The PlaylistTrack table contains information about the tracks in each playlist. It has two columns: PlaylistId and TrackId,…
MFDoom/deepseek-r1-tool-calling:7bThere are \boxed{8} Employees in the database.未推理出正确的的SQLAlright, …, it looks like this table has two main columns: PlaylistId and TrackId…

从上述结果来看,qwen2.5给出的回答最清晰全面,llama3.1也不错,MFDoom/deepseek-r1-tool-calling:7b 未能正确回答第2个问题。

中文问题

    questions = [
        "有多少名员工?",
        "哪个国家的顾客花费最多?",
        "描述 PlaylistTrack 表" 
    ]
有多少名员工?哪个国家的顾客花费最多?描述 PlaylistTrack 表
qwen2.5数据库中一共有 8 名员工。花费最多的国家及其总金额如下:\n\n1. 美国 (USA) - 总\金额: $523.06…\n\n因此,顾客花费最多的国家是美国。PlaylistTrack 表包含两个列:PlaylistIdTrackId。这两个列共同作为主键,表示播放列表和其中的歌曲之间的关系。…
llama3.1有 8 名员工。答案是:美国。根据工具的输出,PlaylistTrack 表是一个连接表,它将Playlist和Track两个表关联起来。它有两个列:PlaylistId和TrackId,…
MFDoom/deepseek-r1-tool-calling:7b未正确做答未正确做答未正确做答

总结

我们实现了用 智能体(Agent)SQlite 对话的功能,通过中英文环境的简单对比实验发现,qwen2.5 表现最稳健。

代码

本文涉及的所有代码以及相关资源都已经共享,参见:

  • github
  • gitee

为便于找到代码,程序文件名称最前面的编号与本系列文章的文档编号相同。

参考

  • Build a Question/Answering system over SQL data

🪐感谢您观看,祝好运🪐


http://www.niftyadmin.cn/n/5868906.html

相关文章

单片机裸机编程-时机管理

对于 RTOS 实时操作系统,我们是通过 TASK(任务)进行底层操作的,这与裸机编程中的函数(fun)类似。不同的任务或函数实现不同的功能,在RTOS中,单片机有信号量、队列等不同任务之间的通…

mac下载MAMP6.8.1

因为mac的小皮面板没有php7.4了 链接:c9cc270e6961c17c.dmg官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘 鹅选一 附上大佬写的教程:MAMP PRO教程 - 牛奔 - 博客园

【Go | 从0实现简单分布式缓存】-3:分布式节点通信

本文目录 一、通信流程二、peers.go三、http.go四、geecache.go五、测试代码 本文为极客兔兔动手写分布式缓存GeeCache学习笔记。 一、通信流程 在前面一节中,已经为 HTTPPool 实现了服务端功能,通信不仅需要服务端还需要客户端,因此本节来…

vscode设置自动换行

vscode设置自动换行 方法 方法 点击文件->首选项->设置。搜索word wrap -> 选择 on 。 搜索Word Wrap,并把选项改为on。

星海智算+ DeepSeek-R1:技术突破与行业应用的协同革新

一、前言 在当今数字化时代,人工智能(AI)正以前所未有的速度改变着商业和社会的方方面面。最近爆火的DeepSeek-R1系列模型,以其强大的推理能力和在中文的推理、代码和数学任务高效的性能得到了全球用户的热议。该模型不仅在多项专…

C++之string类的模拟实现(超详细)

们学习东西,先学习如果使用它,然后再学习如何实现它 文章目录 目录 1. 命名空间以及头文件 2.string类的成员变量 3.string类的成员函数 3.1 构造函数 3.2 析构函数 3.3 拷贝构造函数 3.4 赋值运算符重载 3.5 c_str函数 3.6 size函数 3.7 clea…

HTML5特殊字符

HTML中常用的特殊符号一般都以“&”开头,以“;”结束。

Java数据结构第十四期:走进二叉树的奇妙世界(三)

专栏:数据结构(Java版) 个人主页:手握风云 目录 一、二叉树OJ练习题 1.1. 相同的树 1.2. 另一棵树的子树 1.3. 翻转二叉树 1.4. 平衡二叉树 1.5. 对称二叉树 一、二叉树OJ练习题 1.1. 相同的树 判断两棵树是否相同,我们是否只能遍历一…