Agent 概念

Peng Xia

任何高效的 AI 系统都需要为大语言模型(LLM)提供某种接触现实世界的能力:例如,调用搜索工具以获取外部信息,或者操控特定程序来完成任务。换句话说,LLM 应当具备自主性(agency)。具备代理能力的程序,是 LLM 通向外部世界的桥梁。

代理(Agent)是指一个系统,它利用 AI 模型与环境交互,以实现用户定义的目标。它结合了推理、规划和行动执行(通常通过外部工具)来完成任务。

**AI Agent 是一种程序,其工作流程由 LLM 的输出控制。**LLM 是 Agent 的一部分,扮演其“大脑(mind)”的角色;而 Agent 本身更像是一个“工作流(workflow)”,围绕 LLM 组织了一系列模块与机制,管理从输入到输出的整个流程。

LLM 是 Agent 的组成部分,但 Agent 是围绕 LLM 构建起来的一个智能行为控制系统,它将模型的文本生成能力嵌入到了一个可交互、可执行的工作流中。

你可以将代理理解为由两个主要部分组成:

  1. 大脑(AI 模型):所有的思考都发生在这里。AI 模型负责推理和规划,并根据当前情境决定采取哪些行动(Action)

  2. 身体(能力与工具):代表代理所配备的一切操作能力

可执行的行为范围取决于代理被配备了什么功能。例如,人类没有翅膀,就不能执行“飞行”这个动作,但可以“走路”“奔跑”“跳跃”“抓取”等。同理,LLM 虽然很强大,但它本身只能生成文本。因此,开发者为它实现了额外功能(称为工具 Tools),通过这些工具,LLM 可以完成我们为其实现的各种动作(Actions)

任何使用 LLM 的系统,都会将 LLM 的输出整合进代码流程中。LLM 对程序控制流程的影响程度,就是该系统中 LLM 的“自主性”水平。注意,这里的“代理”并不是一个离散的“是 / 否”状态,而是在一个连续光谱上演进的概念——你赋予 LLM 越多控制权,它的自主性就越强。


下面的表格展示了不同系统中的代理水平差异:

简称 描述 自主性等级
简单 LLM LLM 的输出对程序流程没有影响 ☆☆☆
Router LLM 输出控制一个 if/else 语句分支 ★☆☆
Tool call LLM 输出控制某个函数的调用 ★★☆
Multi-step Agent LLM 输出控制程序的迭代与继续执行 ★★☆
Multi-Agent 一个代理流程可以启动另一个代理流程 ★★★
Code Agents LLM 在代码中运行,能够定义自己的工具或启动其他代理 ★★★

接下来是一个多步代理(Multi-step Agent)的代码结构示例:
(可根据需求翻译或展示代码)

1
2
3
4
5
memory = [user_defined_task]
while llm_should_continue(memory): # this loop is the multi-step part
action = llm_get_next_action(memory) # this is the tool-calling part
observations = execute_action(action)
memory += [action, observations]

该 agentic 系统在一个循环中运行,每一步执行一个新的动作(action)(这个动作通常是调用某些预设的“工具”,这些工具其实就是函数),直到根据其观察结果判断:已经达到了可以解决当前任务的满意状态为止。


Agent 如何控制工作流 - Messages and Special Tokens

message 定义

就像 ChatGPT 一样,用户通常是通过聊天界面与代理(Agent)交互的。因此,我们需要理解 LLM 是如何处理聊天内容的。当你与 ChatGPT 这样的系统对话时,你实际上是在交换消息(messages)。在幕后,这些消息会被拼接并格式化为模型可以理解的提示词(prompt)

这正是“聊天模板(chat templates)”发挥作用的地方。它们是用户和助手之间对话消息与模型特定格式要求之间的桥梁。换句话说,聊天模板负责结构化用户与代理之间的沟通,确保每个模型(即便使用不同的特殊标记符)都能接收到正确格式的提示内容。


我们再次提到特殊标记符(special tokens),因为它们是模型用来区分用户与助手轮次开始和结束的标志。就像每个模型有自己的 EOS(End Of Sequence)标记符一样,它们对对话消息的格式和分隔符也有不同的规范。


消息通常分为以下几种类型:

  • System Message(系统消息,也称为系统提示词):用于定义模型应该如何行为。它们提供的是持久性指令,指导后续的所有交互。在使用代理时,系统消息还会:

    • 提供有关可用工具的信息;
    • 指示模型如何格式化其要采取的动作;
    • 给出思考过程应如何分段的指导原则。
  • User 和 Assistant Messages(用户消息与助手消息):顾名思义,分别对应用户的输入与模型的响应内容。

message 格式化

可以用 tokenizer 的 apply_chat_template 函数来转换 messages,tokenize 置为 false 就不会转换为 token id, add_generation_prompt 则是为下一个 assistant 的 message 加入前缀学习,即 <|im_start|>assistant\n ,这部分属于固定格式,也相当于提示大模型接下来要输出 assistant 角色的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
messages = [
{"role": "system", "content": "You are a math tutor."},
{"role": "user", "content": "What is calculus?"},
{"role": "assistant", "content": "Calculus is a branch of mathematics..."},
{"role": "user", "content": "Can you give me an example?"},
]

from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
)
model_name_or_path = "../DC/qwen2.5-3b"
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
rendered_prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
print(rendered_prompt)
1
2
3
4
5
6
7
8
9
<|im_start|>system
You are a math tutor.<|im_end|>
<|im_start|>user
What is calculus?<|im_end|>
<|im_start|>assistant
Calculus is a branch of mathematics...<|im_end|>
<|im_start|>user
Can you give me an example?<|im_end|>
<|im_start|>assistant

工具

工具定义

工具(Tool)是提供给大语言模型(LLM)调用的函数,每个工具都应该用于完成一个明确的目标

以下是 AI Agent 中常用的一些工具示例:

工具名称 描述
网络搜索 允许 Agent 从互联网上获取最新信息。
图像生成 根据文本描述生成图像。
信息检索 从外部知识库中检索信息。
API 接口 与外部服务的 API(如 GitHub、YouTube、Spotify 等)交互。

一个好的工具应当是对大语言模型能力的补充。例如,当你需要执行算术运算时,提供一个计算器工具比依赖 LLM 自身的推理能力更能获得稳定准确的结果。

工具的存在是为了弥补大模型的能力不足或缺陷,或仅仅是为了确保结果更加稳定与正确。

此外,LLM 是基于训练数据来预测 prompt 的补全结果,这意味着它的内部知识只能覆盖训练数据之前的事件。因此,如果你的 Agent 需要访问实时数据,你必须通过工具提供这些数据。举例来说,如果你问 LLM“今天巴黎的天气如何”,而没有接入搜索工具,那么它可能会凭空捏造一个天气情况(hallucination)


一个工具应包含以下内容:

  • 对函数功能的文字描述
  • 一个可调用对象(Callable);
  • 带类型标注的参数(Arguments);
  • (可选)带类型标注的输出(Outputs)。

需要注意的是,LLM 只能接受文本输入并生成文本输出,它自身无法主动调用工具
所谓“给 Agent 提供工具”,其实是指:教会 LLM 这些工具的存在,并在需要时引导其生成基于文本的调用指令

例如,假设你提供了一个可以查询实时天气的工具,然后问模型“巴黎今天的天气如何?”,LLM 会意识到这是一次调用“天气工具”的机会。它不会自己返回天气数据,而是生成类似 call weather_tool('Paris') 的调用文本。


这时,Agent 系统会读取 LLM 的输出,识别出需要调用工具,代表 LLM 执行实际的工具函数调用,并获取真实的天气信息。

工具调用的过程通常不会展示给用户:Agent 会将调用结果作为一个新消息追加进对话历史,并将更新后的对话再次传入 LLM。这时 LLM 会根据新的上下文生成自然流畅的最终答复。

从用户的角度来看,似乎是 LLM 直接调用了工具,但实际上,整个工具调用过程是由 Agent 在后台完成的


至于大模型怎么知道去调用这些工具,除了需要微调外,最初的要求是把工具告诉大模型。以下就是一个带工具说明的 system message:

1
2
3
4
5
system_message = "You are an AI assistant. Your primary goal is to provide helpful, precise and clear respones.

You have access to the following tools:
{tools_description}
"

要使工具调用机制正常运作,我们必须非常准确和明确地定义以下几点:

  1. 工具的功能是什么
  2. 它需要什么样的输入
  3. 它会输出什么类型的结果

正因如此,工具的描述通常采用既具表达力又精准的结构,例如编程语言格式或 JSON 结构。 这并不是说必须使用这些格式,任何精确且结构一致的格式都是可行的

工具实现 - 功能和描述

1
2
3
def calculator(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b

该工具需要以下输入:

  • aint):一个整数
  • bint):一个整数

该工具的输出是一个整数,可以描述如下:

  • int):ab 的乘积。

我们可以用以下字符串来描述这个 tool

1
Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int

但为了更规范化的获取 tool 描述,可以为 tool 包装为 class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from typing import Callable


