编排智能体:例程与交接
来源: https://developers.openai.com/cookbook/examples/orchestrating_agents 抓取时间: 2026-07-21 16:28:07
在使用语言模型时,通常要获得可靠的性能,您只需要一个好的提示词和正确的工具。然而,在处理许多独特的流程时,事情可能会变得复杂。本指南将介绍解决此问题的一种方法。 我们将介绍 例程(routines) 和 交接(handoffs) 的概念,然后介绍实现方法,并展示如何使用它们以简单、强大且可控的方式编排多个智能体。 最后,我们提供了一个示例仓库 Swarm,它实现了这些想法并包含示例。 让我们从设置导入开始。
from openai import OpenAI
from pydantic import BaseModel
from typing import Optional
import json
client = OpenAI()
例程
"例程" 的概念并没有严格定义,而是旨在表达一组步骤的想法。具体来说,让我们将例程定义为自然语言指令列表(我们将用系统提示词表示),以及完成这些步骤所需的工具。
让我们看一个例子。下面,我们为客户服务智能体定义了一个例程,指示它对用户问题进行分类,然后要么建议修复方案,要么提供退款。我们还定义了必要的函数 execute_refund 和 look_up_item。我们可以称之为客户服务例程、智能体、助手等——但想法本身是相同的:一组步骤和执行它们的工具。
# 客户服务例程
system_message = (
"You are a customer support agent for ACME Inc."
"Always answer in a sentence or less."
"Follow the following routine with the user:"
"1. First, ask probing questions and understand the user's problem deeper.\n"
" - unless the user has already provided a reason.\n"
"2. Propose a fix (make one up).\n"
"3. ONLY if not satisfied, offer a refund.\n"
"4. If accepted, search for the ID and then execute refund."
""
)
def look_up_item(search_query):
"""用于查找商品 ID。
搜索查询可以是描述或关键词。"""
# 返回硬编码的商品 ID - 实际上会是查找操作
return "item_132612938"
def execute_refund(item_id, reason="not provided"):
print("Summary:", item_id, reason) # 简单的摘要
return "success"
例程的主要优势在于其简单性和健壮性。请注意,这些指令包含条件语句,很像状态机或代码中的分支。对于中小型例程,LLM 实际上可以非常健壮地处理这些情况,并具有 "软" 遵循的额外好处——LLM 可以自然地引导对话,而不会陷入死胡同。
执行例程
要执行例程,让我们实现一个简单的循环,它:
- 获取用户输入。
- 将用户消息追加到
messages。 - 调用模型。
- 将模型响应追加到
messages。
def run_full_turn(system_message, messages):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": system_message}] + messages,
)
message = response.choices[0].message
messages.append(message)
if message.content: print("Assistant:", message.content)
return message
messages = []
while True:
user = input("User: ")
messages.append({"role": "user", "content": user})
run_full_turn(system_message, messages)
如您所见,这目前忽略了函数调用,所以让我们添加这部分。 模型要求函数被格式化为函数架构。为方便起见,我们可以定义一个辅助函数,将 Python 函数转换为相应的函数架构。
import inspect
def function_to_schema(func) -> dict:
type_map = {
str: "string",
int: "integer",
float: "number",
bool: "boolean",
list: "array",
dict: "object",
type(None): "null",
}
try:
signature = inspect.signature(func)
except ValueError as e:
raise ValueError(
f"Failed to get signature for function {func.__name__}: {str(e)}"
)
parameters = {}
for param in signature.parameters.values():
try:
param_type = type_map.get(param.annotation, "string")
except KeyError as e:
raise KeyError(
f"Unknown type annotation {param.annotation} for parameter {param.name}: {str(e)}"
)
parameters[param.name] = {"type": param_type}
required = [
param.name
for param in signature.parameters.values()
if param.default == inspect._empty
]
return {
"type": "function",
"function": {
"name": func.__name__,
"description": (func.__doc__ or "").strip(),
"parameters": {
"type": "object",
"properties": parameters,
"required": required,
},
},
}
例如:
def sample_function(param_1, param_2, the_third_one: int, some_optional="John Doe"):
"""
这是我的文档字符串。当您想要时调用此函数。
"""
print("Hello, world")
schema = function_to_schema(sample_function)
print(json.dumps(schema, indent=2))
{
"type": "function",
"function": {
"name": "sample_function",
"description": "这是我的文档字符串。当您想要时调用此函数。",
"parameters": {
"type": "object",
"properties": {
"param_1": {
"type": "string"
},
"param_2": {
"type": "string"
},
"the_third_one": {
"type": "integer"
},
"some_optional": {
"type": "string"
}
},
"required": [
"param_1",
"param_2",
"the_third_one"
]
}
}
}
现在,我们可以使用此函数在调用模型时将工具传递给它。
messages = []
tools = [execute_refund, look_up_item]
tool_schemas = [function_to_schema(tool) for tool in tools]
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Look up the black boot."}],
tools=tool_schemas,
)
message = response.choices[0].message
message.tool_calls[0].function
Function(arguments='{"search_query":"black boot"}', name='look_up_item')
最后,当模型调用工具时,我们需要执行相应的函数并将结果提供回模型。
我们可以通过在 tool_map 中将工具名称映射到 Python 函数,然后在 execute_tool_call 中查找并调用它来实现。最后将结果添加到对话中。
tools_map = {tool.__name__: tool for tool in tools}
def execute_tool_call(tool_call, tools_map):
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"Assistant: {name}({args})")
# 使用提供的参数调用相应的函数
return tools_map[name](**args)
for tool_call in message.tool_calls:
result = execute_tool_call(tool_call, tools_map)
# 将结果添加回对话
result_message = {
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
}
messages.append(result_message)
Assistant: look_up_item({'search_query': 'black boot'})
实际上,我们还希望让模型使用结果生成另一个响应。该响应可能 也 包含工具调用,因此我们可以在循环中运行它,直到没有更多的工具调用。 如果我们把所有内容放在一起,它会看起来像这样:
tools = [execute_refund, look_up_item]
def run_full_turn(system_message, tools, messages):
num_init_messages = len(messages)
messages = messages.copy()
while True:
# 将 Python 函数转换为工具并保存反向映射
tool_schemas = [function_to_schema(tool) for tool in tools]
tools_map = {tool.__name__: tool for tool in tools}
# === 1. 获取 OpenAI 完成 ===
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": system_message}] + messages,
tools=tool_schemas or None,
)
message = response.choices[0].message
messages.append(message)
if message.content: # 打印助手响应
print("Assistant:", message.content)
if not message.tool_calls: # 如果完成工具调用处理,跳出
break
# === 2. 处理工具调用 ===
for tool_call in message.tool_calls:
result = execute_tool_call(tool_call, tools_map)
result_message = {
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
}
messages.append(result_message)
# ==== 3. 返回新消息 =====
return messages[num_init_messages:]
def execute_tool_call(tool_call, tools_map):
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"Assistant: {name}({args})")
# 使用提供的参数调用相应的函数
return tools_map[name](**args)
messages = []
while True:
user = input("User: ")
messages.append({"role": "user", "content": user})
new_messages = run_full_turn(system_message, tools, messages)
messages.extend(new_messages)
现在我们有了一个例程,假设我们想添加更多步骤和更多工具。我们可以在一定程度上这样做,但最终如果我们尝试用太多不同的任务来扩展示例程,它可能会开始遇到困难。这就是我们可以利用多个例程概念的地方——给定用户请求,我们可以加载具有适当步骤和工具的正确例程来解决它。 动态交换系统指令和工具可能看起来令人生畏。但是,如果我们将 "例程" 视为 "智能体",那么 交接 的概念允许我们简单地表示这些交换——就像一个智能体将对话移交给另一个智能体一样。
交接
让我们将 交接 定义为一个智能体(或例程)将活动对话移交给另一个智能体,就像您在电话中被转接到其他人一样。只不过在这种情况下,智能体完全了解您之前的对话! 要查看交接的实际效果,让我们首先为智能体定义一个基本类。
class Agent(BaseModel):
name: str = "Agent"
model: str = "gpt-4o-mini"
instructions: str = "You are a helpful Agent"
tools: list = []
现在为了使我们的代码支持它,我们可以更改 run_full_turn,使其接受一个 Agent,而不是单独的 system_message 和 tools:
def run_full_turn(agent, messages):
num_init_messages = len(messages)
messages = messages.copy()
while True:
# 将 Python 函数转换为工具并保存反向映射
tool_schemas = [function_to_schema(tool) for tool in agent.tools]
tools_map = {tool.__name__: tool for tool in agent.tools}
# === 1. 获取 OpenAI 完成 ===
response = client.chat.completions.create(
model=agent.model,
messages=[{"role": "system", "content": agent.instructions}] + messages,
tools=tool_schemas or None,
)
message = response.choices[0].message
messages.append(message)
if message.content: # 打印助手响应
print("Assistant:", message.content)
if not message.tool_calls: # 如果完成工具调用处理,跳出
break
# === 2. 处理工具调用 ===
for tool_call in message.tool_calls:
result = execute_tool_call(tool_call, tools_map)
result_message = {
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
}
messages.append(result_message)
# ==== 3. 返回新消息 =====
return messages[num_init_messages:]
def execute_tool_call(tool_call, tools_map):
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"Assistant: {name}({args})")
# 使用提供的参数调用相应的函数
return tools_map[name](**args)
我们现在可以轻松运行多个智能体:
def execute_refund(item_name):
return "success"
refund_agent = Agent(
name="Refund Agent",
instructions="You are a refund agent. Help the user with refunds.",
tools=[execute_refund],
)
def place_order(item_name):
return "success"
sales_assistant = Agent(
name="Sales Assistant",
instructions="You are a sales assistant. Sell the user a product.",
tools=[place_order],
)
messages = []
user_query = "Place an order for a black boot."
print("User:", user_query)
messages.append({"role": "user", "content": user_query})
response = run_full_turn(sales_assistant, messages) # 销售助理
messages.extend(response)
user_query = "Actually, I want a refund." # 隐式引用最后一项
print("User:", user_query)
messages.append({"role": "user", "content": user_query})
response = run_full_turn(refund_agent, messages) # 退款智能体
User: Place an order for a black boot.
Assistant: place_order({'item_name': 'black boot'})
Assistant: Your order for a black boot has been successfully placed! If you need anything else, feel free to ask!
User: Actually, I want a refund.
Assistant: execute_refund({'item_name': 'black boot'})
Assistant: Your refund for the black boot has been successfully processed. If you need further assistance, just let me know!
太好了!但我们在这里手动进行了交接——我们希望智能体自己决定何时执行交接。一种简单但出人意料有效的方法是给它们一个 transfer_to_XXX 函数,其中 XXX 是某个智能体。模型足够聪明,知道在有意义进行交接时调用此函数!
交接函数
既然智能体可以表达进行交接的 意图,我们必须让它实际发生。有很多方法可以做到这一点,但有一种特别简洁的方法。
对于我们迄今为止定义的智能体函数,如 execute_refund 或 place_order,它们返回一个字符串,该字符串将提供给模型。如果相反,我们返回一个 Agent 对象来指示我们想要转移到哪个智能体呢?就像这样:
refund_agent = Agent(
name="Refund Agent",
instructions="You are a refund agent. Help the user with refunds.",
tools=[execute_refund],
)
def transfer_to_refunds():
return refund_agent
sales_assistant = Agent(
name="Sales Assistant",
instructions="You are a sales assistant. Sell the user a product.",
tools=[place_order],
)
然后我们可以更新我们的代码来检查函数响应的返回类型,如果它是 Agent,则更新正在使用的智能体!此外,现在 run_full_turn 将需要返回正在使用的最新智能体,以防发生交接。(我们可以在 Response 类中执行此操作,以保持整洁。)
class Response(BaseModel):
agent: Optional[Agent]
messages: list
现在是更新后的 run_full_turn:
def run_full_turn(agent, messages):
current_agent = agent
num_init_messages = len(messages)
messages = messages.copy()
while True:
# 将 Python 函数转换为工具并保存反向映射
tool_schemas = [function_to_schema(tool) for tool in current_agent.tools]
tools = {tool.__name__: tool for tool in current_agent.tools}
# === 1. 获取 OpenAI 完成 ===
response = client.chat.completions.create(
model=agent.model,
messages=[{"role": "system", "content": current_agent.instructions}]
+ messages,
tools=tool_schemas or None,
)
message = response.choices[0].message
messages.append(message)
if message.content: # 打印智能体响应
print(f"{current_agent.name}:", message.content)
if not message.tool_calls: # 如果完成工具调用处理,跳出
break
# === 2. 处理工具调用 ===
for tool_call in message.tool_calls:
result = execute_tool_call(tool_call, tools, current_agent.name)
if type(result) is Agent: # 如果智能体转移,更新当前智能体
current_agent = result
result = (
f"Transfered to {current_agent.name}. Adopt persona immediately."
)
result_message = {
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
}
messages.append(result_message)
# ==== 3. 返回最后使用的智能体和新消息 =====
return Response(agent=current_agent, messages=messages[num_init_messages:])
def execute_tool_call(tool_call, tools, agent_name):
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"{agent_name}:", f"{name}({args})")
return tools[name](**args) # 使用提供的参数调用相应的函数
让我们看一个使用更多智能体的例子。
def escalate_to_human(summary):
"""仅在明确要求时调用此函数。"""
print("Escalating to human agent...")
print("\n=== Escalation Report ===")
print(f"Summary: {summary}")
print("=========================\n")
exit()
def transfer_to_sales_agent():
"""用于任何销售或购买相关的事情。"""
return sales_agent
def transfer_to_issues_and_repairs():
"""用于问题、维修或退款。"""
return issues_and_repairs_agent
def transfer_back_to_triage():
"""如果用户提出超出您职责范围的话题,请调用此函数,
包括转人工。"""
return triage_agent
triage_agent = Agent(
name="Triage Agent",
instructions=(
"You are a customer service bot for ACME Inc. "
"Introduce yourself. Always be very brief. "
"Gather information to direct the customer to the right department. "
"But make your questions subtle and natural."
),
tools=[transfer_to_sales_agent, transfer_to_issues_and_repairs, escalate_to_human],
)
def execute_order(product, price: int):
"""价格应为美元。"""
print("\n\n=== Order Summary ===")
print(f"Product: {product}")
print(f"Price: ${price}")
print("=================\n")
confirm = input("Confirm order? y/n: ").strip().lower()
if confirm == "y":
print("Order execution successful!")
return "Success"
else:
print("Order cancelled!")
return "User cancelled order."
sales_agent = Agent(
name="Sales Agent",
instructions=(
"You are a sales agent for ACME Inc."
"Always answer in a sentence or less."
"Follow the following routine with the user:"
"1. Ask them about any problems in their life related to catching roadrunners.\n"
"2. Casually mention one of ACME's crazy made-up products can help.\n"
" - Don't mention price.\n"
"3. Once the user is bought in, drop a ridiculous price.\n"
"4. Only after everything, and if the user says yes, "
"tell them a crazy caveat and execute their order.\n"
""
),
tools=[execute_order, transfer_back_to_triage],
)
def look_up_item(search_query):
"""用于查找商品 ID。
搜索查询可以是描述或关键词。"""
item_id = "item_132612938"
print("Found item:", item_id)
return item_id
def execute_refund(item_id, reason="not provided"):
print("\n\n=== Refund Summary ===")
print(f"Item ID: {item_id}")
print(f"Reason: {reason}")
print("=================\n")
print("Refund execution successful!")
return "success"
issues_and_repairs_agent = Agent(
name="Issues and Repairs Agent",
instructions=(
"You are a customer support agent for ACME Inc."
"Always answer in a sentence or less."
"Follow the following routine with the user:"
"1. First, ask probing questions and understand the user's problem deeper.\n"
" - unless the user has already provided a reason.\n"
"2. Propose a fix (make one up).\n"
"3. ONLY if not satesfied, offer a refund.\n"
"4. If accepted, search for the ID and then execute refund."
""
),
tools=[execute_refund, look_up_item, transfer_back_to_triage],
)
最后,我们可以在循环中运行它(这不会在 Python 笔记本中运行,所以您可以在单独的 Python 文件中尝试):
agent = triage_agent
messages = []
while True:
user = input("User: ")
messages.append({"role": "user", "content": user})
response = run_full_turn(agent, messages)
agent = response.agent
messages.extend(response.messages)
Swarm
作为概念验证,我们已将这些想法打包到一个名为 Swarm 的示例库中。它仅作为示例,不应直接用于生产。但是,随时可以借鉴这些想法和代码来构建您自己的版本!