Skip to content

Commit b359bb0

Browse files
authored
[Agent Builder] improve agent builder telemetry to report daily usages, correct total… (elastic#255665)
1 parent feeb77d commit b359bb0

6 files changed

Lines changed: 367 additions & 135 deletions

File tree

x-pack/platform/plugins/private/telemetry_collection_xpack/schema/xpack_platform.json

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,19 +362,19 @@
362362
"total": {
363363
"type": "long",
364364
"_meta": {
365-
"description": "Total number of conversations"
365+
"description": "Total number of conversations (all-time)"
366366
}
367367
},
368368
"total_rounds": {
369369
"type": "long",
370370
"_meta": {
371-
"description": "Total conversation rounds across all conversations"
371+
"description": "Total conversation rounds across all conversations (all-time)"
372372
}
373373
},
374374
"avg_rounds_per_conversation": {
375375
"type": "float",
376376
"_meta": {
377-
"description": "Average rounds per conversation"
377+
"description": "Average rounds per conversation (all-time)"
378378
}
379379
},
380380
"rounds_distribution": {
@@ -399,13 +399,90 @@
399399
"tokens_used": {
400400
"type": "long",
401401
"_meta": {
402-
"description": "Total tokens used across all conversations (input + output)"
402+
"description": "Total tokens used (input + output) (all-time)"
403+
}
404+
},
405+
"tokens_input": {
406+
"type": "long",
407+
"_meta": {
408+
"description": "Total input tokens across all conversations (all-time)"
409+
}
410+
},
411+
"tokens_output": {
412+
"type": "long",
413+
"_meta": {
414+
"description": "Total output tokens across all conversations (all-time)"
415+
}
416+
},
417+
"average_tokens_per_conversation": {
418+
"type": "float",
419+
"_meta": {
420+
"description": "Average tokens per conversation (all-time)"
421+
}
422+
}
423+
}
424+
},
425+
"daily": {
426+
"properties": {
427+
"total": {
428+
"type": "long",
429+
"_meta": {
430+
"description": "Total number of conversations (daily, last 24h)"
431+
}
432+
},
433+
"total_rounds": {
434+
"type": "long",
435+
"_meta": {
436+
"description": "Total conversation rounds (daily, last 24h)"
437+
}
438+
},
439+
"avg_rounds_per_conversation": {
440+
"type": "float",
441+
"_meta": {
442+
"description": "Average rounds per conversation (daily, last 24h)"
443+
}
444+
},
445+
"rounds_distribution": {
446+
"type": "array",
447+
"items": {
448+
"properties": {
449+
"bucket": {
450+
"type": "keyword",
451+
"_meta": {
452+
"description": "Round count bucket (1-5, 6-10, 11-20, 21-50, 51+)"
453+
}
454+
},
455+
"count": {
456+
"type": "long",
457+
"_meta": {
458+
"description": "Number of conversations in this bucket"
459+
}
460+
}
461+
}
462+
}
463+
},
464+
"tokens_used": {
465+
"type": "long",
466+
"_meta": {
467+
"description": "Total tokens used (input + output) (daily, last 24h)"
468+
}
469+
},
470+
"tokens_input": {
471+
"type": "long",
472+
"_meta": {
473+
"description": "Total input tokens (daily, last 24h)"
474+
}
475+
},
476+
"tokens_output": {
477+
"type": "long",
478+
"_meta": {
479+
"description": "Total output tokens (daily, last 24h)"
403480
}
404481
},
405482
"average_tokens_per_conversation": {
406483
"type": "float",
407484
"_meta": {
408-
"description": "Average tokens per conversation"
485+
"description": "Average tokens per conversation (daily, last 24h)"
409486
}
410487
}
411488
}

x-pack/platform/plugins/shared/agent_builder/server/telemetry/agent_builder_telemetry.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,43 @@ pipeline. They include:
3737
- How we count: ES `count` on the agents system index.
3838

3939
### conversations
40+
41+
All-time conversation metrics (no date filter).
42+
4043
- `conversations.total`
4144
- What it is: Total number of conversations.
42-
- How we count: ES `hits.total` on the conversations index.
45+
- How we count: ES `hits.total` (with `track_total_hits: true`) on the conversations index.
4346
- `conversations.rounds_distribution[]`
4447
- What it is: Distribution of conversations by number of rounds.
4548
- How we count: ES scripted terms aggregation using `conversation_rounds` (or `rounds` fallback)
4649
and bucket labels `1-5`, `6-10`, `11-20`, `21-50`, `51+`.
4750
- `conversations.total_rounds`
48-
- What it is: Approximate total number of rounds across all conversations.
49-
- How we count: Derived from `rounds_distribution` buckets by multiplying each bucket count by
50-
a midpoint (3, 8, 15, 35, 75) and summing.
51+
- What it is: Exact total number of rounds across all conversations.
52+
- How we count: ES scripted sum aggregation that counts the actual size of each conversation's
53+
`conversation_rounds` (or `rounds`) array.
5154
- `conversations.avg_rounds_per_conversation`
5255
- What it is: Average rounds per conversation.
5356
- How we count: `total_rounds / total`, rounded to two decimals.
5457
- `conversations.tokens_used`
55-
- What it is: Total tokens used across all conversation rounds.
56-
- How we count: ES scripted sum aggregation over `conversation_rounds[*].model_usage`
57-
(`input_tokens + output_tokens`).
58+
- What it is: Total tokens used across all conversation rounds (input + output).
59+
- How we count: Sum of `tokens_input` and `tokens_output`.
60+
- `conversations.tokens_input`
61+
- What it is: Total input tokens across all conversation rounds.
62+
- How we count: ES scripted sum aggregation over `conversation_rounds[*].model_usage.input_tokens`.
63+
- `conversations.tokens_output`
64+
- What it is: Total output tokens across all conversation rounds.
65+
- How we count: ES scripted sum aggregation over `conversation_rounds[*].model_usage.output_tokens`.
5866
- `conversations.average_tokens_per_conversation`
5967
- What it is: Average tokens per conversation.
6068
- How we count: `tokens_used / total`, rounded to two decimals.
6169

70+
### daily
71+
72+
Same structure as `conversations` above, but filtered to the last 24 hours using a
73+
`created_at >= now-24h` range filter. All fields (`total`, `total_rounds`, `tokens_used`,
74+
`tokens_input`, `tokens_output`, `rounds_distribution`, etc.) are computed identically but
75+
only over conversations created in the last day.
76+
6277
### tokens_by_model[]
6378
- `tokens_by_model[].model`
6479
- What it is: LLM model identifier.

x-pack/platform/plugins/shared/agent_builder/server/telemetry/query_utils.test.ts

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -439,33 +439,39 @@ describe('query_utils', () => {
439439
});
440440

441441
describe('getConversationMetrics', () => {
442-
it('returns conversation metrics with aggregation data', async () => {
443-
esClient.search.mockResolvedValue({
444-
took: 1,
445-
timed_out: false,
446-
_shards: { total: 1, successful: 1, skipped: 0, failed: 0 },
447-
hits: { total: { value: 100, relation: 'eq' }, hits: [] },
448-
aggregations: {
449-
rounds_distribution: {
450-
buckets: [
451-
{ key: '1-5', doc_count: 60 },
452-
{ key: '6-10', doc_count: 25 },
453-
{ key: '11-20', doc_count: 10 },
454-
{ key: '21-50', doc_count: 4 },
455-
{ key: '51+', doc_count: 1 },
456-
],
457-
},
458-
total_tokens: {
459-
value: 50000,
460-
},
461-
},
462-
});
442+
const mockConversationResponse = ({
443+
totalHits = 100,
444+
roundsBuckets = [
445+
{ key: '1-5', doc_count: 60 },
446+
{ key: '6-10', doc_count: 25 },
447+
{ key: '11-20', doc_count: 10 },
448+
{ key: '21-50', doc_count: 4 },
449+
{ key: '51+', doc_count: 1 },
450+
],
451+
totalRounds = 745,
452+
inputTokens = 30000,
453+
outputTokens = 20000,
454+
} = {}) => ({
455+
took: 1,
456+
timed_out: false,
457+
_shards: { total: 1, successful: 1, skipped: 0, failed: 0 },
458+
hits: { total: { value: totalHits, relation: 'eq' as const }, hits: [] },
459+
aggregations: {
460+
rounds_distribution: { buckets: roundsBuckets },
461+
total_rounds: { value: totalRounds },
462+
total_input_tokens: { value: inputTokens },
463+
total_output_tokens: { value: outputTokens },
464+
},
465+
});
466+
467+
it('returns conversation metrics with real round counts', async () => {
468+
esClient.search.mockResolvedValue(mockConversationResponse());
463469

464470
const result = await queryUtils.getConversationMetrics();
465471

466472
expect(result).toEqual({
467473
total: 100,
468-
total_rounds: 60 * 3 + 25 * 8 + 10 * 15 + 4 * 35 + 1 * 75, // = 180 + 200 + 150 + 140 + 75 = 745
474+
total_rounds: 745,
469475
avg_rounds_per_conversation: 7.45,
470476
rounds_distribution: [
471477
{ bucket: '1-5', count: 60 },
@@ -475,47 +481,33 @@ describe('query_utils', () => {
475481
{ bucket: '51+', count: 1 },
476482
],
477483
tokens_used: 50000,
484+
tokens_input: 30000,
485+
tokens_output: 20000,
478486
average_tokens_per_conversation: 500,
479487
});
480488
});
481489

482490
it('handles total as number (not object)', async () => {
483491
esClient.search.mockResolvedValue({
484-
took: 1,
485-
timed_out: false,
486-
_shards: { total: 1, successful: 1, skipped: 0, failed: 0 },
492+
...mockConversationResponse({ totalHits: 50 }),
487493
hits: { total: 50, hits: [] },
488-
aggregations: {
489-
rounds_distribution: {
490-
buckets: [{ key: '1-5', doc_count: 50 }],
491-
},
492-
total_tokens: {
493-
value: 10000,
494-
},
495-
},
496494
});
497495

498496
const result = await queryUtils.getConversationMetrics();
499497

500498
expect(result.total).toBe(50);
501-
expect(result.average_tokens_per_conversation).toBe(200);
502499
});
503500

504501
it('returns 0 for average when no conversations', async () => {
505-
esClient.search.mockResolvedValue({
506-
took: 1,
507-
timed_out: false,
508-
_shards: { total: 1, successful: 1, skipped: 0, failed: 0 },
509-
hits: { total: { value: 0, relation: 'eq' }, hits: [] },
510-
aggregations: {
511-
rounds_distribution: {
512-
buckets: [],
513-
},
514-
total_tokens: {
515-
value: 0,
516-
},
517-
},
518-
});
502+
esClient.search.mockResolvedValue(
503+
mockConversationResponse({
504+
totalHits: 0,
505+
roundsBuckets: [],
506+
totalRounds: 0,
507+
inputTokens: 0,
508+
outputTokens: 0,
509+
})
510+
);
519511

520512
const result = await queryUtils.getConversationMetrics();
521513

@@ -525,10 +517,47 @@ describe('query_utils', () => {
525517
avg_rounds_per_conversation: 0,
526518
rounds_distribution: [],
527519
tokens_used: 0,
520+
tokens_input: 0,
521+
tokens_output: 0,
528522
average_tokens_per_conversation: 0,
529523
});
530524
});
531525

526+
it('applies date filter when provided', async () => {
527+
esClient.search.mockResolvedValue(
528+
mockConversationResponse({
529+
totalHits: 10,
530+
totalRounds: 25,
531+
inputTokens: 1000,
532+
outputTokens: 500,
533+
})
534+
);
535+
536+
await queryUtils.getConversationMetrics({ gte: '2024-01-01T00:00:00.000Z' });
537+
538+
expect(esClient.search).toHaveBeenCalledWith(
539+
expect.objectContaining({
540+
query: {
541+
bool: {
542+
filter: [{ range: { created_at: { gte: '2024-01-01T00:00:00.000Z' } } }],
543+
},
544+
},
545+
})
546+
);
547+
});
548+
549+
it('uses match_all when no date filter', async () => {
550+
esClient.search.mockResolvedValue(mockConversationResponse());
551+
552+
await queryUtils.getConversationMetrics();
553+
554+
expect(esClient.search).toHaveBeenCalledWith(
555+
expect.objectContaining({
556+
query: { match_all: {} },
557+
})
558+
);
559+
});
560+
532561
it('does not log warning for index_not_found_exception', async () => {
533562
const error = {
534563
message: 'index_not_found_exception',
@@ -557,6 +586,8 @@ describe('query_utils', () => {
557586
avg_rounds_per_conversation: 0,
558587
rounds_distribution: [],
559588
tokens_used: 0,
589+
tokens_input: 0,
590+
tokens_output: 0,
560591
average_tokens_per_conversation: 0,
561592
});
562593
expect(logger.warn).toHaveBeenCalledWith('Failed to fetch conversation metrics: ES error');

0 commit comments

Comments
 (0)