autotel-genai 0.3.8 → 0.3.10
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/package.json +4 -5
- package/skills/autotel-genai/SKILL.md +0 -290
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-genai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "Gold-standard OpenTelemetry GenAI semantic-convention instrumentation for LLM calls, tools, and agents — cost, tokens, metrics, events, and agent governance.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -65,8 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
"files": [
|
|
67
67
|
"dist",
|
|
68
|
-
"README.md"
|
|
69
|
-
"skills"
|
|
68
|
+
"README.md"
|
|
70
69
|
],
|
|
71
70
|
"keywords": [
|
|
72
71
|
"autotel",
|
|
@@ -84,8 +83,8 @@
|
|
|
84
83
|
"author": "Jag Reehal <jag@jagreehal.com> (https://jagreehal.com)",
|
|
85
84
|
"license": "Apache-2.0",
|
|
86
85
|
"dependencies": {
|
|
87
|
-
"autotel": "
|
|
88
|
-
"autotel-audit": "0.4.
|
|
86
|
+
"autotel": "6.1.0",
|
|
87
|
+
"autotel-audit": "0.4.9"
|
|
89
88
|
},
|
|
90
89
|
"peerDependencies": {
|
|
91
90
|
"@opentelemetry/api": "*",
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: autotel-genai
|
|
3
|
-
description: >
|
|
4
|
-
Use this skill when instrumenting AI/LLM/agent code with OpenTelemetry GenAI semantic conventions — traceGenAI() spans, token usage and cost, gen_ai.* attributes, GenAI metric views, content/evaluation events, the Vercel AI SDK bridge, or the agent identity/delegation/policy/audit governance layer. This is the canonical home for everything GenAI in autotel (the core `autotel` package is AI-free).
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# autotel-genai
|
|
8
|
-
|
|
9
|
-
Gold-standard OpenTelemetry **GenAI** instrumentation: canonical `gen_ai.*`
|
|
10
|
-
semantic conventions (semconv **v1.42.0**) for LLM calls, tools, and agents.
|
|
11
|
-
Canonical-only. There is no legacy `gen.ai.*`, `prompt_tokens`/`completion_tokens`,
|
|
12
|
-
or non-registry `total_tokens` surface.
|
|
13
|
-
|
|
14
|
-
Core `autotel` provides `trace()`/`span()`/`init()`. `autotel-genai` adds the AI
|
|
15
|
-
layer on top.
|
|
16
|
-
|
|
17
|
-
## Setup
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm install autotel autotel-genai
|
|
21
|
-
# autotel is a peer; @opentelemetry/sdk-metrics is an optional peer (for metric views)
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
26
|
-
import { genAiMetricViews } from 'autotel-genai/metrics';
|
|
27
|
-
|
|
28
|
-
// Re-bucket the GenAI histograms (duration, time-to-first-chunk, token usage, cost)
|
|
29
|
-
const sdk = new NodeSDK({
|
|
30
|
-
serviceName: 'my-agent',
|
|
31
|
-
views: [...genAiMetricViews()],
|
|
32
|
-
});
|
|
33
|
-
sdk.start();
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Core Patterns
|
|
37
|
-
|
|
38
|
-
### Trace an LLM call: `traceGenAI`
|
|
39
|
-
|
|
40
|
-
Names the span per spec (`{operation} {model}` → `chat gpt-4o`) and sets the
|
|
41
|
-
request attributes up front. Record the response + usage when the call returns.
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
import {
|
|
45
|
-
traceGenAI,
|
|
46
|
-
recordGenAiResponse,
|
|
47
|
-
recordGenAiUsage,
|
|
48
|
-
} from 'autotel-genai/trace';
|
|
49
|
-
|
|
50
|
-
export const chat = traceGenAI({
|
|
51
|
-
provider: 'openai', // gen_ai.provider.name
|
|
52
|
-
model: 'gpt-4o', // gen_ai.request.model + span name
|
|
53
|
-
operation: 'chat', // gen_ai.operation.name
|
|
54
|
-
temperature: 0.2,
|
|
55
|
-
})((ctx) => async (prompt: string) => {
|
|
56
|
-
const res = await openai.chat.completions.create({
|
|
57
|
-
model: 'gpt-4o',
|
|
58
|
-
messages: [{ role: 'user', content: prompt }],
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
recordGenAiResponse(ctx, {
|
|
62
|
-
model: res.model,
|
|
63
|
-
id: res.id,
|
|
64
|
-
finishReasons: res.choices.map((c) => c.finish_reason), // gen_ai.response.finish_reasons
|
|
65
|
-
});
|
|
66
|
-
// gen_ai.usage.input_tokens / output_tokens + estimated gen_ai.usage.cost.usd
|
|
67
|
-
recordGenAiUsage(ctx, 'gpt-4o', {
|
|
68
|
-
inputTokens: res.usage?.prompt_tokens,
|
|
69
|
-
outputTokens: res.usage?.completion_tokens,
|
|
70
|
-
cacheReadInputTokens: res.usage?.prompt_tokens_details?.cached_tokens,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return res.choices[0].message.content;
|
|
74
|
-
});
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
`operation` drives both the span name's trailing identifier and which metadata
|
|
78
|
-
matters: `retrieval` → `data_source.id`; `execute_tool` → `tool.name`;
|
|
79
|
-
`create_agent`/`invoke_agent`/`plan` → `agent.name`; `invoke_workflow` →
|
|
80
|
-
`workflow.name`; memory ops → bare operation name. Pass `agent`/`tool`/`workflow`
|
|
81
|
-
config to set the matching `gen_ai.*` attributes and name the span.
|
|
82
|
-
|
|
83
|
-
### Cost
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
import { estimateLLMCost, recordLLMCost } from 'autotel-genai/cost';
|
|
87
|
-
|
|
88
|
-
estimateLLMCost('gpt-4o', { inputTokens: 1000, outputTokens: 500 }); // 0.0075
|
|
89
|
-
// recordLLMCost sets ONLY gen_ai.usage.cost.usd (use when tokens are already on the span)
|
|
90
|
-
recordLLMCost(ctx, 'claude-sonnet-4', {
|
|
91
|
-
inputTokens: 4000,
|
|
92
|
-
cacheReadInputTokens: 3500,
|
|
93
|
-
});
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Override/extend pricing per call with `{ pricing: { 'my-model': { inputPer1M, outputPer1M } } }`.
|
|
97
|
-
|
|
98
|
-
### Typed attribute builders
|
|
99
|
-
|
|
100
|
-
When you control the span directly, build canonical maps and merge them:
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
import { genAiRequestAttributes, genAiUsageAttributes } from 'autotel-genai';
|
|
104
|
-
|
|
105
|
-
ctx.setAttributes({
|
|
106
|
-
...genAiRequestAttributes({
|
|
107
|
-
operation: 'chat',
|
|
108
|
-
provider: 'openai',
|
|
109
|
-
model: 'gpt-4o',
|
|
110
|
-
topK: 40,
|
|
111
|
-
}),
|
|
112
|
-
...genAiUsageAttributes({ inputTokens: 412, outputTokens: 87 }),
|
|
113
|
-
});
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Builders omit absent fields, coerce int-typed attributes (`top_k`, `seed`,
|
|
117
|
-
`choice.count`), and JSON-serialise structured attributes (`tool.call.arguments`,
|
|
118
|
-
`memory.records`). Also: `genAiResponseAttributes`, `genAiAgentAttributes`,
|
|
119
|
-
`genAiToolAttributes`, `genAiRetrievalAttributes`, `genAiMemoryAttributes`,
|
|
120
|
-
`genAiWorkflowAttributes`.
|
|
121
|
-
|
|
122
|
-
### Content + evaluation events
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import {
|
|
126
|
-
setGenAiContent,
|
|
127
|
-
recordInferenceDetails,
|
|
128
|
-
recordEvaluationResult,
|
|
129
|
-
recordModelWarnings,
|
|
130
|
-
} from 'autotel-genai/events';
|
|
131
|
-
|
|
132
|
-
// Opt-in content on the span. Gate input/output independently; binary parts
|
|
133
|
-
// (image/audio/file) are base64-encoded, not corrupted by JSON.stringify.
|
|
134
|
-
setGenAiContent(
|
|
135
|
-
ctx,
|
|
136
|
-
{ inputMessages, outputMessages },
|
|
137
|
-
{ recordInputs: false },
|
|
138
|
-
);
|
|
139
|
-
// gen_ai.client.inference.operation.details event (decoupled from the span)
|
|
140
|
-
recordInferenceDetails(ctx, {
|
|
141
|
-
operation: 'chat',
|
|
142
|
-
requestModel: 'gpt-4o',
|
|
143
|
-
inputTokens: 412,
|
|
144
|
-
});
|
|
145
|
-
// gen_ai.evaluation.result event
|
|
146
|
-
recordEvaluationResult(ctx, { name: 'relevance', scoreValue: 0.92 });
|
|
147
|
-
// gen_ai.client.warnings event — surface provider warnings vendors only log
|
|
148
|
-
recordModelWarnings(ctx, [{ type: 'unsupported-setting', setting: 'topK' }]);
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Streaming performance: `autotel-genai/streaming`
|
|
152
|
-
|
|
153
|
-
Streaming latency is two numbers: **time to first chunk** (the wait) and
|
|
154
|
-
**throughput** (how fast tokens then arrive). `createStreamTimer` captures both.
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
import { createStreamTimer, recordStreamTiming } from 'autotel-genai/streaming';
|
|
158
|
-
|
|
159
|
-
const timer = createStreamTimer();
|
|
160
|
-
let text = '';
|
|
161
|
-
for await (const chunk of stream) {
|
|
162
|
-
timer.chunk(); // first call also marks time-to-first-chunk
|
|
163
|
-
text += chunk;
|
|
164
|
-
}
|
|
165
|
-
// gen_ai.response.time_to_first_chunk (spec) + .time_to_finish /
|
|
166
|
-
// .output_tokens_per_second / .time_per_output_chunk (autotel extensions, seconds)
|
|
167
|
-
recordStreamTiming(ctx, timer.finish({ outputTokens }));
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
`computeStreamTiming(...)` is the pure function underneath; it also returns the
|
|
171
|
-
inter-chunk gap distribution `{ min, p10, median, avg, p90, max }`.
|
|
172
|
-
|
|
173
|
-
### Budgets & guardrails: `autotel-genai/guard`
|
|
174
|
-
|
|
175
|
-
An inline kill-switch that runs _during_ a run. Feed it each step; it accumulates
|
|
176
|
-
cost / tokens / loop state and halts when a rule crosses its threshold. Aborting
|
|
177
|
-
an `AbortSignal` and (by default) throwing a `GEN_AI_GUARD_STOP` structured
|
|
178
|
-
error. Deterministic, no LLM in the loop.
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
import {
|
|
182
|
-
createGenAiBudget,
|
|
183
|
-
createGenAiGuard,
|
|
184
|
-
parseGuardRules,
|
|
185
|
-
} from 'autotel-genai/guard';
|
|
186
|
-
|
|
187
|
-
// Preset: cost / token / tool-call / duration ceilings
|
|
188
|
-
const budget = createGenAiBudget({ maxCostUsd: 5, warnAtUsd: 4 });
|
|
189
|
-
budget.record({ kind: 'llm', usage: { costUsd } }, ctx); // throws once cost > $5
|
|
190
|
-
|
|
191
|
-
// Or build rules from a shorthand string (or typed factories)
|
|
192
|
-
const guard = createGenAiGuard({
|
|
193
|
-
rules: parseGuardRules('budget:$2,loop:3/10,max-tools:50,timeout:5m'),
|
|
194
|
-
onStop: 'abort', // 'throw' (default) | 'abort' (signal only) | 'silent'
|
|
195
|
-
});
|
|
196
|
-
guard.record({ kind: 'tool', name: 'search', signature: JSON.stringify(args) });
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Rule factories: `costCeiling`, `tokenCeiling`, `maxToolCalls`, `maxSteps`,
|
|
200
|
-
`maxDuration`, `spinLoop`, `errorLoop`, `contextBudget`. Each fires once. Records
|
|
201
|
-
`gen_ai.guard.*` events + `gen_ai.session.*` accumulators when given a `ctx`.
|
|
202
|
-
|
|
203
|
-
### Vercel AI SDK bridge
|
|
204
|
-
|
|
205
|
-
The primary AI SDK path is `autotelTelemetry()` from `autotel-genai/observer`.
|
|
206
|
-
Register it once and every `generateText` / `streamText` / `embed` call emits a
|
|
207
|
-
live canonical `gen_ai.*` span tree with cost, streaming timing, nested tool
|
|
208
|
-
execution, and nested provider HTTP spans:
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
import { registerTelemetry } from 'ai';
|
|
212
|
-
import { autotelTelemetry, subscribeAiTelemetry } from 'autotel-genai/observer';
|
|
213
|
-
|
|
214
|
-
registerTelemetry(autotelTelemetry());
|
|
215
|
-
|
|
216
|
-
const unsubscribe = subscribeAiTelemetry(); // fallback: zero-config ai:telemetry channel
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Use `subscribeAiTelemetry()` when you cannot add a registration call. It emits
|
|
220
|
-
the same `invoke_agent > chat > execute_tool` tree with usage and cost, but not
|
|
221
|
-
the per-call streaming timing that only the lifecycle integration sees.
|
|
222
|
-
|
|
223
|
-
For `LegacyOpenTelemetry`/older versions, or to enrich spans another
|
|
224
|
-
integration already emitted, use the legacy bridge:
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
import {
|
|
228
|
-
autotelEnrich,
|
|
229
|
-
mapAiSdkAttributes,
|
|
230
|
-
recordAiSdkCost,
|
|
231
|
-
} from 'autotel-genai/ai-sdk';
|
|
232
|
-
|
|
233
|
-
const canonical = mapAiSdkAttributes(span.attributes); // ai.* → gen_ai.*
|
|
234
|
-
recordAiSdkCost(ctx, span.attributes); // sets gen_ai.usage.cost.usd
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
`autotelEnrich()` is for `@ai-sdk/otel`'s `enrichSpan` hook when you want
|
|
238
|
-
autotel provenance and `runtimeContext` fields on spans, but it cannot add
|
|
239
|
-
cost because the hook gets no usage/model payload.
|
|
240
|
-
|
|
241
|
-
### Agent governance: `autotel-genai/agent`
|
|
242
|
-
|
|
243
|
-
Identity, delegation, policy, and audit for agentic workflows (the former
|
|
244
|
-
`autotel-agent` package). Records `agent.*`/`delegation.*`/`tool.*`/`policy.*`
|
|
245
|
-
governance attributes plus canonical `gen_ai.*` when `ai` metadata is present.
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
import { withScopedTool } from 'autotel-genai/agent';
|
|
249
|
-
|
|
250
|
-
await withScopedTool(
|
|
251
|
-
{
|
|
252
|
-
action: 'agent.refund.execute',
|
|
253
|
-
agent: { id: 'refund-specialist' },
|
|
254
|
-
tool: { name: 'stripe_refund_v3' },
|
|
255
|
-
requiredScopes: ['refund:write'],
|
|
256
|
-
delegation: { parentIdentity: 'usr_99824', scope: ['refund:write'] },
|
|
257
|
-
policy: { decision: 'permit', policyId: 'refund-scope-v2' },
|
|
258
|
-
ai: { model: 'gpt-4o', operation: 'execute_tool' },
|
|
259
|
-
},
|
|
260
|
-
{ refundId: 're_123' },
|
|
261
|
-
async () => stripe.refunds.create(req),
|
|
262
|
-
);
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
## Canonical attribute reference (don't deviate)
|
|
266
|
-
|
|
267
|
-
- Provider: `gen_ai.provider.name`: **not** the deprecated `gen_ai.system`.
|
|
268
|
-
- Tokens: `gen_ai.usage.input_tokens` / `output_tokens` / `reasoning.output_tokens`
|
|
269
|
-
/ `cache_read.input_tokens` / `cache_creation.input_tokens`. **never**
|
|
270
|
-
`prompt_tokens` / `completion_tokens` / `total_tokens`.
|
|
271
|
-
- Finish reasons: `gen_ai.response.finish_reasons` (plural, string array).
|
|
272
|
-
- Cost (autotel extension): `gen_ai.usage.cost.usd`.
|
|
273
|
-
- Other autotel extensions (clearly non-spec, namespaced under `gen_ai.*`):
|
|
274
|
-
`gen_ai.guard.*` + `gen_ai.session.*` (guard), `gen_ai.response.time_to_finish`
|
|
275
|
-
/ `output_tokens_per_second` / `time_per_output_chunk` (streaming),
|
|
276
|
-
`gen_ai.client.warnings` (event). `gen_ai.response.time_to_first_chunk` is spec.
|
|
277
|
-
- `gen_ai.request.top_k` is an int; `gen_ai.agent.id` is dropped on internal
|
|
278
|
-
`invoke_agent`/`plan` spans (spec breaking change #242. `traceGenAI` handles
|
|
279
|
-
this automatically).
|
|
280
|
-
|
|
281
|
-
The `GEN_AI` / `GEN_AI_OPERATION` / `GEN_AI_PROVIDER` constants in
|
|
282
|
-
`autotel-genai/semconv` are the source of truth. Use them instead of string
|
|
283
|
-
literals.
|
|
284
|
-
|
|
285
|
-
## Boundaries
|
|
286
|
-
|
|
287
|
-
- ✅ Always: canonical `gen_ai.*` names from `autotel-genai/semconv`; reuse core
|
|
288
|
-
`trace()` / `TraceContext`; pass `genAiMetricViews()` to your MeterProvider.
|
|
289
|
-
- 🚫 Never: legacy `gen.ai.*`, `prompt_tokens`/`completion_tokens`/`total_tokens`,
|
|
290
|
-
`gen_ai.system`, `gen_ai.cost.usd`; never add GenAI code back to core `autotel`.
|