Nested handoff history: speaker names in summary records break Chat Completions agents when a handoff_history_mapper forwards the transcript
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 85/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- python
調査の方向性
src/agents/models/chatcmpl_converter.py、特に Converter.maybe_easy_input_messageを読み、受け付けられるメッセージフィールドと、src/agents/handoffs/history.pyの_parse_summary_lineによって復元されるnameフィールドを比較してください。handoff_history_mapperを介してChat Completionsモデルに渡される話者名を含む、ネストされたhandoff履歴項目の回帰テストを追加してください。再現手順が成功し、既存のconverterテストが通れば完了です。
索引モデルが issue の本文から書いたものです。
説明
Please read this first
- Have you read the docs? Agents SDK docs Yes
- Have you searched for related issues? Yes. Related but different: #4230 (keeps empty turns in nested history), #3319 and #3814 (other nested-history parsing issues).
Describe the bug
With RunConfig(nest_handoff_history=True), conversation history is carried in <CONVERSATION HISTORY> summary records, and the next handoff parses those records back into input items. The record format includes an optional speaker name, role (name): content: the summary writer emits it for items that have a name (_format_transcript_item_legacy in src/agents/handoffs/history.py), and _parse_summary_line restores it as a name key, e.g. {'role': 'user', 'name': 'Dana', 'content': '...'}.
When a RunConfig.handoff_history_mapper returns the normalized transcript to the next agent (the documented way to "return the exact list of input items to forward to the next agent"), and that agent uses a Chat Completions model, the run fails before the request is sent:
UserError: Unhandled item type or structure: {'role': 'user', 'name': 'Dana', 'content': 'Hi, I need my subscription history.'}
The same conversation without speaker names works. The cause is that Converter.maybe_easy_input_message (src/agents/models/chatcmpl_converter.py) only accepts the keys content, role, type and phase, so the items produced by the SDK's own parser are rejected, even though Chat Completions messages support a name field.
Debug information
- Agents SDK version: v0.24.0 (also reproduced on
mainat 27c75c5) - Related library versions (optional, e.g.
any-llm,litellm, orpydantic): openai 3.29.0, pydantic 2.12.3 - Python version: 3.12.13
- Operating system: Linux (aarch64)
- Model and model provider: Chat Completions (
OpenAIChatCompletionsModel); the repro stubs the HTTP transport - Does the issue reproduce with the latest Agents SDK release? Yes (v0.24.0, and
mainat 27c75c5) - Does the issue occur consistently or intermittently? Consistently
without speaker names: Your plan changed in May.
Traceback (most recent call last):
...
File "04_named_history_chat_completions.py", line 40, in run
result = await Runner.run(agents(), [{"role": "assistant", "content": summary},
File "agents/run.py", line 352, in run
return await runner.run(
File "agents/run.py", line 610, in run
return await self._run_impl(starting_agent, input, **kwargs)
File "agents/run.py", line 1909, in _run_impl
turn_result = await run_single_turn(
File "agents/run_internal/run_loop.py", line 2883, in run_single_turn
new_response = await get_new_response(
File "agents/run_internal/run_loop.py", line 3046, in get_new_response
new_response = await get_response_with_retry(
File "agents/run_internal/model_retry.py", line 612, in get_response_with_retry
response = await _await_model_attempt(get_response(), timeout)
File "agents/run_internal/model_retry.py", line 296, in _await_model_attempt
return await awaitable
File "agents/models/openai_chatcompletions.py", line 249, in get_response
response = await self._fetch_response(
File "agents/models/openai_chatcompletions.py", line 621, in _fetch_response
converted_messages = Converter.items_to_messages(
File "agents/models/chatcmpl_converter.py", line 1026, in items_to_messages
raise UserError(f"Unhandled item type or structure: {item}")
agents.exceptions.UserError: Unhandled item type or structure: {'role': 'user', 'name': 'Dana', 'content': 'Hi, I need my subscription history.'}
Repro steps
Self-contained; the model is a stubbed Chat Completions HTTP transport, so no API key or network is needed.
import asyncio
import httpx
from openai import AsyncOpenAI
from agents import Agent, OpenAIChatCompletionsModel, RunConfig, Runner, get_conversation_history_wrappers, handoff
from agents import set_tracing_disabled
set_tracing_disabled(True)
def chat_model(*messages):
"""A Chat Completions model whose HTTP endpoint returns the given assistant messages in order."""
replies = iter(messages)
def respond(request):
return httpx.Response(200, json={"id": "chatcmpl-1", "object": "chat.completion", "created": 0, "model": "gpt-4.1",
"choices": [{"index": 0, "finish_reason": "stop", "message": next(replies)}]})
client = AsyncOpenAI(api_key="test", http_client=httpx.AsyncClient(transport=httpx.MockTransport(respond)))
return OpenAIChatCompletionsModel("gpt-4.1", client)
def agents():
billing = Agent(name="Billing", model=chat_model({"role": "assistant", "content": "Your plan changed in May."}))
return Agent(name="Triage", handoffs=[handoff(billing)], model=chat_model(
{"role": "assistant", "content": None, "tool_calls": [{"id": "call_1", "type": "function", "function": {
"name": "transfer_to_billing", "arguments": "{}"}}]}))
def earlier(user, assistant):
"""An earlier conversation carried over in the nested-history summary format."""
start, end = get_conversation_history_wrappers()
return ("For context, here is the conversation so far between the user and the previous agent:\n"
f"{start}\n1. {user}: Hi, I need my subscription history.\n2. {assistant}: Sure, let me look into it.\n{end}")
async def run(summary):
config = RunConfig(nest_handoff_history=True, handoff_history_mapper=lambda transcript: transcript)
result = await Runner.run(agents(), [{"role": "assistant", "content": summary},
{"role": "user", "content": "When did my plan change?"}], run_config=config)
return result.final_output
async def main():
print("without speaker names:", await run(earlier("user", "assistant")))
print("with speaker names: ", await run(earlier("user (Dana)", "assistant (Support)")))
asyncio.run(main())
Expected behavior
Items recovered from nested-history summaries can be sent to any supported model: either the Chat Completions converter maps name to the message's name field, or the parser keeps the speaker name in a form the converters accept. Both lines of the script print Your plan changed in May.
- 主要言語
- Python
- スター
- 30.5k
- フォーク
- 5k
- 平均マージ
- 16時間 15分
- マージ済み PR(30日)
- 154
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
openai/openai-agents-python のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
openai/openai-agents-python#5594 ·
メンテナーはふだん 1 日以内に返信
-
Default tool error text tells the model to "try again" after a call that may already have taken effect対応中かも @Anurag07-crypto が今日担当しました。 オープン
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
openai/openai-agents-python#5573 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
openai/openai-agents-python#5550 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
openai/openai-agents-python#5529 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
openai/openai-agents-python#5498 ·
メンテナーはふだん 1 日以内に返信
openai/openai-agents-python の issue をすべて見る
似ている issue
-
integration: logger
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
home-assistant/core#191147 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
documentation p4
難易度 1/5 1〜3時間 初心者へのやさしさ 88/100
MillenniumDawn/focus-tree-creation-tool#417 ·
メンテナーはふだん 3 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
CharlesP44/Beem_Energy#96 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
rheeloaded/paperpull#153 ·
メンテナーはふだん 1 日以内に返信