toad-eye 1.1.0 → 2.1.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.
Files changed (90) hide show
  1. package/README.md +132 -87
  2. package/dist/agent.d.ts +41 -0
  3. package/dist/agent.js +91 -0
  4. package/dist/alerts/channels.d.ts +2 -0
  5. package/dist/alerts/channels.js +81 -0
  6. package/dist/alerts/conditions.d.ts +29 -0
  7. package/dist/alerts/conditions.js +147 -0
  8. package/dist/alerts/grafana.d.ts +5 -0
  9. package/dist/alerts/grafana.js +19 -0
  10. package/dist/alerts/index.d.ts +5 -0
  11. package/dist/alerts/index.js +12 -0
  12. package/dist/alerts/manager.d.ts +13 -0
  13. package/dist/alerts/manager.js +87 -0
  14. package/dist/alerts/types.d.ts +46 -0
  15. package/dist/alerts/types.js +1 -0
  16. package/dist/budget/error.d.ts +10 -0
  17. package/dist/budget/error.js +22 -0
  18. package/dist/budget/index.d.ts +3 -0
  19. package/dist/budget/index.js +2 -0
  20. package/dist/budget/tracker.d.ts +35 -0
  21. package/dist/budget/tracker.js +121 -0
  22. package/dist/budget/types.d.ts +34 -0
  23. package/dist/budget/types.js +1 -0
  24. package/dist/cli.js +127 -18
  25. package/dist/core/metrics.d.ts +14 -0
  26. package/dist/core/metrics.js +119 -0
  27. package/dist/core/pricing.d.ts +24 -0
  28. package/dist/core/pricing.js +52 -0
  29. package/dist/{spans.d.ts → core/spans.d.ts} +3 -1
  30. package/dist/core/spans.js +143 -0
  31. package/dist/core/tracer.d.ts +9 -0
  32. package/dist/core/tracer.js +93 -0
  33. package/dist/drift/baseline.d.ts +16 -0
  34. package/dist/drift/baseline.js +16 -0
  35. package/dist/drift/cosine.d.ts +13 -0
  36. package/dist/drift/cosine.js +39 -0
  37. package/dist/drift/embedding-provider.d.ts +21 -0
  38. package/dist/drift/embedding-provider.js +8 -0
  39. package/dist/drift/index.d.ts +9 -0
  40. package/dist/drift/index.js +8 -0
  41. package/dist/drift/monitor.d.ts +29 -0
  42. package/dist/drift/monitor.js +47 -0
  43. package/dist/drift/openai-embeddings.d.ts +6 -0
  44. package/dist/drift/openai-embeddings.js +26 -0
  45. package/dist/export.d.ts +46 -0
  46. package/dist/export.js +108 -0
  47. package/dist/guard.d.ts +26 -0
  48. package/dist/guard.js +45 -0
  49. package/dist/index.d.ts +17 -5
  50. package/dist/index.js +10 -3
  51. package/dist/instrumentations/anthropic.d.ts +1 -0
  52. package/dist/instrumentations/anthropic.js +73 -0
  53. package/dist/instrumentations/create.d.ts +11 -0
  54. package/dist/instrumentations/create.js +165 -0
  55. package/dist/instrumentations/gemini.d.ts +1 -0
  56. package/dist/instrumentations/gemini.js +48 -0
  57. package/dist/instrumentations/openai.d.ts +1 -0
  58. package/dist/instrumentations/openai.js +94 -0
  59. package/dist/instrumentations/registry.d.ts +5 -0
  60. package/dist/instrumentations/registry.js +31 -0
  61. package/dist/instrumentations/types.d.ts +37 -0
  62. package/dist/instrumentations/types.js +1 -0
  63. package/dist/types/attributes.d.ts +48 -0
  64. package/dist/types/attributes.js +53 -0
  65. package/dist/types/config.d.ts +34 -0
  66. package/dist/types/config.js +1 -0
  67. package/dist/types/index.d.ts +17 -0
  68. package/dist/types/index.js +13 -0
  69. package/dist/types/metrics.d.ts +33 -0
  70. package/dist/types/metrics.js +35 -0
  71. package/dist/types/providers.d.ts +5 -0
  72. package/dist/types/providers.js +1 -0
  73. package/dist/types/spans.d.ts +40 -0
  74. package/dist/types/spans.js +1 -0
  75. package/package.json +38 -4
  76. package/templates/alerts.example.yml +54 -0
  77. package/templates/docker-compose.yml +1 -2
  78. package/templates/grafana/dashboards/cost-breakdown.json +208 -0
  79. package/templates/grafana/dashboards/error-drilldown.json +188 -0
  80. package/templates/grafana/dashboards/finops-attribution.json +278 -0
  81. package/templates/grafana/dashboards/latency-analysis.json +134 -0
  82. package/templates/grafana/dashboards/llm-overview.json +161 -30
  83. package/templates/grafana/dashboards/model-comparison.json +143 -0
  84. package/dist/metrics.d.ts +0 -6
  85. package/dist/metrics.js +0 -46
  86. package/dist/spans.js +0 -61
  87. package/dist/tracer.d.ts +0 -4
  88. package/dist/tracer.js +0 -46
  89. package/dist/types.d.ts +0 -69
  90. package/dist/types.js +0 -32
