pi-langfuse 1.4.1 → 1.4.3
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/index.ts +56 -43
- package/package.json +11 -1
- package/src/constants.ts +1 -0
- package/src/handlers/agent.ts +2 -0
- package/src/handlers/generation.ts +6 -6
- package/src/handlers/tool.ts +3 -3
- package/src/handlers/turn.ts +2 -2
- package/src/langfuse.ts +20 -3
- package/src/state.ts +135 -23
- package/src/utils.ts +35 -4
- package/types/langfuse-runtime-shims.d.ts +49 -0
- package/.agents/skills/langfuse/SKILL.md +0 -140
- package/.agents/skills/langfuse/references/cli.md +0 -51
- package/.agents/skills/langfuse/references/error-analysis.md +0 -100
- package/.agents/skills/langfuse/references/instrumentation.md +0 -140
- package/.agents/skills/langfuse/references/prompt-migration.md +0 -234
- package/.agents/skills/langfuse/references/sdk-upgrade.md +0 -181
- package/.agents/skills/langfuse/references/skill-feedback.md +0 -52
- package/.agents/skills/langfuse/references/user-feedback.md +0 -88
- package/AGENTS.md +0 -48
- package/AGENTS_CN.md +0 -57
package/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { basename } from "node:path";
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
|
|
13
|
-
import { state, resetRunState } from "./src/state.js";
|
|
13
|
+
import { state, resetRunState, runWithSession, setCurrentSession } from "./src/state.js";
|
|
14
14
|
import { ensureConfig, promptForConfig, loadConfig } from "./src/config.js";
|
|
15
15
|
import { shutdownRuntime } from "./src/langfuse.js";
|
|
16
16
|
import { getMessageFromEvent, extractAssistantOutput } from "./src/utils.js";
|
|
@@ -51,72 +51,79 @@ export default async function (pi: ExtensionAPI) {
|
|
|
51
51
|
},
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
const getSessionId = (ctx?: any) => {
|
|
55
|
+
try {
|
|
56
|
+
const sessionFile = ctx?.sessionManager?.getSessionFile?.();
|
|
57
|
+
return sessionFile ? basename(sessionFile, ".jsonl") : undefined;
|
|
58
|
+
} catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const withSession = <T>(ctx: any, fn: () => T): T => runWithSession(getSessionId(ctx) ?? state.currentSessionId, fn);
|
|
64
|
+
|
|
65
|
+
pi.on("session_start", async (_event, ctx) => withSession(ctx, async () => {
|
|
55
66
|
state.setupAttemptedThisSession = false;
|
|
56
67
|
await ensureConfig(ctx);
|
|
57
|
-
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
58
|
-
if (sessionFile) {
|
|
59
|
-
state.currentSessionId = basename(sessionFile, ".jsonl");
|
|
60
|
-
}
|
|
61
68
|
resetRunState();
|
|
62
|
-
});
|
|
69
|
+
}));
|
|
63
70
|
|
|
64
|
-
pi.on("model_select", async (event) => {
|
|
71
|
+
pi.on("model_select", async (event, ctx) => withSession(ctx, async () => {
|
|
65
72
|
state.currentModel = event.model?.id || "";
|
|
66
73
|
state.currentProvider = event.model?.provider || "";
|
|
67
|
-
});
|
|
74
|
+
}));
|
|
68
75
|
|
|
69
|
-
pi.on("before_agent_start", async (event, ctx) => {
|
|
76
|
+
pi.on("before_agent_start", async (event, ctx) => withSession(ctx, async () => {
|
|
70
77
|
await startAgentRun(event, ctx);
|
|
71
|
-
});
|
|
78
|
+
}));
|
|
72
79
|
|
|
73
|
-
pi.on("agent_start", async (event, ctx) => {
|
|
80
|
+
pi.on("agent_start", async (event, ctx) => withSession(ctx, async () => {
|
|
74
81
|
if (!state.agentState?.root) {
|
|
75
82
|
await startAgentRun(event, ctx);
|
|
76
83
|
}
|
|
77
|
-
});
|
|
84
|
+
}));
|
|
78
85
|
|
|
79
|
-
pi.on("turn_start", async (event) => {
|
|
86
|
+
pi.on("turn_start", async (event, ctx) => withSession(ctx, async () => {
|
|
80
87
|
await startTurnObservation(event);
|
|
81
|
-
});
|
|
88
|
+
}));
|
|
82
89
|
|
|
83
|
-
pi.on("before_provider_request", async (event) => {
|
|
90
|
+
pi.on("before_provider_request", async (event, ctx) => withSession(ctx, async () => {
|
|
84
91
|
await startGeneration(event);
|
|
85
|
-
});
|
|
92
|
+
}));
|
|
86
93
|
|
|
87
|
-
pi.on("after_provider_response", async (event) => {
|
|
94
|
+
pi.on("after_provider_response", async (event, ctx) => withSession(ctx, async () => {
|
|
88
95
|
updateGenerationMetadata(event);
|
|
89
|
-
});
|
|
96
|
+
}));
|
|
90
97
|
|
|
91
|
-
pi.on("message_update", async (event) => {
|
|
98
|
+
pi.on("message_update", async (event, ctx) => withSession(ctx, async () => {
|
|
92
99
|
recordTTFT(event);
|
|
93
100
|
const message = getMessageFromEvent(event);
|
|
94
101
|
if (message?.role === "assistant" && state.agentState) {
|
|
95
102
|
state.agentState.latestAssistantOutput = extractAssistantOutput(message);
|
|
96
103
|
}
|
|
97
|
-
});
|
|
104
|
+
}));
|
|
98
105
|
|
|
99
|
-
pi.on("message_end", async (event) => {
|
|
106
|
+
pi.on("message_end", async (event, ctx) => withSession(ctx, async () => {
|
|
100
107
|
await finishGenerationFromMessage(event);
|
|
101
|
-
});
|
|
108
|
+
}));
|
|
102
109
|
|
|
103
|
-
pi.on("tool_execution_start", async (event) => {
|
|
110
|
+
pi.on("tool_execution_start", async (event, ctx) => withSession(ctx, async () => {
|
|
104
111
|
await startToolObservation(event);
|
|
105
|
-
});
|
|
112
|
+
}));
|
|
106
113
|
|
|
107
|
-
pi.on("tool_call", async (event) => {
|
|
114
|
+
pi.on("tool_call", async (event, ctx) => withSession(ctx, async () => {
|
|
108
115
|
await startToolObservation(event);
|
|
109
|
-
});
|
|
116
|
+
}));
|
|
110
117
|
|
|
111
|
-
pi.on("tool_result", async (event) => {
|
|
118
|
+
pi.on("tool_result", async (event, ctx) => withSession(ctx, async () => {
|
|
112
119
|
await finishToolObservation(event);
|
|
113
|
-
});
|
|
120
|
+
}));
|
|
114
121
|
|
|
115
|
-
pi.on("tool_execution_end", async (event) => {
|
|
122
|
+
pi.on("tool_execution_end", async (event, ctx) => withSession(ctx, async () => {
|
|
116
123
|
await finishToolObservation(event);
|
|
117
|
-
});
|
|
124
|
+
}));
|
|
118
125
|
|
|
119
|
-
pi.on("turn_end", async (event) => {
|
|
126
|
+
pi.on("turn_end", async (event, ctx) => withSession(ctx, async () => {
|
|
120
127
|
state.turnCount++;
|
|
121
128
|
const message = getMessageFromEvent(event);
|
|
122
129
|
if (message?.role === "assistant") {
|
|
@@ -124,16 +131,16 @@ export default async function (pi: ExtensionAPI) {
|
|
|
124
131
|
await finishGenerationFromMessage(event);
|
|
125
132
|
}
|
|
126
133
|
finishTurnObservation(event);
|
|
127
|
-
});
|
|
134
|
+
}));
|
|
128
135
|
|
|
129
|
-
pi.on("agent_end", async (event) => {
|
|
136
|
+
pi.on("agent_end", async (event, ctx) => withSession(ctx, async () => {
|
|
130
137
|
await finishAgentRun(event);
|
|
131
138
|
setTimeout(() => {
|
|
132
139
|
shutdownRuntime().catch((error) => {
|
|
133
140
|
console.warn("📊 Langfuse: Deferred shutdown failed", error);
|
|
134
141
|
});
|
|
135
142
|
}, 0);
|
|
136
|
-
});
|
|
143
|
+
}));
|
|
137
144
|
|
|
138
145
|
const handleSessionInterruption = (reason: string) => {
|
|
139
146
|
if (state.agentState?.root) {
|
|
@@ -143,15 +150,21 @@ export default async function (pi: ExtensionAPI) {
|
|
|
143
150
|
resetRunState();
|
|
144
151
|
};
|
|
145
152
|
|
|
146
|
-
pi.on("session_before_switch", async () => {
|
|
147
|
-
|
|
153
|
+
pi.on("session_before_switch", async (_event, ctx) => {
|
|
154
|
+
const sessionId = getSessionId(ctx);
|
|
155
|
+
if (sessionId) {
|
|
156
|
+
setCurrentSession(sessionId);
|
|
157
|
+
}
|
|
148
158
|
});
|
|
149
159
|
|
|
150
|
-
pi.on("session_before_fork", async () => {
|
|
151
|
-
|
|
160
|
+
pi.on("session_before_fork", async (_event, ctx) => {
|
|
161
|
+
const sessionId = getSessionId(ctx);
|
|
162
|
+
if (sessionId) {
|
|
163
|
+
setCurrentSession(sessionId);
|
|
164
|
+
}
|
|
152
165
|
});
|
|
153
166
|
|
|
154
|
-
pi.on("session_compact", async (event) => {
|
|
167
|
+
pi.on("session_compact", async (event, ctx) => withSession(ctx, async () => {
|
|
155
168
|
if (state.agentState?.root) {
|
|
156
169
|
const parent = state.agentState.activeTurn ?? state.agentState.root;
|
|
157
170
|
try {
|
|
@@ -169,10 +182,10 @@ export default async function (pi: ExtensionAPI) {
|
|
|
169
182
|
// ignore
|
|
170
183
|
}
|
|
171
184
|
}
|
|
172
|
-
});
|
|
185
|
+
}));
|
|
173
186
|
|
|
174
|
-
pi.on("session_shutdown", async () => {
|
|
187
|
+
pi.on("session_shutdown", async (_event, ctx) => withSession(ctx, async () => {
|
|
175
188
|
handleSessionInterruption("Session shutdown before agent completed");
|
|
176
189
|
await shutdownRuntime();
|
|
177
|
-
});
|
|
190
|
+
}));
|
|
178
191
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-langfuse",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "Langfuse extension for Pi coding agent",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
"homepage": "https://github.com/gooyoung/pi-langfuse#readme",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "index.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"index.ts",
|
|
17
|
+
"src/",
|
|
18
|
+
"types/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"README_CN.md",
|
|
21
|
+
"image.png",
|
|
22
|
+
"skills-lock.json",
|
|
23
|
+
"tsconfig.json"
|
|
24
|
+
],
|
|
15
25
|
"scripts": {
|
|
16
26
|
"typecheck": "tsc --noEmit"
|
|
17
27
|
},
|
package/src/constants.ts
CHANGED
package/src/handlers/agent.ts
CHANGED
|
@@ -19,6 +19,7 @@ export function updateTraceIO(input?: unknown, output?: unknown) {
|
|
|
19
19
|
|
|
20
20
|
export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
|
|
21
21
|
if (!(await ensureConfig(ctx))) {
|
|
22
|
+
state.isTracingDisabled = true;
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -92,6 +93,7 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
|
|
|
92
93
|
updateTraceIO(promptInput, undefined);
|
|
93
94
|
} catch (e) {
|
|
94
95
|
console.warn("📊 Langfuse: Failed to create agent observation", e);
|
|
96
|
+
state.isTracingDisabled = true;
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
99
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import type { GenerationState, ObservationUpdate } from "../types.js";
|
|
14
14
|
|
|
15
15
|
export function getOpenGeneration(): GenerationState | undefined {
|
|
16
|
-
if (!state.agentState) {
|
|
16
|
+
if (state.isTracingDisabled || !state.agentState) {
|
|
17
17
|
return undefined;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -29,7 +29,7 @@ export function getOpenGeneration(): GenerationState | undefined {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export async function startGeneration(event: Record<string, unknown>) {
|
|
32
|
-
if (!state.agentState?.root) {
|
|
32
|
+
if (state.isTracingDisabled || !state.agentState?.root) {
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -79,7 +79,7 @@ export async function startGeneration(event: Record<string, unknown>) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export function updateGenerationMetadata(event: Record<string, unknown>) {
|
|
82
|
-
if (!state.agentState) {
|
|
82
|
+
if (state.isTracingDisabled || !state.agentState) {
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -132,7 +132,7 @@ export function updateGenerationMetadata(event: Record<string, unknown>) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
export function recordTTFT(event: Record<string, unknown>) {
|
|
135
|
-
if (!state.agentState) {
|
|
135
|
+
if (state.isTracingDisabled || !state.agentState) {
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -150,7 +150,7 @@ export function recordTTFT(event: Record<string, unknown>) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
export async function finishGenerationFromMessage(event: Record<string, unknown>) {
|
|
153
|
-
if (!state.agentState) {
|
|
153
|
+
if (state.isTracingDisabled || !state.agentState) {
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -190,7 +190,7 @@ export async function finishGenerationFromMessage(event: Record<string, unknown>
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
export async function createFallbackGenerationFromTurn(event: Record<string, unknown>, message: Record<string, unknown>) {
|
|
193
|
-
if (!state.agentState?.root || state.agentState.generationOrder.length > 0) {
|
|
193
|
+
if (state.isTracingDisabled || !state.agentState?.root || state.agentState.generationOrder.length > 0) {
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
196
|
|
package/src/handlers/tool.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { MAX_TOOL_PAYLOAD_LENGTH } from "../constants.js";
|
|
13
13
|
|
|
14
14
|
export async function startToolObservation(event: Record<string, unknown>) {
|
|
15
|
-
if (!state.agentState?.root) {
|
|
15
|
+
if (state.isTracingDisabled || !state.agentState?.root) {
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -59,7 +59,7 @@ export async function startToolObservation(event: Record<string, unknown>) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export async function finishToolObservation(event: Record<string, unknown>) {
|
|
62
|
-
if (!state.agentState) {
|
|
62
|
+
if (state.isTracingDisabled || !state.agentState) {
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -119,7 +119,7 @@ export async function finishToolObservation(event: Record<string, unknown>) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
export function closeDanglingObservations(statusMessage: string) {
|
|
122
|
-
if (!state.agentState) {
|
|
122
|
+
if (state.isTracingDisabled || !state.agentState) {
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
|
package/src/handlers/turn.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { getRuntime } from "../langfuse.js";
|
|
|
3
3
|
import { shapePayload } from "../utils.js";
|
|
4
4
|
|
|
5
5
|
export async function startTurnObservation(event: Record<string, unknown>) {
|
|
6
|
-
if (!state.agentState?.root) {
|
|
6
|
+
if (state.isTracingDisabled || !state.agentState?.root) {
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -40,7 +40,7 @@ export async function startTurnObservation(event: Record<string, unknown>) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function finishTurnObservation(event?: Record<string, unknown>) {
|
|
43
|
-
if (!state.agentState?.activeTurn) {
|
|
43
|
+
if (state.isTracingDisabled || !state.agentState?.activeTurn) {
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
|
package/src/langfuse.ts
CHANGED
|
@@ -42,7 +42,8 @@ interface RestFallbackStore {
|
|
|
42
42
|
attempted: boolean;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const
|
|
45
|
+
const OTEL_VISIBILITY_TIMEOUT_MS = 1_500;
|
|
46
|
+
const OTEL_VISIBILITY_POLL_INTERVAL_MS = 200;
|
|
46
47
|
|
|
47
48
|
function nowIso() {
|
|
48
49
|
return new Date().toISOString();
|
|
@@ -190,6 +191,23 @@ async function traceExists(rt: LangfuseRuntime, traceId: string): Promise<boolea
|
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
async function waitForTraceVisibility(rt: LangfuseRuntime, traceId: string): Promise<boolean> {
|
|
195
|
+
const deadline = Date.now() + OTEL_VISIBILITY_TIMEOUT_MS;
|
|
196
|
+
|
|
197
|
+
while (true) {
|
|
198
|
+
if (await traceExists(rt, traceId)) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const remainingMs = deadline - Date.now();
|
|
203
|
+
if (remainingMs <= 0) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await delay(Math.min(OTEL_VISIBILITY_POLL_INTERVAL_MS, remainingMs));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
193
211
|
function eventTimestamp(record: { endTime?: string; startTime?: string; timestamp?: string }) {
|
|
194
212
|
return record.endTime ?? record.startTime ?? record.timestamp ?? nowIso();
|
|
195
213
|
}
|
|
@@ -201,8 +219,7 @@ async function fallbackToRestIngestion(rt: LangfuseRuntime) {
|
|
|
201
219
|
}
|
|
202
220
|
store.attempted = true;
|
|
203
221
|
|
|
204
|
-
await
|
|
205
|
-
if (await traceExists(rt, store.trace.id)) {
|
|
222
|
+
if (await waitForTraceVisibility(rt, store.trace.id)) {
|
|
206
223
|
return;
|
|
207
224
|
}
|
|
208
225
|
|
package/src/state.ts
CHANGED
|
@@ -1,38 +1,150 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import type { Config, AgentState } from "./types.js";
|
|
2
3
|
|
|
4
|
+
export interface SessionRunState {
|
|
5
|
+
currentModel: string;
|
|
6
|
+
currentProvider: string;
|
|
7
|
+
agentState: AgentState | null;
|
|
8
|
+
toolCallCount: number;
|
|
9
|
+
errorCount: number;
|
|
10
|
+
turnCount: number;
|
|
11
|
+
tracingDisabled: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_SESSION_ID = "__pi_langfuse_default_session__";
|
|
15
|
+
|
|
16
|
+
let activeSessionId = DEFAULT_SESSION_ID;
|
|
17
|
+
const sessionScope = new AsyncLocalStorage<string>();
|
|
18
|
+
|
|
19
|
+
function createSessionRunState(): SessionRunState {
|
|
20
|
+
return {
|
|
21
|
+
currentModel: "",
|
|
22
|
+
currentProvider: "",
|
|
23
|
+
agentState: null,
|
|
24
|
+
toolCallCount: 0,
|
|
25
|
+
errorCount: 0,
|
|
26
|
+
turnCount: 0,
|
|
27
|
+
tracingDisabled: false,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeSessionId(sessionId?: string) {
|
|
32
|
+
return sessionId || DEFAULT_SESSION_ID;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getActiveSessionId() {
|
|
36
|
+
return sessionScope.getStore() ?? activeSessionId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getSessionRunState(sessionId = getActiveSessionId()): SessionRunState {
|
|
40
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
41
|
+
let sessionState = state.sessionStates.get(normalizedSessionId);
|
|
42
|
+
if (!sessionState) {
|
|
43
|
+
sessionState = createSessionRunState();
|
|
44
|
+
state.sessionStates.set(normalizedSessionId, sessionState);
|
|
45
|
+
}
|
|
46
|
+
return sessionState;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function setCurrentSession(sessionId?: string) {
|
|
50
|
+
activeSessionId = normalizeSessionId(sessionId);
|
|
51
|
+
getSessionRunState(activeSessionId);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function runWithSession<T>(sessionId: string | undefined, fn: () => T): T {
|
|
55
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
56
|
+
setCurrentSession(normalizedSessionId);
|
|
57
|
+
return sessionScope.run(normalizedSessionId, fn);
|
|
58
|
+
}
|
|
59
|
+
|
|
3
60
|
export const state = {
|
|
4
61
|
config: null as Config | null,
|
|
5
62
|
setupAttemptedThisSession: false,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
63
|
+
sessionStates: new Map<string, SessionRunState>(),
|
|
64
|
+
|
|
65
|
+
get currentSessionId() {
|
|
66
|
+
const sessionId = getActiveSessionId();
|
|
67
|
+
return sessionId === DEFAULT_SESSION_ID ? "" : sessionId;
|
|
68
|
+
},
|
|
69
|
+
set currentSessionId(sessionId: string) {
|
|
70
|
+
setCurrentSession(sessionId);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
get currentModel() {
|
|
74
|
+
return getSessionRunState().currentModel;
|
|
75
|
+
},
|
|
76
|
+
set currentModel(model: string) {
|
|
77
|
+
getSessionRunState().currentModel = model;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
get currentProvider() {
|
|
81
|
+
return getSessionRunState().currentProvider;
|
|
82
|
+
},
|
|
83
|
+
set currentProvider(provider: string) {
|
|
84
|
+
getSessionRunState().currentProvider = provider;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
get agentState() {
|
|
88
|
+
return getSessionRunState().agentState;
|
|
89
|
+
},
|
|
90
|
+
set agentState(agentState: AgentState | null) {
|
|
91
|
+
getSessionRunState().agentState = agentState;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
get toolCallCount() {
|
|
95
|
+
return getSessionRunState().toolCallCount;
|
|
96
|
+
},
|
|
97
|
+
set toolCallCount(toolCallCount: number) {
|
|
98
|
+
getSessionRunState().toolCallCount = toolCallCount;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
get errorCount() {
|
|
102
|
+
return getSessionRunState().errorCount;
|
|
103
|
+
},
|
|
104
|
+
set errorCount(errorCount: number) {
|
|
105
|
+
getSessionRunState().errorCount = errorCount;
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
get turnCount() {
|
|
109
|
+
return getSessionRunState().turnCount;
|
|
110
|
+
},
|
|
111
|
+
set turnCount(turnCount: number) {
|
|
112
|
+
getSessionRunState().turnCount = turnCount;
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
get isTracingDisabled() {
|
|
116
|
+
return getSessionRunState().tracingDisabled;
|
|
117
|
+
},
|
|
118
|
+
set isTracingDisabled(disabled: boolean) {
|
|
119
|
+
getSessionRunState().tracingDisabled = disabled;
|
|
120
|
+
},
|
|
16
121
|
};
|
|
17
122
|
|
|
18
|
-
export function resetRunState() {
|
|
19
|
-
state.
|
|
20
|
-
state.toolCallCount = 0;
|
|
21
|
-
state.errorCount = 0;
|
|
22
|
-
state.turnCount = 0;
|
|
23
|
-
state.currentModel = "";
|
|
24
|
-
state.currentProvider = "";
|
|
123
|
+
export function resetRunState(sessionId = getActiveSessionId()) {
|
|
124
|
+
state.sessionStates.set(normalizeSessionId(sessionId), createSessionRunState());
|
|
25
125
|
}
|
|
26
126
|
|
|
27
|
-
export function
|
|
28
|
-
|
|
29
|
-
|
|
127
|
+
export function clearAllSessionStates() {
|
|
128
|
+
state.sessionStates.clear();
|
|
129
|
+
activeSessionId = DEFAULT_SESSION_ID;
|
|
130
|
+
getSessionRunState();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function computeEvaluationScores(sessionId = getActiveSessionId()) {
|
|
134
|
+
const sessionState = getSessionRunState(sessionId);
|
|
135
|
+
const toolSuccessRate =
|
|
136
|
+
sessionState.toolCallCount > 0
|
|
137
|
+
? (sessionState.toolCallCount - sessionState.errorCount) / sessionState.toolCallCount
|
|
138
|
+
: 1;
|
|
139
|
+
const sessionHadErrors = sessionState.errorCount > 0;
|
|
30
140
|
|
|
31
141
|
return {
|
|
32
|
-
tool_call_count:
|
|
33
|
-
turn_count:
|
|
34
|
-
total_tool_errors:
|
|
142
|
+
tool_call_count: sessionState.toolCallCount,
|
|
143
|
+
turn_count: sessionState.turnCount,
|
|
144
|
+
total_tool_errors: sessionState.errorCount,
|
|
35
145
|
tool_success_rate: toolSuccessRate,
|
|
36
146
|
session_had_errors: sessionHadErrors ? 1 : 0,
|
|
37
147
|
};
|
|
38
148
|
}
|
|
149
|
+
|
|
150
|
+
getSessionRunState();
|
package/src/utils.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
MAX_ARRAY_ITEMS,
|
|
3
3
|
MAX_DEPTH,
|
|
4
4
|
MAX_OBJECT_KEYS,
|
|
5
|
+
MAX_PAYLOAD_NODES,
|
|
5
6
|
MAX_STRING_LENGTH,
|
|
6
7
|
MAX_TOOL_PAYLOAD_LENGTH,
|
|
7
8
|
} from "./constants.js";
|
|
@@ -23,11 +24,25 @@ export function tryParseJson(value: string): unknown {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
const PAYLOAD_TOO_LARGE = "[payload too large]";
|
|
28
|
+
|
|
29
|
+
export function shapePayload(value: unknown, options: { maxString?: number; depth?: number; maxNodes?: number } = {}): unknown {
|
|
27
30
|
const maxString = options.maxString ?? MAX_STRING_LENGTH;
|
|
28
31
|
const depth = options.depth ?? MAX_DEPTH;
|
|
32
|
+
const maxNodes = options.maxNodes ?? MAX_PAYLOAD_NODES;
|
|
33
|
+
const budget = { exhausted: false, nodeCount: 0 };
|
|
29
34
|
|
|
30
35
|
function visit(item: unknown, remainingDepth: number, seen: WeakSet<object>): unknown {
|
|
36
|
+
if (budget.exhausted) {
|
|
37
|
+
return PAYLOAD_TOO_LARGE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
budget.nodeCount++;
|
|
41
|
+
if (budget.nodeCount > maxNodes) {
|
|
42
|
+
budget.exhausted = true;
|
|
43
|
+
return PAYLOAD_TOO_LARGE;
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
if (typeof item === "string") {
|
|
32
47
|
const truncated = truncate(item, maxString);
|
|
33
48
|
const parsed = tryParseJson(truncated);
|
|
@@ -59,7 +74,15 @@ export function shapePayload(value: unknown, options: { maxString?: number; dept
|
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
if (Array.isArray(item)) {
|
|
62
|
-
|
|
77
|
+
const output: unknown[] = [];
|
|
78
|
+
const limit = Math.min(item.length, MAX_ARRAY_ITEMS);
|
|
79
|
+
for (let index = 0; index < limit; index++) {
|
|
80
|
+
output.push(visit(item[index], remainingDepth - 1, seen));
|
|
81
|
+
if (budget.exhausted) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return output;
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
if (item instanceof Error) {
|
|
@@ -77,8 +100,16 @@ export function shapePayload(value: unknown, options: { maxString?: number; dept
|
|
|
77
100
|
seen.add(item);
|
|
78
101
|
|
|
79
102
|
const output: Record<string, unknown> = {};
|
|
80
|
-
|
|
81
|
-
|
|
103
|
+
let keyCount = 0;
|
|
104
|
+
for (const key in item as Record<string, unknown>) {
|
|
105
|
+
if (!Object.hasOwn(item, key)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
output[key] = visit((item as Record<string, unknown>)[key], remainingDepth - 1, seen);
|
|
109
|
+
keyCount++;
|
|
110
|
+
if (budget.exhausted || keyCount >= MAX_OBJECT_KEYS) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
82
113
|
}
|
|
83
114
|
return output;
|
|
84
115
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
declare module "@opentelemetry/sdk-trace-base" {
|
|
2
|
+
export class BasicTracerProvider {
|
|
3
|
+
constructor(options?: { spanProcessors?: unknown[] });
|
|
4
|
+
forceFlush?(): Promise<void>;
|
|
5
|
+
shutdown?(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module "@langfuse/otel" {
|
|
10
|
+
export class LangfuseSpanProcessor {
|
|
11
|
+
constructor(options: {
|
|
12
|
+
publicKey: string;
|
|
13
|
+
secretKey: string;
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
});
|
|
16
|
+
forceFlush?(): Promise<void>;
|
|
17
|
+
shutdown?(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare module "@langfuse/tracing" {
|
|
22
|
+
export function setLangfuseTracerProvider(provider: unknown): void;
|
|
23
|
+
|
|
24
|
+
export function startObservation(
|
|
25
|
+
name: string,
|
|
26
|
+
body?: Record<string, unknown>,
|
|
27
|
+
options?: { asType?: string },
|
|
28
|
+
): unknown;
|
|
29
|
+
|
|
30
|
+
export function propagateAttributes<T>(
|
|
31
|
+
params: {
|
|
32
|
+
sessionId?: string;
|
|
33
|
+
traceName?: string;
|
|
34
|
+
metadata?: Record<string, string>;
|
|
35
|
+
tags?: string[];
|
|
36
|
+
},
|
|
37
|
+
fn: () => T,
|
|
38
|
+
): T;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare module "@langfuse/client" {
|
|
42
|
+
export class LangfuseClient {
|
|
43
|
+
constructor(options: {
|
|
44
|
+
publicKey: string;
|
|
45
|
+
secretKey: string;
|
|
46
|
+
baseUrl: string;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|