一、 概述
Building agents with LLM (large language model) as its core controller is a cool concept.
作为追随者,不妨简单花一个上午的时间,更系统的搞定到底什么是 AI Agent。
二、 什么是 AI Agent?
Agent 是指能自主感知环境并采取行动实现目的的智能体。它不是一个新概念,在2016年,横空出世的AlphaGo就是一个典型的Agent,只不过之前的Agent,更多还是以强化学习 RL 为主。
LLM 盛行后对 Agent 概念带来了翻天覆地的变化,通过 prompt LLM 可以直接理解目的并据此执行,这个从理论上是可以解决大量 RL 训练的问题,直观上也更符合对 Agent 的预期和判断。
在基于 LLM 的智能体中,LLM 的充当着智能体的“大脑”的角色,同时还有 3 个关键部分:
规划(Planning) : 智能体会把大型任务分解为子任务,并规划执行任务的流程;智能体会对任务执行的过程进行思考和反思,从而决定是继续执行任务,或判断任务完结并终止运行。
记忆(Memory): 短期记忆,是指在执行任务的过程中的上下文,会在子任务的执行过程产生和暂存,在任务完结后被清空。长期记忆是长时间保留的信息,一般是指外部知识库,通常用向量数据库来存储和检索。
工具使用(Tool Use) :为智能体配备工具 API,比如:计算器、搜索工具、代码执行器、数据库查询工具等。有了这些工具 API,智能体就可以是物理世界交互,解决实际的问题。
三、 AI Agent 的具体实现
我们再把刚刚的图示搬过来
3.1. 规划(Planing)
规划,可以为理解观察和思考。如果用人类来类比,当我们接到一个任务,我们的思维模式可能会像下面这样:
我们首先会思考怎么完成这个任务。
然后我们会审视手头上所拥有的工具,以及如何使用这些工具高效地达成目的。
我们会把任务拆分成子任务(就像我们会使用 Aone 做任务拆分)。
在智能体中,最重要的是让 LLM 具备这以下两个能力:
3.1.1 子任务分解
通过 LLM 使得智能体可以把大型任务分解为更小的、更可控的子任务,从而能够有效完成复杂的任务。
3.1.2 思维链(Chain of Thoughts, CoT)
思维链已经是一种比较标准的提示技术,能显著提升 LLM 完成复杂任务的效果。当我们对 LLM 这样要求「think step by step」,会发现 LLM 会把问题分解成多个步骤,一步一步思考和解决,能使得输出的结果更加准确。这是一种线性的思维方式。
思维链的 prompt 可以像是如下这样(这里只是一个极简的 prompt,实际会按需进行 prompt 调优):
template="Answer the question: Q: {question}? Let's think step by step:"
3.1.3 思维树(Tree-of-thought, ToT)
对 CoT 的进一步扩展,在思维链的每一步,推理出多个分支,拓扑展开成一棵思维树。使用启发式方法评估每个推理分支对问题解决的贡献。选择搜索算法,使用广度优先搜索(BFS)或深度优先搜索(DFS)等算法来探索思维树,并进行前瞻和回溯。
3.1.4 反思和完善
智能体在执行任务过程中,通过 LLM 对完成的子任务进行反思,从错误中吸取教训,并完善未来的步骤,提高任务完成的质量。同时反思任务是否已经完成,并终止任务。
通过巧妙的 promt 提示设计,使得 LLM 重复地执行推理和行动,最终完成任务。当然这里业内主流也有两种方式单 Agent 和 多 Agent ,更多相间第四节内容。
如下是一个大致的 ReAct 的 prompt 模版
Thought(思考): ... Action(行动): ... Observation(观察): ... Thought(思考): ... Action(行动): ... Observation(观察): ... ...(Repeated many times(重复多次))
3.2. 记忆(Memory)
记忆是什么?当我们在思考这个问题,其实人类的大脑已经在使用记忆。记忆是大脑存储、保留和回忆信息的能力。
智能体实现了两种记忆机制:
3.2.1. 短期记忆
在当前任务执行过程中所产生的信息,比如某个工具或某个子任务执行的结果,会写入短期记忆中。记忆在当前任务过程中产生和暂存,在任务完结后被清空。
3.2.2. 长期记忆
长期记忆是长时间保留的信息。一般是指外部知识库,通常用向量数据库来存储和检索。
3.3. 工具使用(Tool use)
LLM 是数字世界中的程序,想要与现实世界互动、获取未知的知识,或是计算某个复杂的公式等,都离不开不工具。所以我们需要为智能体配备各种工具以及赋予它使用工具的能力。
工具是什么?它可以是锤子、螺丝刀,也可以是函数(Function)、软件开发工具包(sdk)。工具是人类智慧的具象化,扩展我们的能力,提升工作效率。在智能体中,工具就是函数(Function),工具使用就是调用函数(Call Function)。
3.3.1 什么是 Function Calling?
Function Calling 是一种实现大型语言模型连接外部工具的机制。通过 API 调用 LLM 时,调用方可以描述函数,包括函数的功能描述、请求参数说明、响应参数说明,让 LLM 根据用户的输入,合适地选择调用哪个函数,同时理解用户的自然语言,并转换为调用函数的请求参数(通过 JSON 格式返回)。调用方使用 LLM 返回的函数名称和参数,调用函数并得到响应。最后,如果需求,把函数的响应传给 LLM,让 LLM 组织成自然语言回复用户。
下图是 OpenAI 针对 Function Calling 流程介绍
我们将其简化下得到如下这张图:
Function Calling 可以做任何事,譬如:
获取内部数据:当用户询问“我最近的订单是什么?”时,LLM 可以从内部系统获取最新的客户数据,然后向用户生成响应
定制行程及活动:LLM 可根据需用户偏好和日历安排会议
进行数学计算:可根据需要进行数学计算
构建工作流程:数据提取管道,获取原始文本,然后将其转换为结构化数据并将其保存在数据库中
应用程序的 UI:可以使用根据用户输入更新 UI,例如在地图上渲染位置
3.3.2 如何使用 Function Calling?
我们以“让 LLM 查询天气” 的场景为例,展示下 Function Calling 在其中的使用
假设你的天气函数已经被实现,我们需要向 LLM 描述这个函数,函数描述的必备要素:
函数名 函数的功能描述 函数的请求参数说明 函数的响应参数说明(可选)
「查询最近天气」的函数描述:
tools = [ { "type": "function", "function": { "name": "get_n_day_weather_forecast", "description": "获取最近n天的天气预报", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "城市或镇区 如:深圳市南山区", }, "format": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "要使用的温度单位,摄氏度 or 华氏度", }, "num_days": { "type": "integer", "description": "预测天数", } }, "required": ["location", "format", "num_days"] }, } } ]
调用 LLM 获得函数的请求参数
Function Calling 是通过请求 LLM 的 Chat API 实现的,在支持 Function Calling 模型的 Chat API 参数中,会有一个 Functions 参数 (或 tools,不同 LLM 的参数会有所不同) ,通过传入这个参数,大模型则会知道拥有哪些参数可供使用。并且会根据用户的输入,推理出应该调用哪些函数,并将自然语言转成函数的请求参数,返回给请求方。下面以 OpenAI 的 SDK 举例:
from openai import OpenAI def chat_completion_request(messages, tools=None, tool_choice=None, model="gpt-3.5-turbo"): try: response = client.chat.completions.create( model=model, messages=messages, tools=tools, tool_choice=tool_choice, ) return response except Exception as e: print("Unable to generate ChatCompletion response") print(f"Exception: {e}") return e if __name__ == "__main__": messages = [] messages.append({"role": "system", "content": "不要假设将哪些值输入到函数中。如果用户请求不明确,请要求澄清"}) messages.append({"role": "user", "content": "未来5天深圳南山区的天气怎么样"}) chat_response = chat_completion_request( messages, tools=tools ) tool_calls = chat_response.choices[0].message.tool_calls print("===回复===") print(tool_calls)
LLM 将会返回 get_n_day_weather_forecast 函数的调用参数:
===回复=== [ChatCompletionMessageToolCall(id='call_JKSdaKINDE12', function=Function(arguments='{"location":"深圳市南山区","format":"celsius","num_days":5}', name='get_n_day_weather_forecast'), type='function')] // 格式化看看:chat_response.choices[0].message.tool_calls: [ { "id": "call_JKSdaKINDE12", "function": { "arguments": { "location": "深圳市南山区", "format": "celsius", "num_days": 5 }, "name": "get_n_day_weather_forecast" }, "type": "function" } ]
调用函数
调用方获得 LLM 返回的函数调用信息(函数名称和调用参数)后,自行调用函数,并得到函数执行的响应。如果有需要,还可以把函数执行的响应追加到 chat API 的对话中传给 LLM,让 LLM 组织成自然语言回复用户。
# 执行函数 for tool_call in tool_calls: function = tool_call.function.name arguments_list = json.loads(tool_call.function.arguments) function_to_call = globals().get(function) result = function_to_call(**arguments_list) print("===" + function + "===") print(result) # 把函数调用结果加入到对话历史中 messages.append( { "tool_call_id": tool_call.id, # 用于标识函数调用的 ID "role": "user", "name": function, "content": "函数执行结果为:" + str(result) } ) # 函数执行结果传给LLM,组织成自然语言回复用户 chat_response = chat_completion_request( messages, tools=tools ) print("===回复===") print(chat_response.choices[0].message.content)
执行结果:
===get_n_day_weather_forecast=== [{'date': '2023-04-01', 'location': '深圳市南山区', 'temperature': '20°C', 'description': '晴朗'}, {'date': '2023-04-02', 'location': '深圳市南山区', 'temperature': '21°C', 'description': '多云'}, {'date': '2023-04-03', 'location': '深圳市南山区', 'temperature': '22°C', 'description': '晴朗'}, {'date': '2023-04-04', 'location': '深圳市南山区', 'temperature': '23°C', 'description': '多云'}, {'date': '2023-04-05', 'location': '深圳市南山区', 'temperature': '24°C', 'description': '晴朗'}] ===回复=== 未来5天深圳南山区的天气情况如下: - 4月1日:晴朗,温度20°C - 4月2日:多云,温度21°C - 4月3日:晴朗,温度22°C - 4月4日:多云,温度23°C - 4月5日:晴朗,温度24°C 请注意天气预报仅供参考,实际情况可能会有所变化。
当然,function calling 也支持更多函数调用行为譬如 并行函数调用,默认在单个响应中生成多个函数调用,并且表明应该并行调用。依然以天气为例,我们通过调用函数同时获取 3个不同位置的天气:
response = Choice( finish_reason='tool_calls', index=0, logprobs=None, message=chat.completionsMessage( content=None, role='assistant', function_call=None, tool_calls=[ chat.completionsMessageToolCall( id='call_62136355', function=Function( arguments='{"city":"New York"}', name='check_weather'), type='function'), chat.completionsMessageToolCall( id='call_62136356', function=Function( arguments='{"city":"London"}', name='check_weather'), type='function'), chat.completionsMessageToolCall( id='call_62136357', function=Function( arguments='{"city":"Tokyo"}', name='check_weather'), type='function') ]) ) # Iterate through tool calls to handle each weather check for tool_call in response.message.tool_calls: arguments = json.loads(tool_call.function.arguments) city = arguments['city'] weather_info = check_weather(city) print(f"Weather in {city}: {weather_info}")
四、 业内主流的 AI Agent 范式
4.1. 单 Agent 框架:ReAct
参考论文:https://arxiv.org/pdf/2210.03629.pdf
ReAct模板 与其他prompt模板的对比:
ReAct 的执行流程:
ReAct模板示例:
emplate = """Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools: {tools} Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [{tool_names}] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Remember to speak as a pirate when giving your final answer. Question: {input} {agent_scratchpad}"""
4.2. 多 Agent 框架:AutoGen
参考论文:https://arxiv.org/pdf/2308.08155.pdf
多 Agent 这里我们以 AutoGen 举例,Autogen是Microsoft开发的一个用于简化LLM工作流的编排、优化和自动化的框架。它提供了可定制和可交谈的代理,充分利用了大模型(比如GPT-4)的能力,并且可以集成人类智慧和工具,在多个代理之间可以实现自动化聊天。
多 Agent 主要通过一系列的流程,按照多个阶段来实现最终的需求
AutoGen 中各角色的定义和协同:
AutoGen中包含两种Agent一种Manager,即:UserProxyAgent、AssistantAgent、GroupChatManager。
AssistantAgent的主要作用是作为中枢大脑提供理解、分析、决策
UserProxyAgent主要作为处理由中枢大脑给出的决策的执行者
GroupChatManager则是能够让多个Agent进行分组的管理者,类似于实际工作中把一个Manager下面的团队分为多个Team的情况。