Skip to content

fix(streaming): aggregate model info per invocation, not per session - #205

Open
LeonxLJX wants to merge 4 commits into
agentevals-dev:mainfrom
LeonxLJX:fix/per-invocation-model-info
Open

fix(streaming): aggregate model info per invocation, not per session#205
LeonxLJX wants to merge 4 commits into
agentevals-dev:mainfrom
LeonxLJX:fix/per-invocation-model-info

Conversation

@LeonxLJX

Copy link
Copy Markdown

Fixes #204

_extract_model_info_from_trace walked trace.all_spans for every invocation, so each invocation in a session reported the same session-wide token/model/provider aggregates, and anything summing them triple-counted usage.

What changed

  • ConversionResult now carries invocation_llm_spans — the LLM spans each invocation was built from, kept in lockstep through ADK conversion, GenAI single-turn conversion, and deduplication.
  • ws_server._extract_model_info_from_trace_extract_model_info_from_llm_spans: aggregates only the spans belonging to that invocation.

Verification

  • Added tests/test_model_info.py (converter span tracking + per-invocation token aggregation + empty-span edge case).
  • Full suite: 731 passed, only the 3 pre-existing Windows symlink containment failures remain (fail on main too).

Verified live with the zero-code examples: each invocation now reports its own in= / out= numbers instead of the session aggregate.

_extract_model_info_from_trace walked trace.all_spans for every invocation,
so each invocation reported the session-wide token/model aggregates and any
summation triple-counted usage. The converter now records the LLM spans each
invocation was built from (ConversionResult.invocation_llm_spans, kept in
lockstep through ADK/GenAI conversion and deduplication) and ws_server
aggregates only those spans per invocation.

Fixes agentevals-dev#204

@krisztianfekete krisztianfekete left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, added a few comments, please check them out!

Comment thread src/agentevals/genai_converter.py Outdated
Comment thread src/agentevals/genai_converter.py Outdated
Comment thread src/agentevals/genai_converter.py Outdated
Comment thread src/agentevals/streaming/ws_server.py Outdated
Comment thread src/agentevals/streaming/ws_server.py Outdated
Comment thread src/agentevals/streaming/ws_server.py Outdated
Comment thread src/agentevals/streaming/ws_server.py Outdated
Comment thread src/agentevals/converter.py
Comment thread src/agentevals/converter.py
Comment thread tests/test_model_info.py Outdated
- Multi-turn: when turn count matches LLM span count, map turn n to span n;
  otherwise attribute all spans to the first turn (empty for the rest) so
  session totals stay honest instead of every turn reporting all spans.
- Dedup: merge dropped duplicate invocations' LLM spans into the survivor so
  their token spend is not discarded; always return the (invocations, spans)
  tuple and updated the test call sites accordingly.
- ws_server: drop the dead \if trace\ guard (convert_traces is 1:1 with
  traces), log loudly on invocation_llm_spans index drift, remove the unused
  Trace import, and stop re-filtering LLM spans (a generate_content-only trace
  would otherwise be dropped to nothing even with usage metadata). The model
  info aggregator is now a @staticmethod.
- find_adk_llm_spans_in returns both call_llm and generate_content spans so a
  provider generate_content span's temperature/model no longer vanish.
- Tests: drive a real TraceSession through _extract_invocations instead of
  calling the aggregator on hand-picked spans; pin the nested sub-agent
  double-count limitation; add a dedup span-merge test.
@LeonxLJX

Copy link
Copy Markdown
Author

Thanks for the thorough review @krisztianfekete! I've pushed 1ebefbb addressing all of your points:

  • Multi-turn mapping: when len(turns) == len(llm_root_spans) each turn now maps to its own span; otherwise all spans are attributed to the first turn and the rest stay empty, so session totals are honest instead of every turn reporting all spans.
  • Dedup token loss: dropped duplicate invocations' LLM spans are now merged into the surviving invocation for the same user text, so real spend is not discarded. _deduplicate_invocations now always returns the (invocations, spans) tuple; updated the five test call sites.
  • ws_server dead guard / drift: removed the no-op if trace (convert_traces is 1:1 with traces); index drift now logs loudly instead of silently blanking token counts.
  • Unused imports: removed the now-unused Trace import (F401 is globally ignored so ruff missed it).
  • Re-filter: dropped the LLM-span re-filter — a generate_content-only trace no longer gets filtered to nothing even when usage metadata is present; the aggregator is now a @staticmethod.
  • generate_content fields: find_adk_llm_spans_in now returns both call_llm and generate_content spans, so a provider generate_content span's temperature/model no longer vanish from modelInfo.
  • Nested sub-agent: agreed it's not resolved — pinned with test_nested_sub_agent_spans_are_pinned (zero coverage existed) and corrected the PR description's claim. Happy to take the pruning on in a follow-up.

Test run on the touched areas: 249 passed (remaining full-suite failures are pre-existing env issues — API keys / installed-distribution version — unrelated to this change).

Comment thread src/agentevals/extraction.py Outdated
Comment thread tests/test_genai_converter.py Outdated
- Add strict=True to zip() in genai_converter.py (B905)
- Rename unused trace_idx to _trace_idx in ws_server.py (B007)
- Run ruff format on genai_converter.py, test_genai_converter.py, test_model_info.py
- Restore 'call_llm_spans or generate_content_spans' return order as
  krisztianfekete requested (the combined list doesn't help because
  generate_content spans never carry ADK llm_request/llm_response attrs)
- Fix garbled arrow characters in test_genai_converter.py docstring
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-invocation token counts are actually whole-session totals

2 participants