class Tool:
"""
A class representing a reusable piece of code (Tool).

Attributes:
name (str): Name of the tool.
description (str): A textual description of what the tool does.
func (callable): The function this tool wraps.
arguments (list): A list of argument.
outputs (str or list): The return type(s) of the wrapped function.
"""
def __init__(self,
name: str,
description: str,
func: Callable,
arguments: list,
outputs: str):
self.name = name
self.description = description
self.func = func
self.arguments = arguments
self.outputs = outputs

def to_string(self) -> str:
"""
Return a string representation of the tool,
including its name, description, arguments, and outputs.
"""
args_str = ", ".join([
f"{arg_name}: {arg_type}" for arg_name, arg_type in self.arguments
])

return (
f"Tool Name: {self.name},"
f" Description: {self.description},"
f" Arguments: {args_str},"
f" Outputs: {self.outputs}"
)

def __call__(self, *args, **kwargs):
"""
Invoke the underlying function (callable) with provided arguments.
"""
return self.func(*args, **kwargs)

calculator_tool = Tool(
"calculator", # name
"Multiply two integers.", # description
calculator, # function to call
[("a", "int"), ("b", "int")], # inputs (names and types)
"int", # output
)

calculator_tool.to_string()
# Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int'

更好的方式是 python 修饰器,decorator,用修饰器修饰函数,就可实现统一的一些功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from functools import wraps
import inspect

class ToolWrapper:
def __init__(self, func):
self.func = func
wraps(func)(self) # 保留原函数元信息

def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)

def to_string(self):
# 提取函数签名和文档字符串
sig = inspect.signature(self.func)
doc = inspect.getdoc(self.func) or ""
return f"Tool name: {self.func.__name__}\nSignature: {sig}\nDescription: {doc}"

def tool(func):
return ToolWrapper(func)

# 示例用法
@tool
def calculator(a: int, b: int) -> int:
"""Multiply two integers."""
return a * b

print(calculator.to_string())
1
2
3
Tool name: calculator
Signature: (a: int, b: int) -> int
Description: Multiply two integers.

Agent 周期 Thought-Action-Observation

周期概述

image

Agent 以**思考(Thought)→ 行动(Action)→ 观察(Observation)**的连续循环方式运行。

我们来逐步拆解这些步骤:

  1. Thought(思考):Agent 中的大语言模型部分决定下一步应该做什么。
  2. Action(行动):Agent 执行一个动作,即调用相应工具并传入对应参数。
  3. Observation(观察):模型对工具返回的结果进行反思和处理。

在许多 Agent 框架中,这些规则与流程会直接嵌入到系统提示词(system prompt)中,以确保每一次循环都遵循预设的逻辑。

1
2
3
4
5
6
7
8
9
10
11
system_message="""
You are an AI assistant designed to help users efficiently and accurately. Your primary goal is to provide helpful, precise, and clear responses.

You have access to the following tools:
Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int

You should think step by step in order to fulfill the objective with a reasoning divided into Thought/Action/Observation steps that can be repeated multiple times if needed.

You should first reflect on the current situation using `Thought: {your_thoughts}`, then (if necessary), call a tool with the proper JSON formatting `Action: {JSON_BLOB}`, or print your final answer starting with the prefix `Final Answer:`
"""

我们可以看到,在 System Message 中我们定义了:

  • Agent 的行为方式;
  • Agent 可使用的工具;
  • 思考–行动–观察(Thought-Action-Observation)循环,并将其嵌入到了对 LLM 的指令中。

为什么需要 thought

*Thought(思考)代表了 Agent 为了解决任务而进行的*内部推理与规划过程。它依赖于 Agent 所用的大语言模型(LLM)在 prompt 中提供的信息上进行*分析与理解。你可以将其看作 Agent 的*内心独白,在其中它思考手头的任务并制定行动策略。

Agent 的思考部分负责利用当前的观察信息,决定接下来的行动。通过这个过程,Agent 可以:

  • 将复杂问题拆解为更易管理的子任务
  • 回顾过往经验;
  • 根据新信息不断调整其策略。

以下是一些常见的思考类型及示例:

思考类型 示例
规划(Planning) “我需要将这个任务分成三步:1)收集数据,2)分析趋势,3)生成报告。”
分析(Analysis) “根据错误信息来看,问题可能出在数据库连接参数上。”
决策(Decision) “考虑到用户的预算限制,我应该推荐中档方案。”
问题解决(Problem Solving) “要优化这段代码,我应该先做性能分析找出瓶颈。”
记忆整合(Memory Integration) “用户之前提到偏好 Python,所以我将用 Python 举例。”
自我反思(Self-Reflection) “我上一次的方法效果不好,应该换一种策略尝试。”
目标设定(Goal Setting) “完成这个任务前,我需要先明确验收标准。”
优先级排序(Prioritization) “安全漏洞应在添加新功能之前优先修复。”

对于专门为函数调用(function-calling)微调过的 LLM,这种“思考过程”并非强制必须的。

