Skip to content

Python Anthropic: structured system blocks and assistant-embedded tool results serialize incorrectly #6450

@rg-ve

Description

@rg-ve

Summary

The Python Anthropic provider appears to have two related serialization gaps at the final Anthropic request boundary:

  1. Provider-specific structured system blocks for Anthropic prompt caching are forwarded as a raw request kwarg instead of being translated into Anthropic system blocks.
  2. SDK messages that contain an assistant message with interleaved function_call, function_result, function_call content are serialized as one Anthropic assistant message containing tool_use, tool_result, tool_use. Anthropic expects tool results on the user/tool-result side of the transcript, immediately following the corresponding assistant tool use.

I traced through the public SDK path, not just private helper methods:

Agent.run(...) -> Agent._prepare_run_context(...) -> Agent._call_chat_client(...) -> BaseChatClient.get_response(...) -> Anthropic _inner_get_response(...) -> anthropic_client.beta.messages.create(...)

The generic SDK layers preserve provider-specific options and messages; the final Anthropic provider serializer is where these need to be handled.

Environment checked

  • Repo: microsoft/agent-framework
  • Branch checked: current main as of 2026-06-10, also consistent with Python release python-1.8.1
  • Package path: python/packages/anthropic/agent_framework_anthropic/_chat_client.py
  • Anthropic SDK: current provider dependency uses anthropic>=0.80.0,<0.80.1

Issue 1: anthropic_system_blocks leaks to Anthropic API kwargs

Anthropic prompt caching requires structured system blocks with cache_control, for example:

[
    {"type": "text", "text": "stable instructions", "cache_control": {"type": "ephemeral", "ttl": "1h"}},
    {"type": "text", "text": "per request context"},
]

The provider currently copies unknown options into run_options:

run_options = {
    k: v for k, v in options.items() if v is not None and k not in {"instructions", "response_format"}
}

So an option such as anthropic_system_blocks remains in the final kwargs sent to beta.messages.create(...), and system remains unset.

Expected

The provider should consume this provider-specific option, exclude it from raw request kwargs, and map it to Anthropic system blocks. If both generic instructions and structured system blocks are supplied, generic instructions should either be appended as a text block or a clear precedence rule should be documented.

Actual full-path capture

Using a dummy Anthropic client that captures the final kwargs from beta.messages.create(...) after Agent.run(...):

FULL_AGENT_PROMPT_CACHE_TRACE
has_anthropic_system_blocks= True
system= None
message_roles= ['user']

This means the raw Anthropic API call receives an unexpected anthropic_system_blocks kwarg and no structured system blocks.

Issue 2: assistant-embedded function results serialize to assistant-role tool_result

When replay/history contains one SDK assistant message with interleaved function calls and results, the Anthropic provider serializes that directly as one assistant message.

Repro shape

Message(
    role="assistant",
    contents=[
        Content.from_function_call(call_id="call_1", name="first", arguments="{}"),
        Content.from_function_result(call_id="call_1", result="ok"),
        Content.from_function_call(call_id="call_2", name="second", arguments="{}"),
    ],
)

Actual full-path capture

Using the public client.get_response(...) path with a dummy Anthropic client:

FULL_CLIENT_TOOL_RESULT_TRACE
roles= ['assistant']
types= [['tool_use', 'tool_result', 'tool_use']]

Expected

The Anthropic provider should split assistant-embedded function results into Anthropic-valid role groups before wire serialization, for example:

roles= ['assistant', 'user', 'assistant']
types= [['tool_use'], ['tool_result'], ['tool_use']]

This is related to prior Anthropic tool-call role issues, but it is not the same shape as a tool_use incorrectly landing in a user message. Here, the remaining invalid shape is tool_result being embedded in an assistant message during replay/history serialization.

Why this matters

The SDK layers between Agent.run(...) and the provider do not currently fix either shape:

  • Agent._prepare_run_context(...) preserves remaining provider-specific options through **opts.
  • BaseChatClient.get_response(...) only performs compaction/annotation before _inner_get_response(...).
  • The Anthropic provider then serializes the messages/options directly into beta.messages.create(...) kwargs.

So consumers cannot get Anthropic prompt caching through the standard MAF options path, and replay/history can produce invalid Anthropic tool-result role ordering unless every application normalizes this outside the provider.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions