Nested handoff history: speaker names in summary records break Chat Completions agents when a handoff_history_mapper forwards the transcript

オープン 初心者向け
#5,595 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 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 main at 27c75c5)
  • Related library versions (optional, e.g. any-llm, litellm, or pydantic): 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 main at 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

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

openai/openai-agents-python のほかの issue

openai/openai-agents-python の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。