Skip to content

fix(structured outputs): send structured output beta header when format is omitted#1158

Merged
RobertCraigie merged 3 commits intoanthropics:nextfrom
karpetrosyan:fix-tool-runner-empty-betas
Feb 3, 2026
Merged

fix(structured outputs): send structured output beta header when format is omitted#1158
RobertCraigie merged 3 commits intoanthropics:nextfrom
karpetrosyan:fix-tool-runner-empty-betas

Conversation

@karpetrosyan
Copy link
Collaborator

@karpetrosyan karpetrosyan commented Feb 3, 2026

This reverts commit 062077e.

@karpetrosyan karpetrosyan requested a review from a team as a code owner February 3, 2026 14:16
@karpetrosyan karpetrosyan changed the title Revert "fix(structured outputs): avoid including beta header if `outp… fix(structured outputs): send structured output beta header when format is omitted Feb 3, 2026
@karpetrosyan
Copy link
Collaborator Author

karpetrosyan commented Feb 3, 2026

That change made the normal tool_runner and parse usage fail because our codegen sends an empty string as a header value when the header value is an empty array, which is invalid for Anthropic.

Now, when a user calls tool_runner without providing output_format, they’ll get this:

anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'Unexpected value(s) `` for the `anthropic-beta` header. Please consult our documentation at docs.claude.com or try again without the header.'}, 'request_id': 'req_011CXmHfwd5GrhJkdjGMDod6'}

Simple repro script:

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "anthropic==0.77.0",
#     "rich",
# ]
# ///

import json
from typing_extensions import Literal

import rich

from anthropic import Anthropic, beta_tool

client = Anthropic()


@beta_tool
def get_weather(location: str, units: Literal["c", "f"]) -> str:
    """Lookup the weather for a given city in either celsius or fahrenheit

    Args:
        location: The city and state, e.g. San Francisco, CA
        units: Unit for the output, either 'c' for celsius or 'f' for fahrenheit
    Returns:
        A dictionary containing the location, temperature, and weather condition.
    """
    # Simulate a weather API call
    print(f"Fetching weather for {location} in {units}")

    # Here you would typically make an API call to a weather service
    # For demonstration, we return a mock response
    if units == "c":
        return json.dumps(
            {
                "location": location,
                "temperature": "20°C",
                "condition": "Sunny",
            }
        )
    else:
        return json.dumps(
            {
                "location": location,
                "temperature": "68°F",
                "condition": "Sunny",
            }
        )


def main() -> None:
    runner = client.beta.messages.tool_runner(
        max_tokens=1024,
        model="claude-sonnet-4-5",
        tools=[get_weather],
        messages=[{"role": "user", "content": "What is the weather in SF?"}],
    )
    for message in runner:
        rich.print(message)


main()

@RobertCraigie
Copy link
Collaborator

is it feasible to do the more correct thing and not send an empty string? or is that much more involved

@karpetrosyan
Copy link
Collaborator Author

I’m also preparing a more accurate fix in the codegen, though I’m not sure what we should do:

  • should we change the default behavior and avoid sending an empty header value for array headers
  • or introduce a flag so customers can configure the behavior?

Just changing it could probably break some code, so the second option seems safer

@karpetrosyan
Copy link
Collaborator Author

I don’t want to just change the codegen behavior to stop sending an empty header value in this case, because empty values are valid in HTTP and might be useful in some cases

@RobertCraigie
Copy link
Collaborator

fwiw I think it'd be pretty safe to just not send an empty header by default, that seems like more sane behaviour to me.

@RobertCraigie
Copy link
Collaborator

sounds like this is a bad break though so we can just merge this for now

@RobertCraigie RobertCraigie merged commit 258494e into anthropics:next Feb 3, 2026
5 of 6 checks passed
@RobertCraigie
Copy link
Collaborator

can you also add a test for this case as well so we can't regress in the future?

@stainless-app stainless-app bot mentioned this pull request Feb 3, 2026
stainless-app bot pushed a commit that referenced this pull request Feb 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants