@narumitw/pi-langfuse 0.50.5 → 0.51.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/README.md +125 -62
- package/dist/chunks/chunk-IASLJKKB.js +316 -0
- package/dist/chunks/chunk-IASLJKKB.js.map +7 -0
- package/dist/chunks/runtime-6MIKLWNJ.js +352 -0
- package/dist/chunks/runtime-6MIKLWNJ.js.map +7 -0
- package/dist/config.d.ts +33 -0
- package/dist/git.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1551 -0
- package/dist/index.js.map +7 -0
- package/dist/index.ts +594 -521
- package/dist/index.ts.map +4 -4
- package/dist/langfuse.d.ts +13 -0
- package/dist/pi-session.d.ts +43 -0
- package/dist/public-api.d.ts +4 -0
- package/dist/runtime-core.d.ts +26 -0
- package/dist/runtime.d.ts +22 -0
- package/dist/sanitizer.d.ts +2 -0
- package/dist/tracing.d.ts +202 -0
- package/package.json +63 -59
- package/src/config.ts +211 -232
- package/src/git.ts +60 -0
- package/src/index.ts +9 -0
- package/src/langfuse.ts +307 -563
- package/src/pi-session.ts +570 -0
- package/src/public-api.ts +17 -0
- package/src/runtime-core.ts +113 -0
- package/src/runtime.ts +393 -227
- package/src/sanitizer.ts +115 -117
- package/src/tracing.ts +828 -843
- package/dist/chunks/runtime-247EMSG3.js +0 -218
- package/dist/chunks/runtime-247EMSG3.js.map +0 -7
package/src/runtime.ts
CHANGED
|
@@ -1,281 +1,447 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { LangfuseSpanProcessor } from "@langfuse/otel";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
type LangfuseObservation,
|
|
5
|
+
type LangfuseObservationAttributes,
|
|
6
|
+
LangfuseOtelSpanAttributes,
|
|
7
|
+
setLangfuseTracerProvider,
|
|
8
|
+
startObservation,
|
|
9
9
|
} from "@langfuse/tracing";
|
|
10
10
|
import type { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
11
11
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
12
|
-
import type
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
import { DEFAULT_BASE_URL, type LangfuseConfig } from "./config.js";
|
|
13
|
+
import {
|
|
14
|
+
type CreateLangfuseRuntimeOptions,
|
|
15
|
+
createLangfuseRuntimeFromBackend,
|
|
16
|
+
getLangfuseRuntimeInternal,
|
|
17
|
+
type LangfuseRuntime,
|
|
18
|
+
type LangfuseRuntimeInternal,
|
|
19
|
+
} from "./runtime-core.js";
|
|
20
|
+
import type { Observation, ObservationAttributes, ObservationType, TraceBackend } from "./tracing.js";
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
CreateLangfuseRuntimeOptions,
|
|
24
|
+
LangfuseRuntime,
|
|
25
|
+
LangfuseRuntimeConfig,
|
|
26
|
+
} from "./runtime-core.js";
|
|
19
27
|
|
|
20
|
-
const RUNTIME_KEY = Symbol.for("@narumitw/pi-langfuse/runtime/
|
|
28
|
+
const RUNTIME_KEY = Symbol.for("@narumitw/pi-langfuse/runtime/v2");
|
|
29
|
+
const LEGACY_RUNTIME_KEY = Symbol.for("@narumitw/pi-langfuse/runtime/v1");
|
|
30
|
+
|
|
31
|
+
interface ResolvedRuntimeConfig {
|
|
32
|
+
publicKey: string;
|
|
33
|
+
secretKey: string;
|
|
34
|
+
baseUrl: string;
|
|
35
|
+
environment?: string;
|
|
36
|
+
release?: string;
|
|
37
|
+
}
|
|
21
38
|
|
|
22
39
|
interface SharedRuntime {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
fingerprint: string;
|
|
41
|
+
runtime: LangfuseRuntimeInternal;
|
|
42
|
+
shutdown: true;
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
type GlobalWithRuntime = typeof globalThis & {
|
|
29
|
-
|
|
46
|
+
[RUNTIME_KEY]?: Promise<SharedRuntime>;
|
|
47
|
+
[LEGACY_RUNTIME_KEY]?: unknown;
|
|
30
48
|
};
|
|
31
49
|
|
|
32
50
|
export interface RuntimeFactories {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
51
|
+
createProcessor(config: ResolvedRuntimeConfig): SpanProcessor;
|
|
52
|
+
createProvider(processor: SpanProcessor): NodeTracerProvider;
|
|
53
|
+
selectProvider(provider: NodeTracerProvider): void;
|
|
36
54
|
}
|
|
37
55
|
|
|
38
56
|
class ProductionObservation implements Observation {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
57
|
+
readonly traceId: string;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
readonly native: LangfuseObservation,
|
|
61
|
+
private readonly secrets: readonly string[],
|
|
62
|
+
) {
|
|
63
|
+
this.traceId = native.traceId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
update(attributes: ObservationAttributes): Observation {
|
|
67
|
+
const { sessionId, userId, ...observationAttributes } = maskObservationAttributes(attributes, this.secrets);
|
|
68
|
+
this.native.updateOtelSpanAttributes(observationAttributes as LangfuseObservationAttributes);
|
|
69
|
+
applySessionId(this.native, sessionId);
|
|
70
|
+
applyUserId(this.native, userId);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
updateTrace(attributes: ObservationAttributes): Observation {
|
|
75
|
+
const { input, output, metadata, name, sessionId, userId, tags, version } = maskObservationAttributes(
|
|
76
|
+
attributes,
|
|
77
|
+
this.secrets,
|
|
78
|
+
);
|
|
79
|
+
if (input !== undefined || output !== undefined) {
|
|
80
|
+
this.native.setTraceIO({ input, output });
|
|
81
|
+
}
|
|
82
|
+
if (name !== undefined) {
|
|
83
|
+
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_NAME, name);
|
|
84
|
+
}
|
|
85
|
+
applySessionId(this.native, sessionId);
|
|
86
|
+
applyUserId(this.native, userId);
|
|
87
|
+
if (tags !== undefined) {
|
|
88
|
+
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, tags);
|
|
89
|
+
}
|
|
90
|
+
if (version !== undefined) {
|
|
91
|
+
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.VERSION, version);
|
|
92
|
+
}
|
|
93
|
+
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
94
|
+
const serialized = serializeMetadataValue(value);
|
|
95
|
+
if (serialized !== undefined) {
|
|
96
|
+
this.native.otelSpan.setAttribute(`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`, serialized);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
end(endTime?: number): Observation {
|
|
103
|
+
this.native.end(endTime);
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
78
106
|
}
|
|
79
107
|
|
|
80
108
|
class ProductionTraceBackend implements TraceBackend {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
constructor(
|
|
110
|
+
private readonly provider: NodeTracerProvider,
|
|
111
|
+
private readonly processor: SpanProcessor,
|
|
112
|
+
private readonly secrets: readonly string[],
|
|
113
|
+
) {}
|
|
114
|
+
|
|
115
|
+
start(
|
|
116
|
+
name: string,
|
|
117
|
+
attributes: ObservationAttributes,
|
|
118
|
+
options: { asType: ObservationType; parent?: Observation; startTime?: Date },
|
|
119
|
+
): Observation {
|
|
120
|
+
const maskedName = maskSecretString(name, this.secrets);
|
|
121
|
+
const { sessionId, userId, ...observationAttributes } = maskObservationAttributes(attributes, this.secrets);
|
|
122
|
+
const parent = options.parent;
|
|
123
|
+
const native =
|
|
124
|
+
parent instanceof ProductionObservation
|
|
125
|
+
? startChild(parent.native, maskedName, observationAttributes, options.asType, options.startTime)
|
|
126
|
+
: startRoot(maskedName, observationAttributes, options.asType, options.startTime);
|
|
127
|
+
applySessionId(native, sessionId);
|
|
128
|
+
applyUserId(native, userId);
|
|
129
|
+
return new ProductionObservation(native, this.secrets);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async forceFlush(): Promise<void> {
|
|
133
|
+
await this.processor.forceFlush();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async shutdown(): Promise<void> {
|
|
137
|
+
await this.provider.shutdown();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function createLangfuseRuntime(options: CreateLangfuseRuntimeOptions = {}): Promise<LangfuseRuntime> {
|
|
142
|
+
return createSharedRuntime(resolveLangfuseRuntimeConfig(options), defaultFactories);
|
|
109
143
|
}
|
|
110
144
|
|
|
111
145
|
export async function createProductionBackend(
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
config: LangfuseConfig,
|
|
147
|
+
factoryOverrides: Partial<RuntimeFactories> = {},
|
|
114
148
|
): Promise<TraceBackend> {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
const runtime = await createSharedRuntime(resolveLangfuseRuntimeConfig({ config, env: false }), {
|
|
150
|
+
...defaultFactories,
|
|
151
|
+
...factoryOverrides,
|
|
152
|
+
});
|
|
153
|
+
return getLangfuseRuntimeInternal(runtime).backend;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function createSharedRuntime(
|
|
157
|
+
config: ResolvedRuntimeConfig,
|
|
158
|
+
factories: RuntimeFactories,
|
|
159
|
+
): Promise<LangfuseRuntime> {
|
|
160
|
+
const globalRuntime = globalThis as GlobalWithRuntime;
|
|
161
|
+
const fingerprint = configFingerprint(config);
|
|
162
|
+
const existing = globalRuntime[RUNTIME_KEY];
|
|
163
|
+
const legacyRuntime = globalRuntime[LEGACY_RUNTIME_KEY];
|
|
164
|
+
if (legacyRuntime !== undefined && legacyRuntime !== existing) {
|
|
165
|
+
throw new Error("An older Langfuse runtime is already loaded; restart the process before enabling this version.");
|
|
166
|
+
}
|
|
167
|
+
if (existing) {
|
|
168
|
+
if (legacyRuntime === undefined) globalRuntime[LEGACY_RUNTIME_KEY] = existing;
|
|
169
|
+
const shared = await existing;
|
|
170
|
+
if (shared.runtime.closed) {
|
|
171
|
+
throw new Error("Langfuse tracing was already shut down; restart the process to enable it again.");
|
|
172
|
+
}
|
|
173
|
+
if (shared.fingerprint !== fingerprint) {
|
|
174
|
+
throw new Error("Langfuse configuration changed; restart the process to apply the new credentials.");
|
|
175
|
+
}
|
|
176
|
+
return shared.runtime;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const initializing = initializeRuntime(config, fingerprint, factories);
|
|
180
|
+
globalRuntime[RUNTIME_KEY] = initializing;
|
|
181
|
+
globalRuntime[LEGACY_RUNTIME_KEY] = initializing;
|
|
182
|
+
try {
|
|
183
|
+
return (await initializing).runtime;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (globalRuntime[RUNTIME_KEY] === initializing) delete globalRuntime[RUNTIME_KEY];
|
|
186
|
+
if (globalRuntime[LEGACY_RUNTIME_KEY] === initializing) delete globalRuntime[LEGACY_RUNTIME_KEY];
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
138
189
|
}
|
|
139
190
|
|
|
140
191
|
async function initializeRuntime(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
192
|
+
config: ResolvedRuntimeConfig,
|
|
193
|
+
fingerprint: string,
|
|
194
|
+
factories: RuntimeFactories,
|
|
144
195
|
): Promise<SharedRuntime> {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
await originalShutdown();
|
|
161
|
-
};
|
|
162
|
-
return runtime;
|
|
196
|
+
const processor = factories.createProcessor(config);
|
|
197
|
+
let provider: NodeTracerProvider | undefined;
|
|
198
|
+
try {
|
|
199
|
+
provider = factories.createProvider(processor);
|
|
200
|
+
factories.selectProvider(provider);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
await (provider?.shutdown() ?? processor.shutdown()).catch(() => undefined);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
const runtime = getLangfuseRuntimeInternal(
|
|
206
|
+
createLangfuseRuntimeFromBackend(
|
|
207
|
+
new ProductionTraceBackend(provider, processor, [config.publicKey, config.secretKey]),
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
return { fingerprint, runtime, shutdown: true };
|
|
163
211
|
}
|
|
164
212
|
|
|
165
213
|
const defaultFactories: RuntimeFactories = {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
selectProvider: setLangfuseTracerProvider,
|
|
214
|
+
createProcessor: (config) => {
|
|
215
|
+
const secrets = [config.secretKey, config.publicKey];
|
|
216
|
+
return new LangfuseSpanProcessor({
|
|
217
|
+
publicKey: config.publicKey,
|
|
218
|
+
secretKey: config.secretKey,
|
|
219
|
+
baseUrl: config.baseUrl,
|
|
220
|
+
environment: config.environment ?? "",
|
|
221
|
+
release: config.release ?? "",
|
|
222
|
+
flushAt: 512,
|
|
223
|
+
flushInterval: 5,
|
|
224
|
+
timeout: 5,
|
|
225
|
+
mask: ({ data }) => maskSecrets(data, secrets),
|
|
226
|
+
shouldExportSpan: ({ otelSpan }) => typeof otelSpan.attributes["langfuse.observation.type"] === "string",
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
createProvider: (processor) => new NodeTracerProvider({ spanProcessors: [processor] }),
|
|
230
|
+
selectProvider: setLangfuseTracerProvider,
|
|
184
231
|
};
|
|
185
232
|
|
|
233
|
+
export function resolveLangfuseRuntimeConfig(options: CreateLangfuseRuntimeOptions): ResolvedRuntimeConfig {
|
|
234
|
+
const env = options.env === false ? undefined : (options.env ?? process.env);
|
|
235
|
+
const publicKey = requiredSetting("publicKey", options.config?.publicKey, env?.LANGFUSE_PUBLIC_KEY);
|
|
236
|
+
const secretKey = requiredSetting("secretKey", options.config?.secretKey, env?.LANGFUSE_SECRET_KEY);
|
|
237
|
+
const explicitBaseUrl = options.config?.baseUrl;
|
|
238
|
+
if (explicitBaseUrl !== undefined && !normalizeString(explicitBaseUrl)) {
|
|
239
|
+
throw new Error("Langfuse baseUrl must be a non-empty string.");
|
|
240
|
+
}
|
|
241
|
+
const rawBaseUrl = selectedSetting(explicitBaseUrl, env?.LANGFUSE_BASE_URL) ?? DEFAULT_BASE_URL;
|
|
242
|
+
const baseUrl = normalizeBaseUrl(rawBaseUrl);
|
|
243
|
+
if (!baseUrl) {
|
|
244
|
+
throw new Error("Langfuse baseUrl must use HTTP or HTTPS without credentials, a query, or a fragment.");
|
|
245
|
+
}
|
|
246
|
+
const environment = optionalSetting("environment", options.config?.environment, env?.LANGFUSE_TRACING_ENVIRONMENT);
|
|
247
|
+
if (environment && (environment.length > 40 || !/^(?!langfuse)[a-z0-9_-]+$/u.test(environment))) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
"Langfuse environment must be at most 40 lowercase letters, numbers, hyphens, or underscores and must not start with langfuse.",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const release = optionalSetting("release", options.config?.release, env?.LANGFUSE_RELEASE);
|
|
253
|
+
return { publicKey, secretKey, baseUrl, ...(environment ? { environment } : {}), ...(release ? { release } : {}) };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function requiredSetting(name: string, explicit: string | undefined, environment: string | undefined): string {
|
|
257
|
+
const value = selectedSetting(explicit, environment);
|
|
258
|
+
if (!value) throw new Error(`Langfuse ${name} is required.`);
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function optionalSetting(
|
|
263
|
+
name: string,
|
|
264
|
+
explicit: string | undefined,
|
|
265
|
+
environment: string | undefined,
|
|
266
|
+
): string | undefined {
|
|
267
|
+
const selected = explicit !== undefined ? explicit : environment;
|
|
268
|
+
if (selected === undefined) return undefined;
|
|
269
|
+
const value = normalizeString(selected);
|
|
270
|
+
if (!value) throw new Error(`Langfuse ${name} must be a non-empty string.`);
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function selectedSetting(explicit: string | undefined, environment: string | undefined): string | undefined {
|
|
275
|
+
if (explicit !== undefined) return normalizeString(explicit);
|
|
276
|
+
return normalizeString(environment);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function normalizeBaseUrl(value: string): string | undefined {
|
|
280
|
+
try {
|
|
281
|
+
const url = new URL(value);
|
|
282
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return undefined;
|
|
283
|
+
if (url.username || url.password || url.search || url.hash) return undefined;
|
|
284
|
+
return url.toString().replace(/\/+$/, "");
|
|
285
|
+
} catch {
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizeString(value: unknown): string | undefined {
|
|
291
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function serializeMetadataValue(value: unknown): string | undefined {
|
|
295
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function maskObservationAttributes(
|
|
299
|
+
attributes: ObservationAttributes,
|
|
300
|
+
secrets: readonly string[],
|
|
301
|
+
): ObservationAttributes {
|
|
302
|
+
return {
|
|
303
|
+
...attributes,
|
|
304
|
+
...(attributes.metadata ? { metadata: maskSecrets(attributes.metadata, secrets) as Record<string, unknown> } : {}),
|
|
305
|
+
...(attributes.name ? { name: maskSecretString(attributes.name, secrets) } : {}),
|
|
306
|
+
...(attributes.sessionId ? { sessionId: maskSecretString(attributes.sessionId, secrets) } : {}),
|
|
307
|
+
...(attributes.userId ? { userId: maskSecretString(attributes.userId, secrets) } : {}),
|
|
308
|
+
...(attributes.tags ? { tags: attributes.tags.map((tag) => maskSecretString(tag, secrets)) } : {}),
|
|
309
|
+
...(attributes.statusMessage ? { statusMessage: maskSecretString(attributes.statusMessage, secrets) } : {}),
|
|
310
|
+
...(attributes.model ? { model: maskSecretString(attributes.model, secrets) } : {}),
|
|
311
|
+
...(attributes.version ? { version: maskSecretString(attributes.version, secrets) } : {}),
|
|
312
|
+
...(attributes.modelParameters
|
|
313
|
+
? {
|
|
314
|
+
modelParameters: Object.fromEntries(
|
|
315
|
+
Object.entries(attributes.modelParameters).map(([key, value]) => [
|
|
316
|
+
maskSecretString(key, secrets),
|
|
317
|
+
typeof value === "string" ? maskSecretString(value, secrets) : value,
|
|
318
|
+
]),
|
|
319
|
+
),
|
|
320
|
+
}
|
|
321
|
+
: {}),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function maskSecretString(value: string, secrets: readonly string[]): string {
|
|
326
|
+
return maskSecrets(value, secrets) as string;
|
|
327
|
+
}
|
|
328
|
+
|
|
186
329
|
function applySessionId(observation: LangfuseObservation, sessionId: string | undefined): void {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
330
|
+
if (sessionId !== undefined) {
|
|
331
|
+
observation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_SESSION_ID, sessionId);
|
|
332
|
+
}
|
|
190
333
|
}
|
|
191
334
|
|
|
192
335
|
function applyUserId(observation: LangfuseObservation, userId: string | undefined): void {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
336
|
+
if (userId !== undefined) {
|
|
337
|
+
observation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_USER_ID, userId);
|
|
338
|
+
}
|
|
196
339
|
}
|
|
197
340
|
|
|
198
341
|
function startRoot(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
342
|
+
name: string,
|
|
343
|
+
attributes: ObservationAttributes,
|
|
344
|
+
type: ObservationType,
|
|
345
|
+
startTime?: Date,
|
|
202
346
|
): LangfuseObservation {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
347
|
+
if (type === "agent") {
|
|
348
|
+
return startObservation(name, attributes as LangfuseObservationAttributes, { asType: "agent", startTime });
|
|
349
|
+
}
|
|
350
|
+
if (type === "generation") {
|
|
351
|
+
return startObservation(name, attributes as LangfuseObservationAttributes, {
|
|
352
|
+
asType: "generation",
|
|
353
|
+
startTime,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
if (type === "tool") {
|
|
357
|
+
return startObservation(name, attributes as LangfuseObservationAttributes, { asType: "tool", startTime });
|
|
358
|
+
}
|
|
359
|
+
return startObservation(name, attributes as LangfuseObservationAttributes, { asType: "span", startTime });
|
|
215
360
|
}
|
|
216
361
|
|
|
217
362
|
function startChild(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
363
|
+
parent: LangfuseObservation,
|
|
364
|
+
name: string,
|
|
365
|
+
attributes: ObservationAttributes,
|
|
366
|
+
type: ObservationType,
|
|
367
|
+
startTime?: Date,
|
|
222
368
|
): LangfuseObservation {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
369
|
+
if (startTime) {
|
|
370
|
+
const parentSpanContext = parent.otelSpan.spanContext();
|
|
371
|
+
if (type === "agent") {
|
|
372
|
+
return startObservation(name, attributes as LangfuseObservationAttributes, {
|
|
373
|
+
asType: "agent",
|
|
374
|
+
parentSpanContext,
|
|
375
|
+
startTime,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
if (type === "generation") {
|
|
379
|
+
return startObservation(name, attributes as LangfuseObservationAttributes, {
|
|
380
|
+
asType: "generation",
|
|
381
|
+
parentSpanContext,
|
|
382
|
+
startTime,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
if (type === "tool") {
|
|
386
|
+
return startObservation(name, attributes as LangfuseObservationAttributes, {
|
|
387
|
+
asType: "tool",
|
|
388
|
+
parentSpanContext,
|
|
389
|
+
startTime,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return startObservation(name, attributes as LangfuseObservationAttributes, {
|
|
393
|
+
asType: "span",
|
|
394
|
+
parentSpanContext,
|
|
395
|
+
startTime,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (type === "agent") {
|
|
399
|
+
return parent.startObservation(name, attributes as LangfuseObservationAttributes, { asType: "agent" });
|
|
400
|
+
}
|
|
401
|
+
if (type === "generation") {
|
|
402
|
+
return parent.startObservation(name, attributes as LangfuseObservationAttributes, { asType: "generation" });
|
|
403
|
+
}
|
|
404
|
+
if (type === "tool") {
|
|
405
|
+
return parent.startObservation(name, attributes as LangfuseObservationAttributes, { asType: "tool" });
|
|
406
|
+
}
|
|
407
|
+
return parent.startObservation(name, attributes as LangfuseObservationAttributes, { asType: "span" });
|
|
241
408
|
}
|
|
242
409
|
|
|
243
|
-
function configFingerprint(config:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
410
|
+
function configFingerprint(config: ResolvedRuntimeConfig): string {
|
|
411
|
+
return createHash("sha256")
|
|
412
|
+
.update(
|
|
413
|
+
`${config.publicKey}\0${config.secretKey}\0${config.baseUrl}\0${config.environment ?? ""}\0${config.release ?? ""}`,
|
|
414
|
+
)
|
|
415
|
+
.digest("hex");
|
|
249
416
|
}
|
|
250
417
|
|
|
251
418
|
export function maskSecrets(data: unknown, secrets: readonly string[]): unknown {
|
|
252
|
-
|
|
419
|
+
return maskSecretValue(data, secrets, new WeakSet<object>());
|
|
253
420
|
}
|
|
254
421
|
|
|
255
|
-
function maskSecretValue(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
422
|
+
function maskSecretValue(data: unknown, secrets: readonly string[], active: WeakSet<object>): unknown {
|
|
423
|
+
if (typeof data === "string") {
|
|
424
|
+
let masked = data.replace(/\b(?:sk|pk)-lf-[A-Za-z0-9_-]+\b/g, "[LANGFUSE_KEY_REDACTED]");
|
|
425
|
+
for (const secret of secrets) {
|
|
426
|
+
if (secret) masked = masked.replaceAll(secret, "[LANGFUSE_KEY_REDACTED]");
|
|
427
|
+
}
|
|
428
|
+
return masked;
|
|
429
|
+
}
|
|
430
|
+
if (!data || typeof data !== "object") return data;
|
|
431
|
+
if (active.has(data)) return "[circular]";
|
|
432
|
+
|
|
433
|
+
active.add(data);
|
|
434
|
+
try {
|
|
435
|
+
if (Array.isArray(data)) {
|
|
436
|
+
return data.map((item) => maskSecretValue(item, secrets, active));
|
|
437
|
+
}
|
|
438
|
+
return Object.fromEntries(
|
|
439
|
+
Object.entries(data).map(([key, value]) => [
|
|
440
|
+
maskSecretString(key, secrets),
|
|
441
|
+
maskSecretValue(value, secrets, active),
|
|
442
|
+
]),
|
|
443
|
+
);
|
|
444
|
+
} finally {
|
|
445
|
+
active.delete(data);
|
|
446
|
+
}
|
|
281
447
|
}
|