本地大模型编程实战(24)用智能体(Agent)实现智能纠错的SQL数据库问答系统(3)

news/2025/2/27 6:14:48

本文将实现这样一个 智能体(Agent)

  • 可以使用自然语言对 SQLite 数据库进行查询。即:用户用自然语言提出问题,智能体也用自然语言根据数据库的查询结果回答问题。
  • 增加一个自动对查询中的专有名词进行纠错的工具,这将明显提升查询的准确性。

本次将使用 qwen2.5llama3.1 做实验。

文章目录

    • 准备
    • 创建 `SQLite` 对象
    • 智能体(Agent)
      • 创建 `SQLite` 工具
      • 创建专有名词矢量数据库
      • 创建检索工具
      • 系统提示词
      • 初始化智能体
    • 见证效果
    • 总结
    • 代码

准备

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

  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__25">创建 SQLite 对象

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

创建 Chinook.db 的详细步骤参见:用langchain实现基于SQL数据构建问答系统(1)

# 获取当前执行的程序文件的文件夹路径
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_41">智能体(Agent)

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

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

SQLite__50">创建 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

创建专有名词矢量数据库

我们打算实现这样一个功能:当查询语句中包含 艺术家(在表:artists中)或者 专辑(在表:albums中)这些专业术语时,需要自动检查拼写并自动纠正。
我们可以通过创建一个向量存储来实现这一点,该向量存储包含数据库中存在的所有不同专有名词。然后,我们可以让智能体在每次用户在问题中包含专有名词时查询该向量存储,以找到该单词的正确拼写。通过这种方式,智能体可以在构建目标查询之前确保它了解用户指的是哪个实体。

为此我们定义一个函数将SQLite查询出来的内容转换为列表:

import ast
import re

def query_as_list(db, query):
    res = db.run(query)
    res = [el for sub in ast.literal_eval(res) for el in sub if el]
    res = [re.sub(r"\b\d+\b", "", string).strip() for string in res]
    return list(set(res))

然后通过下面的函数创建本地矢量数据库:

def create_db():
    """创建矢量数据库"""

    if os.path.exists(persist_directory):
        print("数据库已创建")
        return

    artists = query_as_list(db, "SELECT Name FROM Artist")
    print(f'artists:\n{artists[:5]}\n') 
    albums = query_as_list(db, "SELECT Title FROM Album")
    print(f'albums:\n{albums[:5]}\n')

    documents = artists + albums
    embed_texts_in_batches(documents)
    print('db_artists_albums persisted.')

执行 create_db 后,将输出前5条 artistsalbums 的信息,并将这些专有名字都存储在本地矢量数据库中了:

artists:
['Mundo Livre S/A', 'Michele Campanella', 'Black Label Society', 'Jackson Browne', 'Nação Zumbi']

albums:
['Stormbringer', 'A Real Dead One', 'Revelations', 'Body Count', 'Sex Machine']

创建检索工具

准备好矢量数据库以后,我们就可以基于它创建检索器了,名字为 search_proper_nouns,这些我们检索5条信息使用:

retriever = vectordb.as_retriever(search_kwargs={"k": 5})   # 返回5条信息

from langchain.agents.agent_toolkits import create_retriever_tool
description = """Use to look up values to filter on. Input is an approximate spelling of the proper noun, output is \
valid proper nouns. Use the noun most similar to the search."""
retriever_tool = create_retriever_tool(
    retriever,
    name="search_proper_nouns",
    description=description,
)

系统提示词

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

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}

