pi-langfuse 1.6.0 → 1.6.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 +7 -1
- package/README_CN.md +7 -1
- package/package.json +1 -1
- package/src/commands.ts +1 -0
- package/src/config.ts +14 -0
- package/src/handlers/agent.ts +1 -0
- package/src/langfuse.ts +6 -1
- package/src/types.ts +1 -0
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ Langfuse observability extension for [Pi Coding Agent](https://github.com/earend
|
|
|
34
34
|
- Langfuse public key, starting with `pk-lf-...`
|
|
35
35
|
- Langfuse secret key, starting with `sk-lf-...`
|
|
36
36
|
- Langfuse host, defaulting to `https://cloud.langfuse.com`
|
|
37
|
+
- Optional Langfuse user ID for per-user usage and cost aggregation
|
|
37
38
|
|
|
38
39
|
3. Run Pi normally:
|
|
39
40
|
|
|
@@ -63,7 +64,7 @@ To inspect the active configuration without exposing secrets:
|
|
|
63
64
|
/langfuse-status
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
The status command reports the config source, host, masked public key, capture policy, active-run state, config path, and last runtime error.
|
|
67
|
+
The status command reports the config source, host, masked public key, whether a user ID is configured, capture policy, active-run state, config path, and last runtime error. It never prints the user ID itself.
|
|
67
68
|
|
|
68
69
|
### Method 2: Environment variables
|
|
69
70
|
|
|
@@ -73,9 +74,11 @@ Set these before starting Pi:
|
|
|
73
74
|
export LANGFUSE_PUBLIC_KEY="pk-lf-xxxx"
|
|
74
75
|
export LANGFUSE_SECRET_KEY="sk-lf-xxxx"
|
|
75
76
|
export LANGFUSE_BASE_URL="https://cloud.langfuse.com" # optional; LANGFUSE_HOST is also supported
|
|
77
|
+
export LANGFUSE_USER_ID="user-123" # optional; max 200 characters
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
Saved config takes precedence. Environment variables are only used when `~/.pi/agent/pi-langfuse/config.json` is missing or incomplete.
|
|
81
|
+
If saved credentials exist but `userId` is absent, `LANGFUSE_USER_ID` is still used. An explicitly saved `userId` takes precedence.
|
|
79
82
|
|
|
80
83
|
For short-lived SDK hosts, set the bounded final score-delivery attempt during shutdown:
|
|
81
84
|
|
|
@@ -222,10 +225,13 @@ Create or update `~/.pi/agent/pi-langfuse/config.json`:
|
|
|
222
225
|
"publicKey": "pk-lf-xxxx",
|
|
223
226
|
"secretKey": "sk-lf-xxxx",
|
|
224
227
|
"host": "https://cloud.langfuse.com",
|
|
228
|
+
"userId": "user-123",
|
|
225
229
|
"privacyPreset": "conversations"
|
|
226
230
|
}
|
|
227
231
|
```
|
|
228
232
|
|
|
233
|
+
`userId` is optional, explicit-only, and limited to 200 characters. The extension does not infer it from the local operating-system account. When configured, it is propagated to every observation in the trace so Langfuse can provide per-user filtering and cost aggregation.
|
|
234
|
+
|
|
229
235
|
Fine-grained capture flags can also be persisted:
|
|
230
236
|
|
|
231
237
|
```json
|
package/README_CN.md
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
- Langfuse 公钥,以 `pk-lf-...` 开头
|
|
35
35
|
- Langfuse 密钥,以 `sk-lf-...` 开头
|
|
36
36
|
- Langfuse 主机地址,默认 `https://cloud.langfuse.com`
|
|
37
|
+
- 可选的 Langfuse 用户 ID,用于按用户汇总用量和成本
|
|
37
38
|
|
|
38
39
|
3. 正常运行 Pi:
|
|
39
40
|
|
|
@@ -63,7 +64,7 @@ Langfuse API 密钥可在 **Langfuse Cloud** -> **Settings** -> **API Keys** 中
|
|
|
63
64
|
/langfuse-status
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
状态命令会显示配置来源、主机地址、脱敏后的公钥、是否已配置用户 ID、采集策略、是否有活跃运行、配置文件路径,以及最近一次运行时错误,但不会输出用户 ID 本身。
|
|
67
68
|
|
|
68
69
|
### 方式 2:环境变量
|
|
69
70
|
|
|
@@ -73,9 +74,11 @@ Langfuse API 密钥可在 **Langfuse Cloud** -> **Settings** -> **API Keys** 中
|
|
|
73
74
|
export LANGFUSE_PUBLIC_KEY="pk-lf-xxxx"
|
|
74
75
|
export LANGFUSE_SECRET_KEY="sk-lf-xxxx"
|
|
75
76
|
export LANGFUSE_BASE_URL="https://cloud.langfuse.com" # 可选;也支持 LANGFUSE_HOST
|
|
77
|
+
export LANGFUSE_USER_ID="user-123" # 可选;最长 200 个字符
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
保存的配置优先级更高。只有当 `~/.pi/agent/pi-langfuse/config.json` 缺失或不完整时,扩展才会使用环境变量。
|
|
81
|
+
如果已保存凭据但没有 `userId`,仍会读取 `LANGFUSE_USER_ID`;显式保存的 `userId` 优先级更高。
|
|
79
82
|
|
|
80
83
|
对于短生命周期的 SDK 宿主,可设置关闭时最终分数发送尝试的上限:
|
|
81
84
|
|
|
@@ -203,10 +206,13 @@ export PI_LANGFUSE_SPLIT_REASONING_TOKENS=true
|
|
|
203
206
|
"publicKey": "pk-lf-xxxx",
|
|
204
207
|
"secretKey": "sk-lf-xxxx",
|
|
205
208
|
"host": "https://cloud.langfuse.com",
|
|
209
|
+
"userId": "user-123",
|
|
206
210
|
"privacyPreset": "conversations"
|
|
207
211
|
}
|
|
208
212
|
```
|
|
209
213
|
|
|
214
|
+
`userId` 是可选的显式配置,最长 200 个字符。扩展不会从本机操作系统账户推断该值。配置后,它会传播到 trace 中的每个 observation,以支持 Langfuse 按用户筛选并汇总成本。
|
|
215
|
+
|
|
210
216
|
也可以持久化细粒度采集开关:
|
|
211
217
|
|
|
212
218
|
```json
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -211,6 +211,7 @@ function formatStatus(configPath: string, env: Record<string, string | undefined
|
|
|
211
211
|
`Source: ${configSource(env, configPath)}`,
|
|
212
212
|
`Host: ${safeConfig?.host ?? config.host}`,
|
|
213
213
|
`Public key: ${safeConfig?.publicKey ?? "[REDACTED_SECRET]"}`,
|
|
214
|
+
`User ID: ${config.userId ? "configured" : "not set"}`,
|
|
214
215
|
`Config file: ${configPath}`,
|
|
215
216
|
`Privacy preset: ${inferPreset(policy)}`,
|
|
216
217
|
"Capture:",
|
package/src/config.ts
CHANGED
|
@@ -8,6 +8,16 @@ import { createCapturePolicy, type EnvLike } from "./capture-policy.js";
|
|
|
8
8
|
import { createPayloadLimits } from "./limits.js";
|
|
9
9
|
import { createUsageOptions } from "./usage-options.js";
|
|
10
10
|
|
|
11
|
+
const MAX_LANGFUSE_USER_ID_LENGTH = 200;
|
|
12
|
+
|
|
13
|
+
function normalizeUserId(value: unknown): string | undefined {
|
|
14
|
+
if (typeof value !== "string") {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const trimmed = value.trim();
|
|
18
|
+
return trimmed ? trimmed.slice(0, MAX_LANGFUSE_USER_ID_LENGTH) : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
export function loadConfigFromFile(path = CONFIG_PATH, env: EnvLike = process.env as EnvLike): Config | null {
|
|
12
22
|
if (existsSync(path)) {
|
|
13
23
|
try {
|
|
@@ -23,6 +33,7 @@ export function loadConfigFromFile(path = CONFIG_PATH, env: EnvLike = process.en
|
|
|
23
33
|
publicKey: config.publicKey,
|
|
24
34
|
secretKey: config.secretKey,
|
|
25
35
|
host: config.host || DEFAULT_LANGFUSE_HOST,
|
|
36
|
+
userId: normalizeUserId(config.userId) ?? normalizeUserId(env.LANGFUSE_USER_ID),
|
|
26
37
|
capturePolicy: createCapturePolicy(captureSource),
|
|
27
38
|
limits: createPayloadLimits(env),
|
|
28
39
|
usage: createUsageOptions(captureSource),
|
|
@@ -47,6 +58,7 @@ export function loadConfigFromEnv(env: EnvLike = process.env as EnvLike): Config
|
|
|
47
58
|
publicKey,
|
|
48
59
|
secretKey,
|
|
49
60
|
host: env.LANGFUSE_BASE_URL || env.LANGFUSE_HOST || DEFAULT_LANGFUSE_HOST,
|
|
61
|
+
userId: normalizeUserId(env.LANGFUSE_USER_ID),
|
|
50
62
|
capturePolicy: createCapturePolicy(env),
|
|
51
63
|
limits: createPayloadLimits(env),
|
|
52
64
|
usage: createUsageOptions(env),
|
|
@@ -108,10 +120,12 @@ async function collectConfigFromUI(ctx: any, reason: string): Promise<Config | n
|
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
const hostInput = (await ctx.ui.input("Langfuse host:", DEFAULT_LANGFUSE_HOST))?.trim();
|
|
123
|
+
const userId = normalizeUserId(await ctx.ui.input("Langfuse user ID (optional):", ""));
|
|
111
124
|
return {
|
|
112
125
|
publicKey,
|
|
113
126
|
secretKey,
|
|
114
127
|
host: hostInput || DEFAULT_LANGFUSE_HOST,
|
|
128
|
+
...(userId ? { userId } : {}),
|
|
115
129
|
capturePolicy: createCapturePolicy(),
|
|
116
130
|
};
|
|
117
131
|
}
|
package/src/handlers/agent.ts
CHANGED
|
@@ -96,6 +96,7 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
|
|
|
96
96
|
const root = rt.propagateAttributes(
|
|
97
97
|
{
|
|
98
98
|
sessionId: state.currentSessionId ? truncate(state.currentSessionId, 200) : undefined,
|
|
99
|
+
userId: state.config?.userId,
|
|
99
100
|
traceName: "pi-agent",
|
|
100
101
|
metadata: stringMetadata(captured.metadata),
|
|
101
102
|
},
|
package/src/langfuse.ts
CHANGED
|
@@ -29,6 +29,7 @@ interface RestFallbackTrace {
|
|
|
29
29
|
input?: unknown;
|
|
30
30
|
output?: unknown;
|
|
31
31
|
sessionId?: string;
|
|
32
|
+
userId?: string;
|
|
32
33
|
metadata?: Record<string, unknown>;
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -558,12 +559,15 @@ function wrapObservation(
|
|
|
558
559
|
store.observationById.set(id, record);
|
|
559
560
|
|
|
560
561
|
if (!parentObservationId && !store.trace) {
|
|
562
|
+
const propagated = readPropagatedAttributes(observation);
|
|
561
563
|
store.trace = {
|
|
562
564
|
id: traceId,
|
|
563
565
|
timestamp: record.startTime,
|
|
564
566
|
name,
|
|
565
567
|
input: body?.input,
|
|
566
|
-
sessionId:
|
|
568
|
+
sessionId: propagated.sessionId
|
|
569
|
+
?? (typeof metadata?.sessionId === "string" ? metadata.sessionId : state.currentSessionId || undefined),
|
|
570
|
+
userId: propagated.userId ?? state.config?.userId,
|
|
567
571
|
metadata,
|
|
568
572
|
};
|
|
569
573
|
}
|
|
@@ -743,6 +747,7 @@ async function fallbackToRestIngestion(rt: LangfuseRuntime, signal: AbortSignal)
|
|
|
743
747
|
input: trace.input,
|
|
744
748
|
output: trace.output,
|
|
745
749
|
sessionId: trace.sessionId,
|
|
750
|
+
userId: trace.userId,
|
|
746
751
|
metadata: trace.metadata,
|
|
747
752
|
},
|
|
748
753
|
},
|