文章目录
在前面的文章中,我们了解了 LLM(大语言模型)
如何使用 tool(工具)
,现在我们开始进一步,初探 Agent(智能体)
的玩法。
其中的 tool(工具)
用于从矢量数据库中查询信息,这种方式在 RAG(Retrieval Augmented Generation,检索增强生成)
也很常用。在这种场景中,能否准确的查询出有用信息很关键,为此我们本次将使用多种大模型进行对比演练。包括:
- 多语言通用
LLM
:llma3.1
,deepseek-r1
,qwen2.5
- 专用于嵌入检索的大模型:
shaw/dmeta-embedding-zh
,milkey/m3e
,mxbai-embed-large
,nomic-embed-text
,all-minilm:33m
了解 Agent(智能体)
tool(工具)
与 Agent(智能体)
的区别
工具往往用于大语言模型调用其它功能,比如:搜索、数据库查询、计算等;Agent(智能体)
是一个可以使用 LLM
处理复杂任务的智能体;它通常会决定:
- 何时调用
tool(工具)
- 选择哪个工具
- 处理工具返回的结果
ReAct(Reasoning + Acting)
ReAct(Reasoning + Acting)
是一种用于 Agent(智能体)
的决策方式,它结合了推理(Reasoning)和行动(Acting),让智能体能更灵活地思考和执行任务。
简单来说,ReAct
让智能体在做事情之前,先思考一下,然后再决定下一步行动,而不是盲目执行。
ReAct
的核心思路
- 观察环境(Observations):获取当前任务或问题的信息。
- 推理(Reasoning):分析当前信息,思考如何解决问题。
- 执行行动(Acting):基于推理结果,采取具体的行动(比如调用工具、查询数据库、与用户交互等)。
- 循环执行:智能体会不断重复观察 → 推理 → 行动的过程,直到任务完成。
关于 ReAct
的更多内容,请参阅:ReACT Agent Model
准备
在正式开始撸代码之前,需要准备一下编程环境。
-
计算机
本文涉及的所有代码可以在没有显存的环境中执行。 我使用的机器配置为:- CPU: Intel i5-8400 2.80GHz
- 内存: 16GB
-
Visual Studio Code 和 venv
这是很受欢迎的开发工具,相关文章的代码可以在Visual Studio Code
中开发和调试。 我们用python
的venv
创建虚拟环境, 详见:
在Visual Studio Code中配置venv。 -
Ollama
在Ollama
平台上部署本地大模型非常方便,基于此平台,我们可以让langchain
使用llama3.1
、qwen2.5
、deepseek
等各种本地大模型。详见:
在langchian中使用本地部署的llama3.1大模型 。
建立矢量数据库
我们这里收集了一些动物的信息,并定义了通用的处理本地矢量数据的方法,方便后面对不同的模型进行对比测试。
将文本转化成矢量的过程称之为 嵌入 ,语义相近的文本转化的矢量之间的空间距离较短,所以在做矢量检索时,可以根据语义而不是关键词来查找相近的结果。
数据文件
我们这里使用一个简单的 csv 格式的数据文件,每一行是一种动物特征的说明,格式如下:
名称,学名,特点,作用
狗,Canis lupus familiaris,忠诚、聪明、社交性强,看家护院、导盲、搜救、警务、情感陪伴
猫,Felis catus,独立、高冷、善于捕鼠,消灭害鼠、陪伴、缓解压力
处理本地矢量库的类
这里简单封装了 Chroma
对文本进行嵌入处理的方法,供参考:
python">class LocalVectorDBChroma:"""使用Chroma在本地处理适量数据库"""def __init__(self,model_name,persist_directory,delimiter = ","):self._embedding = OllamaEmbeddings(model=model_name)self._persist_directory = persist_directoryself._delimiter = delimiterdef get_vector_store(self):return Chroma(persist_directory=self._persist_directory,embedding_function=self._embedding)def embed_documents_in_batches(self,documents,batch_size=3):"""按批次嵌入,可以显示进度。vectordb会自动持久化存储在磁盘。"""vectordb = Chroma(persist_directory=self._persist_directory,embedding_function=self._embedding)for i in tqdm(range(0, len(documents), batch_size), desc="嵌入进度"):batch = documents[i:i + batch_size]# 从文本块生成嵌入,并将嵌入存储在本地磁盘。vectordb.add_documents(batch)def embed_csv(self,src_file_path):loader = CSVLoader(file_path=src_file_path,csv_args={"delimiter": self._delimiter},autodetect_encoding=True)docs = loader.load()# 用于将长文本拆分成较小的段,便于嵌入和大模型处理。 text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)"""chunk_size: 每个文本块的最大长度/字符数chunk_overlap: 拆分的文本块之间重叠字符数"""texts = text_splitter.split_documents(docs) # 耗时较长,需要耐心等候...self.embed_documents_in_batches(texts)
建议切分文本的最小块长度大概应该是语义最聚焦的文字段落,在本文使用的 csv 文件中,每一行描述的是一种动物,其语义很集中,而且不到100个字。所以干脆就设定: chunk_size=100 。大概相当于没有切分,哈哈:)
嵌入文本
通过下面的方法,我们生成嵌入,并且将矢量数据存储在本地:
python">from common.MyVectorDB import LocalVectorDBChroma
def create_db(model_name): """生成本地矢量数据库"""persist_directory = get_persist_directory(model_name)if os.path.exists(persist_directory):returndb = LocalVectorDBChroma(model_name,persist_directory) db.embed_csv(src_file_path)
创建 Agent智能体
下面我们利用创建好的矢量数据库的内容,来回答问题。
该 Agent(智能体)
运行起来后,首先生成 tool_calls
其中包含检索参数,然后去数量数据库中检索出最相似的内容,最后基于检索出来的内容生成流畅的回答。
python">def ask_agent(embed_model_name,chat_modal_name,query):"""测试智能体"""persist_directory = get_persist_directory(embed_model_name)db = LocalVectorDBChroma(embed_model_name,persist_directory)# 基于Chroma 的 vector store 生成 检索器vector_store = db.get_vector_store()retriever = vector_store.as_retriever(search_type="similarity",search_kwargs={"k": 2},)# 将 检索器 包装为 工具tools = [retriever.as_tool(name="animal_info_retriever",description="查询动物的信息",)]llm = ChatOllama(model=chat_modal_name,temperature=0.1,verbose=True)agent = create_react_agent(llm, tools)# 显示智能体的详细内容for chunk in agent.stream({"messages": [("human", query)]}):print(chunk)print("----")
经过上述代码处理,检索矢量数据库 成为一个 工具,该工具的控制权交给了 Agent(智能体)
。
显然,这个智能体可以执行
RAG(Retrieval Augmented Generation,检索增强生成)
任务。
测试
我使用了不同的模型对该智能体进行了测试,使用 shaw/dmeta-embedding-zh
做嵌入和检索,使用 qwen2.5
做 Agent(智能体)
效果貌似最好。
最近火出圈的
deepseek
与llama3.1
和qwen2.5
一样,不擅长做文本嵌入和检索,所以效果差;并且langchain
对它的支持不好,执行 create_react_agent 时出错。
定义测试方法
python">def test_model(embed_model_name,chat_modal_name):print(f'\n---------------------{embed_model_name}-----------------------------')create_db(embed_model_name)query = "猪的学名是什么?它对人类有什么用处?"ask_agent(embed_model_name,chat_modal_name,query)query = "蜜蜂的特点是什么?它对人类社会有什么作用?"ask_agent(embed_model_name,chat_modal_name,query)
使用 shaw/dmeta-embedding-zh
和 qwen2.5
看看输出内容:
- 针对第一个问题:“猪的学名是什么?它对人类有什么用处?”的输出:
{'agent': {'messages': [AIMessage(content='', additional_kwargs={}, response_metadata={'model': 'qwen2.5', 'created_at': '2025-02-11T08:50:43.2931377Z', 'done': True, 'done_reason': 'stop', 'total_duration': 3300051500, 'load_duration': 2325341200, 'prompt_eval_count': 170, 'prompt_eval_duration': 227000000, 'eval_count': 25, 'eval_duration': 409000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-69b92604-f8b4-47c1-b87b-b8fc1c80aa90-0', tool_calls=[{'name': 'animal_info_retriever', 'args': {'__arg1': '猪'}, 'id': '2fcf0f7d-7742-4e78-bc3a-198c17c2f6a7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 170, 'output_tokens': 25, 'total_tokens': 195})]}}
----
{'tools': {'messages': [ToolMessage(content="[Document(id='ce2ce384-0af6-4f18-a2b5-2c3a9e4efc6d', metadata={'row': 5, 'source': 'D:\\\\project\\\\programming-with-local-large-language-model\\\\server\\\\services\\\\practice\\\\assert/animals.csv'}, page_content='名称: 猪\\n学名: Sus scrofa domesticus\\n特点: 智商高,好奇心强,适应能力强\\n作用: 主要肉类来源,生物医学研究(猪心脏瓣膜移植)'), Document(id='c21cf91a-73d8-470f-a10a-2b297c37bd13', metadata={'row': 6, 'source': 'D:\\\\project\\\\programming-with-local-large-language-model\\\\server\\\\services\\\\practice\\\\assert/animals.csv'}, page_content='名 称: 鸡\\n学名: Gallus gallus domesticus\\n特点: 适应性强、繁殖快、能为人类提供大量食物\\n作用: 鸡蛋、鸡肉是全球最主要的蛋白质来源之一')]", name='animal_info_retriever', id='0c7a7414-94ab-4197-b48a-b87035adea92', tool_call_id='2fcf0f7d-7742-4e78-bc3a-198c17c2f6a7')]}}
----
{'agent': {'messages': [AIMessage(content='猪的学名为 Sus scrofa domesticus。猪对人类有很多用处,它们不仅是主要的肉类来源,还被用于生物医学研究中,例如猪心脏瓣膜移植等。', additional_kwargs={}, response_metadata={'model': 'qwen2.5', 'created_at': '2025-02-11T08:50:45.5121761Z', 'done': True, 'done_reason': 'stop', 'total_duration': 1031134000, 'load_duration': 39932600, 'prompt_eval_count': 451, 'prompt_eval_duration': 48000000, 'eval_count': 42, 'eval_duration': 932000000, 'message': Message(role='assistant', content='猪的学名为 Sus scrofa domesticus。猪对人类有很多用处,它们不仅是主要的肉类来源,还被用于生物医学研究中,例如猪心脏瓣膜移植等。', images=None, tool_calls=None)}, id='run-9f3aa665-9fd8-4bdb-8f5e-9c5ebca0a3de-0', usage_metadata={'input_tokens': 451, 'output_tokens': 42, 'total_tokens': 493})]}}
----
在第1行, 智能体推理出 tool_calls
内容为:
[{'name': 'animal_info_retriever', 'args': {'__arg1': '猪'}, 'id': '2fcf0f7d-7742-4e78-bc3a-198c17c2f6a7', 'type': 'tool_call'}]
可见:
- 针对第二个问题:“蜜蜂的特点是什么?它对人类社会有什么作用?”的输出:
{'agent': {'messages': [AIMessage(content='', additional_kwargs={}, response_metadata={'model': 'qwen2.5', 'created_at': '2025-02-11T08:50:49.2508879Z', 'done': True, 'done_reason': 'stop', 'total_duration': 2965904700, 'load_duration': 28819600, 'prompt_eval_count': 168, 'prompt_eval_duration': 30000000, 'eval_count': 155, 'eval_duration': 2899000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-e4bb705d-1849-4ab7-968a-a78016266a36-0', tool_calls=[{'name': 'animal_info_retriever', 'args': {'__arg1': '蜜蜂'}, 'id': '640ef103-9ff9-4b17-b399-fc5df958a572', 'type': 'tool_call'}], usage_metadata={'input_tokens': 168, 'output_tokens': 155, 'total_tokens': 323})]}}
----
{'tools': {'messages': [ToolMessage(content="[Document(id='3f129e89-e9b5-4c4a-a626-f71773630bae', metadata={'row': 8, 'source': 'D:\\\\project\\\\programming-with-local-large-language-model\\\\server\\\\services\\\\practice\\\\assert/animals.csv'}, page_content='名称: 蜜蜂\\n学名: Apis mellifera\\n特点: 勤劳、复杂的社会结构,对生态系统至关重要\\n作用: 授粉(维持全球农业)、蜂蜜生产、人类健康(蜂胶、蜂毒疗法)'), Document(id='20c7070c-f9ef-4441-834f-08d3ed8900d0', metadata={'row': 9, 'source': 'D:\\\\project\\\\programming-with-local-large-language-model\\\\server\\\\services\\\\practice\\\\assert/animals.csv'}, page_content='名称: 老鼠\\n学名: Rattus spp.\\n特点: 繁殖快,适应力极强,人类生活的“影子伙伴”\\n作用: 害虫(传播疾病)、科学研究的重要实验动物(医学、心理学)')]", name='animal_info_retriever', id='6acbb57a-4115-46ff-97ad-c0b7bdca7b80', tool_call_id='640ef103-9ff9-4b17-b399-fc5df958a572')]}}
----
{'agent': {'messages': [AIMessage(content='根据提供的信息,蜜蜂具有以下特点和作用:\n\n**特点:**\n- 勤劳:蜜蜂是非常勤劳的昆虫。\n- 复杂的社会结构:蜜蜂有着复杂的社会组织形式。\n- 对生态系统至关重要:蜜蜂在生态系统中扮演着重要角色。\n\n**作用:**\n- 授粉(维持全球农业):蜜蜂是重要的授粉者,对农业生产具有重要作用。\n- 蜂蜜生产:蜜蜂可以产生蜂蜜等产品供人类食用和使用。\n- 人类健康(蜂胶、蜂毒疗法):蜜蜂的 副产品如蜂胶和蜂毒在传统医学中被用于治疗某些疾病。\n\n请注意,返回的信息中也列出了老鼠的相关信息,但您似乎对蜜蜂更感兴趣。如果您需要更多关于蜜蜂的信息或其他问题,请随时告诉我!', additional_kwargs={}, response_metadata={'model': 'qwen2.5', 'created_at': '2025-02-11T08:50:52.8271688Z', 'done': True, 'done_reason': 'stop', 'total_duration': 3509658400, 'load_duration': 55295500, 'prompt_eval_count': 467, 'prompt_eval_duration': 45000000, 'eval_count': 166, 'eval_duration': 3382000000, 'message': Message(role='assistant', content='根据提供的信息,蜜蜂具有以下特点和作用:\n\n**特点:**\n- 勤劳:蜜蜂是非常勤劳的 昆虫。\n- 复杂的社会结构:蜜蜂有着复杂的社会组织形式。\n- 对生态系统至关重要:蜜蜂在生态系统中扮演着重要角色。\n\n**作用:**\n- 授粉(维持全球农业):蜜蜂是重要的授粉者,对农业生产具有重要作用。\n- 蜂蜜生 产:蜜蜂可以产生蜂蜜等产品供人类食用和使用。\n- 人类健康(蜂胶、蜂毒疗法):蜜蜂的副产品如蜂胶和蜂毒在传统医学中被用于治疗某些疾病。\n\n请注意,返回的信息中也列出了老鼠的相关信息,但您似乎对蜜蜂更感兴趣。 如果您需要更多关于蜜蜂的信息或其他问题,请随时告诉我!', images=None, tool_calls=None)}, id='run-611234fd-a1a9-4613-916c-0885228e9cbd-0', usage_metadata={'input_tokens': 467, 'output_tokens': 166, 'total_tokens': 633})]}}
----
显然,这一系列操作也很到位。
文本嵌入和检索
经过简单测试, llama3.1
和 qwen2.5
驱动 智能体 都没有问题:它们都可以准确的推理出 tool_calls
,并可以妥善的组织语言回复。
主要的差别在于 嵌入 和 检索 的能力,我们分别跑一下这下模型,看看检索结果:
猪的学名是什么?它对人类有什么用处? | 蜜蜂的特点是什么?它对人类社会有什么作用? | |
---|---|---|
shaw/dmeta-embedding-zh | 猪、鸡 | 蜜蜂、老鼠 |
milkey/m3e | 猪、羊 | 蜜蜂、猫 |
mxbai-embed-large | 猪、老鼠 | 老鼠、蜜蜂 |
nomic-embed-text | 鸡、牛 | 鸡、牛 |
all-minilm:33m | 马、猫 | 马、蜜蜂 |
llama3.1 | 猪、大象 | 猪、蜜蜂 |
qwen2.5 | 猪、猫 | 猪、大象 |
经过上述简单对比,可见 shaw/dmeta-embedding-zh
和 milkey/m3e
做中文嵌入和检索最靠谱。
总结
通过上述编程演练,我们创建了一个可以查询本地知识库的智能体,这种方式也特别适合实现 RAG(Retrieval Augmented Generation,检索增强生成)
系统。
在本系统中,提供高质量回复的基础是能够根据语义准确的从矢量数据库中找到信息,我们发现,不同的模型在文本嵌入和矢量数据查询方面表现差别较大。
代码
本文涉及的所有代码以及相关资源都已经共享,参见:
- github
- gitee
为便于找到代码,程序文件名称最前面的编号与本系列文章的文档编号相同。
🪐感谢您观看,祝好运🪐