Skip to content

AnthropicLlm never populates LlmResponse.model_version #6847

Description

@omar-infinitusai

🔴 Required Information

Describe the Bug:
AnthropicLlm (google/adk/models/anthropic_llm.py) never sets LlmResponse.model_version,
on either the streaming or non-streaming path. Every other first-party model wrapper populates
it from the underlying API response:

  • google_llm.py sets model_version=model from the Gemini response.
  • lite_llm.py sets model_version=response.model / model_version=part.model (this covers
    Claude-via-LiteLLM too, so that path does not have this bug).
  • apigee_llm.py sets model_version=response.get('model').

AnthropicLlm has the same data available and does not read it:

  • Non-streaming: message_to_generate_content_response() (anthropic_llm.py:672) takes
    message: anthropic_types.Message and builds the LlmResponse at line 686 without ever
    touching message.model — the Anthropic SDK's Message object exposes the actual resolved
    model id there.
  • Streaming: the message_start event handler (anthropic_llm.py:998) reads
    event.message.usage for token accounting (lines 999-1002) but never reads
    event.message.model, so the aggregated LlmResponse yielded at the end of the stream
    (anthropic_llm.py:1112) also has no model_version.

This matters because Anthropic model aliases (e.g. claude-sonnet-5) resolve to a dated
snapshot server-side, and model_version is the mechanism ADK already uses elsewhere (event
telemetry, DatabaseSessionService-persisted Event rows, the gen_ai.response.model span
attribute in telemetry/tracing.py) to record which snapshot actually served a given response.
For the Anthropic direct-API path, all of that ends up null.

Steps to Reproduce:

  1. Install google-adk (reproduced on 2.7.0, but present since the module's introduction).
  2. Configure an LlmAgent with model=AnthropicLlm(model="claude-sonnet-5") (ANTHROPIC_API_KEY set).
  3. Run any turn, streaming or non-streaming, and inspect the yielded LlmResponse(s) — or, if
    using DatabaseSessionService/any other persisted SessionService, inspect the stored
    Event row for that turn.
  4. Compare LlmResponse.model_version against the same check with Gemini or LiteLlm(model="claude-...").

Expected Behavior:
LlmResponse.model_version is populated with the model id/snapshot Anthropic actually served
(message.model non-streaming, event.message.model streaming), matching the behavior of
google_llm.py, lite_llm.py, and apigee_llm.py.

Observed Behavior:
LlmResponse.model_version is always None for every response AnthropicLlm produces, on
both the streaming and non-streaming paths.

Environment Details:

  • ADK Library Version (pip show google-adk): 2.7.0
  • Desktop OS: macOS
  • Python Version (python -V): 3.13.7

Model Information:

  • Are you using LiteLLM: No — using AnthropicLlm directly (AsyncAnthropic, keyed by ANTHROPIC_API_KEY), not the LiteLLM path.
  • Which model is being used: claude-sonnet-5 (also reproduces on other Claude ids)

🟡 Optional Information

Regression:
Not a regression — model_version appears to have never been wired into AnthropicLlm since
the class was introduced.

Additional Context:
This looks like the same class of gap as #5394 ("AnthropicLlm never populates
LlmResponse.finish_reason"), which was part of a batch of AnthropicLlm parity issues
(#5392#5397) filed against this file. That issue's linked PR (#5512) showed the finish_reason
line had originally been commented out with # TODO: Deal with these later, and although both
community PRs for it (#5441, #5512) were closed unmerged, the fix did land internally by 2.7.0
(anthropic_llm.py:1121 now sets finish_reason=to_google_genai_finish_reason(stop_reason)).
model_version doesn't appear to be tracked anywhere in that batch or elsewhere in the tracker —
filing this separately so it doesn't fall through the same gap.

Minimal Reproduction Code:

from google.adk.models.anthropic_llm import AnthropicLlm
from google.adk.models.llm_request import LlmRequest
from google.genai import types

llm = AnthropicLlm(model="claude-sonnet-5")
request = LlmRequest(
    contents=[types.Content(role="user", parts=[types.Part.from_text(text="hi")])],
)

async def main():
    async for response in llm.generate_content_async(request):
        print("model_version:", response.model_version)  # always None

import asyncio
asyncio.run(main())

How often has this issue occurred?:

  • Always (100%)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions