toad-eye 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -152
- package/dist/agent.d.ts +19 -28
- package/dist/agent.js +46 -27
- package/dist/alerts/channels.js +24 -6
- package/dist/alerts/index.js +46 -2
- package/dist/alerts/manager.d.ts +1 -0
- package/dist/alerts/manager.js +36 -21
- package/dist/budget/tracker.d.ts +16 -6
- package/dist/budget/tracker.js +38 -17
- package/dist/budget/types.d.ts +2 -0
- package/dist/cli.d.ts +8 -1
- package/dist/cli.js +106 -54
- package/dist/core/metrics.d.ts +5 -0
- package/dist/core/metrics.js +30 -0
- package/dist/core/pricing.d.ts +2 -0
- package/dist/core/pricing.js +6 -0
- package/dist/core/spans.d.ts +5 -3
- package/dist/core/spans.js +84 -19
- package/dist/core/tracer.d.ts +0 -3
- package/dist/core/tracer.js +44 -8
- package/dist/drift/baseline.js +7 -1
- package/dist/drift/monitor.js +6 -2
- package/dist/drift/openai-embeddings.js +5 -1
- package/dist/export.js +1 -1
- package/dist/guard.d.ts +6 -8
- package/dist/guard.js +19 -21
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/dist/instrumentations/anthropic.d.ts +1 -1
- package/dist/instrumentations/anthropic.js +12 -20
- package/dist/instrumentations/create.d.ts +0 -4
- package/dist/instrumentations/create.js +136 -44
- package/dist/instrumentations/gemini.d.ts +1 -1
- package/dist/instrumentations/gemini.js +39 -7
- package/dist/instrumentations/openai.d.ts +1 -1
- package/dist/instrumentations/openai.js +10 -19
- package/dist/instrumentations/registry.d.ts +3 -1
- package/dist/instrumentations/registry.js +28 -5
- package/dist/instrumentations/types.d.ts +12 -8
- package/dist/types/attributes.d.ts +3 -0
- package/dist/types/attributes.js +3 -0
- package/dist/types/config.d.ts +22 -2
- package/dist/types/index.d.ts +3 -3
- package/dist/types/metrics.d.ts +3 -0
- package/dist/types/metrics.js +5 -0
- package/dist/types/providers.d.ts +6 -1
- package/dist/types/spans.d.ts +11 -2
- package/dist/vercel.d.ts +53 -0
- package/dist/vercel.js +106 -0
- package/package.json +29 -3
- package/templates/grafana/dashboards/agent-workflow.json +207 -0
- package/templates/grafana/dashboards/provider-health.json +191 -0
- package/templates/otel-collector.yml +25 -1
package/dist/vercel.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel AI SDK integration — enriches AI SDK OTel spans with toad-eye
|
|
3
|
+
* cost tracking and metrics. No monkey-patching needed.
|
|
4
|
+
*
|
|
5
|
+
* The Vercel AI SDK already emits OTel spans when `experimental_telemetry`
|
|
6
|
+
* is enabled. This module adds a SpanProcessor that intercepts those spans
|
|
7
|
+
* and calculates cost from token usage.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { initObservability } from 'toad-eye';
|
|
12
|
+
* import { withToadEye } from 'toad-eye/vercel';
|
|
13
|
+
*
|
|
14
|
+
* initObservability({ serviceName: 'my-app' });
|
|
15
|
+
*
|
|
16
|
+
* const result = await generateText({
|
|
17
|
+
* model: openai('gpt-4o'),
|
|
18
|
+
* prompt: 'Hello',
|
|
19
|
+
* experimental_telemetry: withToadEye(),
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { SpanProcessor, ReadableSpan } from "@opentelemetry/sdk-trace-base";
|
|
24
|
+
/**
|
|
25
|
+
* OTel SpanProcessor that enriches Vercel AI SDK spans with toad-eye
|
|
26
|
+
* cost attributes and records metrics.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ToadEyeAISpanProcessor implements SpanProcessor {
|
|
29
|
+
forceFlush(): Promise<void>;
|
|
30
|
+
shutdown(): Promise<void>;
|
|
31
|
+
onStart(): void;
|
|
32
|
+
onEnd(span: ReadableSpan): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Helper for `experimental_telemetry` option — enables telemetry with
|
|
36
|
+
* optional toad-eye metadata.
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* const result = await generateText({
|
|
40
|
+
* model: openai('gpt-4o'),
|
|
41
|
+
* prompt: 'Hello',
|
|
42
|
+
* experimental_telemetry: withToadEye({ functionId: 'my-feature' }),
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function withToadEye(options?: {
|
|
47
|
+
functionId?: string;
|
|
48
|
+
metadata?: Record<string, string>;
|
|
49
|
+
}): {
|
|
50
|
+
functionId?: string;
|
|
51
|
+
metadata?: Record<string, string>;
|
|
52
|
+
isEnabled: true;
|
|
53
|
+
};
|
package/dist/vercel.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel AI SDK integration — enriches AI SDK OTel spans with toad-eye
|
|
3
|
+
* cost tracking and metrics. No monkey-patching needed.
|
|
4
|
+
*
|
|
5
|
+
* The Vercel AI SDK already emits OTel spans when `experimental_telemetry`
|
|
6
|
+
* is enabled. This module adds a SpanProcessor that intercepts those spans
|
|
7
|
+
* and calculates cost from token usage.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { initObservability } from 'toad-eye';
|
|
12
|
+
* import { withToadEye } from 'toad-eye/vercel';
|
|
13
|
+
*
|
|
14
|
+
* initObservability({ serviceName: 'my-app' });
|
|
15
|
+
*
|
|
16
|
+
* const result = await generateText({
|
|
17
|
+
* model: openai('gpt-4o'),
|
|
18
|
+
* prompt: 'Hello',
|
|
19
|
+
* experimental_telemetry: withToadEye(),
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { calculateCost } from "./core/pricing.js";
|
|
24
|
+
import { recordRequestCost, recordTokens, recordRequest, recordRequestDuration, } from "./core/metrics.js";
|
|
25
|
+
// Vercel AI SDK span operation names
|
|
26
|
+
const AI_SDK_OPERATIONS = new Set([
|
|
27
|
+
"ai.generateText",
|
|
28
|
+
"ai.streamText",
|
|
29
|
+
"ai.generateObject",
|
|
30
|
+
"ai.streamObject",
|
|
31
|
+
]);
|
|
32
|
+
function isAiSdkSpan(span) {
|
|
33
|
+
const opId = span.attributes["ai.operationId"];
|
|
34
|
+
if (typeof opId === "string")
|
|
35
|
+
return AI_SDK_OPERATIONS.has(opId);
|
|
36
|
+
return AI_SDK_OPERATIONS.has(span.name);
|
|
37
|
+
}
|
|
38
|
+
function getStringAttr(span, key) {
|
|
39
|
+
const val = span.attributes[key];
|
|
40
|
+
return typeof val === "string" ? val : undefined;
|
|
41
|
+
}
|
|
42
|
+
function getNumberAttr(span, key) {
|
|
43
|
+
const val = span.attributes[key];
|
|
44
|
+
return typeof val === "number" ? val : 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* OTel SpanProcessor that enriches Vercel AI SDK spans with toad-eye
|
|
48
|
+
* cost attributes and records metrics.
|
|
49
|
+
*/
|
|
50
|
+
export class ToadEyeAISpanProcessor {
|
|
51
|
+
forceFlush() {
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
}
|
|
54
|
+
shutdown() {
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
}
|
|
57
|
+
onStart() {
|
|
58
|
+
// no-op — we process on end
|
|
59
|
+
}
|
|
60
|
+
onEnd(span) {
|
|
61
|
+
if (!isAiSdkSpan(span))
|
|
62
|
+
return;
|
|
63
|
+
const model = getStringAttr(span, "gen_ai.request.model") ??
|
|
64
|
+
getStringAttr(span, "ai.model.id") ??
|
|
65
|
+
"unknown";
|
|
66
|
+
const provider = getStringAttr(span, "gen_ai.system") ??
|
|
67
|
+
getStringAttr(span, "ai.model.provider")?.split(".")[0] ??
|
|
68
|
+
"unknown";
|
|
69
|
+
const inputTokens = getNumberAttr(span, "gen_ai.usage.input_tokens") ||
|
|
70
|
+
getNumberAttr(span, "ai.usage.promptTokens");
|
|
71
|
+
const outputTokens = getNumberAttr(span, "gen_ai.usage.output_tokens") ||
|
|
72
|
+
getNumberAttr(span, "ai.usage.completionTokens");
|
|
73
|
+
// Calculate cost from token usage
|
|
74
|
+
const cost = calculateCost(model, inputTokens, outputTokens);
|
|
75
|
+
// We can't modify a ReadableSpan's attributes directly, but we can
|
|
76
|
+
// record metrics which is the primary value-add
|
|
77
|
+
const durationMs = (span.endTime[0] - span.startTime[0]) * 1000 +
|
|
78
|
+
(span.endTime[1] - span.startTime[1]) / 1_000_000;
|
|
79
|
+
recordRequest(provider, model);
|
|
80
|
+
recordRequestDuration(durationMs, provider, model);
|
|
81
|
+
if (cost > 0) {
|
|
82
|
+
recordRequestCost(cost, provider, model);
|
|
83
|
+
}
|
|
84
|
+
if (inputTokens + outputTokens > 0) {
|
|
85
|
+
recordTokens(inputTokens + outputTokens, provider, model);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Helper for `experimental_telemetry` option — enables telemetry with
|
|
91
|
+
* optional toad-eye metadata.
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* const result = await generateText({
|
|
95
|
+
* model: openai('gpt-4o'),
|
|
96
|
+
* prompt: 'Hello',
|
|
97
|
+
* experimental_telemetry: withToadEye({ functionId: 'my-feature' }),
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function withToadEye(options) {
|
|
102
|
+
return {
|
|
103
|
+
isEnabled: true,
|
|
104
|
+
...options,
|
|
105
|
+
};
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toad-eye",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Auto-instrumentation, cost tracking, budget guards, and observability for LLM services",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./alerts": {
|
|
14
|
+
"import": "./dist/alerts/index.js",
|
|
15
|
+
"types": "./dist/alerts/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./drift": {
|
|
18
|
+
"import": "./dist/drift/index.js",
|
|
19
|
+
"types": "./dist/drift/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./export": {
|
|
22
|
+
"import": "./dist/export.js",
|
|
23
|
+
"types": "./dist/export.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./vercel": {
|
|
26
|
+
"import": "./dist/vercel.js",
|
|
27
|
+
"types": "./dist/vercel.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
8
30
|
"bin": {
|
|
9
31
|
"toad-eye": "dist/cli.js"
|
|
10
32
|
},
|
|
@@ -51,13 +73,15 @@
|
|
|
51
73
|
"@opentelemetry/resources": "^2.6.0",
|
|
52
74
|
"@opentelemetry/sdk-metrics": "^2.6.0",
|
|
53
75
|
"@opentelemetry/sdk-node": "^0.213.0",
|
|
76
|
+
"@opentelemetry/sdk-trace-base": "^2.6.0",
|
|
77
|
+
"@opentelemetry/sdk-trace-node": "^2.6.0",
|
|
54
78
|
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
55
|
-
"nodemailer": "^8.0.3",
|
|
56
79
|
"yaml": "^2.7.0"
|
|
57
80
|
},
|
|
58
81
|
"peerDependencies": {
|
|
59
82
|
"@anthropic-ai/sdk": ">=0.30.0",
|
|
60
83
|
"@google/generative-ai": ">=0.20.0",
|
|
84
|
+
"nodemailer": ">=8.0.0",
|
|
61
85
|
"openai": ">=4.0.0"
|
|
62
86
|
},
|
|
63
87
|
"peerDependenciesMeta": {
|
|
@@ -69,11 +93,13 @@
|
|
|
69
93
|
},
|
|
70
94
|
"@google/generative-ai": {
|
|
71
95
|
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"nodemailer": {
|
|
98
|
+
"optional": true
|
|
72
99
|
}
|
|
73
100
|
},
|
|
74
101
|
"devDependencies": {
|
|
75
102
|
"@types/nodemailer": "^7.0.11",
|
|
76
|
-
"playwright": "^1.58.2",
|
|
77
103
|
"vitest": "^4.1.0"
|
|
78
104
|
}
|
|
79
105
|
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
{
|
|
2
|
+
"annotations": { "list": [] },
|
|
3
|
+
"editable": true,
|
|
4
|
+
"fiscalYearStartMonth": 0,
|
|
5
|
+
"graphTooltip": 1,
|
|
6
|
+
"id": null,
|
|
7
|
+
"links": [],
|
|
8
|
+
"panels": [
|
|
9
|
+
{
|
|
10
|
+
"title": "Avg Steps per Query",
|
|
11
|
+
"type": "stat",
|
|
12
|
+
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
|
|
13
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
14
|
+
"targets": [
|
|
15
|
+
{
|
|
16
|
+
"expr": "sum(gen_ai_agent_steps_per_query_sum) / sum(gen_ai_agent_steps_per_query_count) or vector(0)",
|
|
17
|
+
"refId": "A"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"fieldConfig": {
|
|
21
|
+
"defaults": {
|
|
22
|
+
"unit": "short",
|
|
23
|
+
"decimals": 1,
|
|
24
|
+
"thresholds": {
|
|
25
|
+
"steps": [
|
|
26
|
+
{ "color": "green", "value": null },
|
|
27
|
+
{ "color": "yellow", "value": 10 },
|
|
28
|
+
{ "color": "red", "value": 20 }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"overrides": []
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"title": "Total Agent Queries",
|
|
37
|
+
"type": "stat",
|
|
38
|
+
"gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 },
|
|
39
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
40
|
+
"targets": [
|
|
41
|
+
{
|
|
42
|
+
"expr": "sum(gen_ai_agent_steps_per_query_count)",
|
|
43
|
+
"refId": "A"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"fieldConfig": {
|
|
47
|
+
"defaults": { "unit": "short" },
|
|
48
|
+
"overrides": []
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"title": "Total Tool Invocations",
|
|
53
|
+
"type": "stat",
|
|
54
|
+
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
|
|
55
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
56
|
+
"targets": [
|
|
57
|
+
{
|
|
58
|
+
"expr": "sum(gen_ai_agent_tool_usage_total)",
|
|
59
|
+
"refId": "A"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"fieldConfig": {
|
|
63
|
+
"defaults": { "unit": "short" },
|
|
64
|
+
"overrides": []
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"title": "Unique Tools Used",
|
|
69
|
+
"type": "stat",
|
|
70
|
+
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 },
|
|
71
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
72
|
+
"targets": [
|
|
73
|
+
{
|
|
74
|
+
"expr": "count(count by (gen_ai_agent_tool_name) (gen_ai_agent_tool_usage_total))",
|
|
75
|
+
"refId": "A"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"fieldConfig": {
|
|
79
|
+
"defaults": { "unit": "short" },
|
|
80
|
+
"overrides": []
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"title": "Agent Step Type Breakdown",
|
|
85
|
+
"description": "Distribution of think / act / observe / answer steps",
|
|
86
|
+
"type": "piechart",
|
|
87
|
+
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
|
|
88
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
89
|
+
"targets": [
|
|
90
|
+
{
|
|
91
|
+
"expr": "sum by (gen_ai_agent_step_type) (gen_ai_agent_steps_per_query_sum)",
|
|
92
|
+
"legendFormat": "{{gen_ai_agent_step_type}}",
|
|
93
|
+
"refId": "A"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"options": {
|
|
97
|
+
"legend": {
|
|
98
|
+
"displayMode": "table",
|
|
99
|
+
"placement": "right",
|
|
100
|
+
"values": ["value", "percent"]
|
|
101
|
+
},
|
|
102
|
+
"pieType": "donut"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"title": "Tool Usage Frequency",
|
|
107
|
+
"description": "Invocations per tool name",
|
|
108
|
+
"type": "barchart",
|
|
109
|
+
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
|
|
110
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
111
|
+
"targets": [
|
|
112
|
+
{
|
|
113
|
+
"expr": "sort_desc(sum by (gen_ai_agent_tool_name) (gen_ai_agent_tool_usage_total))",
|
|
114
|
+
"legendFormat": "{{gen_ai_agent_tool_name}}",
|
|
115
|
+
"refId": "A"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"title": "Steps per Query Over Time",
|
|
121
|
+
"type": "timeseries",
|
|
122
|
+
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 12 },
|
|
123
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
124
|
+
"targets": [
|
|
125
|
+
{
|
|
126
|
+
"expr": "sum(rate(gen_ai_agent_steps_per_query_sum[5m])) / sum(rate(gen_ai_agent_steps_per_query_count[5m]))",
|
|
127
|
+
"legendFormat": "avg steps/query",
|
|
128
|
+
"refId": "avg"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"expr": "histogram_quantile(0.95, sum by (le) (rate(gen_ai_agent_steps_per_query_bucket[5m])))",
|
|
132
|
+
"legendFormat": "p95 steps/query",
|
|
133
|
+
"refId": "p95"
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
"fieldConfig": {
|
|
137
|
+
"defaults": {
|
|
138
|
+
"unit": "short",
|
|
139
|
+
"custom": { "fillOpacity": 10 }
|
|
140
|
+
},
|
|
141
|
+
"overrides": []
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"title": "Tool Invocations Over Time",
|
|
146
|
+
"type": "timeseries",
|
|
147
|
+
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 20 },
|
|
148
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
149
|
+
"targets": [
|
|
150
|
+
{
|
|
151
|
+
"expr": "sum by (gen_ai_agent_tool_name) (rate(gen_ai_agent_tool_usage_total[5m]))",
|
|
152
|
+
"legendFormat": "{{gen_ai_agent_tool_name}}",
|
|
153
|
+
"refId": "A"
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"fieldConfig": {
|
|
157
|
+
"defaults": {
|
|
158
|
+
"unit": "reqps",
|
|
159
|
+
"custom": { "fillOpacity": 20, "stacking": { "mode": "normal" } }
|
|
160
|
+
},
|
|
161
|
+
"overrides": []
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"title": "Steps Distribution Histogram",
|
|
166
|
+
"description": "How many steps do agent queries typically take?",
|
|
167
|
+
"type": "histogram",
|
|
168
|
+
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 20 },
|
|
169
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
170
|
+
"targets": [
|
|
171
|
+
{
|
|
172
|
+
"expr": "sum by (le) (gen_ai_agent_steps_per_query_bucket)",
|
|
173
|
+
"format": "heatmap",
|
|
174
|
+
"refId": "A"
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"fieldConfig": {
|
|
178
|
+
"defaults": {
|
|
179
|
+
"custom": { "fillOpacity": 80 }
|
|
180
|
+
},
|
|
181
|
+
"overrides": []
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
"schemaVersion": 39,
|
|
186
|
+
"tags": ["toad-eye", "agent"],
|
|
187
|
+
"templating": {
|
|
188
|
+
"list": [
|
|
189
|
+
{
|
|
190
|
+
"name": "tool",
|
|
191
|
+
"type": "query",
|
|
192
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
193
|
+
"query": "label_values(gen_ai_agent_tool_usage_total, gen_ai_agent_tool_name)",
|
|
194
|
+
"includeAll": true,
|
|
195
|
+
"multi": true,
|
|
196
|
+
"current": { "text": "All", "value": "$__all" },
|
|
197
|
+
"regex": ""
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
},
|
|
201
|
+
"time": { "from": "now-6h", "to": "now" },
|
|
202
|
+
"timepicker": {},
|
|
203
|
+
"timezone": "utc",
|
|
204
|
+
"title": "toad-eye / Agent Workflow",
|
|
205
|
+
"uid": "toad-eye-agent-workflow",
|
|
206
|
+
"version": 1
|
|
207
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
{
|
|
2
|
+
"annotations": { "list": [] },
|
|
3
|
+
"editable": true,
|
|
4
|
+
"fiscalYearStartMonth": 0,
|
|
5
|
+
"graphTooltip": 1,
|
|
6
|
+
"id": null,
|
|
7
|
+
"links": [],
|
|
8
|
+
"panels": [
|
|
9
|
+
{
|
|
10
|
+
"title": "Provider Status",
|
|
11
|
+
"description": "healthy (< 10% errors) / degraded (10-50%) / down (> 50%)",
|
|
12
|
+
"type": "stat",
|
|
13
|
+
"gridPos": { "h": 6, "w": 8, "x": 0, "y": 0 },
|
|
14
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
15
|
+
"targets": [
|
|
16
|
+
{
|
|
17
|
+
"expr": "sum by (gen_ai_provider_name) (rate(gen_ai_client_errors_total{gen_ai_provider_name=~\"$provider\"}[5m])) / sum by (gen_ai_provider_name) (rate(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\"}[5m]))",
|
|
18
|
+
"legendFormat": "{{gen_ai_provider_name}}",
|
|
19
|
+
"refId": "A"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"fieldConfig": {
|
|
23
|
+
"defaults": {
|
|
24
|
+
"unit": "percentunit",
|
|
25
|
+
"thresholds": {
|
|
26
|
+
"mode": "absolute",
|
|
27
|
+
"steps": [
|
|
28
|
+
{ "color": "green", "value": null },
|
|
29
|
+
{ "color": "yellow", "value": 0.1 },
|
|
30
|
+
{ "color": "red", "value": 0.5 }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"mappings": [
|
|
34
|
+
{
|
|
35
|
+
"type": "range",
|
|
36
|
+
"options": {
|
|
37
|
+
"from": 0,
|
|
38
|
+
"to": 0.1,
|
|
39
|
+
"result": { "text": "HEALTHY", "color": "green" }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "range",
|
|
44
|
+
"options": {
|
|
45
|
+
"from": 0.1,
|
|
46
|
+
"to": 0.5,
|
|
47
|
+
"result": { "text": "DEGRADED", "color": "yellow" }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"type": "range",
|
|
52
|
+
"options": {
|
|
53
|
+
"from": 0.5,
|
|
54
|
+
"to": 1,
|
|
55
|
+
"result": { "text": "DOWN", "color": "red" }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"overrides": []
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"title": "Request Volume by Provider",
|
|
65
|
+
"type": "timeseries",
|
|
66
|
+
"gridPos": { "h": 6, "w": 16, "x": 8, "y": 0 },
|
|
67
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
68
|
+
"targets": [
|
|
69
|
+
{
|
|
70
|
+
"expr": "sum by (gen_ai_provider_name) (rate(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\"}[5m]))",
|
|
71
|
+
"legendFormat": "{{gen_ai_provider_name}}",
|
|
72
|
+
"refId": "A"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"fieldConfig": {
|
|
76
|
+
"defaults": {
|
|
77
|
+
"unit": "reqps",
|
|
78
|
+
"custom": { "fillOpacity": 15 }
|
|
79
|
+
},
|
|
80
|
+
"overrides": []
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"title": "Error Rate by Provider (5m window)",
|
|
85
|
+
"type": "timeseries",
|
|
86
|
+
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 6 },
|
|
87
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
88
|
+
"targets": [
|
|
89
|
+
{
|
|
90
|
+
"expr": "sum by (gen_ai_provider_name) (rate(gen_ai_client_errors_total{gen_ai_provider_name=~\"$provider\"}[5m])) / sum by (gen_ai_provider_name) (rate(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\"}[5m]))",
|
|
91
|
+
"legendFormat": "{{gen_ai_provider_name}} error rate",
|
|
92
|
+
"refId": "A"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"fieldConfig": {
|
|
96
|
+
"defaults": {
|
|
97
|
+
"unit": "percentunit",
|
|
98
|
+
"custom": { "fillOpacity": 10 },
|
|
99
|
+
"thresholds": {
|
|
100
|
+
"mode": "absolute",
|
|
101
|
+
"steps": [
|
|
102
|
+
{ "color": "green", "value": null },
|
|
103
|
+
{ "color": "yellow", "value": 0.1 },
|
|
104
|
+
{ "color": "red", "value": 0.5 }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"overrides": []
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"title": "Error Breakdown by Type",
|
|
113
|
+
"type": "timeseries",
|
|
114
|
+
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 6 },
|
|
115
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
116
|
+
"targets": [
|
|
117
|
+
{
|
|
118
|
+
"expr": "sum by (gen_ai_provider_name, error_type) (rate(gen_ai_client_errors_total{gen_ai_provider_name=~\"$provider\"}[5m]))",
|
|
119
|
+
"legendFormat": "{{gen_ai_provider_name}} — {{error_type}}",
|
|
120
|
+
"refId": "A"
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
"fieldConfig": {
|
|
124
|
+
"defaults": {
|
|
125
|
+
"unit": "reqps",
|
|
126
|
+
"custom": { "fillOpacity": 20, "stacking": { "mode": "normal" } }
|
|
127
|
+
},
|
|
128
|
+
"overrides": []
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"title": "Provider Availability (24h / 7d / 30d)",
|
|
133
|
+
"description": "Percentage of time with error rate below 10%",
|
|
134
|
+
"type": "table",
|
|
135
|
+
"gridPos": { "h": 6, "w": 24, "x": 0, "y": 14 },
|
|
136
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
137
|
+
"targets": [
|
|
138
|
+
{
|
|
139
|
+
"expr": "1 - (sum by (gen_ai_provider_name) (rate(gen_ai_client_errors_total[24h])) / sum by (gen_ai_provider_name) (rate(gen_ai_client_requests_total[24h])))",
|
|
140
|
+
"format": "table",
|
|
141
|
+
"instant": true,
|
|
142
|
+
"refId": "uptime_24h"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"expr": "1 - (sum by (gen_ai_provider_name) (rate(gen_ai_client_errors_total[7d])) / sum by (gen_ai_provider_name) (rate(gen_ai_client_requests_total[7d])))",
|
|
146
|
+
"format": "table",
|
|
147
|
+
"instant": true,
|
|
148
|
+
"refId": "uptime_7d"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"transformations": [
|
|
152
|
+
{
|
|
153
|
+
"id": "organize",
|
|
154
|
+
"options": {
|
|
155
|
+
"renameByName": {
|
|
156
|
+
"gen_ai_provider_name": "Provider",
|
|
157
|
+
"Value #uptime_24h": "Uptime 24h",
|
|
158
|
+
"Value #uptime_7d": "Uptime 7d"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
"fieldConfig": {
|
|
164
|
+
"defaults": { "unit": "percentunit", "decimals": 2 },
|
|
165
|
+
"overrides": []
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
"schemaVersion": 39,
|
|
170
|
+
"tags": ["toad-eye", "provider-health"],
|
|
171
|
+
"templating": {
|
|
172
|
+
"list": [
|
|
173
|
+
{
|
|
174
|
+
"name": "provider",
|
|
175
|
+
"type": "query",
|
|
176
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
177
|
+
"query": "label_values(gen_ai_client_requests_total, gen_ai_provider_name)",
|
|
178
|
+
"includeAll": true,
|
|
179
|
+
"multi": true,
|
|
180
|
+
"current": { "text": "All", "value": "$__all" },
|
|
181
|
+
"regex": ""
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
"time": { "from": "now-6h", "to": "now" },
|
|
186
|
+
"timepicker": {},
|
|
187
|
+
"timezone": "utc",
|
|
188
|
+
"title": "toad-eye / Provider Health",
|
|
189
|
+
"uid": "toad-eye-provider-health",
|
|
190
|
+
"version": 1
|
|
191
|
+
}
|
|
@@ -8,6 +8,30 @@ processors:
|
|
|
8
8
|
batch:
|
|
9
9
|
timeout: 1s
|
|
10
10
|
|
|
11
|
+
# Tail-based sampling: keeps 100% errors + slow traces, samples healthy traffic.
|
|
12
|
+
# Only applies to traces pipeline — metrics always get 100%.
|
|
13
|
+
# To disable: remove tail_sampling from traces pipeline processors.
|
|
14
|
+
tail_sampling:
|
|
15
|
+
decision_wait: 10s
|
|
16
|
+
num_traces: 100000
|
|
17
|
+
expected_new_traces_per_sec: 100
|
|
18
|
+
policies:
|
|
19
|
+
# Keep all traces with errors
|
|
20
|
+
- name: errors-always
|
|
21
|
+
type: status_code
|
|
22
|
+
status_code:
|
|
23
|
+
status_codes: [ERROR]
|
|
24
|
+
# Keep all slow traces (> 2s)
|
|
25
|
+
- name: slow-traces
|
|
26
|
+
type: latency
|
|
27
|
+
latency:
|
|
28
|
+
threshold_ms: 2000
|
|
29
|
+
# Sample 10% of remaining healthy traffic
|
|
30
|
+
- name: probabilistic-fallback
|
|
31
|
+
type: probabilistic
|
|
32
|
+
probabilistic:
|
|
33
|
+
sampling_percentage: 10
|
|
34
|
+
|
|
11
35
|
exporters:
|
|
12
36
|
otlphttp/jaeger:
|
|
13
37
|
endpoint: http://jaeger:4318
|
|
@@ -21,7 +45,7 @@ service:
|
|
|
21
45
|
pipelines:
|
|
22
46
|
traces:
|
|
23
47
|
receivers: [otlp]
|
|
24
|
-
processors: [batch]
|
|
48
|
+
processors: [tail_sampling, batch]
|
|
25
49
|
exporters: [otlphttp/jaeger]
|
|
26
50
|
metrics:
|
|
27
51
|
receivers: [otlp]
|