Skip to content

feat(core): Accumulate tokens for gen_ai.invoke_agent spans from child LLM calls #17281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

RulaKhaled
Copy link
Member

Problem

Currently, gen_ai.invoke_agent spans (representing operations like generateText()) contain inaccurate token usage information. Users can only see token data on individual gen_ai.generate_text child spans, but the tokens are not accumulated across nested spans, making it difficult to track total token consumption for complete AI operations.

Solution

Implement token accumulation for gen_ai.invoke_agent spans by iterating over client LLM child spans and aggregating their token usage.

Copy link

linear bot commented Aug 1, 2025

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

github-actions bot commented Aug 1, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 23.76 kB - -
@sentry/browser - with treeshaking flags 22.35 kB - -
@sentry/browser (incl. Tracing) 39.43 kB - -
@sentry/browser (incl. Tracing, Replay) 77.52 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 67.39 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 82.22 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 94.35 kB - -
@sentry/browser (incl. Feedback) 40.45 kB - -
@sentry/browser (incl. sendFeedback) 28.45 kB - -
@sentry/browser (incl. FeedbackAsync) 33.34 kB - -
@sentry/react 25.5 kB - -
@sentry/react (incl. Tracing) 41.4 kB - -
@sentry/vue 28.2 kB - -
@sentry/vue (incl. Tracing) 41.23 kB - -
@sentry/svelte 23.79 kB - -
CDN Bundle 25.28 kB - -
CDN Bundle (incl. Tracing) 39.3 kB - -
CDN Bundle (incl. Tracing, Replay) 75.38 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 80.82 kB - -
CDN Bundle - uncompressed 73.86 kB - -
CDN Bundle (incl. Tracing) - uncompressed 116.35 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 230.56 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 243.37 kB - -
@sentry/nextjs (client) 43.46 kB - -
@sentry/sveltekit (client) 39.87 kB - -
@sentry/node-core 47.53 kB -0.01% -1 B 🔽
@sentry/node 146.36 kB +0.12% +168 B 🔺
@sentry/node - without tracing 91.63 kB - -
@sentry/aws-serverless 103.08 kB +0.01% +1 B 🔺

View base workflow run

@mydea mydea changed the title feat(core): Accumulate tokens for gen_ai.invoke_agent spans from child LLM calls feat(core): Accumulate tokens for gen_ai.invoke_agent spans from child LLM calls Aug 4, 2025
// Second pass: accumulate tokens for gen_ai.invoke_agent spans
// TODO: Determine how to handle token aggregation for tool call spans.
for (const span of event.spans) {
accumulateTokensFromChildSpans(span, event.spans);
Copy link
Member

Choose a reason for hiding this comment

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

hmm, can we combine this into a single pass somehow? 🤔 that would be more efficient I suppose.

Copy link
Member Author

@RulaKhaled RulaKhaled Aug 4, 2025

Choose a reason for hiding this comment

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

we can but it's safer to leave them separate because we are processing child spans to accumulate the tokens, we need to make sure they are all transformed completely to the attribute we are expecting, otherwise we could end up with a parent span first that have child spans that still use vercel naming so we end up not processing it.

Copy link
Member

Choose a reason for hiding this comment

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

hmm overall this is quite expensive, looking at it :D because we loop over all spans a lot:

  1. Once in the first pass
  2. Once in the second pass
    a. for each second pass, we iterate over all spans again when filtering
    b. then finally once more over the filtered spans (which is a subset already, but still)

IMHO it's probably worth it to find a way to streamline this to get to max. 2 passes. I would propose something like this:

  1. We create an object to hold the summarized tokens. e.g. something like this: const tmpAmounts: Map<string, TokenSummary> = new Map();
  2. We pass this into the first pass function as second argument, so that can mutate it
  3. The function that transforms attributes, at the very end, updates the passed in map by incrementing the token summary for the map item of the parentSpanId, e.g. something like this (simplified...):
const parentSpanId = spanToJSON(span).parent_span_id;
if (parentSpanId) {
  const parentSummary = tmpAmounts.get(parentSpanId) || { total: 0 };
  parentSummary.total += amount;
  tmpAmounts.set(parentSpanId, parentSummary);
}
  1. Finally, after the first pass, we iterate over tmpAmounts and update the parent spans with the stored summaries.

I think this or something like this should reduce overhead considerably 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

very nice, i'll try it out.


// Second pass: apply accumulated token data to parent spans
for (const span of event.spans) {
if (span.op !== 'gen_ai.invoke_agent') {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (span.op !== 'gen_ai.invoke_agent') {
if (span.op !== 'gen_ai.invoke_agent' || !tokenAccumulator.has(span.span_id)) {

should also be a reasonable shortcut?

Copy link
Member

@mydea mydea left a comment

Choose a reason for hiding this comment

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

sweet!

@RulaKhaled RulaKhaled merged commit ffa8dd8 into develop Aug 4, 2025
182 checks passed
@RulaKhaled RulaKhaled deleted the rolaabuhasna/js-662-token-attributes-on-gen_aiinvoke_agent-span branch August 4, 2025 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants