From 9a516c17ba0fb61e3ca55a04b4a2dbb042ec3f7c Mon Sep 17 00:00:00 2001 From: JeremyFunk Date: Wed, 30 Sep 2026 03:18:41 +0200 Subject: [PATCH] fix(ingest): count TypeScript ADK's call_llm as the model call #1143 excluded every google_adk call_llm span before the inference-op check. Python ADK's call_llm is a wrapper with no operation over its generate_content child, but TypeScript ADK has no generate_content span: the Maple span processor stamps op chat on call_llm, so those sessions lost every LLM call and their tokens. Without the arm, the op decides: Python's opless wrapper still falls through to false, TypeScript's chat span is the call. --- apps/ingest/src/ai_session/usage.rs | 32 ++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/ingest/src/ai_session/usage.rs b/apps/ingest/src/ai_session/usage.rs index a46145c15..389879209 100644 --- a/apps/ingest/src/ai_session/usage.rs +++ b/apps/ingest/src/ai_session/usage.rs @@ -179,8 +179,6 @@ fn is_model_call(vendor: &str, span: &Span, facts: &Facts) -> bool { let span_name = span.name.as_str(); let op = facts.operation(); match vendor { - // `call_llm` wraps its `generate_content` child with the same figures. - "google_adk" if span_name == "call_llm" => false, _ if INFERENCE_OPS.contains(&op) => true, "litellm" => matches!(op, "acompletion" | "completion"), "semantic_kernel" => matches!(op, "chat.completions" | "chat.streaming_completions"), @@ -1001,9 +999,9 @@ mod tests { ); } - /// `captures/docs_google-adk_a`: `call_llm` and its `generate_content` - /// child carry the same figures. ADK's AnthropicLlm passes the raw usage - /// through (SRC). + /// `captures/docs_google-adk_a`: Python ADK's `call_llm` and its + /// `generate_content` child carry the same figures, and only the child + /// names an operation. ADK's AnthropicLlm passes the raw usage through (SRC). #[test] fn google_adk_generate_content_owns_usage() { let usage = [ @@ -1047,6 +1045,30 @@ mod tests { ); } + /// PROD `verify2-adk-ts-synthetic`: TypeScript ADK has no `generate_content` + /// span, so the `call_llm` the Maple processor stamps `chat` is the call. + #[test] + fn google_adk_ts_call_llm_owns_usage() { + check( + "verify2-adk-ts-synthetic", + "gcp.vertex.agent", + "google_adk", + &[( + "call_llm", + &[ + ("gcp.vertex.agent.session_id", "verify2-adk-ts-synthetic-1"), + ("gen_ai.operation.name", "chat"), + ("gen_ai.provider.name", "gcp.gemini"), + ("gen_ai.request.model", "gemini-2.5-flash"), + ("gen_ai.usage.cache_read.input_tokens", "400"), + ("gen_ai.usage.input_tokens", "1000"), + ("gen_ai.usage.output_tokens", "100"), + ], + Some([600, 400, 0, 100, 0]), + )], + ); + } + /// `captures/docs_mastra_b` for the keys, PROD `blind-ts-mastra` for the /// reasoning figure Maple used to drop. #[test]