@@ -0,0 +1,134 @@
1
+ {
2
+ "annotations": { "list": [] },
3
+ "editable": true,
4
+ "fiscalYearStartMonth": 0,
5
+ "graphTooltip": 1,
6
+ "id": null,
7
+ "links": [],
8
+ "panels": [
9
+ {
10
+ "title": "Latency p50 / p95 / p99 by Model",
11
+ "type": "timeseries",
12
+ "gridPos": { "h": 8, "w": 24, "x": 0, "y": 0 },
13
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
14
+ "targets": [
15
+ {
16
+ "expr": "histogram_quantile(0.50, sum(rate(gen_ai_client_operation_duration_milliseconds_bucket{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (le, gen_ai_request_model))",
17
+ "legendFormat": "p50 {{ gen_ai_request_model }}",
18
+ "refId": "A"
19
+ },
20
+ {
21
+ "expr": "histogram_quantile(0.95, sum(rate(gen_ai_client_operation_duration_milliseconds_bucket{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (le, gen_ai_request_model))",
22
+ "legendFormat": "p95 {{ gen_ai_request_model }}",
23
+ "refId": "B"
24
+ },
25
+ {
26
+ "expr": "histogram_quantile(0.99, sum(rate(gen_ai_client_operation_duration_milliseconds_bucket{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (le, gen_ai_request_model))",
27
+ "legendFormat": "p99 {{ gen_ai_request_model }}",
28
+ "refId": "C"
29
+ }
30
+ ],
31
+ "fieldConfig": {
32
+ "defaults": {
33
+ "unit": "ms",
34
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
35
+ },
36
+ "overrides": []
37
+ }
38
+ },
39
+ {
40
+ "title": "Latency Distribution",
41
+ "type": "histogram",
42
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
43
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
44
+ "targets": [
45
+ {
46
+ "expr": "sum(rate(gen_ai_client_operation_duration_milliseconds_bucket{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (le)",
47
+ "legendFormat": "{{ le }}",
48
+ "refId": "A",
49
+ "format": "heatmap"
50
+ }
51
+ ],
52
+ "fieldConfig": {
53
+ "defaults": {
54
+ "unit": "ms",
55
+ "custom": { "fillOpacity": 50 }
56
+ },
57
+ "overrides": []
58
+ }
59
+ },
60
+ {
61
+ "title": "Avg Latency by Model",
62
+ "type": "timeseries",
63
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
64
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
65
+ "targets": [
66
+ {
67
+ "expr": "sum(rate(gen_ai_client_operation_duration_milliseconds_sum{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (gen_ai_request_model) / sum(rate(gen_ai_client_operation_duration_milliseconds_count{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (gen_ai_request_model)",
68
+ "legendFormat": "{{ gen_ai_request_model }}",
69
+ "refId": "A"
70
+ }
71
+ ],
72
+ "fieldConfig": {
73
+ "defaults": {
74
+ "unit": "ms",
75
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
76
+ },
77
+ "overrides": []
78
+ }
79
+ },
80
+ {
81
+ "title": "Slow Requests p99 by Provider",
82
+ "type": "timeseries",
83
+ "gridPos": { "h": 8, "w": 24, "x": 0, "y": 16 },
84
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
85
+ "targets": [
86
+ {
87
+ "expr": "histogram_quantile(0.99, sum(rate(gen_ai_client_operation_duration_milliseconds_bucket{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (le, gen_ai_provider_name))",
88
+ "legendFormat": "p99 {{ gen_ai_provider_name }}",
89
+ "refId": "A"
90
+ }
91
+ ],
92
+ "fieldConfig": {
93
+ "defaults": {
94
+ "unit": "ms",
95
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
96
+ },
97
+ "overrides": []
98
+ }
99
+ }
100
+ ],
101
+ "refresh": "5s",
102
+ "schemaVersion": 39,
103
+ "tags": ["llm", "observability", "toad-eye"],
104
+ "templating": {
105
+ "list": [
106
+ {
107
+ "name": "provider",
108
+ "type": "query",
109
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
110
+ "query": "label_values(gen_ai_client_requests_total, gen_ai_provider_name)",
111
+ "includeAll": true,
112
+ "allValue": ".*",
113
+ "current": { "text": "All", "value": "$__all" },
114
+ "multi": true
115
+ },
116
+ {
117
+ "name": "model",
118
+ "type": "query",
119
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
120
+ "query": "label_values(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\"}, gen_ai_request_model)",
121
+ "includeAll": true,
122
+ "allValue": ".*",
123
+ "current": { "text": "All", "value": "$__all" },
124
+ "multi": true
125
+ }
126
+ ]
127
+ },
128
+ "time": { "from": "now-15m", "to": "now" },
129
+ "timepicker": {},
130
+ "timezone": "browser",
131
+ "title": "toad-eye: Latency Analysis",
132
+ "uid": "toad-eye-latency",
133
+ "version": 1
134
+ }
@@ -7,14 +7,115 @@
7
7
  "links": [],
8
8
  "panels": [
9
9
  {
10
- "title": "Request rate (per minute)",
10
+ "title": "Total Requests",
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_client_requests_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"})",
17
+ "refId": "A"
18
+ }
19
+ ],
20
+ "fieldConfig": {
21
+ "defaults": {
22
+ "unit": "short",
23
+ "thresholds": {
24
+ "steps": [
25
+ { "color": "green", "value": null },
26
+ { "color": "yellow", "value": 1000 },
27
+ { "color": "red", "value": 10000 }
28
+ ]
29
+ }
30
+ },
31
+ "overrides": []
32
+ }
33
+ },
34
+ {
35
+ "title": "Error Rate %",
36
+ "type": "stat",
37
+ "gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 },
38
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
39
+ "targets": [
40
+ {
41
+ "expr": "sum(gen_ai_client_errors_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}) / sum(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}) * 100",
42
+ "refId": "A"
43
+ }
44
+ ],
45
+ "fieldConfig": {
46
+ "defaults": {
47
+ "unit": "percent",
48
+ "thresholds": {
49
+ "steps": [
50
+ { "color": "green", "value": null },
51
+ { "color": "yellow", "value": 5 },
52
+ { "color": "red", "value": 20 }
53
+ ]
54
+ }
55
+ },
56
+ "overrides": []
57
+ }
58
+ },
59
+ {
60
+ "title": "Avg Latency",
61
+ "type": "stat",
62
+ "gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
63
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
64
+ "targets": [
65
+ {
66
+ "expr": "sum(rate(gen_ai_client_operation_duration_milliseconds_sum{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) / sum(rate(gen_ai_client_operation_duration_milliseconds_count{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m]))",
67
+ "refId": "A"
68
+ }
69
+ ],
70
+ "fieldConfig": {
71
+ "defaults": {
72
+ "unit": "ms",
73
+ "thresholds": {
74
+ "steps": [
75
+ { "color": "green", "value": null },
76
+ { "color": "yellow", "value": 1000 },
77
+ { "color": "red", "value": 5000 }
78
+ ]
79
+ }
80
+ },
81
+ "overrides": []
82
+ }
83
+ },
84
+ {
85
+ "title": "Total Cost",
86
+ "type": "stat",
87
+ "gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 },
88
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
89
+ "targets": [
90
+ {
91
+ "expr": "sum(gen_ai_client_request_cost_USD_sum{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"})",
92
+ "refId": "A"
93
+ }
94
+ ],
95
+ "fieldConfig": {
96
+ "defaults": {
97
+ "unit": "currencyUSD",
98
+ "thresholds": {
99
+ "steps": [
100
+ { "color": "green", "value": null },
101
+ { "color": "yellow", "value": 10 },
102
+ { "color": "red", "value": 100 }
103
+ ]
104
+ }
105
+ },
106
+ "overrides": []
107
+ }
108
+ },
109
+ {
110
+ "title": "Request Rate per minute",
11
111
  "type": "timeseries",
12
- "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
112
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
13
113
  "datasource": { "type": "prometheus", "uid": "prometheus" },
14
114
  "targets": [
15
115
  {
16
- "expr": "sum(rate(llm_requests_total[1m])) by (provider) * 60",
17
- "legendFormat": "{{ provider }}"
116
+ "expr": "sum(rate(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[1m])) by (gen_ai_provider_name) * 60",
117
+ "legendFormat": "{{ gen_ai_provider_name }}",
118
+ "refId": "A"
18
119
  }
19
120
  ],
20
121
  "fieldConfig": {
@@ -26,14 +127,15 @@
26
127
  }
27
128
  },
28
129
  {
29
- "title": "Error rate (per minute)",
130
+ "title": "Error Rate per minute",
30
131
  "type": "timeseries",
31
- "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
132
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
32
133
  "datasource": { "type": "prometheus", "uid": "prometheus" },
33
134
  "targets": [
34
135
  {
35
- "expr": "sum(rate(llm_errors_total[1m])) by (provider) * 60",
36
- "legendFormat": "{{ provider }}"
136
+ "expr": "sum(rate(gen_ai_client_errors_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[1m])) by (gen_ai_provider_name) * 60",
137
+ "legendFormat": "{{ gen_ai_provider_name }}",
138
+ "refId": "A"
37
139
  }
38
140
  ],
39
141
  "fieldConfig": {
@@ -48,22 +150,24 @@
48
150
  {
49
151
  "title": "Latency p50 / p95 (ms)",
50
152
  "type": "timeseries",
51
- "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
153
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 12 },
52
154
  "datasource": { "type": "prometheus", "uid": "prometheus" },
53
155
  "targets": [
54
156
  {
55
- "expr": "histogram_quantile(0.50, sum(rate(llm_request_duration_milliseconds_bucket[1m])) by (le, provider))",
56
- "legendFormat": "p50 {{ provider }}"
157
+ "expr": "histogram_quantile(0.50, sum(rate(gen_ai_client_operation_duration_milliseconds_bucket{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (le))",
158
+ "legendFormat": "p50",
159
+ "refId": "A"
57
160
  },
58
161
  {
59
- "expr": "histogram_quantile(0.95, sum(rate(llm_request_duration_milliseconds_bucket[1m])) by (le, provider))",
60
- "legendFormat": "p95 {{ provider }}"
162
+ "expr": "histogram_quantile(0.95, sum(rate(gen_ai_client_operation_duration_milliseconds_bucket{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (le))",
163
+ "legendFormat": "p95",
164
+ "refId": "B"
61
165
  }
62
166
  ],
63
167
  "fieldConfig": {
64
168
  "defaults": {
65
169
  "unit": "ms",
66
- "custom": { "drawStyle": "line", "fillOpacity": 10, "lineWidth": 2 }
170
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
67
171
  },
68
172
  "overrides": []
69
173
  }
@@ -71,12 +175,13 @@
71
175
  {
72
176
  "title": "Cost per minute (USD)",
73
177
  "type": "timeseries",
74
- "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
178
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 12 },
75
179
  "datasource": { "type": "prometheus", "uid": "prometheus" },
76
180
  "targets": [
77
181
  {
78
- "expr": "sum(rate(llm_request_cost_USD_sum[1m])) by (provider) * 60",
79
- "legendFormat": "{{ provider }}"
182
+ "expr": "sum(rate(gen_ai_client_request_cost_USD_sum{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[1m])) by (gen_ai_provider_name) * 60",
183
+ "legendFormat": "{{ gen_ai_provider_name }}",
184
+ "refId": "A"
80
185
  }
81
186
  ],
82
187
  "fieldConfig": {
@@ -88,13 +193,14 @@
88
193
  }
89
194
  },
90
195
  {
91
- "title": "Total tokens consumed",
196
+ "title": "Total Tokens",
92
197
  "type": "stat",
93
- "gridPos": { "h": 4, "w": 8, "x": 0, "y": 16 },
198
+ "gridPos": { "h": 4, "w": 8, "x": 0, "y": 20 },
94
199
  "datasource": { "type": "prometheus", "uid": "prometheus" },
95
200
  "targets": [
96
201
  {
97
- "expr": "sum(llm_tokens_total)"
202
+ "expr": "sum(gen_ai_client_token_usage_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"})",
203
+ "refId": "A"
98
204
  }
99
205
  ],
100
206
  "fieldConfig": {
@@ -112,13 +218,14 @@
112
218
  }
113
219
  },
114
220
  {
115
- "title": "Total requests",
221
+ "title": "Total Requests",
116
222
  "type": "stat",
117
- "gridPos": { "h": 4, "w": 8, "x": 8, "y": 16 },
223
+ "gridPos": { "h": 4, "w": 8, "x": 8, "y": 20 },
118
224
  "datasource": { "type": "prometheus", "uid": "prometheus" },
119
225
  "targets": [
120
226
  {
121
- "expr": "sum(llm_requests_total)"
227
+ "expr": "sum(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"})",
228
+ "refId": "A"
122
229
  }
123
230
  ],
124
231
  "fieldConfig": {
@@ -136,13 +243,14 @@
136
243
  }
137
244
  },
138
245
  {
139
- "title": "Total errors",
246
+ "title": "Total Errors",
140
247
  "type": "stat",
141
- "gridPos": { "h": 4, "w": 8, "x": 16, "y": 16 },
248
+ "gridPos": { "h": 4, "w": 8, "x": 16, "y": 20 },
142
249
  "datasource": { "type": "prometheus", "uid": "prometheus" },
143
250
  "targets": [
144
251
  {
145
- "expr": "sum(llm_errors_total)"
252
+ "expr": "sum(gen_ai_client_errors_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"})",
253
+ "refId": "A"
146
254
  }
147
255
  ],
