feat(closes OPEN-11891): add google-genai tracer with Vertex support - #660
Open
viniciusdsmello wants to merge 2 commits into
Open
Conversation
trace_gemini targeted only the legacy google-generativeai SDK, so users of
the unified google.genai.Client -- including Vertex mode, which is what
Google Cloud customers use -- got no auto-instrumentation at all
(auto_instrument() returned {"gemini": False} behind a logger.debug) and a
hard ValueError on an explicit trace_gemini(client) call.
Adds google_genai_tracer covering client.models.generate_content and
generate_content_stream, their async equivalents under client.aio.models,
and chat sessions via client.chats -- the last for free, since
Chats(modules=client.models) shares the patched Models instance.
trace_gemini now sniffs the client type and dispatches, importing only the
SDK it was handed, so the issue's repro works unchanged. trace_google_genai
is exported for explicitness.
Model names reduce to the bare slug (last path segment), covering models/...
and full Vertex resource paths. Verified load-bearing against the live API:
models/gemini-2.5-flash prices at $0.00 where bare gemini-2.5-flash prices
at $2.80 on identical tokens.
Tokens use total_token_count with thinking tokens folded into
completion_tokens. candidates_token_count excludes thoughts and thinking is
on by default for 2.5-series models, so prompt+candidates undercounts badly
-- a live call measured 382 real tokens (11 prompt + 29 candidates + 342
thoughts) where the old formula reported 40. Also fixed in gemini_tracer,
which shared the bug, along with its async streaming path: `async for i,
chunk in enumerate(chunks)` raises, since enumerate returns a plain
iterator, so that path could never have run.
provider is "gemini" for both Gemini tracers -- a real cost-table slug.
Lookup is an exact lowercased (provider, model) match with no aliasing, and
"Google" prices gemini-3-pro-preview at $0 and carries stale
OpenRouter-sourced prices for the -latest aliases. google_adk_tracer is
deliberately left on "Google": BaseLlmFlow._call_llm_async is
model-agnostic, so LiteLlm and Claude-on-Vertex agents reach the same
wrapper and would be paired with a mismatched slug.
Suppresses its own span when the ADK tracer already has an LLM step open.
ADK's api_client IS a genai.Client calling aio.models.generate_content, so
both integrations activate for ADK users and would otherwise double-count
spans and cost.
57 tests, no network, including a real Runner + LlmAgent turn asserting
exactly one chat_completion span with both tracers active. Verified against
a pristine origin/main worktree at the same path: no regressions.
End-to-end verified on live AI Studio (non-streaming, streaming, async
streaming, chats, prefixed model names) with cost resolving on every path.
Vertex mode is unit-tested only -- no Vertex credentials available.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8GauFiK5edFUfGnbjJrtz
CI's lint env has neither google-genai nor google-adk installed, so `from google import genai` resolves to an unknown attribute on the `google` namespace package (reportMissingTypeStubs + reportAttributeAccessIssue). Locally, where the package IS installed, pyright instead flags `inference_id` as an undeclared kwarg on generate_content -- which is the point, since the tracer pops it before calling through. Suppress those three at file level in both new test files, and fix a genuine typing slip: the async streaming comprehension yielded list[str | None] against a List[str] annotation. Verified against a venv with no google-* packages (matching CI) as well as the local env with both installed: 0 errors either way, and no pyright errors attributable to the new files repo-wide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A8GauFiK5edFUfGnbjJrtz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes OPEN-11891.
trace_geminitargeted only the legacygoogle-generativeaiSDK. Users on the current unified SDK (google.genai.Client, includingvertexai=True— what Google Cloud customers use) got no auto-instrumentation at all, silently: the registry probedgoogle.generativeai, failed_is_installed, and skipped behind alogger.debug. Explicittrace_gemini(client)raised. Reported from a Vertex AI customer PoC that hand-rolledchat_completionspans as a workaround.What's covered
client.models.generate_contentclient.models.generate_content_streamIteratorclient.aio.models.generate_contentclient.aio.models.generate_content_streamAsyncIterator—await, thenasync forclient.chats.create(...).send_message(...)Chats(modules=client.models)shares the patchedModelsinstancetrace_gemininow sniffs the client type and dispatches, importing only the SDK it was handed, so the issue's repro works unchanged.trace_google_genaiis exported for explicitness.Three things worth a reviewer's attention
1. Model-name normalization is the biggest fix here, bigger than the tracer. Cost lookup is an exact lowercased
(provider, model)match with no aliasing, and cost rows are keyed on bare slugs. Verified by publishing rows and readingopenlayer_costback:The API accepts prefixed names and Vertex hands out full resource paths (
projects/…/publishers/google/models/…), so this was a live silent-$0 path._normalize_model_nametakes the last/segment. Version suffixes are left intact deliberately — upstream coverage for them is inconsistent (gemini-2.0-flash-001prices,gemini-2.5-flash-001doesn't, under every slug) and rewriting them would misreport which model ran.2. Token accounting was wrong by ~10x on thinking models.
candidates_token_countexcludesthoughts_token_count, and thinking is on by default for 2.5-series. A live call measured:prompt + candidatesSo
tokensnow usestotal_token_count, andcompletion_tokens = candidates + thoughtsbecause Vertex bills thinking as output and the backend prices fromcompletionTokens. The raw split goes to metadata. Streaming caveat:usage_metadatais on every chunk and cumulative — last-chunk-wins, summing would multiply-count. Same bug fixed ingemini_tracer.3. ADK would have double-billed without the suppression.
google.adk…Gemini.api_clientis agoogle.genai.Client, andgenerate_content_asynccallsself.api_client.aio.models.generate_content. Sincegoogle-adkdepends ongoogle-genai, agoogle.genairegistry entry activates for every ADK user, and each ADK LLM call would emit two spans and two costs. The tracer stands down when the ADK tracer already has an LLM step open, checked viasys.modules(no import —google_adk_traceris 66 KB and a guarded lazy import would build anImportErroron every call in the common case). There's a realRunner+LlmAgenttest asserting exactly one span with both tracers active, plus a control proving the check is contextual rather than "is ADK importable".provider = "gemini"
Both Gemini tracers now emit the real cost slug rather than the
"Google"display label. Measured, by publishing rows and reading cost back rather than counting the feed:"Google"prices 7 of 9 current models correctly — it is not broadly broken, and an earlier draft of this change overstated the gap.gemini-3-pro-previewresolves only undergemini($14.00 vs $0.00).gemini-*/gemma-*models, 6 disagree —gemini-flash-latestis 3.4x ($0.00525underGooglevs$0.00155undergemini).gemini's figure matchesgemini-2.5-flashpricing exactly; theGooglerow looks like stale pro-tier pricing.So the switch buys accuracy and future-proofing (
googlerows come from OpenRouter's catalog, which lags Google's newest and Vertex-only models;geminirows come from LiteLLM). It does move reported dollar amounts for existing legacy-SDK users on the-latestaliases — happy to splitgemini_tracer.pyout into its own change if you'd rather not carry that here.google_adk_traceris deliberately left on"Google".BaseLlmFlow._call_llm_asyncresolves the LLM via__get_llm()and is model-agnostic, soLiteLlmand Claude-on-Vertex agents hit the same wrapper —"gemini"there would pair the slug with aclaude-*model and miss the lookup entirely.Verification
origin/mainworktree at the same filesystem path. (Comparing across paths is misleading — a stray.envat the checkout root leaks a real key intotest_tracer_configuration.py; those failures and the xdist-order flakiness intest_claude_agent_sdk/test_transformreproduce identically onorigin/main.)provider=gemini,model=gemini-2.5-flash.Known gap
Vertex mode has no live verification. The available credential was an AI Studio key, so
llm_system: google_vertex, the resource-path model shapes, andgcp_project/gcp_locationextraction are unit-tested only. Those last two readclient._api_client, private API that agoogle-genaibump could break with nothing in CI to catch it. A reviewer with a GCP project could close this.Follow-ups worth filing
gemini-2.5-flash-001) are unpriced under every slug — a server-side suffix fallback would fix all SDKs at once.google_adk_tracersetsmodel_name = llm_request.modelwith no normalization, so ADK users configuring full resource paths price at $0 regardless of slug.providerdoes double duty as UI label and exact cost key, which is the root of OPEN-9928 / OPEN-11695 / OPEN-11901. A server-side alias map would let SDKs emit friendly labels.🤖 Generated with Claude Code
https://claude.ai/code/session_01A8GauFiK5edFUfGnbjJrtz