在此全面的参考文档中,您可以快速找到有关 Agent Framework 的每个组件的详细信息。
文件夹结构
如需构建代理,您需要为该代理创建一个以代理命名的文件夹,其中至少包含以下文件:
agent.py
:代理的主文件。您必须在全局变量root_agent
下定义根代理。__init__.py
:Python 模块文件。它必须至少包含一行from agents import Agent
以导入Agent
类。
(可选)建议您添加以下文件:
requirements.txt
:代理的 Python 要求文件。README.md
:代理的自述文件。其中应包含有关如何设置和运行代理的指令。
最低要求 agent.py
构建代理首先要创建 Agent
类的实例。最低要求代理应具有以下属性:
name
:代理的名称。model
:要使用的 LLM 模型的名称。instruction
:代理的自然语言指令。
例如:
from agents import Agent
root_agent = Agent(
model='gemini-1.5-flash',
name='root_agent',
instruction="Be polite and answer all users' questions.",
)
如需创建最低要求代理,您可以复制文件夹 _empty_agent
并修改 agent.py
文件。
属性
name
name: str
代理的名称。
ID
- 名称必须遵循标识符命名惯例。
- 如果字符串仅包含字母数字字符 (a-z) 和 (0-9) 或下划线 (_),则会被视为有效标识符。有效标识符不能以数字开头,也不能包含任何空格。
唯一
- 名称在整个代理树中必须是唯一的。
根代理
- 虽然根代理的变量必须命名为
root_agent
,但您可以为根代理设置更有意义的名称属性。
- 虽然根代理的变量必须命名为
模型
model: str
要使用的 LLM 模型的名称。
对于 LLM 代理是必需的
- 只有 LLM 代理才需要 model 属性。
- 您无需为顺序、循环或其他非 LLM 代理设置 model 属性。
父代理
- 您可以省略 model 属性。在这种情况下,代理将使用父级/祖先代理的 model 属性。
格式
- 模型名称格式因 LLM 的供应商而异。
- 对于 Gemini,它类似于
gemini-1.5-flash
或gemini-2.0-flash-exp
。 - 对于 Vertex 上的 Anthropic,它类似于
claude-3-5-sonnet-v2@20241022
- 虽然框架的设计允许使用任何模型供应商,但我们目前仅支持 Gemini 和 Vertex 上的 Anthropic。
指令
instruction: str | InstructionProvider
代理的自然语言指令。
对于 LLM 代理是必需的
- 只有 LLM 代理才需要指令。
- 您无需为顺序、循环或其他非 LLM 代理设置 instruction 属性。
数据类型
- 指令可以是字符串。
- 指令也可以是可调用的 InstructionProvider
InstructionProvider
- InstructionProvider 定义为
Callable[[InvocationContext], str]
- 它使您可以提供根据调用上下文动态生成指令的函数。
- InstructionProvider 定义为
状态
- 指令是一个字符串模板,您可以使用 {var} 语法将动态值插入到指令中。
var
用于插入名为 var 的状态变量的值。artifact.var
用于插入名为 var 的制品的文本内容。- 如果状态变量或制品不存在,代理会引发错误。如果您想忽略该错误,可以向变量名称附加
?
,例如{var?}
。
指南
- 首先说明代理的身份、它可以执行的操作以及它不能执行的操作。
- 您可以使用 Markdown 格式,让指南更易于阅读(无论是供人阅读还是供模型阅读)。
- 如果代理可以执行多个任务,请为其提供任务列表,并为每个任务提供单独的部分。
- 如果任务包含多个步骤,请为其提供步骤列表以及每个步骤的详细指令。
- 如果代理可以使用工具,请在
tools
属性下列出这些工具。工具定义中包含有关如何使用这些工具的指令,但您可以将有关何时使用这些工具的详细指令添加到代理指令中,以提高性能。 - 对于多代理系统,请说明何时应将相应代理移交给其他代理。
- 您可以在指令中添加输入和预期输出的示例,也可以使用
examples
属性来提供示例。 - 指令内容应详尽而具体。请将代理视为正在接受培训的新员工。
- 不要依赖于必须 100% 遵循的规则的相关指令。模型本质上具有一定程度的自由度,并且可能会出错。请依靠工具和回调来强制执行严格的规则。
说明
description: str
说明此代理可以执行的操作。此说明作为系统指令的一部分发送到模型,以便:
代理本身可根据说明了解自己的功能。
代理可以了解其他代理可以执行的操作,并根据其说明决定是否转移。
global_instruction
global_instruction: str | InstructionProvider
整个代理树的全局指令。
指令会告诉特定代理要执行的操作以及如何执行,而全局指令则适用于整个代理树中的所有代理。
全局指令的用法与 instruction 属性类似,包括数据类型、对 InstructionProvider 的支持以及是否能够访问状态变量和制品。
- 身份和行为
- 您可以使用全局指令为整个代理树设置身份和行为/标准,而不是如何为特定代理执行特定任务。
generate_content_config
generate_content_config: google.genai.types.GenerateContentConfig
代理的其他模型配置。这些参数将合并到对模型的请求中。
有一些属性不应在此字段中设置,因为它们由框架管理:- tools
- system_instruction
- response_schema
示例
examples: list[Example] | BaseExampleProvider
代理的少样本示例。研究表明,提供少样本示例可以提高代理的性能。
- 静态示例
- 您可以提供静态示例列表。示例定义如下,其中
input
是输入内容,output
是预期输出内容。
- 您可以提供静态示例列表。示例定义如下,其中
class Example(BaseModel):
input: types.Content
output: list[types.Content]
输出列表
- 输出可以是
Content
的列表。 - 因此,您可以将一系列内容定义为预期输出。例如,模型应先进行函数调用,然后生成一些文本。
- 输出可以是
BaseExampleProvider
- 您还可以提供
BaseExampleProvider
类的实例。 BaseExampleProvider
类具有get_examples(query: str)
方法,并返回Example
的列表。- 借助
BaseExampleProvider
,您可以根据查询动态生成示例。
- 您还可以提供
greeting_prompt
greeting_prompt: str
您可以设置将发送到模型以生成问候语消息的提示。当您使用空会话和空用户输入调用代理时,系统会使用问候语提示。
计划
planning: bool
将 planning
设置为 True 会为代理启用规划模式。在规划模式下,代理会先生成一个计划来处理用户查询,然后逐步执行该计划。
code_executor
code_executor: BaseCodeExecutor
您构建的代理能够通过编写和执行代码来解决问题。
您可以通过以下两种方式启用代码执行:
某些模型能够直接执行代码。例如,Gemini 2.0 模型可在实时模式下会自动生成和执行代码,而无需单独的代码执行工具。
您可以将
code_executor
属性设置为BaseCodeExecutor
类的实例,以启用代码执行。目前,我们提供VertexCodeExecutor
和UnsafeLocalCodeExecutor
类(由于 LLM 生成的代码可能会造成严重破坏,因此请仅将UnsafeLocalCodeExecutor
用于原型设计),您可以用于在 Vertex AI 上执行代码。我们将来会添加更多代码执行器。
input_schema
input_schema: type[BaseModel]
如果您将 Pydantic 模型指定为 input_schema
,代理将强制执行输入架构。输入内容必须是符合该架构的 JSON 字符串。
output_schema
output_schema: type[BaseModel]
如果您将 Pydantic 模型指定为 output_schema
,代理将强制执行输出架构。输出内容始终是符合该架构的 JSON 字符串。
output_key
output_key: str
代理会将其输出存储到状态变量中(采用 output_key
属性指定的名称)。
include_contents
include_contents: Literal['default', 'none']
代理默认会包含会话历史记录(聊天记录)的内容。您可以将 include_contents
属性设为 none
以停用此行为,在这种情况下,特定代理将不会看到聊天记录。当代理不需要了解聊天记录时,此方法非常有用。
工具
使用工具的能力将代理与纯模型区分开来。就好比以复杂和多样化的方式使用工具的能力被认为是人类的决定性特征一样。
在代理框架中,您可以通过 tools
属性向代理提供工具。tools
属性是一个工具列表,其中每个工具可以是:
- Python 函数。
- 实现
BaseTool
类的类。
Python 函数工具
您可以将 Python 函数定义为工具。
参数
- 您的函数可以有任意数量的参数。
- 每个参数可以是任何类型,只要该类型可进行 JSON 序列化即可。
- 请勿为参数设置默认值,模型不支持默认值。
返回类型
- 返回值类型必须是字典。
- 如果您返回不是字典的任何内容,框架会将其封装到具有单个键
result
的字典中。 - 请尽量在返回值中提供描述性内容。例如,返回
error_message: str
时,应返回人类可读的错误消息,而不是数字错误代码。请注意,此返回值是供模型读取和理解的,而不是供一段代码执行的。 - 最好使用
status
键指示success
、error
、pending
等,以便模型了解操作的一般状态。
文档字符串
- 函数的文档字符串将用作工具说明,并发送到模型。因此,文档字符串的质量越高,模型使用工具的效果便越好。
简单性
- 虽然您可以十分自由地定义函数,但应尽量保持简单易用,以便模型能够更准确地使用函数。
- 参数越少越好。
- 尽可能使用简单的数据类型(例如
str
、int
),而不是自定义类。 - 函数名称和参数非常重要。如果您有一个名为
do_stuff()
的函数,即使您告诉模型该函数用于取消航班,模型仍可能拒绝使用该函数。 - 将复杂的函数拆分为较小的函数。例如,将
update_profile(profile: Profile)
拆分为update_name(name: str)
、update_age(age: int)
等。
在指令中引用
- 您可以使用工具的名称在指令中引用相应工具。
- 如果函数的名称和文档字符串足够详细,您可以只关注何时在指令中使用工具。
- 指示代理如何处理不同的返回值。例如,如果工具返回错误消息,代理应放弃、重试还是询问更多信息?
- 工具可以按顺序使用,一个工具可以依赖于另一个工具的输出。在指令中说明序列。
工具上下文
在工具函数中,您可以添加特殊参数 tool_context:
ToolContext
,以获取有关调用工具的上下文的更多信息。
ToolContext
类位于 agents.types
模块中,具有以下属性:
function_call_event_id: str
- 触发工具调用的事件的 ID。
function_call_id: str
- 函数调用的 ID。
state: State
- 用于读取和更新状态变量的字典式对象。
actions: EventActions
- 工具可以执行的其他操作。
EventActions
类位于 agents.events
模块中,具有以下属性,用于允许工具执行其他操作:
skip_summarization: bool
- 如果设置为 True,框架将跳过调用工具的事件的总结步骤。
transfer_to_agent: str
- 如果进行了设置,框架将转移到具有
transfer_to_agent
属性指定的名称的代理。
- 如果进行了设置,框架将转移到具有
escalate: bool
- 如果设置为 True,代理会将查询上报到其父代理。在 LoopFlow 中从子代理进行上报意味着循环结束。
AsyncFunctionTool
AsyncFunctionTool 是 FunctionTool 的子类。它专为需要很长时间才能完成的工具而设计。
如需创建 AsyncFunctionTool,您需要定义常规 Python 函数并将其封装到 AsyncFunctionTool
类中。类似于此内容:AsyncFunctionTool(func=your_function)
AsyncFunctionTool 仍会调用您的 Python 函数,您可以在其中启动可能花费很长时间的任务。您可以向模型返回中间结果,以告知模型任务尚未完成。添加 status: 'pending'
、progress: 20
、estimated_completion_time: '...'
等信息有助于模型向用户提供有意义的回答。
稍后,当操作完成后,您可以使用新的 FunctionResponse 调用代理,以提供最终结果。此时,代理将为用户生成最终回答。
AgentTool
借助 AgentTool,您可以调用其他代理来执行任务。这等同于创建 Python 函数(使用函数参数调用其他代理),并将该代理的回答用作函数的返回值。
AgentTool 与子代理不同:
- 当代理 A 以 AgentTool 的形式调用代理 B 时,代理 B 的答案会传递给代理 A,代理 A 会对答案进行总结并为用户生成回答。未来的用户输入将继续由代理 A 回答。
- 当代理 A 以子代理的形式调用代理 B 时,回答用户的责任会完全转移给代理 B。代理 A 将不参与。在这种情况下,未来的用户输入将由代理 B 回答。
如需将代理用作工具,您可以使用 AgentTool
类封装代理。例如 tools=[AgentTool(agent=agent_b)]
AgentTool 具有以下属性,用于自定义其行为:
skip_summarization
- 如果设置为 True,框架将跳过调用 LLM 以汇总工具代理的回答。
回调
回调类型
您可以通过定义回调进一步自定义代理的行为。我们支持两种类型的回调:
- BeforeCallbacks 在代理执行操作之前调用。您可以修改操作、跳过操作或执行其他操作。
- AfterCallbacks 在代理执行操作之后调用。您可以使用此回调修改操作的结果,或执行其他操作。
支持的操作
我们提供的 BeforeCallbacks 和 AfterCallbacks 可用于以下操作:
- 调用代理。
- 调用 LLM。
- 调用工具。
回调列表
因此,我们有以下 6 个回调,它们都是 Agent
类的属性:
before_agent_callback
def before_agent_callback(invocation_context: InvocationContext) -> Content | None
- 一个调用可以包含多个代理调用。因此,此回调可能会多次调用。
- 如果您从此回调返回
Content
,代理将跳过当前代理调用,并使用返回的Content
作为回答。
after_agent_callback
def after_agent_callback(invocation_context: InvocationContext) -> Content | None
- 一个调用可以包含多个代理调用。因此,此回调可能会多次调用。
- 如果您从此回调返回
Content
,代理会将返回的Content
附加到自己的回答后面。
before_model_callback
def before_model_callback(
invocation_context: InvocationContext,
llm_request: LlmRequest) -> LlmResponse | None
- 一个代理调用可以包含多个 LLM 调用。因此,此回调可能会多次调用。
- 如果您从此回调返回
LlmResponse
,代理将使用返回的LlmResponse
作为回答,并跳过模型调用。
before_model_callback
def after_model_callback(
invocation_context: InvocationContext,
llm_response: LlmResponse) -> LlmResponse | None
- 一个代理调用可以包含多个 LLM 调用。因此,此回调可能会多次调用。
- 如果您从此回调返回
LlmResponse
,代理将使用返回的LlmResponse
作为回答,而不是模型生成的回答。
before_tool_callback
def before_tool_callback(
invocation_context: InvocationContext,
tool: BaseTool,
args: dict[str, Any],
tool_context: ToolContext) -> dict | None
- 一个模型调用可以包含多个工具调用。因此,此回调可能会多次调用。
- 如果您从此回调返回
dict
,代理将使用返回的dict
作为回答,并跳过工具调用。
after_tool_callback
def after_tool_callback(
invocation_context: InvocationContext,
tool: BaseTool,
args: dict[str, Any],
tool_context: ToolContext,
response: dict) -> dict | None
- 一个模型调用可以包含多个工具调用。因此,此回调可能会多次调用。
- 如果您从此回调返回
dict
,代理将使用返回的dict
作为回答,而不是工具生成的回答。
会话
构建代理时,您无需直接操控会话对象。框架将为您管理会话对象。不过,了解会话是什么以及会话的工作原理仍然很有用。
Agent Framework 中的会话具有两个主要组件:
- 事件:事件列表。
- 状态:状态变量的字典式对象。
事件
事件只是一个简单的事件对象列表。您可以将其视为用户与代理之间或不同代理之间的聊天记录。它不仅会记录用户或模型的字词,还会记录代理执行的所有操作,包括调用工具、工具的回答、调用其他代理等。
事件列表是一个仅允许附加操作的列表。您只能向列表中添加事件,而无法移除或修改任何事件。事件发生后便成为事实,无法更改。我们采用这种设计方式,是为了让系统保持简单,并且我们可以随时返回到特定时间,查看系统的确切快照。
状态
状态是一个字典式对象,其中包含所有状态变量。您可以从以下位置访问它:
- 在指令中,您可以使用
{var}
语法插入名为 var 的状态变量的值。 - 在回调中,您可以通过
invocation_context.state['key']
访问状态变量。您还可以通过invocation_context.state['key'] = value
更新状态变量。 - 在工具中,您可以通过
tool_context.state['key']
访问状态变量。您还可以通过tool_context.state['key'] = value
更新状态变量。
状态与特定会话相关联。因此,它是用于存储在此会话上下文中有用的信息的理想位置。
代理树中的所有代理都可以访问状态,从而使状态成为代理之间进行通信的理想位置。一个代理可以执行操作并将其结果存储在状态中,另一个代理随后可以从状态中读取结果并继续执行工作。
制品
当模型或您的工具创建文件(无论是图片、视频、文档还是任何其他格式)时,您可以将其存储为制品。制品是与特定会话相关联的文件,可供代理或您自己的代码访问。
应用场景
- 当您的代理与用户一起创建/修改文件时。例如,可帮助用户生成和修改图片的代理。
- 当您希望代理回答有关文件的问题,或根据用户的指令修改文件时。
性能优势
处理大文件的另一种方法是将其存储为聊天记录中的字节。不过,这种方法也存在一些缺点:
- 这会导致会话历史记录变慢。
- 很难从聊天记录中检索文件。
- 系统会针对所有请求将相应字节发送到模型,即使对话与这些文件无关也是如此。
访问制品
您可以通过以下几种方式访问制品:
- 在指令中,您可以使用
{artifact.var}
语法插入名为 var 的制品的文本内容。尚不支持二进制制品。 - 在回调中,您可以通过
invocation_context.get_artifact('key')
访问制品。您可以通过invocation_context.set_artifact('key', value)
更新制品。 - 在工具中,您可以通过
tool_context.get_artifact('key')
访问制品。您可以通过tool_context.set_artifact('key', value)
更新制品。
多代理系统
单个代理和列表工具可以在构建复杂系统方面发挥很大作用。但有时,将逻辑拆分为多个代理可以提高整个系统的性能和可维护性。
在以下情况下,您可以考虑使用多个代理:
- 代理的指令过长时(包含多个任务以及每个任务各自的步骤)。
- 当您需要运行更具确定性的工作流时。例如对于研究代理,它总是生成计划,然后执行计划,接下来总结发现结果。
分层代理树
children: list[BaseAgent]
您可以通过创建分层代理树来构建多代理系统。根代理是系统的入口点,可以根据其配置方式调用其他代理。
一个代理可以有多个子代理。子代理也可以有自己的子代理。代理树可以是任意深度,但出于性能考虑,我们建议使用较浅的树。
如需构建代理树,您可以将其他代理作为某个父代理的子级。
流
flow: str | BaseFlow | FlowCallable
当代理收到用户查询时,可以选择自行处理查询,或是将查询移交给其他代理。这由其 flow
属性定义。
我们提供了一些预定义流程,您也可以定义自己的流程。
sequential
:代理会按顺序逐个调用其子代理。loop
:代理会循环调用其子代理。直到任何子代理将tool_context.actions.escalate
设置为 True。single
:这是基于 LLM 的流程。代理会调用 LLM 来回答用户查询,并在需要时调用其工具。auto
:这是基于 LLM 的流程。代理会调用 LLM 来回答用户查询,并在需要时调用其工具。它还可以将查询转移到其子级、同级或父级。- 自定义流程:您可以通过实现
BaseFlow
类来定义自己的流程,也可以仅遵循接口来定义 Python 函数。