Skip to content

AnthropicLlm never populates LlmResponse.model_version #6847

Closed

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%)

Activity

  1. added a commit that references this issue on Aug 21, 2026
    d2dac59
  2. aaravxsingh15 commented on Aug 22, 2026

    @aaravxsingh15

    Hi, I’d like to work on this issue. I plan to populate LlmResponse.model_version for both streaming and non-streaming Anthropic responses and add focused unit tests. Please let me know if this approach is acceptable.

  3. omar-infinitusai commented on Aug 22, 2026

    @omar-infinitusai
    ContributorAuthor

    Hey @feiiiiii5 and @aaravxsingh15 ! Thanks for opening the PRs 🙏 . I was hoping to also capture the model_version for partial responses during reasoning. This proposed solution accounts for that: #6850

  4. added
    models[Component] This issue is related to model support
    on Aug 24, 2026
  5. sanketpatil06 commented on Aug 25, 2026

    @sanketpatil06

    Hi @omar-infinitusai, thanks for reporting this issue and submitting the fix. We observed the same behavior on our end.
    Your PR #6850 is currently under review by our team, and we will let you know if anything further is needed.

  6. added a commit that references this issue on Sep 3, 2026
    408f57d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

models[Component] This issue is related to model support

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions