Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handoff does not work with Claude 3.7 Sonnet #270

Open
nishant-satpathy opened this issue Mar 20, 2025 · 8 comments
Open

Handoff does not work with Claude 3.7 Sonnet #270

nishant-satpathy opened this issue Mar 20, 2025 · 8 comments
Labels
enhancement New feature or request question Question about using the SDK

Comments

@nishant-satpathy
Copy link

nishant-satpathy commented Mar 20, 2025

I am attempting use claude-3.7-sonnet with the Agents SDK. I have it set up like this:

claude_client = AsyncOpenAI(
    base_url="https://api.anthropic.com/v1/", 
    api_key=os.getenv("ANTHROPIC_API_KEY")
)

manager_agent = Agent(
    name="Manager Agent",
    instructions="""You are a manager who will decide which sub agent to handoff the input to based on the user's input. 
    """,
    model=OpenAIChatCompletionsModel(model="claude-3-7-sonnet-20250219", openai_client=claude_client),
    handoffs=[agent_1, agent_2, agent_3, agent_4]
)

When I execute this, I get the following error:

openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid 'input[1].id': '__fake_id__'. Expected an ID that begins with 'msg'.", 'type': 'invalid_request_error', 'param': 'input[1].id', 'code': 'invalid_value'}}

I noticed that there was some documentation around __fake_id__ here:

FAKE_RESPONSES_ID = "__fake_id__"
"""This is a placeholder ID used to fill in the `id` field in Responses API related objects. It's
useful when you're creating Responses objects from non-Responses APIs, e.g. the OpenAI Chat
Completions API or other LLM providers.
"""

From the examples, it looks like we have to use OpenAIChatCompletionsModel for using an LLM not provided by OpenAI, so does this mean that handoffs are not possible with any non OpenAI LLM?

Side Note: I noticed that there was a comment on issue #120 that mentions that if the model does not support tool calling, handoff calling is not supported, but claude-3.7 does support tool calling IIRC.

Would appreciate any insights on this, thanks!

@nishant-satpathy nishant-satpathy added the question Question about using the SDK label Mar 20, 2025
@rm-openai
Copy link
Collaborator

I tried the attached example on Anthropic, and it worked ok. I'm guessing yours is more complex - could you share a script please?

import asyncio
import os

from openai import AsyncOpenAI

from agents import (
    Agent,
    OpenAIChatCompletionsModel,
    Runner,
    enable_verbose_stdout_logging,
    function_tool,
    set_tracing_disabled,
)

enable_verbose_stdout_logging()

BASE_URL = os.getenv("EXAMPLE_BASE_URL") or ""
API_KEY = os.getenv("EXAMPLE_API_KEY") or ""
MODEL_NAME = os.getenv("EXAMPLE_MODEL_NAME") or ""

if not BASE_URL or not API_KEY or not MODEL_NAME:
    raise ValueError(
        "Please set EXAMPLE_BASE_URL, EXAMPLE_API_KEY, EXAMPLE_MODEL_NAME via env var or code."
    )

"""This example uses a custom provider for a specific agent. Steps:
1. Create a custom OpenAI client.
2. Create a `Model` that uses the custom client.
3. Set the `model` on the Agent.

Note that in this example, we disable tracing under the assumption that you don't have an API key
from platform.openai.com. If you do have one, you can either set the `OPENAI_API_KEY` env var
or call set_tracing_export_api_key() to set a tracing specific key.
"""
client = AsyncOpenAI(base_url=BASE_URL, api_key=API_KEY)
set_tracing_disabled(disabled=True)

@function_tool
def get_weather(city: str):
    print(f"[debug] getting weather for {city}")
    return f"The weather in {city} is sunny."


async def main():
    # This agent will use the custom LLM provider
    agent_2 = Agent(
        name="Spanish agent",
        instructions="You only respond in Spanish.",
        handoff_description="Spanish speaking agent",
        model=OpenAIChatCompletionsModel(model=MODEL_NAME, openai_client=client),
    )
    agent = Agent(
        name="Assistant",
        instructions="You only respond in haikus. Transfer to the spanish agent if the user asks in spanish",
        model=OpenAIChatCompletionsModel(model=MODEL_NAME, openai_client=client),
        tools=[get_weather],
        handoffs=[agent_2],
    )

    result = await Runner.run(agent, "Hola como estas?")
    print(result.final_output)


if __name__ == "__main__":
    asyncio.run(main())

@nishant-satpathy
Copy link
Author

@rm-openai thanks for your reply, hmm thats interesting. Here is the script:

import os
from dotenv import load_dotenv
from agents import Agent, Runner, OpenAIChatCompletionsModel, OpenAIResponsesModel
from data_models.models_1 import Model_1
from data_models.models_2 import Model_2
from tools.knowledge_query_tool import query_knowledge_tool
from openai import AsyncOpenAI, OpenAI

# Load environment variables from .env file
load_dotenv()

claude_client = AsyncOpenAI(
    base_url="https://api.anthropic.com/v1/", 
    api_key=os.getenv("ANTHROPIC_API_KEY")
)

# Verify API key is loaded
if not os.getenv("OPENAI_API_KEY"):
    raise ValueError(
        "OPENAI_API_KEY not found in environment variables. Please check your .env file."
    )

"""
================================================================================
SYSTEM PROMPTS
================================================================================
"""
with open("./system_prompts/generation_prompt_1.txt", "r") as file:
    generation_prompt_1 = file.read()

with open("./system_prompts/generation_prompt_2.txt", "r") as file:
    generation_prompt_2 = file.read()

with open("./system_prompts/generation_prompt_3.txt", "r") as f:
    generation_prompt_3 = f.read()

"""
================================================================================
AGENTS
================================================================================
"""
claude_agent_1 = Agent(
    name="Claude Specialist Agent 1",
    instructions=generation_prompt_1,
    model=OpenAIChatCompletionsModel(model="claude-3-7-sonnet-20250219", openai_client=claude_client),
)

agent_1 = Agent(
    name="Specialist Agent 1",
    instructions=generation_prompt_1,
    handoff_description="Specialist agent for creating marketing journeys",
    model="gpt-4o"
)

agent_2 = Agent(
    name="Specialist Agent 2",
    instructions=generation_prompt_2,
    handoff_description="Specialist agent for creating marketing campaigns",
    model="gpt-4o",
    output_type=Model_1
)

agent_3 = Agent(
    name="Specialist Agent 3",
    instructions=generation_prompt_3,
    handoff_description="Specialist agent for creating segmentation queries",
    model="gpt-4o",
    output_type=Model_2
)

knowledge_query_agent = Agent(
    name="Knowledge Query Agent",
    instructions="""You are an agent that can look up documentation regarding a particular customer. 
    The input will be a query about a customer
    . Use the query_knowledge_tool to look up documentation that may exist for the customer. return the output of the tool as is, even if there is an error. Do NOT parse the query, pass it through to the tool as is.""",
    model=OpenAIChatCompletionsModel(model="claude-3-7-sonnet-20250219", openai_client=claude_client),
    handoff_description="Specialist agent for looking up documentation regarding a particular customer",
    tools=[query_knowledge_tool]
)

manager_agent = Agent(
    name="Manager Agent",
    instructions="""You are a manager who will decide which sub agent to handoff the input to based on the user's input. 
    """,
    model=OpenAIChatCompletionsModel(model="claude-3-7-sonnet-20250219", openai_client=claude_client),
    handoffs=[agent_1, agent_2, agent_3, knowledge_query_agent]
)

def main():
    user_input = input("How can we help you today? \n")

    while True:
        if user_input == "exit":
            break
        result = Runner.run_sync(manager_agent, user_input)
        # result = Runner.run_sync(manager_agent, user_input)
        print(result.final_output)
        print("\n")
        user_input = input("Is there anything else you need help with? \n")

if __name__ == "__main__":
    main()

@nishant-satpathy
Copy link
Author

@rm-openai so I dug into this more, and looks like I am running into this error if a Claude powered Agent tries to handoff to an OpenAI powered Agent. Handoff is working if Claude Agent hands off to a Claude Agent and if an OpenAI Agent hands off to a Claude Agent or to another OpenAI Agent.

I verified this behavior in the example script you shared, by changing the Spanish Agent to use gpt-4o. and the Assistant agent as Claude and I got the same error.

mport asyncio
import os

from openai import AsyncOpenAI

from agents import (
    Agent,
    OpenAIChatCompletionsModel,
    Runner,
    enable_verbose_stdout_logging,
    function_tool,
    set_tracing_disabled,
)

enable_verbose_stdout_logging()

BASE_URL = "https://api.anthropic.com/v1/"
API_KEY = ""
MODEL_NAME = "claude-3-7-sonnet-20250219"

if not BASE_URL or not API_KEY or not MODEL_NAME:
    raise ValueError(
        "Please set EXAMPLE_BASE_URL, EXAMPLE_API_KEY, EXAMPLE_MODEL_NAME via env var or code."
    )

"""This example uses a custom provider for a specific agent. Steps:
1. Create a custom OpenAI client.
2. Create a `Model` that uses the custom client.
3. Set the `model` on the Agent.

Note that in this example, we disable tracing under the assumption that you don't have an API key
from platform.openai.com. If you do have one, you can either set the `OPENAI_API_KEY` env var
or call set_tracing_export_api_key() to set a tracing specific key.
"""
client = AsyncOpenAI(base_url=BASE_URL, api_key=API_KEY)
set_tracing_disabled(disabled=True)

@function_tool
def get_weather(city: str):
    print(f"[debug] getting weather for {city}")
    return f"The weather in {city} is sunny."


async def main():
    # This agent will use the custom LLM provider
    agent_2 = Agent(
        name="Spanish agent",
        instructions="You only respond in Spanish.",
        handoff_description="Spanish speaking agent",
        # model=OpenAIChatCompletionsModel(model=MODEL_NAME, openai_client=client),
        model="gpt-4o",
    )

    agent = Agent(
        name="Assistant",
        instructions="You only respond in haikus. Transfer to the spanish agent if the user asks in spanish",
        model=OpenAIChatCompletionsModel(model=MODEL_NAME, openai_client=client),
        # model="gpt-4o",
        tools=[get_weather],
        handoffs=[agent_2],
    )

    result = await Runner.run(agent, "Hola como estas?")
    print(result.final_output)


if __name__ == "__main__":
    asyncio.run(main())

Error:

openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid 'input[1].id': '__fake_id__'. Expected an ID that begins with 'msg'.", 'type': 'invalid_request_error', 'param': 'input[1].id', 'code': 'invalid_value'}}

@rm-openai
Copy link
Collaborator

@nishant-satpathy just debugged this, and looks like it's the issue we mention here

While our SDK supports both the OpenAIResponsesModel and the OpenAIChatCompletionsModel shapes, we recommend using a single model shape for each workflow because the two shapes support a different set of features and tools.

The easiest fix is to use Chat Completions everywhere. For example, this addition fixed it

from agents import set_default_openai_api

set_default_openai_api("chat_completions")

@nishant-satpathy
Copy link
Author

@rm-openai good callout. Changing the default model shape worked! Are there any particular features I would be sacrificing by defaulting to using the Completions Model over the Responses Model?

@rm-openai
Copy link
Collaborator

Yes, hosted tools (e.g. web search, file search, computer use). You can still use web search in chat completions but via a specific model rather than as a tool.

I'm going to see if I can do something to improve this though, since we want you to be able to mix and match models without issues.

@rm-openai rm-openai added the enhancement New feature or request label Mar 21, 2025
@nishant-satpathy
Copy link
Author

@rm-openai sounds good, thanks!

@grexzen
Copy link

grexzen commented Mar 23, 2025

You can always run a proxy to transform Anthropic calls to OpenAI and viceversa btw.

Done that before so I can use OpenAI tools with Claude.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Question about using the SDK
Projects
None yet
Development

No branches or pull requests

3 participants