当前位置: 首页 > article >正文

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

本文将用 智能体(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.kler.cn/a/562163.html

相关文章:

  • kubernetes中pod spec设置的limits request会转换为哪些参数进行限制
  • [杂学笔记]OSI七层模型作用、HTTP协议中的各种方法、HTTP的头部字段、TLS握手、指针与引用的使用场景、零拷贝技术
  • 形式化数学编程在AI医疗中的探索路径分析
  • 爬虫运行后如何保存数据?
  • 【redis】数据类型之hyperloglog
  • 利用Java爬虫获取VIP商品详情实战案例指南
  • 如何使用深度学习进行手写数字识别(MNIST)
  • 基于Prometheus与Grafana构建实时监控与告警体系,保障微服务稳定性!
  • python如何去除列表末尾的None
  • Nmap网络安全审计
  • 超多目标优化:基于导航变量的多目标粒子群优化算法(NMOPSO)的无人机三维路径规划,MATLAB代码
  • https和http有什么区别
  • kotlin 知识点 七 泛型的高级特性
  • 【gitlab】认识 持续集成与部署
  • Java 值传递
  • python实现基于文心一言大模型的sql小工具
  • Unity Shader Graph 2D - Procedural程序化图形循环加载进度效果
  • 从零开始:使用PyTorch构建DeepSeek R1模型及其训练详解
  • 【愚公系列】《Python网络爬虫从入门到精通》034-DataFrame简单数据计算整理
  • PHP入门基础学习五(函数1)