AI Agent 相关调研

一、 概述

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的情况。

 

五、 参考及资料

https://lilianweng.github.io/posts/2023-06-23-agent/

https://platform.openai.com/docs/guides/function-calling