toad-eye 2.0.0 → 2.1.1
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 +290 -109
- package/dist/agent.d.ts +41 -0
- package/dist/agent.js +91 -0
- package/dist/budget/error.d.ts +10 -0
- package/dist/budget/error.js +22 -0
- package/dist/budget/index.d.ts +3 -0
- package/dist/budget/index.js +2 -0
- package/dist/budget/tracker.d.ts +35 -0
- package/dist/budget/tracker.js +121 -0
- package/dist/budget/types.d.ts +34 -0
- package/dist/budget/types.js +1 -0
- package/dist/cli.js +66 -28
- package/dist/core/metrics.d.ts +14 -0
- package/dist/core/metrics.js +119 -0
- package/dist/{spans.d.ts → core/spans.d.ts} +3 -1
- package/dist/core/spans.js +143 -0
- package/dist/core/tracer.d.ts +9 -0
- package/dist/core/tracer.js +93 -0
- package/dist/drift/baseline.d.ts +16 -0
- package/dist/drift/baseline.js +16 -0
- package/dist/drift/cosine.d.ts +13 -0
- package/dist/drift/cosine.js +39 -0
- package/dist/drift/embedding-provider.d.ts +21 -0
- package/dist/drift/embedding-provider.js +8 -0
- package/dist/drift/index.d.ts +9 -0
- package/dist/drift/index.js +8 -0
- package/dist/drift/monitor.d.ts +29 -0
- package/dist/drift/monitor.js +47 -0
- package/dist/drift/openai-embeddings.d.ts +6 -0
- package/dist/drift/openai-embeddings.js +26 -0
- package/dist/export.d.ts +46 -0
- package/dist/export.js +108 -0
- package/dist/guard.d.ts +26 -0
- package/dist/guard.js +45 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.js +8 -3
- package/dist/instrumentations/anthropic.js +20 -1
- package/dist/instrumentations/create.js +130 -32
- package/dist/instrumentations/openai.js +38 -2
- package/dist/instrumentations/types.d.ts +9 -1
- package/dist/types/attributes.d.ts +12 -0
- package/dist/types/attributes.js +15 -0
- package/dist/types/config.d.ts +14 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/metrics.d.ts +8 -0
- package/dist/types/metrics.js +12 -0
- package/dist/types/spans.d.ts +21 -0
- package/package.json +28 -5
- package/templates/grafana/dashboards/finops-attribution.json +278 -0
- package/dist/metrics.d.ts +0 -6
- package/dist/metrics.js +0 -61
- package/dist/spans.js +0 -87
- package/dist/tracer.d.ts +0 -7
- package/dist/tracer.js +0 -55
- package/dist/types.d.ts +0 -104
- package/dist/types.js +0 -58
- /package/dist/{pricing.d.ts → core/pricing.d.ts} +0 -0
- /package/dist/{pricing.js → core/pricing.js} +0 -0
|
@@ -1,8 +1,132 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { diag } from "@opentelemetry/api";
|
|
3
|
-
import { traceLLMCall } from "../spans.js";
|
|
4
|
-
import { calculateCost } from "../pricing.js";
|
|
2
|
+
import { trace, diag, SpanStatusCode } from "@opentelemetry/api";
|
|
3
|
+
import { traceLLMCall } from "../core/spans.js";
|
|
4
|
+
import { calculateCost } from "../core/pricing.js";
|
|
5
|
+
import { recordRequestDuration, recordRequestCost, recordTokens, recordRequest, recordError, } from "../core/metrics.js";
|
|
6
|
+
import { GEN_AI_ATTRS, INSTRUMENTATION_NAME } from "../types/index.js";
|
|
5
7
|
const require = createRequire(import.meta.url);
|
|
8
|
+
function isModuleInstalled(moduleName) {
|
|
9
|
+
try {
|
|
10
|
+
require.resolve(moduleName);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
function loadModule(moduleName) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
|
+
const sdk = require(moduleName);
|
|
21
|
+
return sdk.default ?? sdk;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Wrap an async iterable stream to intercept chunks.
|
|
25
|
+
* Yields every chunk transparently to the caller while collecting them.
|
|
26
|
+
* On stream end, calls onComplete with all accumulated chunks.
|
|
27
|
+
*/
|
|
28
|
+
async function* wrapAsyncIterable(stream, onChunk, onComplete, onError) {
|
|
29
|
+
const chunks = [];
|
|
30
|
+
try {
|
|
31
|
+
for await (const chunk of stream) {
|
|
32
|
+
chunks.push(chunk);
|
|
33
|
+
onChunk(chunk);
|
|
34
|
+
yield chunk;
|
|
35
|
+
}
|
|
36
|
+
onComplete(chunks);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
onError(err);
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function createStreamingHandler(
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
original, patch, providerName) {
|
|
46
|
+
const tracer = trace.getTracer(INSTRUMENTATION_NAME);
|
|
47
|
+
return async function handleStreaming(thisArg, body, rest) {
|
|
48
|
+
const req = patch.extractRequest(body);
|
|
49
|
+
const start = performance.now();
|
|
50
|
+
const response = await original.call(thisArg, body, ...rest);
|
|
51
|
+
// The response should be an async iterable (Stream, MessageStream, etc.)
|
|
52
|
+
// We wrap it to intercept chunks
|
|
53
|
+
const span = tracer.startSpan(`gen_ai.${providerName}.${req.model}`);
|
|
54
|
+
span.setAttributes({
|
|
55
|
+
[GEN_AI_ATTRS.PROVIDER]: providerName,
|
|
56
|
+
[GEN_AI_ATTRS.REQUEST_MODEL]: req.model,
|
|
57
|
+
[GEN_AI_ATTRS.TEMPERATURE]: req.temperature ?? 1.0,
|
|
58
|
+
[GEN_AI_ATTRS.OPERATION]: "chat",
|
|
59
|
+
});
|
|
60
|
+
const wrapped = wrapAsyncIterable(response, () => { }, (chunks) => {
|
|
61
|
+
const duration = performance.now() - start;
|
|
62
|
+
const res = patch.extractStreamResponse(chunks);
|
|
63
|
+
const cost = calculateCost(req.model, res.inputTokens, res.outputTokens);
|
|
64
|
+
span.setAttributes({
|
|
65
|
+
[GEN_AI_ATTRS.RESPONSE_MODEL]: req.model,
|
|
66
|
+
[GEN_AI_ATTRS.INPUT_TOKENS]: res.inputTokens,
|
|
67
|
+
[GEN_AI_ATTRS.OUTPUT_TOKENS]: res.outputTokens,
|
|
68
|
+
[GEN_AI_ATTRS.COST]: cost,
|
|
69
|
+
[GEN_AI_ATTRS.STATUS]: "success",
|
|
70
|
+
});
|
|
71
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
72
|
+
span.end();
|
|
73
|
+
recordRequest(providerName, req.model);
|
|
74
|
+
recordRequestDuration(duration, providerName, req.model);
|
|
75
|
+
recordRequestCost(cost, providerName, req.model);
|
|
76
|
+
recordTokens(res.inputTokens + res.outputTokens, providerName, req.model);
|
|
77
|
+
}, (err) => {
|
|
78
|
+
const duration = performance.now() - start;
|
|
79
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
+
span.setAttributes({
|
|
81
|
+
[GEN_AI_ATTRS.STATUS]: "error",
|
|
82
|
+
[GEN_AI_ATTRS.ERROR]: message,
|
|
83
|
+
});
|
|
84
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
85
|
+
span.end();
|
|
86
|
+
recordRequest(providerName, req.model);
|
|
87
|
+
recordRequestDuration(duration, providerName, req.model);
|
|
88
|
+
recordError(providerName, req.model);
|
|
89
|
+
});
|
|
90
|
+
// Return an object that looks like the original stream but with our wrapper
|
|
91
|
+
// Preserve the original object shape — SDKs may have extra methods/properties
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
const proxy = Object.create(response);
|
|
94
|
+
proxy[Symbol.asyncIterator] = () => wrapped[Symbol.asyncIterator]();
|
|
95
|
+
return proxy;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createPatchedMethod(
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
original, patch, providerName) {
|
|
101
|
+
const streamHandler = patch.isStreaming && patch.extractStreamResponse
|
|
102
|
+
? createStreamingHandler(original, patch, providerName)
|
|
103
|
+
: null;
|
|
104
|
+
return function patchedMethod(body, ...rest) {
|
|
105
|
+
if (patch.shouldSkip?.(body)) {
|
|
106
|
+
return original.call(this, body, ...rest);
|
|
107
|
+
}
|
|
108
|
+
// Handle streaming calls
|
|
109
|
+
if (streamHandler && patch.isStreaming?.(body)) {
|
|
110
|
+
return streamHandler(this, body, rest);
|
|
111
|
+
}
|
|
112
|
+
const req = patch.extractRequest(body);
|
|
113
|
+
return traceLLMCall({
|
|
114
|
+
provider: providerName,
|
|
115
|
+
model: req.model,
|
|
116
|
+
prompt: req.prompt,
|
|
117
|
+
temperature: req.temperature,
|
|
118
|
+
}, async () => {
|
|
119
|
+
const response = await original.call(this, body, ...rest);
|
|
120
|
+
const res = patch.extractResponse(response, req.model);
|
|
121
|
+
return {
|
|
122
|
+
completion: res.completion,
|
|
123
|
+
inputTokens: res.inputTokens,
|
|
124
|
+
outputTokens: res.outputTokens,
|
|
125
|
+
cost: calculateCost(req.model, res.inputTokens, res.outputTokens),
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
}
|
|
6
130
|
/**
|
|
7
131
|
* Factory for creating SDK instrumentations.
|
|
8
132
|
* Eliminates boilerplate: SDK resolution, prototype patching, error handling, cleanup.
|
|
@@ -12,42 +136,16 @@ export function createInstrumentation(config) {
|
|
|
12
136
|
return {
|
|
13
137
|
name: config.name,
|
|
14
138
|
enable() {
|
|
15
|
-
|
|
16
|
-
require.resolve(config.moduleName);
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
139
|
+
if (!isModuleInstalled(config.moduleName))
|
|
19
140
|
return false;
|
|
20
|
-
}
|
|
21
141
|
try {
|
|
22
|
-
|
|
23
|
-
const sdk = require(config.moduleName);
|
|
24
|
-
const mod = sdk.default ?? sdk;
|
|
142
|
+
const mod = loadModule(config.moduleName);
|
|
25
143
|
for (const patch of config.patches) {
|
|
26
144
|
const proto = patch.getPrototype(mod);
|
|
27
145
|
if (!proto?.[patch.method])
|
|
28
146
|
continue;
|
|
29
147
|
const original = proto[patch.method];
|
|
30
|
-
proto[patch.method] =
|
|
31
|
-
if (patch.shouldSkip?.(body)) {
|
|
32
|
-
return original.call(this, body, ...rest);
|
|
33
|
-
}
|
|
34
|
-
const req = patch.extractRequest(body);
|
|
35
|
-
return traceLLMCall({
|
|
36
|
-
provider: config.name,
|
|
37
|
-
model: req.model,
|
|
38
|
-
prompt: req.prompt,
|
|
39
|
-
temperature: req.temperature,
|
|
40
|
-
}, async () => {
|
|
41
|
-
const response = await original.call(this, body, ...rest);
|
|
42
|
-
const res = patch.extractResponse(response, req.model);
|
|
43
|
-
return {
|
|
44
|
-
completion: res.completion,
|
|
45
|
-
inputTokens: res.inputTokens,
|
|
46
|
-
outputTokens: res.outputTokens,
|
|
47
|
-
cost: calculateCost(req.model, res.inputTokens, res.outputTokens),
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
};
|
|
148
|
+
proto[patch.method] = createPatchedMethod(original, patch, config.name);
|
|
51
149
|
activePatches.push({ proto, method: patch.method, original });
|
|
52
150
|
}
|
|
53
151
|
return activePatches.length > 0;
|
|
@@ -4,14 +4,31 @@ function extractMessages(messages) {
|
|
|
4
4
|
if (!Array.isArray(messages))
|
|
5
5
|
return "";
|
|
6
6
|
return messages
|
|
7
|
-
.map((m) =>
|
|
7
|
+
.map((m) => {
|
|
8
|
+
if (typeof m.content === "string")
|
|
9
|
+
return m.content;
|
|
10
|
+
if (Array.isArray(m.content)) {
|
|
11
|
+
return m.content
|
|
12
|
+
.map((part) => {
|
|
13
|
+
if (part.type === "text")
|
|
14
|
+
return part.text ?? "";
|
|
15
|
+
if (part.type === "image_url")
|
|
16
|
+
return "[image]";
|
|
17
|
+
if (part.type === "input_audio")
|
|
18
|
+
return "[audio]";
|
|
19
|
+
return "";
|
|
20
|
+
})
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.join("");
|
|
23
|
+
}
|
|
24
|
+
return "";
|
|
25
|
+
})
|
|
8
26
|
.filter(Boolean)
|
|
9
27
|
.join("\n");
|
|
10
28
|
}
|
|
11
29
|
const chatCompletions = {
|
|
12
30
|
getPrototype: (sdk) => (sdk?.Chat?.Completions ?? sdk?.Completions)?.prototype,
|
|
13
31
|
method: "create",
|
|
14
|
-
shouldSkip: (body) => !!body?.stream,
|
|
15
32
|
extractRequest: (body) => {
|
|
16
33
|
const b = body;
|
|
17
34
|
return {
|
|
@@ -28,6 +45,25 @@ const chatCompletions = {
|
|
|
28
45
|
outputTokens: r?.usage?.completion_tokens ?? 0,
|
|
29
46
|
};
|
|
30
47
|
},
|
|
48
|
+
isStreaming: (body) => !!body?.stream,
|
|
49
|
+
extractStreamResponse: (chunks) => {
|
|
50
|
+
// OpenAI stream chunks: { choices: [{ delta: { content } }], usage?: { ... } }
|
|
51
|
+
let completion = "";
|
|
52
|
+
let inputTokens = 0;
|
|
53
|
+
let outputTokens = 0;
|
|
54
|
+
for (const chunk of chunks) {
|
|
55
|
+
const c = chunk;
|
|
56
|
+
const delta = c?.choices?.[0]?.delta?.content;
|
|
57
|
+
if (delta)
|
|
58
|
+
completion += delta;
|
|
59
|
+
// Usage is typically in the final chunk (when stream_options.include_usage is set)
|
|
60
|
+
if (c?.usage) {
|
|
61
|
+
inputTokens = c.usage.prompt_tokens ?? inputTokens;
|
|
62
|
+
outputTokens = c.usage.completion_tokens ?? outputTokens;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { completion, inputTokens, outputTokens };
|
|
66
|
+
},
|
|
31
67
|
};
|
|
32
68
|
const embeddings = {
|
|
33
69
|
getPrototype: (sdk) => sdk?.Embeddings?.prototype,
|
|
@@ -24,6 +24,14 @@ export interface PatchTarget {
|
|
|
24
24
|
inputTokens: number;
|
|
25
25
|
outputTokens: number;
|
|
26
26
|
};
|
|
27
|
-
/** Return true if this call should skip patching
|
|
27
|
+
/** Return true if this call should skip patching entirely */
|
|
28
28
|
shouldSkip?: (body: unknown) => boolean;
|
|
29
|
+
/** Return true if this call is a streaming request */
|
|
30
|
+
isStreaming?: (body: unknown) => boolean;
|
|
31
|
+
/** Extract final stats from accumulated stream chunks. Required when isStreaming is set. */
|
|
32
|
+
extractStreamResponse?: (chunks: unknown[]) => {
|
|
33
|
+
completion: string;
|
|
34
|
+
inputTokens: number;
|
|
35
|
+
outputTokens: number;
|
|
36
|
+
};
|
|
29
37
|
}
|
|
@@ -15,11 +15,23 @@ export declare const GEN_AI_ATTRS: {
|
|
|
15
15
|
readonly OPERATION: "gen_ai.operation.name";
|
|
16
16
|
readonly FINISH_REASONS: "gen_ai.response.finish_reasons";
|
|
17
17
|
readonly ERROR: "error.type";
|
|
18
|
+
readonly AGENT_STEP_TYPE: "gen_ai.agent.step.type";
|
|
19
|
+
readonly AGENT_STEP_NUMBER: "gen_ai.agent.step.number";
|
|
20
|
+
readonly AGENT_TOOL_NAME: "gen_ai.agent.tool.name";
|
|
21
|
+
readonly AGENT_STEP_CONTENT: "gen_ai.agent.step.content";
|
|
22
|
+
readonly GUARD_MODE: "gen_ai.toad_eye.guard.mode";
|
|
23
|
+
readonly GUARD_PASSED: "gen_ai.toad_eye.guard.passed";
|
|
24
|
+
readonly GUARD_FAILURE_REASON: "gen_ai.toad_eye.guard.failure_reason";
|
|
25
|
+
readonly GUARD_RULE_NAME: "gen_ai.toad_eye.guard.rule_name";
|
|
18
26
|
readonly PROMPT: "gen_ai.toad_eye.prompt";
|
|
19
27
|
readonly COMPLETION: "gen_ai.toad_eye.completion";
|
|
20
28
|
readonly COST: "gen_ai.toad_eye.cost";
|
|
21
29
|
readonly STATUS: "gen_ai.toad_eye.status";
|
|
22
30
|
readonly SESSION_ID: "session.id";
|
|
31
|
+
readonly TEAM: "toad_eye.team";
|
|
32
|
+
readonly USER_ID: "toad_eye.user_id";
|
|
33
|
+
readonly FEATURE: "toad_eye.feature";
|
|
34
|
+
readonly ENVIRONMENT: "toad_eye.environment";
|
|
23
35
|
};
|
|
24
36
|
/** @deprecated Use GEN_AI_ATTRS instead. Kept for backward compatibility. */
|
|
25
37
|
export declare const LLM_ATTRS: {
|
package/dist/types/attributes.js
CHANGED
|
@@ -16,12 +16,27 @@ export const GEN_AI_ATTRS = {
|
|
|
16
16
|
OPERATION: "gen_ai.operation.name",
|
|
17
17
|
FINISH_REASONS: "gen_ai.response.finish_reasons",
|
|
18
18
|
ERROR: "error.type",
|
|
19
|
+
// Agent step attributes
|
|
20
|
+
AGENT_STEP_TYPE: "gen_ai.agent.step.type",
|
|
21
|
+
AGENT_STEP_NUMBER: "gen_ai.agent.step.number",
|
|
22
|
+
AGENT_TOOL_NAME: "gen_ai.agent.tool.name",
|
|
23
|
+
AGENT_STEP_CONTENT: "gen_ai.agent.step.content",
|
|
24
|
+
// Guard (shadow mode) attributes
|
|
25
|
+
GUARD_MODE: "gen_ai.toad_eye.guard.mode",
|
|
26
|
+
GUARD_PASSED: "gen_ai.toad_eye.guard.passed",
|
|
27
|
+
GUARD_FAILURE_REASON: "gen_ai.toad_eye.guard.failure_reason",
|
|
28
|
+
GUARD_RULE_NAME: "gen_ai.toad_eye.guard.rule_name",
|
|
19
29
|
// toad-eye extensions
|
|
20
30
|
PROMPT: "gen_ai.toad_eye.prompt",
|
|
21
31
|
COMPLETION: "gen_ai.toad_eye.completion",
|
|
22
32
|
COST: "gen_ai.toad_eye.cost",
|
|
23
33
|
STATUS: "gen_ai.toad_eye.status",
|
|
24
34
|
SESSION_ID: "session.id",
|
|
35
|
+
// FinOps attribution
|
|
36
|
+
TEAM: "toad_eye.team",
|
|
37
|
+
USER_ID: "toad_eye.user_id",
|
|
38
|
+
FEATURE: "toad_eye.feature",
|
|
39
|
+
ENVIRONMENT: "toad_eye.environment",
|
|
25
40
|
};
|
|
26
41
|
/** @deprecated Use GEN_AI_ATTRS instead. Kept for backward compatibility. */
|
|
27
42
|
export const LLM_ATTRS = {
|
package/dist/types/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LLMProvider } from "./providers.js";
|
|
2
|
+
import type { BudgetConfig, BudgetExceededMode, DowngradeCallback } from "../budget/types.js";
|
|
2
3
|
/**
|
|
3
4
|
* Configuration for initObservability().
|
|
4
5
|
* This is what the user passes when connecting toad-eye to their service.
|
|
@@ -6,13 +7,26 @@ import type { LLMProvider } from "./providers.js";
|
|
|
6
7
|
export interface ToadEyeConfig {
|
|
7
8
|
readonly serviceName: string;
|
|
8
9
|
readonly endpoint?: string | undefined;
|
|
10
|
+
/** API key for toad-eye cloud. When set, transport switches to HTTPS cloud endpoint automatically. */
|
|
11
|
+
readonly apiKey?: string | undefined;
|
|
12
|
+
/** Custom cloud endpoint override. Defaults to https://cloud.toad-eye.dev. */
|
|
13
|
+
readonly cloudEndpoint?: string | undefined;
|
|
14
|
+
readonly attributes?: Readonly<Record<string, string>> | undefined;
|
|
9
15
|
/** Set to false to disable recording prompt/completion text in spans. */
|
|
10
16
|
readonly recordContent?: boolean | undefined;
|
|
11
17
|
/** Record SHA-256 hash of content instead of plain text. Allows prompt comparison without reading. */
|
|
12
18
|
readonly hashContent?: boolean | undefined;
|
|
19
|
+
/** Salt for hashContent — prepended before hashing to prevent rainbow table attacks on short strings. */
|
|
20
|
+
readonly salt?: string | undefined;
|
|
13
21
|
/** Regex patterns to redact from prompt/completion text before recording. */
|
|
14
22
|
readonly redactPatterns?: readonly RegExp[] | undefined;
|
|
15
23
|
readonly instrument?: readonly LLMProvider[] | undefined;
|
|
24
|
+
/** Budget limits — daily, per-user, per-model spend caps in USD. */
|
|
25
|
+
readonly budgets?: BudgetConfig | undefined;
|
|
26
|
+
/** What to do when budget is exceeded: 'warn' (log), 'block' (throw), 'downgrade' (callback). */
|
|
27
|
+
readonly onBudgetExceeded?: BudgetExceededMode | undefined;
|
|
28
|
+
/** Callback for 'downgrade' mode — receives original provider/model, returns replacement. */
|
|
29
|
+
readonly downgradeCallback?: DowngradeCallback | undefined;
|
|
16
30
|
/** Static session ID — all spans will carry this value as `session.id`. */
|
|
17
31
|
readonly sessionId?: string | undefined;
|
|
18
32
|
/** Dynamic session ID extractor — called per traceLLMCall to resolve session ID. */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -14,4 +14,4 @@ export type { ToadEyeConfig } from "./config.js";
|
|
|
14
14
|
export { GEN_AI_ATTRS, LLM_ATTRS } from "./attributes.js";
|
|
15
15
|
export { GEN_AI_METRICS, LLM_METRICS } from "./metrics.js";
|
|
16
16
|
export type { MetricName } from "./metrics.js";
|
|
17
|
-
export type { SpanStatus, LLMSpanAttributes } from "./spans.js";
|
|
17
|
+
export type { SpanStatus, LLMSpanAttributes, AgentStepType, AgentStepInput, GuardMode, GuardResult, } from "./spans.js";
|
package/dist/types/metrics.d.ts
CHANGED
|
@@ -12,6 +12,14 @@ export declare const GEN_AI_METRICS: {
|
|
|
12
12
|
readonly REQUEST_COST: "gen_ai.client.request.cost";
|
|
13
13
|
readonly REQUESTS: "gen_ai.client.requests";
|
|
14
14
|
readonly ERRORS: "gen_ai.client.errors";
|
|
15
|
+
readonly AGENT_STEPS_PER_QUERY: "gen_ai.agent.steps_per_query";
|
|
16
|
+
readonly AGENT_TOOL_USAGE: "gen_ai.agent.tool_usage";
|
|
17
|
+
readonly GUARD_EVALUATIONS: "gen_ai.toad_eye.guard.evaluations";
|
|
18
|
+
readonly GUARD_WOULD_BLOCK: "gen_ai.toad_eye.guard.would_block";
|
|
19
|
+
readonly SEMANTIC_DRIFT: "gen_ai.toad_eye.semantic_drift";
|
|
20
|
+
readonly BUDGET_EXCEEDED: "gen_ai.toad_eye.budget.exceeded";
|
|
21
|
+
readonly BUDGET_BLOCKED: "gen_ai.toad_eye.budget.blocked";
|
|
22
|
+
readonly BUDGET_DOWNGRADED: "gen_ai.toad_eye.budget.downgraded";
|
|
15
23
|
};
|
|
16
24
|
/** @deprecated Use GEN_AI_METRICS instead. Kept for backward compatibility. */
|
|
17
25
|
export declare const LLM_METRICS: {
|
package/dist/types/metrics.js
CHANGED
|
@@ -12,6 +12,18 @@ export const GEN_AI_METRICS = {
|
|
|
12
12
|
REQUEST_COST: "gen_ai.client.request.cost",
|
|
13
13
|
REQUESTS: "gen_ai.client.requests",
|
|
14
14
|
ERRORS: "gen_ai.client.errors",
|
|
15
|
+
// Agent metrics
|
|
16
|
+
AGENT_STEPS_PER_QUERY: "gen_ai.agent.steps_per_query",
|
|
17
|
+
AGENT_TOOL_USAGE: "gen_ai.agent.tool_usage",
|
|
18
|
+
// Guard metrics
|
|
19
|
+
GUARD_EVALUATIONS: "gen_ai.toad_eye.guard.evaluations",
|
|
20
|
+
GUARD_WOULD_BLOCK: "gen_ai.toad_eye.guard.would_block",
|
|
21
|
+
// Drift metrics
|
|
22
|
+
SEMANTIC_DRIFT: "gen_ai.toad_eye.semantic_drift",
|
|
23
|
+
// Budget metrics
|
|
24
|
+
BUDGET_EXCEEDED: "gen_ai.toad_eye.budget.exceeded",
|
|
25
|
+
BUDGET_BLOCKED: "gen_ai.toad_eye.budget.blocked",
|
|
26
|
+
BUDGET_DOWNGRADED: "gen_ai.toad_eye.budget.downgraded",
|
|
15
27
|
};
|
|
16
28
|
/** @deprecated Use GEN_AI_METRICS instead. Kept for backward compatibility. */
|
|
17
29
|
export const LLM_METRICS = {
|
package/dist/types/spans.d.ts
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import type { LLMProvider } from "./providers.js";
|
|
2
2
|
/** Span status values used in trace attributes. */
|
|
3
3
|
export type SpanStatus = "success" | "error";
|
|
4
|
+
/** ReAct agent step types: think → act → observe → answer */
|
|
5
|
+
export type AgentStepType = "think" | "act" | "observe" | "answer";
|
|
6
|
+
/** Input for traceAgentStep — describes a single agent step */
|
|
7
|
+
export interface AgentStepInput {
|
|
8
|
+
readonly type: AgentStepType;
|
|
9
|
+
readonly stepNumber: number;
|
|
10
|
+
readonly content?: string | undefined;
|
|
11
|
+
readonly toolName?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Guard execution mode */
|
|
14
|
+
export type GuardMode = "shadow" | "enforce";
|
|
15
|
+
/**
|
|
16
|
+
* Result of a toad-guard validation — the contract between toad-guard and toad-eye.
|
|
17
|
+
* toad-guard produces this, toad-eye consumes it via recordGuardResult().
|
|
18
|
+
*/
|
|
19
|
+
export interface GuardResult {
|
|
20
|
+
readonly mode: GuardMode;
|
|
21
|
+
readonly passed: boolean;
|
|
22
|
+
readonly ruleName: string;
|
|
23
|
+
readonly failureReason?: string | undefined;
|
|
24
|
+
}
|
|
4
25
|
/**
|
|
5
26
|
* Attributes attached to every LLM span (trace).
|
|
6
27
|
* These become searchable fields in Jaeger and filterable dimensions in Grafana.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toad-eye",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Auto-instrumentation, cost tracking, and observability for LLM services",
|
|
3
|
+
"version": "2.1.1",
|
|
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",
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc -p tsconfig.build.json",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
18
20
|
"prepublishOnly": "npm run build"
|
|
19
21
|
},
|
|
20
22
|
"repository": {
|
|
@@ -36,7 +38,10 @@
|
|
|
36
38
|
"gemini",
|
|
37
39
|
"cost-tracking",
|
|
38
40
|
"auto-instrumentation",
|
|
39
|
-
"gen-ai"
|
|
41
|
+
"gen-ai",
|
|
42
|
+
"budget-guards",
|
|
43
|
+
"finops",
|
|
44
|
+
"streaming"
|
|
40
45
|
],
|
|
41
46
|
"license": "ISC",
|
|
42
47
|
"dependencies": {
|
|
@@ -47,10 +52,28 @@
|
|
|
47
52
|
"@opentelemetry/sdk-metrics": "^2.6.0",
|
|
48
53
|
"@opentelemetry/sdk-node": "^0.213.0",
|
|
49
54
|
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
50
|
-
"nodemailer": "^
|
|
55
|
+
"nodemailer": "^8.0.3",
|
|
51
56
|
"yaml": "^2.7.0"
|
|
52
57
|
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@anthropic-ai/sdk": ">=0.30.0",
|
|
60
|
+
"@google/generative-ai": ">=0.20.0",
|
|
61
|
+
"openai": ">=4.0.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"openai": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@anthropic-ai/sdk": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@google/generative-ai": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
53
74
|
"devDependencies": {
|
|
54
|
-
"@types/nodemailer": "^7.0.11"
|
|
75
|
+
"@types/nodemailer": "^7.0.11",
|
|
76
|
+
"playwright": "^1.58.2",
|
|
77
|
+
"vitest": "^4.1.0"
|
|
55
78
|
}
|
|
56
79
|
}
|