
精读 `parallelization.py`(65行)的完整实现:`asyncio.gather × 3` 并发模型、`ItemHelpers.text_message_outputs()` 提取纯文本、`translation_picker` 择优(fan-in)、`trace` 上下文包裹。拆解 Parallelization 的两大使用场景(延迟优化 fan-out 分工 vs 同任务多跑 best-pick),对比 `asyncio.gather` 与 `Agent.as_tool() + parallel_tool_calls` 两条实现路径,并附 Parallelization / Deterministic / LLM-as-Judge 三模式横向对比矩阵。结尾三条实践建议覆盖路径选择、picker 结构化输出、温度设置对 best-pick 质量的影响。
parallelization.py 的核心设计。"Running multiple agents in parallel is a common pattern. This can be useful for both latency (e.g. if you have multiple steps that don't depend on each other) and also for other reasons e.g. generating multiple responses and picking the best one."
parallelization.py 演示的就是这种用法。with trace("Parallel translation"):
# 三路并发:同 Agent + 同输入 + asyncio.gather
res_1, res_2, res_3 = await asyncio.gather(
Runner.run(spanish_agent, msg),
Runner.run(spanish_agent, msg),
Runner.run(spanish_agent, msg),
)
# 从每个 RunResult 提取纯文本
translations = [
ItemHelpers.text_message_outputs(res_1.new_items),
ItemHelpers.text_message_outputs(res_2.new_items),
ItemHelpers.text_message_outputs(res_3.new_items),
]
# 串行调用择优 Agent(fan-in 阶段)
best = await Runner.run(
translation_picker,
f"Input: {msg}\n\nTranslations:\n{chr(10).join(translations)}",
)asyncio.gather 是 Python 标准库的并发原语,不是 SDK 的特有 API。它把三个 Runner.run 协程提交给同一个事件循环并发调度——三路同时发起 LLM API 请求,等所有响应都回来后才继续1。spanish_agent 实例,同一段 msg。这是刻意为之——利用 LLM 每次采样的随机性,三次运行会产生三个不同的翻译结果。Runner.run 都有独立的运行上下文,彼此不共享状态。这一点很重要,下文陷阱部分会再提到。Runner.run 返回的是 RunResult,里面的 new_items 是 List[RunItem]——涵盖助手消息(MessageOutputItem)、推理内容(ReasoningItem)、工具调用记录等富元数据4。ItemHelpers.text_message_outputs(res.new_items) 做的事情很纯粹:从这个列表里只取文本消息,拼成一个字符串1。translations 传给 translation_picker。translation_picker = Agent(
name="translation_picker",
instructions="You pick the best Spanish translation from the given options.",
)translation_picker 在 asyncio.gather 结束之后才被调用,是一次独立的串行 LLM 请求。整体时序是:fan-out(三路并发)→ fan-in(串行择优),总耗时约等于 max(T₁, T₂, T₃) + T_picker。with trace("Parallel translation"):
# ... 整个工作流trace 是 SDK 提供的上下文管理器,把包裹的代码块在 OpenAI 追踪系统中标记为单个工作流1。用 with trace(...) 一包,asyncio.gather 里的三路并发 + 最后的 translation_picker 就在追踪界面里是同一条 trace 记录。| 维度 | asyncio.gather 路径 | Agent.as_tool() 路径 |
|---|---|---|
| 实现方式 | Python 原生异步并发,代码显式控制 | meta_agent = Agent(tools=[a.as_tool(),...], model_settings=ModelSettings(parallel_tool_calls=True)) |
| 延迟 | 更低——无额外规划调用开销 | 更高——Meta Agent 先做一次规划请求,决定调用哪些工具 |
| 执行确定性 | 高——代码决定哪些 Agent 运行、几路 | 低——LLM 动态决定调用哪些工具 |
| 自定义空间 | 高——可自由加条件分支、错误处理、多层 fan-out | 低——依赖 LLM 规划能力 |
| 适用场景 | 延迟敏感、执行路径固定 | 更便利、希望 LLM 自主规划调用顺序 |
"Ultimately, the approach you use will depend on the balance you want between: 1. Convenience vs. customization ... 2. Planning vs. determinism ... 3. Latency sensitivity"
ToolExecutionConfig(max_function_tool_concurrency=...) 6,用于控制 SDK 侧本地函数工具的最大并发执行数。这与 ModelSettings.parallel_tool_calls(控制模型是否允许在一次响应中并行发出多个工具调用)是两个完全独立的维度,不要混淆。| API | 层次 | 作用 |
|---|---|---|
asyncio.gather | Python 标准库 | 并发调度多个协程 |
Agent(name, instructions) | SDK | 定义 Agent 角色 |
Runner.run(agent, input) | SDK | 异步执行单个 Agent,返回 RunResult |
RunResult.new_items | SDK | 运行过程中产生的所有富元数据项 |
ItemHelpers.text_message_outputs(items) | SDK | 从 RunItem 列表提取纯文本 |
trace("workflow_name") | SDK | 将整段代码标记为单个追踪工作流 |
Agent.as_tool(...) | SDK | 将 Agent 包装为工具(第二种并行路径使用) |
"Most applications only need a few result properties or helpers: final_output, to_input_list(), new_items, last_agent..."
new_items + ItemHelpers.text_message_outputs(),没有用 final_output——因为 best-pick 场景下需要「所有文本」,而不是「最终输出」。这是一个容易犯错的细节。parallelization.py 里的 spanish_agent 和 translation_picker 都没有显式设置 model,跑示例时用的是当前 SDK 的默认模型。三路并发意味着这个模型会被同时发起三个 API 请求,成本是单次的三倍。agent_patterns/ 目录下,parallelization 和 deterministic、llm_as_a_judge 是并列的三种编排模式,覆盖了 Agent 编排的三个核心维度189:| 维度 | Parallelization | Deterministic | LLM-as-Judge |
|---|---|---|---|
| 执行模型 | asyncio.gather 并发,fan-out + fan-in | 串行链式 await,单次通过 | while True 循环迭代 |
| 总耗时 | max(T₁,T₂,T₃) + T_picker | T₁ + T₂ + T₃ | N × (T_gen + T_eval) |
| 质量策略 | 广度优先:同时跑多个,挑最好 | 流水线:每步依赖前一步,含 gate 门控 | 深度优先:同一结果反复打磨 |
| 不达标时 | 三个候选里 picker 选最好的 | exit(0) 硬停止 | 追加反馈,generator 重试 |
| 代码复杂度 | 最低(51 行有效代码) | 中(含结构化输出、质量门控) | 最高(含状态管理、循环控制) |
| 典型场景 | 多路独立子任务 / 同任务多跑取优 | 固定步骤流水线 / 资格校验 | 需要迭代改进的内容生成 |
asyncio.gather 里并行运行多个 Runner.run 时,trace 系统会产生多个 trace,但只有一个被正确填充数据,其余是空的。该问题被标记为 bug/needs-more-info 并已关闭,目前没有找到官方修复记录。Runner.run 运行在独立的上下文里,trace 上下文管理器在并发场景下的行为和串行场景有差异。asyncio.gather 场景下需要额外留意追踪数据的完整性,不要把「trace 里没看到」等同于「没有执行」。asyncio.gather——延迟最低,执行可预期,错误处理也由代码自己控制。Agent.as_tool() + ModelSettings(parallel_tool_calls=True)——更灵活,代价是一次额外的规划 API 调用和更长的上下文。translation_picker 的默认 instructions 是纯文本选择,返回也是纯文本。在生产场景里,可以给 picker 加一个 output_type,比如 @dataclass class PickResult: chosen_index: int; reason: str。这样 picker 的输出是机器可读的,downstream 逻辑可以直接拿索引,不需要再解析一次纯文本。asyncio.gather 确实是并发发出三个 API 请求,但如果用的是 gpt-5.4-mini 这样的轻量模型,单次翻译任务的成本本身就很低。关键问题不是「三倍贵」,而是「三倍返回的候选质量差异够不够大」——如果 temperature 很低导致三次结果几乎一样,picker 无从择优,这个模式就失去了意义。确保 spanish_agent 的采样温度足够高,才能让三路结果真正不同。当前版本:本文基于 OpenAI Agents SDK v0.17.0(2026-05-08 发布)。
下期预告 #23:agent_patterns/的下一个模式——Input Guardrails + Output Guardrails。 SDK 里的 guardrail 不只是一个过滤层,它本身也是并行运行的——asyncio.gather在这里以另一种方式出现。
openai.github.io
openai.github.io
developers.openai.com
openai.github.io
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.