本次将使用
qwen2.5
、llama3.1
做实验。
准备
您可以按下面的步骤准备本地编程环境。
- 计算机
本文涉及的代码可以在没有显存的环境中执行。建议最低配置为:
- CPU: Intel i5-8400 2.80GHz
- 内存: 16GB
-
Visual Studio Code 和 venv
Visual Studio Code
是很受欢迎的开发工具,建议用venv
创建虚拟环境, 详见:
在Visual Studio Code中配置venv。 -
Ollama
基于Ollama
平台,我们可以在本地方便的使用llama3.1
、qwen2.5
、deepseek
等各种LLM(大语言模型)
。详见:
在langchian中使用本地部署的llama3.1大模型 。
SQLite__25">创建 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_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条 artists
和 albums
的信息,并将这些专有名字都存储在本地矢量数据库中了:
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` 工具查找过滤值!
不要试图猜测专有名词 - 使用此功能查找类似名称。
初始化智能体
我们将使用 langchain
的 create_react_agent
方法初始化 reAct
智能体。
React智能体(React Agent
)指的是一种能自主决策和执行任务的AI智能体,它结合了大语言模型(LLM)和工具调用,可以根据环境和任务需求动态调整自己的行为。
简单理解:
- React = 解释 + 计算(Reason + Act)
- 先分析当前的任务和数据(Reason)
- 然后做出相应的行动(Act)
- 如何工作?
- 先阅读输入信息
- 决定是否调用某个工具(如数据库查询、API 调用)
- 处理返回的结果,再次分析,继续执行任务
- 示例:
- 您输入:“明天的天气如何?”
- 智能体会先思考:“这个问题需要调用天气 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.5 | There 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.1 | There 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
🪐感谢您观看,祝好运🪐