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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel-genai",
3
- "version": "0.3.8",
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": "5.0.0",
88
- "autotel-audit": "0.4.7"
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`.