一种关键方法是 ReAct 方法,即将“推理(Reasoning)”与“行动(Acting)”结合。这是一种简单的提示技术:在模型解码前加一句 “Let’s think step by step”(让我们一步步思考)。

这种方式鼓励模型生成的下一个 token 更倾向于生成一个计划,而不是直接给出最终答案。因为模型被引导去分解问题为子任务,从而使它在处理复杂问题时能更加细致,通常比直接输出最终答案更少出错。

ReAct

Action

Action 类型

**Action(行动)*是 AI Agent 用来*与外部环境交互的具体操作步骤

无论是上网搜索信息,还是控制某个物理设备,每一个 Action 都是 Agent 有意执行的操作

例如,一个客户服务 Agent 可能会执行以下操作:检索客户数据、提供帮助文章,或将问题转交给人工客服。


不同类型的 Agent 在执行 Action 时方式不同:

Agent 类型 描述
JSON Agent 所需执行的 Action 以 JSON 格式指定。
Code Agent Agent 编写一段代码,由外部系统解释执行。
Function-calling Agent 属于 JSON Agent 的一个子类型,经过微调后为每个 Action 生成一个新消息。它是 OpenAI 提供的一种更严格、有组织的格式

Action 的用途多种多样,例如:

Action 类型 描述
信息获取(Information Gathering) 执行网页搜索、查询数据库、检索文档等操作。
工具调用(Tool Usage) 调用 API、执行计算、运行代码等操作。
环境交互(Environment Interaction) 操控数字界面或控制实体设备等。
信息沟通(Communication) 通过聊天与用户互动,或与其他 Agent 协作。

值得注意的是,LLM 本身只能处理文本,它会使用文本来描述想要执行的 Action 以及所需的参数

为了让 Agent 正常运行,LLM 必须在完整生成一个 Action 所需的所有 tokens 后停止解码,然后将控制权交还给 Agent。 这一步至关重要,因为它确保了生成结果是可解析的——无论该格式是 JSON、代码还是函数调用(function-calling)。

暂停-解析

实现 Action 的一个关键方法是 “停止并解析(stop and parse)”机制
这种方法确保 Agent 的输出是结构化且可预测的,主要包括以下三个步骤:

  1. 以结构化格式生成输出:Agent 以明确、预先定义的格式(如 JSON 或代码)输出其要执行的动作。
  2. 停止进一步生成:当动作的文本已完全生成后,LLM 停止继续生成 token,以避免多余或错误的输出。
  3. 解析输出内容:由外部的解析器读取结构化的动作文本,确定需要调用哪个工具,并提取所需参数。

例如,一个需要查询天气的 Agent 可能会输出如下内容:

1
2
3
4
5
6
Thought: I need to check the current weather for New York.
Action :
{
"action": "get_weather",
"action_input": {"location": "New York"}
}

另一种方法是使用 Code Agent(代码代理)。其核心思想是:不再输出简单的 JSON 对象,而是由 Code Agent 生成一个可执行的代码块 —— 通常使用像 Python 这样高级的编程语言。img

相比于类似 JSON 的片段,使用代码来编写 Action 具有以下优势:

  • 可组合性(Composability):你能在 JSON 中嵌套 Action 吗?能像定义 Python 函数那样预定义一组 JSON 操作并重复调用吗?代码可以。
  • 对象管理能力(Object management):如果你用 JSON 来执行 generate_image,你该如何存储它的输出?
  • 通用性(Generality):代码天生就是为了表达计算机可以做的几乎任何事情。
  • 训练数据中的表示能力(Representation in LLM training data):大量高质量的代码样例早已被用于训练 LLM,这意味着模型已经对这类任务具备了良好的理解与生成能力!

Observation

**Observation(观察)*是指 Agent 感知其行为后果的方式
它们为 Agent 的思考过程提供了关键信息,并指引其未来的行动。观察可以看作是来自环境的*反馈信号
,无论是 API 返回的数据、错误信息,还是系统日志,都会影响下一轮的思考循环。

在观察阶段,Agent 会:

  • 收集反馈:接收执行操作后的数据或确认(无论是成功还是失败);
  • 追加结果:将新获得的信息整合进当前上下文,相当于更新自身“记忆”;
  • 调整策略:利用更新后的上下文优化下一步的思考和行动。

这种反馈的循环式融入机制确保了 Agent 始终动态对齐其目标,并能根据真实世界的结果不断学习与调整。

在执行某个 Action 后,Agent 框架通常依照以下步骤进行:

  1. 解析 Action:识别要调用的函数及其参数;
  2. 执行该 Action
  3. 将执行结果追加为 Observation(观察)
Comments