DeepSeek + ReAct 实现 Agent

打印 上一主题 下一主题

主题 1002|帖子 1002|积分 3006

1. 大模型自身的范围

近来 DeepSeek 的爆火,再一次点燃了大家对于大模型的热情,身边许多一样平常不关注 AI 范畴的人,也开始交换 DeepSeek 的使用体验。DeepSeek 简直非常强盛,它将推理本领拉齐到了 GPT 水平的同时,还大幅降低了训练资源,成为当下最炙手可热的大模型
但是,即使像 DS 云云强盛的大模型,它也不是全能的,有一些问题它也回答不了,特别是一些垂直范畴的知识或实时性比较高的知识。比如我们想做一个居家生活小助手,直接调用 DeepSeek 是行不通的(未开启联网搜索的环境下)。

那么如何办理这个问题呢?每当我们遇到这种需要模型做自主判断、自行调用工具、自行决定下一步举措的时候,Agent 就轮到进场了。
Agent 概述

后面偶尔机我们会详细讲解下 AI Agent,这里简单理解: Agent 是一种基于 LLM 驱动的、可以或许自主感知四周环境、做出决定、采取举措达成特定目标的体系。一个 Agent 体系的整体架构如下:

一个 Agent 通常包罗了以下几个关键模块:


  • 规划(Planning):它负责将大目标分解成小的子目标,也可以对已有行为进行反思和自我改善。
  • 记忆(Memory):包罗短期记忆和恒久记忆,短期记忆提供上下文内的学习,恒久记忆则提供长时间保留和回忆信息的本领。
  • 工具(Tools):通过调用外部 API 获取外部信息(作为感知器),执行外部动作(作为执行器)。
Agent 看起来是一个非常复杂的体系,但是现实上有一种快速的实现方式,那就是 LLM + ReAct 框架。那么什么是 ReAct 呢?
ReAct 焦点原理

ReAct 现实上是两个单词的缩写:Reasoning + Acting,也就是推理 + 举措,它是一个引导大语言模型进行推理和举措的思维框架。在《ReAct: Synergizing Reasoning and Acting in Language Models》这篇论文中首次提出。
我们引用论文中的示例来解释一下。假设我们想要问大模型这样一个问题:
   除了苹果遥控器,另有哪些装备可以控制苹果遥控器最初计划用来交互的步调?Aside from the Apple Remote, what other devices can control the program Apple Remote was originally designed to interact with?
  使用 ReAct 框架,可以引导大模型进行如下的推理:

在这个例子中,大模型为了完成一个复杂任务时,首先会进行子任务拆分,且每个子任务的执行都会经过如下几个阶段:


  • Thought 思索:大模型根据任务进行思索和推理,订定执行计划。
  • Action 举措:大模型从可用的工具列表中筛选出可用的工具,执行详细的动作。
  • Observation 观察:动作执行完成后,由大模型观察执行效果,并判断是继续下一步动作,照旧执行结束返回效果。
理论好像大概相识了,但详细要如何实现呢?下面我们就基于 DeepSeek 大模型 + ReAct 框架,实现一个简单的 Agent,办理文章开头提出的那个查询水果价格的问题。
ReAct Agent 实战

申请 api_key

一步一步来。首先,我们需要搞定 DeepSeek 的 api_key。
近来 DeepSeek 实在太火了,由于算力和资源的限定,DeepSeek 的官方平台可能没有那么稳定,我们可以考虑使用大厂的云平台,比如阿里云百炼,使用阿里云私有部署的 DeepSeek 模型。详细的流程就不睁开了,大家按照官网的说明操作即可。

定义 Tools 工具

**工具本质上就是我们为大模型提供的扩展本领,它可以是一些 Open API(如 Google 搜索、高德天气等等),也可以是我们内部的一些函数,乃至是第三方的服务。**这里为了方便演示,我们实现一个本地的函数 query_fruit_unit_price 作为工具, 它以 Mock 的方式查询水果的价格。别的,我们也针对该工具编写标准的调用参数:
  1. # -*- coding: utf-8 -*-
  2. """
  3. @Time    : 2025/3/4 20:35
  4. @Author  : ZhangShenao
  5. @File    : tools.py
  6. @Desc    : 工具模块
  7. """
  8. def query_fruit_unit_price(fruit_name: str) -> str:
  9.     """
  10.     查询水果单价
  11.     :param fruit_name: 水果名称
  12.     :return: 水果单价
  13.     """
  14.     if fruit_name == "苹果":
  15.         return "2.8"
  16.     if fruit_name == "香蕉":
  17.         return "1.6"
  18.     return "未查询到该种类水果的价格"
  19. # 可以调用的外部工具描述
  20. TOOLS_DESCRIPTION = [
  21.     {
  22.         "name": "query_fruit_unit_price",
  23.         "description": "使用该工具可以查询到指定种类水果的单价",
  24.         "parameters": {
  25.             "type": "object",
  26.             "properties": {
  27.                 "fruit_name": {
  28.                     "type": "string",
  29.                     "description": "水果名称",
  30.                 }
  31.             },
  32.             "required": ["fruit_name"]
  33.         },
  34.     },
  35. ]
复制代码
构造 Promot

ReAct 的 Prompt 比较复杂,但是我们并不需要本身探索,因为 LangChain 官方的 Prompt Hub 中已经提供了一个 Prompt 模版,个人认为这是一段可以封神的 Promopt,我们直接拿来用就可以了
  1. {instructions}
  2. TOOLS:
  3. ------
  4. You have access to the following tools:
  5. {tools}
  6. To use a tool, please use the following format:
  7. ```
  8. Thought: Do I need to use a tool? Yes
  9. Action: the action to take, should be one of [{tool_names}]
  10. Action Input: the input to the action
  11. Observation: the result of the action
  12. ```
  13. When you have a response to say to the Human, or if you do not need to use a tool, you MUST use the format:
  14. ```
  15. Thought: Do I need to use a tool? No
  16. Final Answer: [your response here]
  17. ```
  18. Begin!
  19. Previous conversation history:
  20. {chat_history}
  21. New input: {input}
  22. {agent_scratchpad}
复制代码
这段 Prompt 中重要包罗下面这些关键内容:


  • instructions:类似 System Prompt,是为大模型设置的指令和人设。
  • tools:定义工具列表和描述信息,告诉大模型有哪些工具是可以使用的,而且详细的用法是什么。
  • chat_history:谈天历史,即对话的上下文信息。
  • agent_scratchpad:这是一个 Agent 剪贴板,用于记录 Agent 的思索过程。这部分是可选的,并不影响整个 Agent 的执行过程。
对这段 Prompt 进行格式化,就可以生成一个完备的 ReAct Prompt 了。
  1. # -*- coding: utf-8 -*-
  2. """
  3. @Time    : 2025/3/4 20:37
  4. @Author  : ZhangShenao
  5. @File    : prompt.py
  6. @Desc    : prompt提示词模块
  7. """
  8. from typing import List, Dict
  9. # ReAct Prompt模板
  10. REACT_PROMPT_TEMPLATE = """
  11. {instructions}
  12. TOOLS:
  13. ------
  14. You have access to the following tools:
  15. {tools}
  16. To use a tool, please use the following format:
  17. ```
  18. Thought: Do I need to use a tool? Yes
  19. Action: the action to take, should be one of [{tool_names}]
  20. Action Input: the input to the action
  21. ```
  22. Then wait for Human will response to you the result of action by use Observation.
  23. ... (this Thought/Action/Action Input/Observation can repeat N times)
  24. When you have a response to say to the Human, or if you do not need to use a tool, you MUST use the format:
  25. ```
  26. Thought: Do I need to use a tool? No
  27. Final Answer: [your response here]
  28. ```
  29. Begin!
  30. New input: {input}
  31. """
  32. def build_react_prompt(instructions: str, query: str, tool_desc: List[Dict], tool_name: str) -> str:
  33.     """
  34.     构造ReAct Prompt
  35.     :param instructions: 系统指令
  36.     :param query: 用户的提问
  37.     :param tool_desc: 外部工具描述
  38.     :param tool_name: 外部工具名称
  39.     :return: React Prompt
  40.     """
  41.     return REACT_PROMPT_TEMPLATE.format(instructions=instructions,
  42.                                         tools=tool_desc,
  43.                                         tool_names=tool_name,
  44.                                         input=query)
复制代码
封装 LLM

接下来,我们对大模型进行一些简单的封装,便于后面的调用。
  1. # -*- coding: utf-8 -*-
  2. """
  3. @Time    : 2025/3/4 20:45
  4. @Author  : ZhangShenao
  5. @File    : llm.py
  6. @Desc    : LLM大模型模块
  7. """
  8. import os
  9. from typing import List, Dict
  10. import dotenv
  11. from openai import OpenAI, Stream
  12. from openai.types.chat import ChatCompletion, ChatCompletionChunk
  13. class LLM:
  14.     def __init__(self, model_name: str):
  15.         """
  16.         初始化LLM大模型
  17.         :param model_name: LLM大模型名称
  18.         """
  19.         # 加载环境变量
  20.         dotenv.load_dotenv()
  21.         # 创建通义百炼客户端,兼容OpenAI协议
  22.         self._client = OpenAI(
  23.             api_key=os.getenv("DASHSCOPE_API_KEY"),
  24.             base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
  25.         )
  26.         self._model_name = model_name
  27.     def send_msg(self, messages: List[Dict]) -> ChatCompletion | Stream[ChatCompletionChunk]:
  28.         """
  29.         发送消息
  30.         :param messages: 消息列表
  31.         :return: 消息发送结果
  32.         """
  33.         return self._client.chat.completions.create(
  34.             model=self._model_name,
  35.             messages=messages,
  36.         )
