pi-langfuse 1.0.0 → 1.2.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/.trae/documents/optimize_langfuse_reporting.md +68 -0
- package/.trae/documents/pi-langfuse-refactor.md +78 -0
- package/AGENTS.md +33 -42
- package/README.md +270 -59
- package/README_CN.md +273 -61
- package/image.png +0 -0
- package/index.ts +109 -491
- package/package.json +18 -3
- package/src/config.ts +98 -0
- package/src/constants.ts +15 -0
- package/src/handlers/agent.ts +136 -0
- package/src/handlers/generation.ts +239 -0
- package/src/handlers/tool.ts +126 -0
- package/src/handlers/turn.ts +53 -0
- package/src/langfuse.ts +75 -0
- package/src/state.ts +38 -0
- package/src/types.ts +94 -0
- package/src/utils.ts +273 -0
- package/tsconfig.json +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { state } from "../state.js";
|
|
2
|
+
import { getRuntime } from "../langfuse.js";
|
|
3
|
+
import { shapePayload } from "../utils.js";
|
|
4
|
+
|
|
5
|
+
export async function startTurnObservation(event: Record<string, unknown>) {
|
|
6
|
+
if (!state.agentState?.root) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// If a turn is already active, close it (fallback safety)
|
|
11
|
+
if (state.agentState.activeTurn) {
|
|
12
|
+
state.agentState.activeTurn.end();
|
|
13
|
+
state.agentState.activeTurn = undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const turnIndex = event.turnIndex ?? state.turnCount;
|
|
18
|
+
const observation = state.agentState.root.startObservation
|
|
19
|
+
? state.agentState.root.startObservation(
|
|
20
|
+
"turn",
|
|
21
|
+
{
|
|
22
|
+
input: shapePayload(event.context ?? event),
|
|
23
|
+
metadata: { turnIndex },
|
|
24
|
+
},
|
|
25
|
+
{ asType: "span" },
|
|
26
|
+
)
|
|
27
|
+
: (await getRuntime()).startObservation(
|
|
28
|
+
"turn",
|
|
29
|
+
{
|
|
30
|
+
input: shapePayload(event.context ?? event),
|
|
31
|
+
metadata: { turnIndex },
|
|
32
|
+
},
|
|
33
|
+
{ asType: "span" },
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
state.agentState.activeTurn = observation;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.warn("📊 Langfuse: Failed to start turn observation", e);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function finishTurnObservation(event?: Record<string, unknown>) {
|
|
43
|
+
if (!state.agentState?.activeTurn) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
state.agentState.activeTurn.end();
|
|
49
|
+
state.agentState.activeTurn = undefined;
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.warn("📊 Langfuse: Failed to finish turn observation", e);
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/langfuse.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { LangfuseRuntime, LangfuseScoreClient } from "./types.js";
|
|
2
|
+
import { state } from "./state.js";
|
|
3
|
+
|
|
4
|
+
let runtime: LangfuseRuntime | null = null;
|
|
5
|
+
|
|
6
|
+
export async function getRuntime(): Promise<LangfuseRuntime> {
|
|
7
|
+
if (!state.config) {
|
|
8
|
+
throw new Error("Langfuse config is not set");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (!runtime) {
|
|
12
|
+
const [{ NodeSDK }, { LangfuseSpanProcessor }, tracing, { LangfuseClient }] = await Promise.all([
|
|
13
|
+
import("@opentelemetry/sdk-node"),
|
|
14
|
+
import("@langfuse/otel"),
|
|
15
|
+
import("@langfuse/tracing"),
|
|
16
|
+
import("@langfuse/client"),
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const spanProcessor = new LangfuseSpanProcessor({
|
|
20
|
+
publicKey: state.config.publicKey,
|
|
21
|
+
secretKey: state.config.secretKey,
|
|
22
|
+
baseUrl: state.config.host,
|
|
23
|
+
});
|
|
24
|
+
const sdk = new NodeSDK({ spanProcessors: [spanProcessor] });
|
|
25
|
+
sdk.start();
|
|
26
|
+
|
|
27
|
+
runtime = {
|
|
28
|
+
startObservation: tracing.startObservation as unknown as LangfuseRuntime["startObservation"],
|
|
29
|
+
propagateAttributes: tracing.propagateAttributes as unknown as LangfuseRuntime["propagateAttributes"],
|
|
30
|
+
scoreClient: new LangfuseClient({
|
|
31
|
+
publicKey: state.config.publicKey,
|
|
32
|
+
secretKey: state.config.secretKey,
|
|
33
|
+
baseUrl: state.config.host,
|
|
34
|
+
}) as LangfuseScoreClient,
|
|
35
|
+
spanProcessor,
|
|
36
|
+
sdk,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return runtime as LangfuseRuntime;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function shutdownRuntime(): Promise<void> {
|
|
44
|
+
if (!runtime) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await runtime.scoreClient.flush?.();
|
|
50
|
+
await runtime.scoreClient.shutdown?.();
|
|
51
|
+
await runtime.spanProcessor?.forceFlush?.();
|
|
52
|
+
await runtime.spanProcessor?.shutdown?.();
|
|
53
|
+
await runtime.sdk?.shutdown?.();
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.warn("📊 Langfuse: Failed to flush/shutdown cleanly", e);
|
|
56
|
+
} finally {
|
|
57
|
+
runtime = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function sendScore(name: string, value: number, options: { traceId?: string; observationId?: string } = {}) {
|
|
62
|
+
try {
|
|
63
|
+
const rt = await getRuntime();
|
|
64
|
+
rt.scoreClient.score?.create({
|
|
65
|
+
name,
|
|
66
|
+
value,
|
|
67
|
+
dataType: name === "session_had_errors" || name === "tool_is_error" ? "BOOLEAN" : "NUMERIC",
|
|
68
|
+
traceId: options.traceId,
|
|
69
|
+
observationId: options.observationId,
|
|
70
|
+
sessionId: options.traceId ? undefined : state.currentSessionId || undefined,
|
|
71
|
+
});
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.warn(`📊 Langfuse: Failed to send score ${name}`, e);
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/state.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Config, AgentState } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export const state = {
|
|
4
|
+
config: null as Config | null,
|
|
5
|
+
setupAttemptedThisSession: false,
|
|
6
|
+
|
|
7
|
+
currentSessionId: "",
|
|
8
|
+
currentModel: "",
|
|
9
|
+
currentProvider: "",
|
|
10
|
+
agentState: null as AgentState | null,
|
|
11
|
+
|
|
12
|
+
// Evaluation tracking state
|
|
13
|
+
toolCallCount: 0,
|
|
14
|
+
errorCount: 0,
|
|
15
|
+
turnCount: 0,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function resetRunState() {
|
|
19
|
+
state.agentState = null;
|
|
20
|
+
state.toolCallCount = 0;
|
|
21
|
+
state.errorCount = 0;
|
|
22
|
+
state.turnCount = 0;
|
|
23
|
+
state.currentModel = "";
|
|
24
|
+
state.currentProvider = "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function computeEvaluationScores() {
|
|
28
|
+
const toolSuccessRate = state.toolCallCount > 0 ? (state.toolCallCount - state.errorCount) / state.toolCallCount : 1;
|
|
29
|
+
const sessionHadErrors = state.errorCount > 0;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
tool_call_count: state.toolCallCount,
|
|
33
|
+
turn_count: state.turnCount,
|
|
34
|
+
total_tool_errors: state.errorCount,
|
|
35
|
+
tool_success_rate: toolSuccessRate,
|
|
36
|
+
session_had_errors: sessionHadErrors ? 1 : 0,
|
|
37
|
+
};
|
|
38
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
publicKey: string;
|
|
3
|
+
secretKey: string;
|
|
4
|
+
host: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface LangfuseObservation {
|
|
8
|
+
id?: string;
|
|
9
|
+
traceId?: string;
|
|
10
|
+
update(body?: ObservationUpdate): LangfuseObservation;
|
|
11
|
+
end(body?: ObservationUpdate): void;
|
|
12
|
+
startObservation?(
|
|
13
|
+
name: string,
|
|
14
|
+
body?: ObservationUpdate,
|
|
15
|
+
options?: { asType?: "agent" | "generation" | "tool" | "span" },
|
|
16
|
+
): LangfuseObservation;
|
|
17
|
+
setTraceIO?(body?: { input?: unknown; output?: unknown }): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ObservationUpdate {
|
|
21
|
+
input?: unknown;
|
|
22
|
+
output?: unknown;
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
model?: string;
|
|
25
|
+
usageDetails?: Record<string, number>;
|
|
26
|
+
usage?: Record<string, number>;
|
|
27
|
+
costDetails?: Record<string, number>;
|
|
28
|
+
level?: "DEBUG" | "DEFAULT" | "WARNING" | "ERROR";
|
|
29
|
+
statusMessage?: string;
|
|
30
|
+
completionStartTime?: Date;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface LangfuseScoreClient {
|
|
34
|
+
score?: {
|
|
35
|
+
create(body: {
|
|
36
|
+
traceId?: string;
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
observationId?: string;
|
|
39
|
+
name: string;
|
|
40
|
+
value: number;
|
|
41
|
+
dataType?: "NUMERIC" | "BOOLEAN";
|
|
42
|
+
}): unknown;
|
|
43
|
+
};
|
|
44
|
+
flush?: () => Promise<void>;
|
|
45
|
+
shutdown?: () => Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface LangfuseRuntime {
|
|
49
|
+
startObservation: (
|
|
50
|
+
name: string,
|
|
51
|
+
body?: ObservationUpdate,
|
|
52
|
+
options?: { asType?: "agent" | "generation" | "tool" | "span" },
|
|
53
|
+
) => LangfuseObservation;
|
|
54
|
+
propagateAttributes: (
|
|
55
|
+
params: {
|
|
56
|
+
sessionId?: string;
|
|
57
|
+
traceName?: string;
|
|
58
|
+
metadata?: Record<string, string>;
|
|
59
|
+
tags?: string[];
|
|
60
|
+
},
|
|
61
|
+
fn: () => LangfuseObservation,
|
|
62
|
+
) => LangfuseObservation;
|
|
63
|
+
scoreClient: LangfuseScoreClient;
|
|
64
|
+
spanProcessor?: { forceFlush?: () => Promise<void>; shutdown?: () => Promise<void> };
|
|
65
|
+
sdk?: { start?: () => void; shutdown?: () => Promise<void> };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface GenerationState {
|
|
69
|
+
observation: LangfuseObservation;
|
|
70
|
+
requestKey: string;
|
|
71
|
+
ended: boolean;
|
|
72
|
+
metadata: Record<string, unknown>;
|
|
73
|
+
ttftRecorded?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ToolState {
|
|
77
|
+
observation: LangfuseObservation;
|
|
78
|
+
toolName: string;
|
|
79
|
+
ended: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface AgentState {
|
|
83
|
+
root?: LangfuseObservation;
|
|
84
|
+
activeTurn?: LangfuseObservation;
|
|
85
|
+
traceId?: string;
|
|
86
|
+
promptInput?: unknown;
|
|
87
|
+
cwd?: string;
|
|
88
|
+
generationSeq: number;
|
|
89
|
+
activeGenerations: Map<string, GenerationState>;
|
|
90
|
+
generationOrder: string[];
|
|
91
|
+
activeTools: Map<string, ToolState>;
|
|
92
|
+
latestAssistantOutput?: unknown;
|
|
93
|
+
providerMetadataByRequest: Map<string, Record<string, unknown>>;
|
|
94
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_ARRAY_ITEMS,
|
|
3
|
+
MAX_DEPTH,
|
|
4
|
+
MAX_OBJECT_KEYS,
|
|
5
|
+
MAX_STRING_LENGTH,
|
|
6
|
+
MAX_TOOL_PAYLOAD_LENGTH,
|
|
7
|
+
} from "./constants.js";
|
|
8
|
+
|
|
9
|
+
export function truncate(value: string, maxLength = MAX_STRING_LENGTH): string {
|
|
10
|
+
return value.length > maxLength ? `${value.slice(0, maxLength)}... [truncated]` : value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function tryParseJson(value: string): unknown {
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
if (!trimmed || !["{", "["].includes(trimmed[0])) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(trimmed);
|
|
21
|
+
} catch {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function shapePayload(value: unknown, options: { maxString?: number; depth?: number } = {}): unknown {
|
|
27
|
+
const maxString = options.maxString ?? MAX_STRING_LENGTH;
|
|
28
|
+
const depth = options.depth ?? MAX_DEPTH;
|
|
29
|
+
|
|
30
|
+
function visit(item: unknown, remainingDepth: number, seen: WeakSet<object>): unknown {
|
|
31
|
+
if (typeof item === "string") {
|
|
32
|
+
const truncated = truncate(item, maxString);
|
|
33
|
+
const parsed = tryParseJson(truncated);
|
|
34
|
+
if (parsed === truncated) {
|
|
35
|
+
return truncated;
|
|
36
|
+
}
|
|
37
|
+
return visit(parsed, remainingDepth - 1, seen);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
item === null ||
|
|
42
|
+
typeof item === "undefined" ||
|
|
43
|
+
typeof item === "number" ||
|
|
44
|
+
typeof item === "boolean"
|
|
45
|
+
) {
|
|
46
|
+
return item;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof item === "bigint") {
|
|
50
|
+
return item.toString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof item === "function" || typeof item === "symbol") {
|
|
54
|
+
return `[${typeof item}]`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (remainingDepth <= 0) {
|
|
58
|
+
return `[max depth ${depth} reached]`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(item)) {
|
|
62
|
+
return item.slice(0, MAX_ARRAY_ITEMS).map((entry) => visit(entry, remainingDepth - 1, seen));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (item instanceof Error) {
|
|
66
|
+
return {
|
|
67
|
+
name: item.name,
|
|
68
|
+
message: item.message,
|
|
69
|
+
stack: item.stack ? truncate(item.stack, maxString) : undefined,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof item === "object") {
|
|
74
|
+
if (seen.has(item)) {
|
|
75
|
+
return "[circular]";
|
|
76
|
+
}
|
|
77
|
+
seen.add(item);
|
|
78
|
+
|
|
79
|
+
const output: Record<string, unknown> = {};
|
|
80
|
+
for (const [key, entry] of Object.entries(item as Record<string, unknown>).slice(0, MAX_OBJECT_KEYS)) {
|
|
81
|
+
output[key] = visit(entry, remainingDepth - 1, seen);
|
|
82
|
+
}
|
|
83
|
+
return output;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return String(item);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return visit(value, depth, new WeakSet<object>());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function safeSerialize(value: unknown, maxLength = MAX_TOOL_PAYLOAD_LENGTH): string {
|
|
93
|
+
try {
|
|
94
|
+
return truncate(JSON.stringify(shapePayload(value, { maxString: maxLength }), null, 2), maxLength);
|
|
95
|
+
} catch {
|
|
96
|
+
return `[unserializable ${typeof value}]`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function extractTextContent(content: unknown, maxLength?: number): string | undefined {
|
|
101
|
+
if (typeof content === "string") {
|
|
102
|
+
return maxLength ? truncate(content, maxLength) : content;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!Array.isArray(content)) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const text = content
|
|
110
|
+
.map((item) => {
|
|
111
|
+
if (!item || typeof item !== "object") return "";
|
|
112
|
+
const block = item as { type?: string; text?: string; thinking?: string };
|
|
113
|
+
return block.type === "text" && block.text ? block.text : "";
|
|
114
|
+
})
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join("\n");
|
|
117
|
+
|
|
118
|
+
if (!text) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return maxLength ? truncate(text, maxLength) : text;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function extractToolCalls(message: Record<string, unknown>): unknown | undefined {
|
|
126
|
+
return (
|
|
127
|
+
message.toolCalls ??
|
|
128
|
+
message.tool_calls ??
|
|
129
|
+
message.function_calls ??
|
|
130
|
+
(message.content && Array.isArray(message.content)
|
|
131
|
+
? message.content.filter((block) => {
|
|
132
|
+
return block && typeof block === "object" && ["tool_use", "tool_call"].includes(String((block as { type?: string }).type));
|
|
133
|
+
})
|
|
134
|
+
: undefined)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function extractAssistantOutput(message: unknown): unknown | undefined {
|
|
139
|
+
if (!message || typeof message !== "object") {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const msg = message as Record<string, unknown>;
|
|
144
|
+
const text = extractTextContent(msg.content);
|
|
145
|
+
if (text) {
|
|
146
|
+
return text;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const toolCalls = extractToolCalls(msg);
|
|
150
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
151
|
+
return { toolCalls: shapePayload(toolCalls) };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (toolCalls) {
|
|
155
|
+
return { toolCalls: shapePayload(toolCalls) };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return shapePayload(msg);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function extractFinalAssistant(messages: unknown): Record<string, unknown> | undefined {
|
|
162
|
+
if (!Array.isArray(messages)) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
return messages.filter((message) => message?.role === "assistant").pop() as Record<string, unknown> | undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getRequestKey(event: Record<string, unknown>, fallback: string): string {
|
|
169
|
+
return String(
|
|
170
|
+
event.requestId ??
|
|
171
|
+
event.providerRequestId ??
|
|
172
|
+
event.messageId ??
|
|
173
|
+
event.turnId ??
|
|
174
|
+
event.turnIndex ??
|
|
175
|
+
event.id ??
|
|
176
|
+
fallback,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function getToolCallId(event: Record<string, unknown>): string | undefined {
|
|
181
|
+
const id = event.toolCallId ?? event.id ?? event.callId ?? event.tool_use_id ?? event.toolUseId;
|
|
182
|
+
return id === undefined || id === null ? undefined : String(id);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function getToolName(event: Record<string, unknown>): string {
|
|
186
|
+
return String(
|
|
187
|
+
event.toolName ??
|
|
188
|
+
event.name ??
|
|
189
|
+
event.tool ??
|
|
190
|
+
event.functionName ??
|
|
191
|
+
(event.call && typeof event.call === "object" ? (event.call as Record<string, unknown>).name : undefined) ??
|
|
192
|
+
"tool",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function getToolInput(event: Record<string, unknown>): unknown {
|
|
197
|
+
return (
|
|
198
|
+
event.input ??
|
|
199
|
+
event.args ??
|
|
200
|
+
event.arguments ??
|
|
201
|
+
event.params ??
|
|
202
|
+
(event.call && typeof event.call === "object" ? (event.call as Record<string, unknown>).input : undefined) ??
|
|
203
|
+
event
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function getProviderPayload(event: Record<string, unknown>): unknown {
|
|
208
|
+
return event.request ?? event.payload ?? event.body ?? event.providerPayload ?? event.messages ?? event;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function getMessageFromEvent(event: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
212
|
+
if (event.message && typeof event.message === "object") {
|
|
213
|
+
return event.message as Record<string, unknown>;
|
|
214
|
+
}
|
|
215
|
+
if (event.role || event.content) {
|
|
216
|
+
return event;
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function extractUsage(messageOrEvent: Record<string, unknown>): Record<string, number> | undefined {
|
|
222
|
+
const usage = (messageOrEvent.usage ??
|
|
223
|
+
(messageOrEvent.message && typeof messageOrEvent.message === "object"
|
|
224
|
+
? (messageOrEvent.message as Record<string, unknown>).usage
|
|
225
|
+
: undefined)) as Record<string, unknown> | undefined;
|
|
226
|
+
if (!usage || typeof usage !== "object") {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const input = Number(usage.input ?? usage.inputTokens ?? usage.prompt_tokens ?? usage.promptTokens ?? 0);
|
|
231
|
+
const output = Number(usage.output ?? usage.outputTokens ?? usage.completion_tokens ?? usage.completionTokens ?? 0);
|
|
232
|
+
const total = Number(usage.total ?? usage.totalTokens ?? usage.total_tokens ?? input + output);
|
|
233
|
+
const cacheRead = Number(usage.cacheRead ?? usage.cache_read ?? usage.cachedTokens ?? 0);
|
|
234
|
+
const cacheWrite = Number(usage.cacheWrite ?? usage.cache_write ?? 0);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
input,
|
|
238
|
+
output,
|
|
239
|
+
total,
|
|
240
|
+
...(cacheRead ? { cacheRead } : {}),
|
|
241
|
+
...(cacheWrite ? { cacheWrite } : {}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function extractCostDetails(messageOrEvent: Record<string, unknown>): Record<string, number> | undefined {
|
|
246
|
+
const usage = (messageOrEvent.usage ??
|
|
247
|
+
(messageOrEvent.message && typeof messageOrEvent.message === "object"
|
|
248
|
+
? (messageOrEvent.message as Record<string, unknown>).usage
|
|
249
|
+
: undefined)) as Record<string, unknown> | undefined;
|
|
250
|
+
const cost = (messageOrEvent.cost ?? usage?.cost ?? messageOrEvent.costDetails) as Record<string, unknown> | undefined;
|
|
251
|
+
if (!cost || typeof cost !== "object") {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const input = Number(cost.input ?? cost.inputCost ?? 0);
|
|
256
|
+
const output = Number(cost.output ?? cost.outputCost ?? 0);
|
|
257
|
+
const total = Number(cost.total ?? cost.totalCost ?? input + output);
|
|
258
|
+
|
|
259
|
+
return { input, output, total };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function extractResponseMetadata(event: Record<string, unknown>): Record<string, unknown> {
|
|
263
|
+
return shapePayload(
|
|
264
|
+
{
|
|
265
|
+
status: event.status ?? event.statusCode ?? event.httpStatus,
|
|
266
|
+
headers: event.headers,
|
|
267
|
+
responseHeaders: event.responseHeaders,
|
|
268
|
+
providerMetadata: event.providerMetadata ?? event.metadata,
|
|
269
|
+
requestId: event.requestId ?? event.providerRequestId,
|
|
270
|
+
},
|
|
271
|
+
{ depth: 4, maxString: 4_000 },
|
|
272
|
+
) as Record<string, unknown>;
|
|
273
|
+
}
|