148
256
  "fieldConfig": {
@@ -163,11 +271,34 @@
163
271
  "refresh": "5s",
164
272
  "schemaVersion": 39,
165
273
  "tags": ["llm", "observability", "toad-eye"],
166
- "templating": { "list": [] },
274
+ "templating": {
275
+ "list": [
276
+ {
277
+ "name": "provider",
278
+ "type": "query",
279
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
280
+ "query": "label_values(gen_ai_client_requests_total, gen_ai_provider_name)",
281
+ "includeAll": true,
282
+ "allValue": ".*",
283
+ "current": { "text": "All", "value": "$__all" },
284
+ "multi": true
285
+ },
286
+ {
287
+ "name": "model",
288
+ "type": "query",
289
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
290
+ "query": "label_values(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\"}, gen_ai_request_model)",
291
+ "includeAll": true,
292
+ "allValue": ".*",
293
+ "current": { "text": "All", "value": "$__all" },
294
+ "multi": true
295
+ }
296
+ ]
297
+ },
167
298
  "time": { "from": "now-15m", "to": "now" },
168
299
  "timepicker": {},
169
300
  "timezone": "browser",
170
- "title": "toad-eye: LLM observability",
171
- "uid": "toad-eye-llm-overview",
172
- "version": 2
301
+ "title": "toad-eye: Overview",
302
+ "uid": "toad-eye-overview",
303
+ "version": 1
173
304
  }
