基于DeepSeek从零构建ReAct AI智能体
基于DeepSeek从零构建ReAct AI智能体
AI智能体在处理复杂任务和多步推理问题上发挥着关键作用。其中,ReAct AI智能体通过将逻辑推理与动态行动执行相结合,有效解决了传统AI系统在面对复杂问题时的困境。本文将深入探讨如何基于DeepSeek模型从零构建ReAct AI智能体,重点关注在不借助框架的情况下,如何实现记忆管理和工具运用。
一、ReAct AI智能体概述
传统AI系统在处理简单任务时表现出色,它按照预定义逻辑处理输入、分析数据并产生响应。然而,在面对复杂、多步且需要持续调整和外部交互的问题时,传统方法就显得力不从心。例如,回答“2024年国际足联金球奖得主出生城市的天气如何”这类问题,涉及到确定得主、找到其出生地以及查询该地天气等多个步骤,传统AI系统难以应对。
ReAct AI智能体则通过引入“思考→行动→观察”的循环模式,有效解决了上述问题。该概念在2022年的论文“ReAct: Synergizing Reasoning and Acting in Language Models”中被提出。在这个循环中,智能体首先对输入进行分析思考,确定所需采取的行动,然后执行该行动并观察结果,再根据观察结果进一步调整思考和行动,不断迭代直至得出最终答案。这种模式使智能体能够根据实时反馈灵活调整策略,更好地适应复杂多变的任务需求。
二、DeepSeek模型介绍
DeepSeek在近期因提供的大语言模型(LLMs)而备受关注。它具有性能出色、价格亲民、开源以及采用创新训练方法等优点。目前,DeepSeek平台提供了两种模型选项:DeepSeek-Chat和DeepSeek-Reasoner。
DeepSeek-Chat基于DeepSeek-V3模型,是一款通用型模型,可用于多种任务,与GPT-4o类似。而DeepSeek-Reasoner基于DeepSeek-R1模型,专门针对复杂推理任务进行了优化,在高级问题解决、编程和数学证明等方面表现卓越,与OpenAI的相关模型在高级推理和分析能力上形成竞争。
然而,DeepSeek模型也存在知识截止的问题。即便其在线平台提供了“搜索”功能来获取最新信息,但在未启用该功能时,模型无法知晓特定时间点之后发生的事件。比如,询问未开启搜索功能的DeepSeek“谁赢得了2024年F1世界锦标赛?”,它将无法给出答案。这一局限性凸显了为AI智能体集成外部工具以检索信息的重要性。
三、构建ReAct智能体的准备工作
(一)环境与工具搭建
为了让智能体能够在合适的环境中运行并使用外部工具,需要进行一系列的准备工作。首先定义一个抽象类BaseTool,其中包含抽象方法use。任何继承自BaseTool的工具类都需定义name和description这两个关键属性,用于帮助智能体理解工具的用途和操作,同时要实现use方法来定义工具的具体执行逻辑。
在本智能体的实现中,使用了三种工具:Wikipedia API、Web Search和Weather API。以Weather类为例,它继承自BaseTool,利用OpenWeather API来获取实时天气数据。在初始化时,从环境变量中加载API密钥,并设置基本的URL。use方法根据传入的城市名称构建请求URL,发送请求并处理响应。若请求成功,返回包含温度、天气状况、湿度和风速等信息的天气数据;若请求失败,则返回相应的错误信息。
(二)定义智能体类
智能体类Agent是整个系统的核心部分。在其构造函数中,进行了多项初始化操作。通过DeepInfra提供的API密钥,使用OpenAI API访问deepseek-ai/DeepSeek-V3模型。同时初始化了存储工具的字典tools、存储消息的列表messages,设置了最大迭代次数max_iterations,加载了系统提示system_prompt,以及为后续记忆管理组件准备的相关属性,如old_chats_summary、messages_to_summarize、max_messages_tokens、tokenizer和summary_prompt等。
Agent类中还定义了一系列方法。register_tool用于注册工具,add_message用于添加消息到聊天历史中,get_chat_history用于获取聊天历史并进行格式化处理,load_prompt用于加载提示文件等。而think、decide和act这三个方法是智能体执行逻辑的核心,它们协同工作,实现了“思考→行动→观察”的循环。
四、实现“思考 - 行动 - 观察”循环
(一)执行入口execute方法
execute方法是处理用户查询的入口。它首先将当前迭代次数重置为0,将用户消息添加到聊天历史中,然后调用think方法启动“思考→行动→观察”循环。在循环结束后,从聊天历史中提取并返回与本次请求相关的所有消息。
(二)思考与决策:think和decide方法
think方法负责与DeepSeek模型进行交互。它在每次调用时增加当前迭代次数,若超过最大迭代次数,则向聊天历史中添加提示信息并结束循环。在每次迭代中,根据系统提示和当前日期生成一个提示prompt,调用call_DeepSeek方法获取模型的响应,并将响应添加到聊天历史中作为assistant消息,随后调用decide方法进行分析。
decide方法用于解析模型的响应,判断其中是否包含最终答案或行动请求。如果包含最终答案,则结束当前处理流程;若包含行动请求,如“Action: weather: Paris”,则提取工具名称和查询内容,调用act方法执行相应的工具操作;如果既没有最终答案也没有行动请求,则输出提示信息。
(三)行动与观察:act方法
act方法根据decide方法的指令执行相应的工具操作。它首先检查请求的工具是否在已注册的工具列表中,若存在,则调用该工具的use方法,并将执行结果格式化为观察信息,添加到聊天历史中作为tool消息,然后再次调用think方法,继续下一轮循环;若工具未注册,则向聊天历史中添加错误信息。
在处理工具观察结果的消息角色时,需要特别注意。对于不同的模型,合适的消息角色有所不同。在使用DeepSeek模型时,使用tool或user角色较为合适。为了满足DeepSeek API对工具调用消息结构的要求,在将消息发送给模型时,若消息角色为tool,则生成一个随机的tool_call_id。
五、定义系统提示
系统提示在引导模型生成正确响应方面起着关键作用。采用few-shot prompting方法,即在提示中加入示例,帮助模型理解如何构建响应和管理工作流程。系统提示主要包含以下内容:
- 循环说明:明确智能体运行在“思考、行动、暂停、观察”的循环中,在循环结束时输出答案。
- 行动定义:指导模型如何使用“思考”来描述对问题的分析思路,使用“行动”来选择并执行可用的操作,然后返回“暂停”,等待观察结果。
- 工具介绍:向模型展示可用的工具及其描述,以便模型在需要时选择合适的工具。
- 规则设定:规定了一些特殊情况下的处理方式,例如当输入是问候或告别语时,直接友好回应,无需进入“思考 - 行动”循环;如果已经知道问题的部分或全部答案,则直接使用知识回答,无需依赖外部行动;若需要执行多个行动,则应分别进行调用;最后,必须提供最终答案。
- 日期信息:告知模型当前的日期,作为额外的参考信息。
例如,在“Who is Franco Colapinto?”这个示例中,模型根据提示进行思考,确定需要在Wikipedia上查找信息,执行相应的行动,等待观察结果,最终得出答案。
六、智能体测试
在完成智能体的核心逻辑和系统提示的定义后,需要对其进行测试,以确保在各种情况下都能正确响应。通过向智能体提出一系列不同类型的问题,观察其行为和回答是否符合预期。
- 简单推理测试:如“What is the weather in the capital of the country where Lionel Messi was born?”,智能体能够利用自身知识确定梅西出生于阿根廷,其首都为布宜诺斯艾利斯,然后直接使用天气工具查询该地天气,展示了智能体在处理常见事实时,能合理利用内部知识并在必要时调用外部工具的能力。
- 比较推理测试:对于“Where is colder at the moment, Madrid or Buenos Aires?”这个问题,智能体成功获取了马德里和布宜诺斯艾利斯的天气数据,并进行比较分析,得出正确结论,证明了其具备比较和综合分析多源数据的能力。
- 多步复杂推理测试:当面对“What is the weather in the city where the 2024 Ballon d’Or winner was born?”这类复杂问题时,智能体能够通过网络搜索确定2024年金球奖得主是Rodri,得知其出生于马德里后,再查询马德里的天气,准确地完成了多步推理和信息检索任务。
- 多部分问题测试:在回答“What is the weather in the city where the 2024 Ballon d’Or winner was born? and also tell me the weather in Buenos Aires”时,智能体不仅能处理复杂的多步推理,还能在获取马德里天气后,继续查询布宜诺斯艾利斯的天气,并将结果整合为一个连贯的最终答案,展示了其良好的上下文管理和多任务处理能力。
七、基于令牌使用的记忆总结
随着对话的进行,若每次都将完整的聊天历史发送给模型,虽然能保持上下文连续性,但会导致效率低下。因为大语言模型通过将输入分解为令牌(tokens)进行处理,且模型的上下文窗口有限,过多的令牌会增加成本。因此,需要优化令牌的使用,采用的策略是在令牌数量超过指定限制时,对旧消息进行总结并删除,以释放空间并保留关键上下文。
(一)理解令牌计数
不同的大语言模型使用不同的令牌化方式,消息中的角色标签和分隔符等元数据也会增加令牌数量。为了准确计算令牌数,通过定义num_tokens_from_content和num_tokens_from_messages两个函数进行对比。num_tokens_from_content仅计算消息内容的令牌数,而num_tokens_from_messages使用Transformer库中的apply_chat_template函数将消息转换为模型期望的格式后再计算令牌数。通过实验验证,使用apply_chat_template函数计算的令牌数与调用API时的实际令牌数更为吻合。
(二)添加记忆总结功能
在Agent类的构造函数中添加了与记忆管理相关的属性,如old_chats_summary用于存储旧消息的总结,max_messages_tokens设置最大令牌限制,messages_to_summarize指定需要总结的消息数量,summary_prompt加载总结提示模板,tokenizer用于令牌化操作。
memory_management方法负责监控聊天历史的令牌数。当令牌数超过限制且用户消息数量大于指定的需要总结的消息数量时,调用extract_first_queries方法提取最早的若干条用户消息及其相关的对话内容,然后使用summarize_old_chats方法调用模型对这些内容进行总结。若总结成功,则更新old_chats_summary,并删除被总结的消息。
在call_DeepSeek方法中,每次调用模型前先调用memory_management方法进行令牌数检查和处理。若存在旧消息总结,则将其添加到提示中,为模型提供更多上下文信息。
通过这种方式,智能体在处理长对话时变得更加高效,既能避免上下文窗口被旧消息填满,又能通过简洁的总结保留重要信息。
八、集成FastAPI和Streamlit
为了更方便地与智能体进行交互,将其与FastAPI和Streamlit进行集成。FastAPI作为现代Web框架,用于快速构建API,在本项目中充当后端,负责处理智能体的响应。Streamlit是一个开源的Python库,适合创建交互式Web应用程序,用于构建前端的聊天界面。
FastAPI的代码位于Agent/app.py文件中,Streamlit UI的代码定义在chat_ui.py文件中。Streamlit界面包含一个用户查询输入框、一个显示智能体最终答案的聊天显示区域以及一个侧边栏,用于展示智能体的最后推理过程(思考、行动和观察)。通过这种集成,用户可以在一个直观的界面中与智能体进行交互,查看智能体的推理过程和最终答案。
通过上述步骤,成功地基于DeepSeek从零构建了一个ReAct AI智能体,详细介绍了从环境搭建、工具集成、“思考 - 行动 - 观察”循环的实现、系统提示的设计、记忆管理到与FastAPI和Streamlit集成的全过程。在这个过程中,不依赖于LangChain等框架,深入理解了AI智能体的核心机制,对智能体的逻辑和执行流程拥有了完全的控制权。
git:https://github.com/Wencho8/ReAct-AI-Agent-from-Scratch-using-DeepSeek?tab=readme-ov-file