If you need to filter on a proper noun, you must ALWAYS first look up the filter value using the "search_proper_nouns" tool!
Do not try to guess at the proper name - use this function to find similar ones.""".format(
    table_names=db.get_usable_table_names()
)

system_message = SystemMessage(content=system)

上述提示词对大模型生成SQL语句的行为做了比较严格的限制,以防止生成错误的SQL破坏数据库。
可以看到这个提示词相对于上一篇文章的提示词在后面多了两句话:

如果您需要过滤专有名词,则必须始终先使用 `search_proper_nouns` 工具查找过滤值!
不要试图猜测专有名词 - 使用此功能查找类似名称。

初始化智能体

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

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

下面定义问答方法。到现在为止,智能体可以统筹使用两个工具了:

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

    tools = create_tools(llm_model_name)
    tools.append(retriever_tool)

    llm = ChatOllama(model=llm_model_name,temperature=1, 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("----")

见证效果

下面我们出3个问题,看看两个大模型的表现如何。这3个问题是:

    questions = [
        "How many Employees are there?",
        "Which country's customers spent the most?",
        "How many albums does Itzhak Perlmam have?",
    ]

对于前两个问题,我们在上一篇文章:用智能体(Agent)实现基于SQL数据构建问答系统(2)有详细的讨论,本次我们重点关注第3个问题。

在第3个问题中,出现了一个词 Itzhak Perlmam ,这是一个艺术家的名字,属于专有名词,我们查一下矢量数据库,看看和这名字相似的五个词:

print(retriever_tool.invoke("Itzhak Perlmam"))
Mela Tenenbaum, Pro Musica Prague & Richard Kapp
Itzhak Perlman
Felix Schmidt, London Symphony Orchestra & Rafael Frühbeck de Burgos
J.S. Bach: Chaconne, Suite in E Minor, Partita in E Major & Prelude, Fugue and Allegro
C. Monteverdi, Nigel Rogers - Chiaroscuro; London Baroque; London Cornett & Sackbu

从上面的记录可以看到:其中的 Itzhak Perlman 最接近我们要找的艺术家,我们用 qwen2.5 先试一下第3个问题,看看智能体能否正确的查出该艺术家拥有的专辑数量。

[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': 'search_proper_nouns', 'args': {'query': 'Itzhak Perlmam'}, ..., 'type': 'tool_call'}], ...]}}
----
{'tools': {'messages': [ToolMessage(content='Mela Tenenbaum, Pro Musica Prague & Richard Kapp\n\nItzhak Perlman\n\n...)]}}
----
{'agent': {'messages': [AIMessage(content='', ..., tool_calls=[{'name': 'sql_db_query', 'args': {'query': "SELECT COUNT(*) FROM Album WHERE Artist LIKE '%Itzhak Perlman%';"}, ..., 'type': 'tool_call'}], ...)]}}
----
{'tools': {'messages': [ToolMessage(content="Error: (sqlite3.OperationalError) no such column: Artist\n[SQL: SELECT COUNT(*) FROM Album WHERE Artist LIKE '%Itzhak Perlman%';]\n..., name='sql_db_query', ...)]}}
----
{'agent': {'messages': [AIMessage(content='', ... tool_calls=[{'name': 'sql_db_schema', 'args': {'table_names': 'Album, Artist'}, ..., 'type': 'tool_call'}], ...)]}}
----
{'tools': {'messages': [ToolMessage(content='\nCREATE TABLE "Album" (\n\t"AlbumId" INTEGER NOT NULL, \n\t"Title" NVARCHAR(160) NOT NULL, ..., name='sql_db_schema', ...)]}}
----
{'agent': {'messages': [AIMessage(content='', ..., tool_calls=[{'name': 'sql_db_query_checker', 'args': {'query': "SELECT COUNT(*) FROM Album WHERE Name LIKE '%Itzhak Perlman%';"}, ..., 'type': 'tool_call'}], ...)]}}
----
{'tools': {'messages': [ToolMessage(content="The provided SQL query does not contain any of the common mistakes listed. Here is the original query:\n\n```sql\nSELECT COUNT(*) FROM Album WHERE Name LIKE '%Itzhak Perlman%';\n```\n\nNo corrections are needed. The query is correct as it stands.", name='sql_db_query_checker', ...)]}}
----
{'agent': {'messages': [AIMessage(content='', ..., tool_calls=[{'name': 'sql_db_query', 'args': {'query': "SELECT COUNT(*) FROM Album WHERE Artist.Name LIKE '%Itzhak Perlman%';"}, ..., 'type': 'tool_call'}], ...)]}}
----
{'tools': {'messages': [ToolMessage(content="Error: (sqlite3.OperationalError) no such column: Artist.Name\n[SQL: SELECT COUNT(*) FROM Album WHERE Artist.Name LIKE '%Itzhak Perlman%';]\n..., name='sql_db_query', ...)]}}
----
{'agent': {'messages': [AIMessage(content='', ..., tool_calls=[{'name': 'sql_db_query', 'args': {'query': "SELECT COUNT(*) FROM Album INNER JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Artist.Name LIKE '%Itzhak Perlman%';"}, ..., 'type': 'tool_call'}], ...)]}}
----
{'tools': {'messages': [ToolMessage(content='[(1,)]', name='sql_db_query', ...)]}}
----
{'agent': {'messages': [AIMessage(content='The query executed successfully and returned a result. It shows that Itzhak Perlman has 1 album in the database.\n\nTherefore, the answer to your question is: Itzhak Perlman has 1 album.', ...)]}}
----

通过以上的输出我们发现:qwen2.5 在提示词的指导下,首先通过 search_proper_nouns 查出了 Itzhak Perlmam 应该为 Itzhak Perlman,然后利用 sql_db_schema 推测正确的SQL语句,尝试到第3次终于推理出了正确的SQL语句,并给出了正确的回答。很棒!

我用 llama3.1 对第3个问题试了3次,每次推理的过程都不同,不过3次都未能推理出正确的SQL语句,所以没能给出正确回答。

下面我们用这3个问题,试试这两个模型:

How many Employees are there?Which country’s customers spent the most?How many albums does Itzhak Perlmam have?
qwen2.5There are 8 employees in the database.The country with the highest spending by customers is USA,…Itzhak Perlman has 1 album in the database.
llama3.1There are 8 Employees.The customers who spent the most were from the USA.未给出正确回答

总结

我们这次实现了一个可以调度 查询SQLite数据库智能纠错专有名词 两个工具的智能体(Agent)。
我们发现:qwen2.5 是可以智能的调度这两个工具完成任务的,能力很强!

代码

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

  • github
  • gitee

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

参考

  • Build a Question/Answering system over SQL data

🪐感谢您观看,祝好运🪐


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

相关文章

CAS (Compare and swap “比较和交换“) [ Java EE 初阶 ]

目录 什么是 CAS CAS 是怎么实现的 CAS 有哪些应用 1. 实现原子类 2. 实现自旋锁 3. CAS 的 ABA 问题 什么是 CAS // 能够比较和交换某个寄存器中的值和内存中的值, 看是否相等, 如果相等, 则把另一个寄存器中的值和内存中的值进行交换 // CAS伪代码 boolean CAS(addres…

微信小程序数据缓存与本地存储:优化用户体验

在前几篇文章中,我们学习了微信小程序的基础知识、数据绑定、事件处理、页面导航与路由、网络请求与API调用以及组件封装与复用。这些知识帮助我们构建了具备基本功能的小程序。然而,在实际开发中,如何高效地管理数据、提升用户体验是一个重要课题。本文将深入探讨微信小程序…

大白话css第一章基础入门

大白话css第一章基础入门 了解CSS基本概念 CSS是干啥的:就好比你装修房子,HTML呢是把房子的框架、房间布局都搭好了,像客厅、卧室、厨房都有了,但是房子里面空空的,啥都没装饰。这时候CSS就上场啦,它就是…

【Win10】Anaconda + Pycharm 环境搭建教程

一、 Anaconda 安装包下载 1. Anaconda官方 https://www.anaconda.com/ 下载较慢, 页面直观 2. 清华镜像站 https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ 二、 Pycharm 安装包下载 https://www.jetbrains.com/pycharm/ 进入官网后,点击此处的Do…

Pytorch实现之脑电波图像生成

简介 简介:采用双GAN模型架构来生成脑电波与目标图像。 论文题目:Image Generation from Brainwaves using Dual Generative Adversarial Training(使用双生成对抗训练的脑电波图像生成) 会议:IEEE Global Conference on Consumer Electronics (GCCE) 摘要:表示通过无…

数据存储:一文掌握存储数据到mysql的详细使用

文章目录 一、环境准备1.1 安装MySQL数据库1.2 安装Python MySQL驱动 二、连接到MySQL数据库三、执行基本的CRUD操作3.1 创建(Create):插入数据3.2 读取(Read):查询数据3.3 更新(Update&#xf…

FPGA开发时序图绘制

开始的时候画时序图都是拿 visio 硬连,但是那个线宽太难统一了,丑不拉几的,遂学习 waveform 语法使用代码来画时序图。 开始 Vscode 中安装 waveform render 或者在 GitHub 搜索 wavedrom 安装即可。由于 vscode 是我常用的编辑器&#xff…

将pcap数据包的packet转成文本的几种方法

本文将介绍一下通过手动和自动化的方法将pcap数据包的packet导出为hex dump,json这两种形式。 有多种形式的文本格式可以用来用来表示pcap数据内容,例如K12, base64,hex dump,json,csv。但是应用最为广泛的形式为hex …