@@ -0,0 +1,143 @@
1
+ {
2
+ "annotations": { "list": [] },
3
+ "editable": true,
4
+ "fiscalYearStartMonth": 0,
5
+ "graphTooltip": 1,
6
+ "id": null,
7
+ "links": [],
8
+ "panels": [
9
+ {
10
+ "title": "Latency by Model (p95)",
11
+ "type": "timeseries",
12
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
13
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
14
+ "targets": [
15
+ {
16
+ "expr": "histogram_quantile(0.95, sum(rate(gen_ai_client_operation_duration_milliseconds_bucket{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (le, gen_ai_request_model))",
17
+ "legendFormat": "p95 {{ gen_ai_request_model }}",
18
+ "refId": "A"
19
+ }
20
+ ],
21
+ "fieldConfig": {
22
+ "defaults": {
23
+ "unit": "ms",
24
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
25
+ },
26
+ "overrides": []
27
+ }
28
+ },
29
+ {
30
+ "title": "Cost by Model (avg per request)",
31
+ "type": "timeseries",
32
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
33
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
34
+ "targets": [
35
+ {
36
+ "expr": "sum(rate(gen_ai_client_request_cost_USD_sum{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (gen_ai_request_model) / sum(rate(gen_ai_client_request_cost_USD_count{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (gen_ai_request_model)",
37
+ "legendFormat": "{{ gen_ai_request_model }}",
38
+ "refId": "A"
39
+ }
40
+ ],
41
+ "fieldConfig": {
42
+ "defaults": {
43
+ "unit": "currencyUSD",
44
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
45
+ },
46
+ "overrides": []
47
+ }
48
+ },
49
+ {
50
+ "title": "Error Rate by Model (%)",
51
+ "type": "timeseries",
52
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
53
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
54
+ "targets": [
55
+ {
56
+ "expr": "sum(rate(gen_ai_client_errors_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (gen_ai_request_model) / sum(rate(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (gen_ai_request_model) * 100",
57
+ "legendFormat": "{{ gen_ai_request_model }}",
58
+ "refId": "A"
59
+ }
60
+ ],
61
+ "fieldConfig": {
62
+ "defaults": {
63
+ "unit": "percent",
64
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
65
+ },
66
+ "overrides": []
67
+ }
68
+ },
69
+ {
70
+ "title": "Throughput by Model (req/min)",
71
+ "type": "timeseries",
72
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
73
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
74
+ "targets": [
75
+ {
76
+ "expr": "sum(rate(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[1m])) by (gen_ai_request_model) * 60",
77
+ "legendFormat": "{{ gen_ai_request_model }}",
78
+ "refId": "A"
79
+ }
80
+ ],
81
+ "fieldConfig": {
82
+ "defaults": {
83
+ "unit": "reqpm",
84
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
85
+ },
86
+ "overrides": []
87
+ }
88
+ },
89
+ {
90
+ "title": "Tokens per Request by Model",
91
+ "type": "timeseries",
92
+ "gridPos": { "h": 8, "w": 24, "x": 0, "y": 16 },
93
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
94
+ "targets": [
95
+ {
96
+ "expr": "sum(rate(gen_ai_client_token_usage_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (gen_ai_request_model) / sum(rate(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\", gen_ai_request_model=~\"$model\"}[5m])) by (gen_ai_request_model)",
97
+ "legendFormat": "{{ gen_ai_request_model }}",
98
+ "refId": "A"
99
+ }
100
+ ],
101
+ "fieldConfig": {
102
+ "defaults": {
103
+ "unit": "short",
104
+ "custom": { "drawStyle": "line", "fillOpacity": 15, "lineWidth": 2 }
105
+ },
106
+ "overrides": []
107
+ }
108
+ }
109
+ ],
110
+ "refresh": "5s",
111
+ "schemaVersion": 39,
112
+ "tags": ["llm", "observability", "toad-eye"],
113
+ "templating": {
114
+ "list": [
115
+ {
116
+ "name": "provider",
117
+ "type": "query",
118
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
119
+ "query": "label_values(gen_ai_client_requests_total, gen_ai_provider_name)",
120
+ "includeAll": true,
121
+ "allValue": ".*",
122
+ "current": { "text": "All", "value": "$__all" },
123
+ "multi": true
124
+ },
125
+ {
126
+ "name": "model",
127
+ "type": "query",
128
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
129
+ "query": "label_values(gen_ai_client_requests_total{gen_ai_provider_name=~\"$provider\"}, gen_ai_request_model)",
130
+ "includeAll": true,
131
+ "allValue": ".*",
132
+ "current": { "text": "All", "value": "$__all" },
133
+ "multi": true
134
+ }
135
+ ]
136
+ },
137
+ "time": { "from": "now-15m", "to": "now" },
138
+ "timepicker": {},
139
+ "timezone": "browser",
140
+ "title": "toad-eye: Model Comparison",
141
+ "uid": "toad-eye-comparison",
142
+ "version": 1
143
+ }
package/dist/metrics.d.ts DELETED
@@ -1,6 +0,0 @@
1
- export declare function initMetrics(): void;
2
- export declare function recordRequestDuration(ms: number, provider: string, model: string): void;
3
- export declare function recordRequestCost(usd: number, provider: string, model: string): void;
4
- export declare function recordTokens(count: number, provider: string, model: string): void;
5
- export declare function recordRequest(provider: string, model: string): void;
6
- export declare function recordError(provider: string, model: string): void;
package/dist/metrics.js DELETED
@@ -1,46 +0,0 @@
1
- import { metrics } from "@opentelemetry/api";
2
- import { LLM_METRICS, INSTRUMENTATION_NAME } from "./types.js";
3
- let requestDuration;
4
- let requestCost;
5
- let tokensTotal;
6
- let requestsTotal;
7
- let errorsTotal;
8
- let initialized = false;
9
- export function initMetrics() {
10
- if (initialized)
11
- return;
12
- const meter = metrics.getMeter(INSTRUMENTATION_NAME);
13
- requestDuration = meter.createHistogram(LLM_METRICS.REQUEST_DURATION, {
14
- description: "LLM request duration in milliseconds",
15
- unit: "ms",
16
- });
17
- requestCost = meter.createHistogram(LLM_METRICS.REQUEST_COST, {
18
- description: "LLM request cost in USD",
19
- unit: "USD",
20
- });
21
- tokensTotal = meter.createCounter(LLM_METRICS.TOKENS, {
22
- description: "Total tokens consumed",
23
- });
24
- requestsTotal = meter.createCounter(LLM_METRICS.REQUESTS, {
25
- description: "Total LLM requests",
26
- });
27
- errorsTotal = meter.createCounter(LLM_METRICS.ERRORS, {
28
- description: "Total failed LLM requests",
29
- });
30
- initialized = true;
31
- }
32
- export function recordRequestDuration(ms, provider, model) {
33
- requestDuration.record(ms, { provider, model });
34
- }
35
- export function recordRequestCost(usd, provider, model) {
36
- requestCost.record(usd, { provider, model });
37
- }
38
- export function recordTokens(count, provider, model) {
39
- tokensTotal.add(count, { provider, model });
40
- }
41
- export function recordRequest(provider, model) {
42
- requestsTotal.add(1, { provider, model });
43
- }
44
- export function recordError(provider, model) {
45
- errorsTotal.add(1, { provider, model });
46
- }
package/dist/spans.js DELETED
@@ -1,61 +0,0 @@
1
- import { trace, SpanStatusCode } from "@opentelemetry/api";
2
- import { LLM_ATTRS, INSTRUMENTATION_NAME } from "./types.js";
3
- import { recordRequestDuration, recordRequestCost, recordTokens, recordRequest, recordError, } from "./metrics.js";
4
- import { getConfig } from "./tracer.js";
5
- const tracer = trace.getTracer(INSTRUMENTATION_NAME);
6
- function shouldRecordContent() {
7
- return getConfig()?.recordContent !== false;
8
- }
9
- function setBaseAttributes(span, input) {
10
- span.setAttributes({
11
- [LLM_ATTRS.PROVIDER]: input.provider,
12
- [LLM_ATTRS.MODEL]: input.model,
13
- [LLM_ATTRS.TEMPERATURE]: input.temperature ?? 1.0,
14
- ...(shouldRecordContent() && { [LLM_ATTRS.PROMPT]: input.prompt }),
15
- });
16
- }
17
- export async function traceLLMCall(input, fn) {
18
- return tracer.startActiveSpan(`llm.${input.provider}.${input.model}`, async (span) => {
19
- const start = performance.now();
20
- setBaseAttributes(span, input);
21
- try {
22
- const output = await fn();
23
- const duration = performance.now() - start;
24
- span.setAttributes({
25
- ...(shouldRecordContent() && {
26
- [LLM_ATTRS.COMPLETION]: output.completion,
27
- }),
28
- [LLM_ATTRS.INPUT_TOKENS]: output.inputTokens,
29
- [LLM_ATTRS.OUTPUT_TOKENS]: output.outputTokens,
30
- [LLM_ATTRS.COST]: output.cost,
31
- [LLM_ATTRS.STATUS]: "success",
32
- });
33
- span.setStatus({ code: SpanStatusCode.OK });
34
- recordRequest(input.provider, input.model);
35
- recordRequestDuration(duration, input.provider, input.model);
36
- recordRequestCost(output.cost, input.provider, input.model);
37
- recordTokens(output.inputTokens + output.outputTokens, input.provider, input.model);
38
- return output;
39
- }
40
- catch (error) {
41
- const duration = performance.now() - start;
42
- const message = error instanceof Error ? error.message : String(error);
43
- span.setAttributes({
44
- [LLM_ATTRS.COMPLETION]: "",
45
- [LLM_ATTRS.INPUT_TOKENS]: 0,
46
- [LLM_ATTRS.OUTPUT_TOKENS]: 0,
47
- [LLM_ATTRS.COST]: 0,
48
- [LLM_ATTRS.STATUS]: "error",
49
- [LLM_ATTRS.ERROR]: message,
50
- });
51
- span.setStatus({ code: SpanStatusCode.ERROR, message });
52
- recordRequest(input.provider, input.model);
53
- recordRequestDuration(duration, input.provider, input.model);
54
- recordError(input.provider, input.model);
55
- throw error;
56
- }
57
- finally {
58
- span.end();
59
- }
60
- });
61
- }