复制代码
执行 Agent

  1. # -*- coding: utf-8 -*-
  2. """
  3. @Time    : 2025/3/4 20:45
  4. @Author  : ZhangShenao
  5. @File    : agent.py
  6. @Desc    : Agent模块
  7. """
  8. import json
  9. import re
  10. from llm import LLM
  11. from prompt import build_react_prompt
  12. from tools import TOOLS_DESCRIPTION, query_fruit_unit_price
  13. if __name__ == '__main__':
  14.     # 构造Prompt
  15.     instructions = "你是一个居家生活小助手,可以回答用户的日常问题。"
  16.     query = "我想买2个苹果和3根香蕉,一共需要多少钱?"
  17.     prompt = build_react_prompt(instructions=instructions,
  18.                                 query=query,
  19.                                 tool_desc=TOOLS_DESCRIPTION,
  20.                                 tool_name="query_fruit_unit_price",
  21.                                 )
  22.     # 创建LLM
  23.     llm = LLM(model_name="deepseek-v3")
  24.     # 保存上下文
  25.     print(f"初始提问: {prompt}")
  26.     messages = [{"role": "user", "content": prompt}]
  27.     # 执行ReAct过程
  28.     while True:
  29.         response = llm.send_msg(messages)
  30.         response_text = response.choices[0].message.content
  31.         print(f"大模型的回复:\n{response_text}")
  32.         # 通过正则表达式匹配,判断是否结束执行
  33.         final_answer_match = re.search(r'Final Answer:\s*(.*)', response_text)
  34.         if final_answer_match:
  35.             final_answer = final_answer_match.group(1)
  36.             print("最终答案:", final_answer)
  37.             break
  38.         # 保存上下文
  39.         messages.append(response.choices[0].message)
  40.         # 通过正则表达式匹配,解析Function Calling参数
  41.         action_match = re.search(r'Action:\s*(\w+)', response_text)
  42.         action_input_match = re.search(r'Action Input:\s*({.*?}|".*?")', response_text, re.DOTALL)
  43.         # 匹配需要调用的工具
  44.         if action_match and action_input_match:
  45.             tool_name = action_match.group(1)
  46.             params = json.loads(action_input_match.group(1))
  47.             print(f"需要执行Function Calling, 工具名称: {tool_name}, 调用参数: {params}")
  48.             # 调用工具,获取执行结果
  49.             if tool_name == "query_fruit_unit_price":
  50.                 observation = query_fruit_unit_price(params['fruit_name'])
  51.                 print(f"工具的执行结果: \n{observation}", )
  52.                 # 保存上下文
  53.                 messages.append({"role": "user", "content": f"Observation: {observation}"})
复制代码
可以看到完备执行过程:


从执行过程可以看出,大模型重要进行了以下的推理过程:

  • Planning:判断出需要调用工具。
  • Action:生成现实的 Function Calling 参数。
  • Observation: 工具调用完成后,观察执行效果,最终生成回答。
可以看出:基于 DeepSeek 模型强盛的推理本领,再联合 ReAct 框架的驱动,我们非常轻松就可以实现一个简单的 Agent。
最后,对于以上的实现做出一些提示:


  • 这里我们使用的是 DeepSeek-V3 这个 Chat Model,而并没有选择 DeepSeek-R1 这个 Reasoning Model,因为在我们的场景里,模型的推理本领是由 ReAct Prompt 驱动的,而 DeepSeek-R1 自身内置了思维链,可能与我们的 Prompt 产生冲突。
  • 最终的执行效果在不同的模型上可能有差异,特别是一些小参数模型,可能无法识别出工具调用,进而产生幻觉。
  • 在我们的实现中,工具参数调用的解析是基于正则表达式匹配来完成的,这种方式可能存在肯定的偏差,可以接纳大模型的 Function Calling 功能来优化,但是需要对 Prompt 进行一些改造,这个工作就留给大家来完成了。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

怀念夏天

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表