pi-langfuse 1.4.0 → 1.4.2
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 +5 -1
- package/index.ts +61 -44
- package/package.json +11 -1
- package/src/handlers/tool.ts +23 -6
- package/src/state.ts +126 -23
- package/src/types.ts +2 -0
- package/src/utils.ts +4 -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/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Langfuse provides open-source observability for LLM applications. This extension
|
|
|
17
17
|
- **REST fallback for self-hosted Langfuse**: Uses the Langfuse OpenTelemetry SDK first, then verifies that the trace is visible. If a self-hosted OTel ingestion pipeline accepts spans but does not materialize traces, the extension writes the run through Langfuse's REST ingestion API.
|
|
18
18
|
- **Per-Request Generations**: Records a separate `generation` observation for every provider request, including the actual provider payload instead of only the original prompt.
|
|
19
19
|
- **Final Message Capture**: Uses finalized assistant messages for generation and root outputs, so Langfuse shows what the user actually saw in Pi.
|
|
20
|
-
- **Tool Observability**: Creates Langfuse `tool` observations for every tool call, including arguments, results, and
|
|
20
|
+
- **Tool Observability**: Creates Langfuse `tool` observations for every tool call, including arguments, results, error states, and payload/latency metrics.
|
|
21
21
|
- **Parallel Tool Safety**: Correlates tool observations by `toolCallId`, avoiding result mix-ups when Pi runs tools concurrently.
|
|
22
22
|
- **Session Correlation**: Groups traces from the same Pi session under a shared Langfuse session ID.
|
|
23
23
|
- **Cost and Token Tracking**: Records usage and cost details on each generation when Pi/provider payloads expose them.
|
|
@@ -32,6 +32,7 @@ Langfuse provides open-source observability for LLM applications. This extension
|
|
|
32
32
|
- The first generation in a tool-using run can show the assistant's tool-call message, the tool observation shows execution I/O, and the follow-up generation shows the final natural-language answer.
|
|
33
33
|
- Tool failures are marked on the tool observation and reflected in trace-level scores, while later generations still preserve the tool error result in their input history.
|
|
34
34
|
- Shutdown and interrupted runs flush pending telemetry and mark unfinished observations as cancelled/warning instead of silently losing the trace.
|
|
35
|
+
- Agent-end runtime shutdown is deferred so Langfuse flushing does not block Pi's visible turn completion.
|
|
35
36
|
|
|
36
37
|
## Prerequisites
|
|
37
38
|
|
|
@@ -265,6 +266,9 @@ Trace (name: "pi-agent")
|
|
|
265
266
|
| `output` | Tool result, shaped and truncated for readability |
|
|
266
267
|
| `metadata.toolCallId` | Stable Pi tool call identifier |
|
|
267
268
|
| `metadata.isError` | Whether the tool failed |
|
|
269
|
+
| `metadata.durationMs` | Approximate tool runtime in milliseconds |
|
|
270
|
+
| `metadata.inputBytes` | UTF-8 byte size of the shaped tool input payload |
|
|
271
|
+
| `metadata.outputBytes` | UTF-8 byte size of the shaped tool output payload |
|
|
268
272
|
| `level` | `ERROR` for failed tool calls, otherwise `DEFAULT` |
|
|
269
273
|
|
|
270
274
|
### Observation-Level Scores
|
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,12 +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
|
-
|
|
132
|
-
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
shutdownRuntime().catch((error) => {
|
|
140
|
+
console.warn("📊 Langfuse: Deferred shutdown failed", error);
|
|
141
|
+
});
|
|
142
|
+
}, 0);
|
|
143
|
+
}));
|
|
133
144
|
|
|
134
145
|
const handleSessionInterruption = (reason: string) => {
|
|
135
146
|
if (state.agentState?.root) {
|
|
@@ -139,15 +150,21 @@ export default async function (pi: ExtensionAPI) {
|
|
|
139
150
|
resetRunState();
|
|
140
151
|
};
|
|
141
152
|
|
|
142
|
-
pi.on("session_before_switch", async () => {
|
|
143
|
-
|
|
153
|
+
pi.on("session_before_switch", async (_event, ctx) => {
|
|
154
|
+
const sessionId = getSessionId(ctx);
|
|
155
|
+
if (sessionId) {
|
|
156
|
+
setCurrentSession(sessionId);
|
|
157
|
+
}
|
|
144
158
|
});
|
|
145
159
|
|
|
146
|
-
pi.on("session_before_fork", async () => {
|
|
147
|
-
|
|
160
|
+
pi.on("session_before_fork", async (_event, ctx) => {
|
|
161
|
+
const sessionId = getSessionId(ctx);
|
|
162
|
+
if (sessionId) {
|
|
163
|
+
setCurrentSession(sessionId);
|
|
164
|
+
}
|
|
148
165
|
});
|
|
149
166
|
|
|
150
|
-
pi.on("session_compact", async (event) => {
|
|
167
|
+
pi.on("session_compact", async (event, ctx) => withSession(ctx, async () => {
|
|
151
168
|
if (state.agentState?.root) {
|
|
152
169
|
const parent = state.agentState.activeTurn ?? state.agentState.root;
|
|
153
170
|
try {
|
|
@@ -165,10 +182,10 @@ export default async function (pi: ExtensionAPI) {
|
|
|
165
182
|
// ignore
|
|
166
183
|
}
|
|
167
184
|
}
|
|
168
|
-
});
|
|
185
|
+
}));
|
|
169
186
|
|
|
170
|
-
pi.on("session_shutdown", async () => {
|
|
187
|
+
pi.on("session_shutdown", async (_event, ctx) => withSession(ctx, async () => {
|
|
171
188
|
handleSessionInterruption("Session shutdown before agent completed");
|
|
172
189
|
await shutdownRuntime();
|
|
173
|
-
});
|
|
190
|
+
}));
|
|
174
191
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-langfuse",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
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/handlers/tool.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
shapePayload,
|
|
8
8
|
extractTextContent,
|
|
9
9
|
truncate,
|
|
10
|
+
estimatePayloadBytes,
|
|
10
11
|
} from "../utils.js";
|
|
11
12
|
import { MAX_TOOL_PAYLOAD_LENGTH } from "../constants.js";
|
|
12
13
|
|
|
@@ -22,27 +23,36 @@ export async function startToolObservation(event: Record<string, unknown>) {
|
|
|
22
23
|
|
|
23
24
|
try {
|
|
24
25
|
const toolName = getToolName(event);
|
|
26
|
+
const toolInput = getToolInput(event);
|
|
27
|
+
const shapedInput = shapePayload(toolInput, { maxString: MAX_TOOL_PAYLOAD_LENGTH });
|
|
28
|
+
const inputBytes = estimatePayloadBytes(shapedInput, MAX_TOOL_PAYLOAD_LENGTH);
|
|
25
29
|
const parent = state.agentState.activeTurn ?? state.agentState.root;
|
|
26
30
|
const tool = parent.startObservation
|
|
27
31
|
? parent.startObservation(
|
|
28
32
|
toolName,
|
|
29
33
|
{
|
|
30
|
-
input:
|
|
31
|
-
metadata: { toolName, toolCallId },
|
|
34
|
+
input: shapedInput,
|
|
35
|
+
metadata: { toolName, toolCallId, inputBytes },
|
|
32
36
|
},
|
|
33
37
|
{ asType: "tool" },
|
|
34
38
|
)
|
|
35
39
|
: (await getRuntime()).startObservation(
|
|
36
40
|
toolName,
|
|
37
41
|
{
|
|
38
|
-
input:
|
|
39
|
-
metadata: { toolName, toolCallId },
|
|
42
|
+
input: shapedInput,
|
|
43
|
+
metadata: { toolName, toolCallId, inputBytes },
|
|
40
44
|
},
|
|
41
45
|
{ asType: "tool" },
|
|
42
46
|
);
|
|
43
47
|
|
|
44
48
|
state.toolCallCount++;
|
|
45
|
-
state.agentState.activeTools.set(toolCallId, {
|
|
49
|
+
state.agentState.activeTools.set(toolCallId, {
|
|
50
|
+
observation: tool,
|
|
51
|
+
toolName,
|
|
52
|
+
ended: false,
|
|
53
|
+
startedAt: Date.now(),
|
|
54
|
+
inputBytes,
|
|
55
|
+
});
|
|
46
56
|
} catch (e) {
|
|
47
57
|
console.warn("📊 Langfuse: Failed to start tool observation", e);
|
|
48
58
|
}
|
|
@@ -73,15 +83,22 @@ export async function finishToolObservation(event: Record<string, unknown>) {
|
|
|
73
83
|
event;
|
|
74
84
|
|
|
75
85
|
try {
|
|
86
|
+
const shapedOutput = shapePayload(output, { maxString: MAX_TOOL_PAYLOAD_LENGTH });
|
|
87
|
+
const outputBytes = estimatePayloadBytes(shapedOutput, MAX_TOOL_PAYLOAD_LENGTH);
|
|
88
|
+
const durationMs = Math.max(0, Date.now() - activeTool.startedAt);
|
|
89
|
+
|
|
76
90
|
activeTool.observation
|
|
77
91
|
.update({
|
|
78
|
-
output:
|
|
92
|
+
output: shapedOutput,
|
|
79
93
|
level: isError ? "ERROR" : "DEFAULT",
|
|
80
94
|
statusMessage: isError ? truncate(String(event.error ?? output), 1_000) : undefined,
|
|
81
95
|
metadata: {
|
|
82
96
|
toolName: activeTool.toolName,
|
|
83
97
|
toolCallId,
|
|
84
98
|
isError,
|
|
99
|
+
durationMs,
|
|
100
|
+
inputBytes: activeTool.inputBytes,
|
|
101
|
+
outputBytes,
|
|
85
102
|
},
|
|
86
103
|
})
|
|
87
104
|
.end();
|
package/src/state.ts
CHANGED
|
@@ -1,38 +1,141 @@
|
|
|
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
|
+
}
|
|
12
|
+
|
|
13
|
+
const DEFAULT_SESSION_ID = "__pi_langfuse_default_session__";
|
|
14
|
+
|
|
15
|
+
let activeSessionId = DEFAULT_SESSION_ID;
|
|
16
|
+
const sessionScope = new AsyncLocalStorage<string>();
|
|
17
|
+
|
|
18
|
+
function createSessionRunState(): SessionRunState {
|
|
19
|
+
return {
|
|
20
|
+
currentModel: "",
|
|
21
|
+
currentProvider: "",
|
|
22
|
+
agentState: null,
|
|
23
|
+
toolCallCount: 0,
|
|
24
|
+
errorCount: 0,
|
|
25
|
+
turnCount: 0,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeSessionId(sessionId?: string) {
|
|
30
|
+
return sessionId || DEFAULT_SESSION_ID;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getActiveSessionId() {
|
|
34
|
+
return sessionScope.getStore() ?? activeSessionId;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getSessionRunState(sessionId = getActiveSessionId()): SessionRunState {
|
|
38
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
39
|
+
let sessionState = state.sessionStates.get(normalizedSessionId);
|
|
40
|
+
if (!sessionState) {
|
|
41
|
+
sessionState = createSessionRunState();
|
|
42
|
+
state.sessionStates.set(normalizedSessionId, sessionState);
|
|
43
|
+
}
|
|
44
|
+
return sessionState;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function setCurrentSession(sessionId?: string) {
|
|
48
|
+
activeSessionId = normalizeSessionId(sessionId);
|
|
49
|
+
getSessionRunState(activeSessionId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function runWithSession<T>(sessionId: string | undefined, fn: () => T): T {
|
|
53
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
54
|
+
setCurrentSession(normalizedSessionId);
|
|
55
|
+
return sessionScope.run(normalizedSessionId, fn);
|
|
56
|
+
}
|
|
57
|
+
|
|
3
58
|
export const state = {
|
|
4
59
|
config: null as Config | null,
|
|
5
60
|
setupAttemptedThisSession: false,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
61
|
+
sessionStates: new Map<string, SessionRunState>(),
|
|
62
|
+
|
|
63
|
+
get currentSessionId() {
|
|
64
|
+
const sessionId = getActiveSessionId();
|
|
65
|
+
return sessionId === DEFAULT_SESSION_ID ? "" : sessionId;
|
|
66
|
+
},
|
|
67
|
+
set currentSessionId(sessionId: string) {
|
|
68
|
+
setCurrentSession(sessionId);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
get currentModel() {
|
|
72
|
+
return getSessionRunState().currentModel;
|
|
73
|
+
},
|
|
74
|
+
set currentModel(model: string) {
|
|
75
|
+
getSessionRunState().currentModel = model;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
get currentProvider() {
|
|
79
|
+
return getSessionRunState().currentProvider;
|
|
80
|
+
},
|
|
81
|
+
set currentProvider(provider: string) {
|
|
82
|
+
getSessionRunState().currentProvider = provider;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
get agentState() {
|
|
86
|
+
return getSessionRunState().agentState;
|
|
87
|
+
},
|
|
88
|
+
set agentState(agentState: AgentState | null) {
|
|
89
|
+
getSessionRunState().agentState = agentState;
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
get toolCallCount() {
|
|
93
|
+
return getSessionRunState().toolCallCount;
|
|
94
|
+
},
|
|
95
|
+
set toolCallCount(toolCallCount: number) {
|
|
96
|
+
getSessionRunState().toolCallCount = toolCallCount;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
get errorCount() {
|
|
100
|
+
return getSessionRunState().errorCount;
|
|
101
|
+
},
|
|
102
|
+
set errorCount(errorCount: number) {
|
|
103
|
+
getSessionRunState().errorCount = errorCount;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
get turnCount() {
|
|
107
|
+
return getSessionRunState().turnCount;
|
|
108
|
+
},
|
|
109
|
+
set turnCount(turnCount: number) {
|
|
110
|
+
getSessionRunState().turnCount = turnCount;
|
|
111
|
+
},
|
|
16
112
|
};
|
|
17
113
|
|
|
18
|
-
export function resetRunState() {
|
|
19
|
-
state.
|
|
20
|
-
state.toolCallCount = 0;
|
|
21
|
-
state.errorCount = 0;
|
|
22
|
-
state.turnCount = 0;
|
|
23
|
-
state.currentModel = "";
|
|
24
|
-
state.currentProvider = "";
|
|
114
|
+
export function resetRunState(sessionId = getActiveSessionId()) {
|
|
115
|
+
state.sessionStates.set(normalizeSessionId(sessionId), createSessionRunState());
|
|
25
116
|
}
|
|
26
117
|
|
|
27
|
-
export function
|
|
28
|
-
|
|
29
|
-
|
|
118
|
+
export function clearAllSessionStates() {
|
|
119
|
+
state.sessionStates.clear();
|
|
120
|
+
activeSessionId = DEFAULT_SESSION_ID;
|
|
121
|
+
getSessionRunState();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function computeEvaluationScores(sessionId = getActiveSessionId()) {
|
|
125
|
+
const sessionState = getSessionRunState(sessionId);
|
|
126
|
+
const toolSuccessRate =
|
|
127
|
+
sessionState.toolCallCount > 0
|
|
128
|
+
? (sessionState.toolCallCount - sessionState.errorCount) / sessionState.toolCallCount
|
|
129
|
+
: 1;
|
|
130
|
+
const sessionHadErrors = sessionState.errorCount > 0;
|
|
30
131
|
|
|
31
132
|
return {
|
|
32
|
-
tool_call_count:
|
|
33
|
-
turn_count:
|
|
34
|
-
total_tool_errors:
|
|
133
|
+
tool_call_count: sessionState.toolCallCount,
|
|
134
|
+
turn_count: sessionState.turnCount,
|
|
135
|
+
total_tool_errors: sessionState.errorCount,
|
|
35
136
|
tool_success_rate: toolSuccessRate,
|
|
36
137
|
session_had_errors: sessionHadErrors ? 1 : 0,
|
|
37
138
|
};
|
|
38
139
|
}
|
|
140
|
+
|
|
141
|
+
getSessionRunState();
|
package/src/types.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -97,6 +97,10 @@ export function safeSerialize(value: unknown, maxLength = MAX_TOOL_PAYLOAD_LENGT
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
export function estimatePayloadBytes(value: unknown, maxLength = MAX_TOOL_PAYLOAD_LENGTH): number {
|
|
101
|
+
return new TextEncoder().encode(safeSerialize(value, maxLength)).length;
|
|
102
|
+
}
|
|
103
|
+
|
|
100
104
|
export function extractTextContent(content: unknown, maxLength?: number): string | undefined {
|
|
101
105
|
if (typeof content === "string") {
|
|
102
106
|
return maxLength ? truncate(content, maxLength) : content;
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: langfuse
|
|
3
|
-
description: Interact with Langfuse and access its documentation. Use when needing to (1) query or modify Langfuse data programmatically via the CLI — traces, prompts, datasets, scores, sessions, and any other API resource, (2) look up Langfuse documentation, concepts, integration guides, or SDK usage, or (3) understand how any Langfuse feature works. This skill covers CLI-based API access (via npx) and multiple documentation retrieval methods.
|
|
4
|
-
allowed-tools:
|
|
5
|
-
- WebFetch(domain:langfuse.com)
|
|
6
|
-
- Bash(curl *langfuse.com/*)
|
|
7
|
-
- Bash(npx langfuse-cli api __schema *)
|
|
8
|
-
- Bash(npx langfuse-cli api * --help *)
|
|
9
|
-
- Bash(npx langfuse-cli api * list *)
|
|
10
|
-
- Bash(npx langfuse-cli api * get *)
|
|
11
|
-
- Bash(bunx langfuse-cli api __schema *)
|
|
12
|
-
- Bash(bunx langfuse-cli api * --help *)
|
|
13
|
-
- Bash(bunx langfuse-cli api * list *)
|
|
14
|
-
- Bash(bunx langfuse-cli api * get *)
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
# Langfuse
|
|
18
|
-
|
|
19
|
-
This skill helps you use Langfuse effectively across all common workflows: instrumenting applications, migrating prompts, debugging traces, and accessing data programmatically.
|
|
20
|
-
|
|
21
|
-
## Core Principles
|
|
22
|
-
|
|
23
|
-
Follow these principles for ALL Langfuse work:
|
|
24
|
-
|
|
25
|
-
1. **Documentation First**: NEVER implement based on memory. Always fetch current docs before writing code (Langfuse updates frequently) See the section below on how to access documentation.
|
|
26
|
-
2. **CLI for Data Access**: Use `langfuse-cli` when querying/modifying Langfuse data. See the section below on how to use the CLI.
|
|
27
|
-
3. **Best Practices by Use Case**: Check the relevant reference file below for use-case-specific guidelines before implementing
|
|
28
|
-
4. **Use latest Langfuse versions**: Unless the user specified otherwise or there's a good reason, always use the latest version of Langfuse SDKs/APIs.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
## Use case specific references
|
|
32
|
-
|
|
33
|
-
- instrumenting an existing function/application: references/instrumentation.md
|
|
34
|
-
- migrating prompts from a codebase into Langfuse: references/prompt-migration.md
|
|
35
|
-
- capturing user feedback (thumbs, ratings, implicit signals) as scores on traces: references/user-feedback.md
|
|
36
|
-
- further tips on using the Langfuse CLI: references/cli.md
|
|
37
|
-
- upgrading or migrating Langfuse SDKs to the latest version: references/sdk-upgrade.md
|
|
38
|
-
- systematic error analysis — reading traces, building failure taxonomy, deciding what to fix: references/error-analysis.md
|
|
39
|
-
- submitting feedback about this skill: references/skill-feedback.md
|
|
40
|
-
|
|
41
|
-
## 1. Langfuse API via CLI
|
|
42
|
-
|
|
43
|
-
Use the `langfuse-cli` to interact with the full Langfuse REST API from the command line. Run via npx (no install required):
|
|
44
|
-
|
|
45
|
-
Start by discovering the schema and available arguments:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# Discover all available resources
|
|
49
|
-
npx langfuse-cli api __schema
|
|
50
|
-
|
|
51
|
-
# List actions for a resource
|
|
52
|
-
npx langfuse-cli api <resource> --help
|
|
53
|
-
|
|
54
|
-
# Show args/options for a specific action
|
|
55
|
-
npx langfuse-cli api <resource> <action> --help
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Credentials
|
|
59
|
-
|
|
60
|
-
Set environment variables before making calls:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
export LANGFUSE_PUBLIC_KEY=pk-lf-...
|
|
64
|
-
export LANGFUSE_SECRET_KEY=sk-lf-...
|
|
65
|
-
export LANGFUSE_HOST=https://cloud.langfuse.com # example for EU cloud. For US cloud it's us.cloud.langfuse.com, and can also be a self-hosted URL. The server must always be specified in order to access Langfuse.
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
If not set, ask the user to set them in their shell or a `.env` file (do not ask them to paste keys into chat for security reasons). Keys are found in Langfuse UI → Settings → API Keys.
|
|
69
|
-
|
|
70
|
-
### Detailed CLI Reference
|
|
71
|
-
|
|
72
|
-
For common workflows, tips, and full usage patterns, see [references/cli.md](references/cli.md).
|
|
73
|
-
|
|
74
|
-
## 2. Langfuse Documentation
|
|
75
|
-
|
|
76
|
-
Three methods to access Langfuse docs, in order of preference. **Always prefer your application's native web fetch and search tools** (e.g., `WebFetch`, `WebSearch`, `mcp_fetch`, etc.) over `curl` when available. The URLs and patterns below work with any fetching method — the `curl` examples are just illustrative.
|
|
77
|
-
|
|
78
|
-
### 2a. Documentation Index (llms.txt)
|
|
79
|
-
|
|
80
|
-
Fetch the full index of all documentation pages:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
curl -s https://langfuse.com/llms.txt
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Returns a structured list of every doc page with titles and URLs. Use this to discover the right page for a topic, then fetch that page directly.
|
|
87
|
-
|
|
88
|
-
Alternatively, you can start on `https://langfuse.com/docs` and explore the site to find the page you need.
|
|
89
|
-
|
|
90
|
-
### 2b. Fetch Individual Pages as Markdown
|
|
91
|
-
|
|
92
|
-
Any page listed in llms.txt can be fetched as markdown by appending `.md` to its path or by using `Accept: text/markdown` in the request headers. Use this when you know which page contains the information needed. Returns clean markdown with code examples and configuration details.
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
curl -s "https://langfuse.com/docs/observability/overview.md"
|
|
96
|
-
curl -s "https://langfuse.com/docs/observability/overview" -H "Accept: text/markdown"
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### 2c. Search Documentation
|
|
100
|
-
|
|
101
|
-
When you need to find information across all docs and github issues/discussions without knowing the specific page:
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
curl -s "https://langfuse.com/api/search-docs?query=<url-encoded-query>"
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Example:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
curl -s "https://langfuse.com/api/search-docs?query=How+do+I+trace+LangGraph+agents"
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Returns a JSON response with:
|
|
114
|
-
|
|
115
|
-
- `query`: the original query
|
|
116
|
-
- `answer`: a JSON string containing an array of matching documents, each with:
|
|
117
|
-
- `url`: link to the doc page
|
|
118
|
-
- `title`: page title
|
|
119
|
-
- `source.content`: array of relevant text excerpts from the page
|
|
120
|
-
|
|
121
|
-
Search is a great fallback if you cannot find the relevant pages or need more context. Especially useful when debugging issues as all GitHub Issues and Discussions are also indexed. Responses can be large — extract only the relevant portions.
|
|
122
|
-
|
|
123
|
-
### Documentation Workflow
|
|
124
|
-
|
|
125
|
-
1. Start with **llms.txt** to orient — scan for relevant page titles
|
|
126
|
-
2. **Fetch specific pages** when you identify the right one
|
|
127
|
-
3. Fall back to **search** when the topic is unclear and you want more context
|
|
128
|
-
|
|
129
|
-
## Skill Feedback
|
|
130
|
-
|
|
131
|
-
When the user expresses that something about this skill is not working as expected, gives incorrect guidance, is missing information, or could be improved — offer to submit feedback to the Langfuse skill maintainers. This includes when:
|
|
132
|
-
|
|
133
|
-
- The skill gave wrong or outdated instructions
|
|
134
|
-
- A workflow didn't produce the expected result
|
|
135
|
-
- The user wishes the skill covered something it doesn't
|
|
136
|
-
- The user explicitly says something like "this should work differently" or "this is wrong"
|
|
137
|
-
|
|
138
|
-
**Do NOT trigger this** for issues with Langfuse itself (the product) — only for issues with this skill's instructions and behavior.
|
|
139
|
-
|
|
140
|
-
When triggered, follow the process in [references/skill-feedback.md](references/skill-feedback.md).
|