@librechat/agents 3.2.43 → 3.2.45
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/dist/cjs/graphs/Graph.cjs +17 -8
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +20 -21
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +65 -13
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseConfig.cjs +106 -0
- package/dist/cjs/langfuseConfig.cjs.map +1 -0
- package/dist/cjs/langfuseRuntimeContext.cjs +48 -0
- package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -0
- package/dist/cjs/langfuseRuntimeScope.cjs +57 -0
- package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs +14 -119
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/messages/reducer.cjs +15 -2
- package/dist/cjs/messages/reducer.cjs.map +1 -1
- package/dist/cjs/run.cjs +25 -12
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +5 -2
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/utils/misc.cjs +17 -0
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +16 -7
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +22 -22
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +65 -14
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseConfig.mjs +102 -0
- package/dist/esm/langfuseConfig.mjs.map +1 -0
- package/dist/esm/langfuseRuntimeContext.mjs +44 -0
- package/dist/esm/langfuseRuntimeContext.mjs.map +1 -0
- package/dist/esm/langfuseRuntimeScope.mjs +53 -0
- package/dist/esm/langfuseRuntimeScope.mjs.map +1 -0
- package/dist/esm/langfuseToolOutputTracing.mjs +13 -115
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/reducer.mjs +15 -2
- package/dist/esm/messages/reducer.mjs.map +1 -1
- package/dist/esm/run.mjs +21 -8
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +5 -2
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/utils/misc.mjs +17 -1
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/types/instrumentation.d.ts +0 -1
- package/dist/types/langfuse.d.ts +5 -1
- package/dist/types/langfuseConfig.d.ts +7 -0
- package/dist/types/langfuseRuntimeContext.d.ts +26 -0
- package/dist/types/langfuseRuntimeScope.d.ts +17 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +4 -11
- package/dist/types/types/graph.d.ts +6 -0
- package/dist/types/utils/misc.d.ts +1 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +20 -13
- package/src/instrumentation.ts +33 -29
- package/src/langfuse.ts +114 -7
- package/src/langfuseConfig.ts +214 -0
- package/src/langfuseRuntimeContext.ts +93 -0
- package/src/langfuseRuntimeScope.ts +135 -0
- package/src/langfuseToolOutputTracing.ts +25 -263
- package/src/messages/reducer.spec.ts +32 -0
- package/src/messages/reducer.ts +23 -7
- package/src/run.ts +44 -34
- package/src/specs/deterministic-trace-id.test.ts +4 -11
- package/src/specs/langfuse-callbacks.test.ts +278 -22
- package/src/specs/langfuse-config.test.ts +10 -3
- package/src/specs/langfuse-instrumentation.test.ts +32 -12
- package/src/specs/langfuse-routing.integration.test.ts +543 -0
- package/src/specs/langfuse-runtime-context.test.ts +92 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +353 -5
- package/src/tools/ToolNode.ts +10 -5
- package/src/tools/__tests__/ToolNode.langfuse.test.ts +21 -11
- package/src/types/graph.ts +9 -0
- package/src/utils/misc.ts +18 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { traceIdFromSeed } from "./langfuseRuntimeContext.mjs";
|
|
1
2
|
import { isPresent } from "./utils/misc.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { resolveLangfuseConfigForSpan, resolveTraceIdSeedForSpan } from "./langfuseRuntimeScope.mjs";
|
|
4
|
+
import { createLibreChatTraceAttributes, hasLangfuseConfigCredentials, hasLangfuseEnvConfig, hasLangfuseEnvCredentials } from "./langfuse.mjs";
|
|
5
|
+
import { createLangfuseSpanProcessor } from "./langfuseToolOutputTracing.mjs";
|
|
6
6
|
import { ROOT_CONTEXT, context, createContextKey } from "@opentelemetry/api";
|
|
7
|
+
import { setLangfuseTracerProvider } from "@langfuse/tracing";
|
|
7
8
|
import { createHash, randomBytes } from "node:crypto";
|
|
8
9
|
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
|
|
9
10
|
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
|
|
@@ -11,24 +12,17 @@ import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-ho
|
|
|
11
12
|
/**
|
|
12
13
|
* Per-run seed for deterministic Langfuse trace ids. When a run opts in
|
|
13
14
|
* (`LangfuseConfig.deterministicTraceId`), it executes its stream inside
|
|
14
|
-
* `runWithTraceIdSeed(runId,
|
|
15
|
-
* trace id from that seed instead of a
|
|
16
|
-
* (e.g. a host app recording user
|
|
17
|
-
*
|
|
18
|
-
* id
|
|
19
|
-
* ids, so default behavior is
|
|
15
|
+
* `runWithTraceIdSeed(runId, ...)` from `./langfuseRuntimeContext`, and the
|
|
16
|
+
* IdGenerator below derives the root trace id from that seed instead of a
|
|
17
|
+
* random one. This lets external systems (e.g. a host app recording user
|
|
18
|
+
* feedback after the fact) attach scores or observations to the trace by
|
|
19
|
+
* regenerating the same id from the run/message id; no trace lookup required.
|
|
20
|
+
* With no active seed it falls back to random ids, so default behavior is
|
|
21
|
+
* unchanged.
|
|
20
22
|
*/
|
|
21
|
-
const traceIdSeedStore = new AsyncLocalStorage();
|
|
22
|
-
function runWithTraceIdSeed(seed, fn) {
|
|
23
|
-
return isPresent(seed) ? traceIdSeedStore.run(seed, fn) : fn();
|
|
24
|
-
}
|
|
25
|
-
/** sha256(seed) → first 32 hex chars; matches `@langfuse/tracing` `createTraceId`. */
|
|
26
|
-
function traceIdFromSeed(seed) {
|
|
27
|
-
return createHash("sha256").update(seed, "utf8").digest("hex").slice(0, 32);
|
|
28
|
-
}
|
|
29
23
|
var SeededTraceIdGenerator = class {
|
|
30
24
|
generateTraceId() {
|
|
31
|
-
const seed =
|
|
25
|
+
const seed = resolveTraceIdSeedForSpan(context.active());
|
|
32
26
|
return isPresent(seed) ? traceIdFromSeed(seed) : randomBytes(16).toString("hex");
|
|
33
27
|
}
|
|
34
28
|
generateSpanId() {
|
|
@@ -68,10 +62,13 @@ function getLangfuseSpanProcessorParams(langfuse) {
|
|
|
68
62
|
baseUrl: langfuse.baseUrl
|
|
69
63
|
};
|
|
70
64
|
}
|
|
65
|
+
function hashCacheKeyValue(value) {
|
|
66
|
+
return isPresent(value) ? createHash("sha256").update(value, "utf8").digest("hex") : void 0;
|
|
67
|
+
}
|
|
71
68
|
function getLangfuseTracerProviderKey(params, langfuse) {
|
|
72
69
|
return JSON.stringify({
|
|
73
70
|
publicKey: params.publicKey,
|
|
74
|
-
|
|
71
|
+
secretKeyHash: hashCacheKeyValue(params.secretKey),
|
|
75
72
|
baseUrl: params.baseUrl,
|
|
76
73
|
environment: params.environment,
|
|
77
74
|
toolOutputTracing: langfuse?.toolOutputTracing
|
|
@@ -91,8 +88,11 @@ var RoutingLangfuseSpanProcessor = class {
|
|
|
91
88
|
return processor;
|
|
92
89
|
}
|
|
93
90
|
onStart(span, parentContext) {
|
|
94
|
-
const
|
|
91
|
+
const langfuse = resolveLangfuseConfigForSpan(parentContext);
|
|
92
|
+
const processor = this.ensureProcessor(langfuse);
|
|
95
93
|
if (processor == null) return;
|
|
94
|
+
const librechatTraceAttributes = createLibreChatTraceAttributes(langfuse?.librechatTraceAttributes ?? {});
|
|
95
|
+
if (Object.keys(librechatTraceAttributes).length > 0) span.setAttributes(librechatTraceAttributes);
|
|
96
96
|
this.spanProcessors.set(span, processor);
|
|
97
97
|
processor.onStart(span, parentContext);
|
|
98
98
|
}
|
|
@@ -127,6 +127,6 @@ function initializeLangfuseTracingFromEnv() {
|
|
|
127
127
|
}
|
|
128
128
|
initializeLangfuseTracingFromEnv();
|
|
129
129
|
//#endregion
|
|
130
|
-
export { initializeLangfuseTracing
|
|
130
|
+
export { initializeLangfuseTracing };
|
|
131
131
|
|
|
132
132
|
//# sourceMappingURL=instrumentation.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.mjs","names":[],"sources":["../../src/instrumentation.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { createHash, randomBytes } from 'node:crypto';\nimport { setLangfuseTracerProvider } from '@langfuse/tracing';\nimport { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport { context, ROOT_CONTEXT, createContextKey } from '@opentelemetry/api';\nimport { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';\nimport type {\n IdGenerator,\n ReadableSpan,\n Span,\n SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport type { LangfuseSpanProcessorParams } from '@langfuse/otel';\nimport type { Context } from '@opentelemetry/api';\nimport type * as t from '@/types';\nimport {\n hasLangfuseConfigCredentials,\n hasLangfuseEnvCredentials,\n hasLangfuseEnvConfig,\n} from '@/langfuse';\nimport {\n createLangfuseSpanProcessor,\n getContextLangfuseConfig,\n} from '@/langfuseToolOutputTracing';\nimport { isPresent } from '@/utils/misc';\n\n/**\n * Per-run seed for deterministic Langfuse trace ids. When a run opts in\n * (`LangfuseConfig.deterministicTraceId`), it executes its stream inside\n * `runWithTraceIdSeed(runId, …)` and the IdGenerator below derives the root\n * trace id from that seed instead of a random one. This lets external systems\n * (e.g. a host app recording user feedback after the fact) attach scores or\n * observations to the trace by regenerating the same id from the run/message\n * id — no trace lookup required. With no active seed it falls back to random\n * ids, so default behavior is unchanged.\n */\nconst traceIdSeedStore = new AsyncLocalStorage<string>();\n\nexport function runWithTraceIdSeed<T>(\n seed: string | undefined,\n fn: () => T\n): T {\n return isPresent(seed) ? traceIdSeedStore.run(seed, fn) : fn();\n}\n\n/** sha256(seed) → first 32 hex chars; matches `@langfuse/tracing` `createTraceId`. */\nfunction traceIdFromSeed(seed: string): string {\n return createHash('sha256').update(seed, 'utf8').digest('hex').slice(0, 32);\n}\n\nclass SeededTraceIdGenerator implements IdGenerator {\n generateTraceId(): string {\n const seed = traceIdSeedStore.getStore();\n return isPresent(seed)\n ? traceIdFromSeed(seed)\n : randomBytes(16).toString('hex');\n }\n\n generateSpanId(): string {\n return randomBytes(8).toString('hex');\n }\n}\n\nlet langfuseTracerProvider: BasicTracerProvider | undefined;\nlet langfuseRoutingSpanProcessor: RoutingLangfuseSpanProcessor | undefined;\nconst contextManagerProbeKey = createContextKey(\n 'langfuse-context-manager-probe'\n);\n\nfunction hasActiveContextManager(): boolean {\n return context.with(\n ROOT_CONTEXT.setValue(contextManagerProbeKey, true),\n () => context.active().getValue(contextManagerProbeKey) === true\n );\n}\n\nexport function ensureOpenTelemetryContextManager(): void {\n if (hasActiveContextManager()) {\n return;\n }\n\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n if (!context.setGlobalContextManager(contextManager)) {\n contextManager.disable();\n }\n}\n\nfunction getLangfuseSpanProcessorParams(\n langfuse?: t.LangfuseConfig\n): LangfuseSpanProcessorParams | undefined {\n if (langfuse?.enabled === false) {\n return undefined;\n }\n if (hasLangfuseConfigCredentials(langfuse)) {\n return {\n publicKey: langfuse.publicKey,\n secretKey: langfuse.secretKey,\n ...(isPresent(langfuse.baseUrl) ? { baseUrl: langfuse.baseUrl } : {}),\n };\n }\n if (hasLangfuseEnvConfig()) {\n const baseUrl =\n langfuse?.baseUrl ??\n process.env.LANGFUSE_BASE_URL ??\n process.env.LANGFUSE_BASEURL;\n return {\n publicKey: process.env.LANGFUSE_PUBLIC_KEY as string,\n secretKey: process.env.LANGFUSE_SECRET_KEY as string,\n ...(isPresent(baseUrl) ? { baseUrl } : {}),\n };\n }\n if (isPresent(langfuse?.baseUrl) && hasLangfuseEnvCredentials()) {\n return {\n publicKey: process.env.LANGFUSE_PUBLIC_KEY as string,\n secretKey: process.env.LANGFUSE_SECRET_KEY as string,\n baseUrl: langfuse.baseUrl,\n };\n }\n return undefined;\n}\n\nfunction getLangfuseTracerProviderKey(\n params: LangfuseSpanProcessorParams,\n langfuse?: t.LangfuseConfig\n): string {\n return JSON.stringify({\n publicKey: params.publicKey,\n secretKey: params.secretKey,\n baseUrl: params.baseUrl,\n environment: params.environment,\n toolOutputTracing: langfuse?.toolOutputTracing,\n });\n}\n\nclass RoutingLangfuseSpanProcessor implements SpanProcessor {\n private readonly processors = new Map<string, SpanProcessor>();\n private readonly spanProcessors = new WeakMap<object, SpanProcessor>();\n\n ensureProcessor(langfuse?: t.LangfuseConfig): SpanProcessor | undefined {\n const params = getLangfuseSpanProcessorParams(langfuse);\n if (params == null) {\n return undefined;\n }\n\n const processorKey = getLangfuseTracerProviderKey(params, langfuse);\n const existing = this.processors.get(processorKey);\n if (existing != null) {\n return existing;\n }\n\n const processor = createLangfuseSpanProcessor(params, langfuse);\n this.processors.set(processorKey, processor);\n return processor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n const processor = this.ensureProcessor(\n getContextLangfuseConfig(parentContext)\n );\n if (processor == null) {\n return;\n }\n\n this.spanProcessors.set(span, processor);\n processor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n this.spanProcessors.get(span)?.onEnd(span);\n }\n\n async forceFlush(): Promise<void> {\n await Promise.all(\n Array.from(this.processors.values(), (processor) =>\n processor.forceFlush()\n )\n );\n }\n\n async shutdown(): Promise<void> {\n await Promise.all(\n Array.from(this.processors.values(), (processor) => processor.shutdown())\n );\n }\n}\n\nexport function initializeLangfuseTracing(\n langfuse?: t.LangfuseConfig\n): BasicTracerProvider | undefined {\n const params = getLangfuseSpanProcessorParams(langfuse);\n if (params == null) {\n return undefined;\n }\n\n if (langfuseTracerProvider != null) {\n langfuseRoutingSpanProcessor?.ensureProcessor(langfuse);\n return langfuseTracerProvider;\n }\n\n ensureOpenTelemetryContextManager();\n langfuseRoutingSpanProcessor = new RoutingLangfuseSpanProcessor();\n langfuseRoutingSpanProcessor.ensureProcessor(langfuse);\n langfuseTracerProvider = new BasicTracerProvider({\n spanProcessors: [langfuseRoutingSpanProcessor],\n idGenerator: new SeededTraceIdGenerator(),\n });\n\n setLangfuseTracerProvider(langfuseTracerProvider);\n return langfuseTracerProvider;\n}\n\nexport function initializeLangfuseTracingFromEnv():\n | BasicTracerProvider\n | undefined {\n return initializeLangfuseTracing();\n}\n\ninitializeLangfuseTracingFromEnv();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instrumentation.mjs","names":[],"sources":["../../src/instrumentation.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport { setLangfuseTracerProvider } from '@langfuse/tracing';\nimport { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport { context, ROOT_CONTEXT, createContextKey } from '@opentelemetry/api';\nimport { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';\nimport type {\n IdGenerator,\n ReadableSpan,\n Span,\n SpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport type { LangfuseSpanProcessorParams } from '@langfuse/otel';\nimport type { Context } from '@opentelemetry/api';\nimport type * as t from '@/types';\nimport {\n createLibreChatTraceAttributes,\n hasLangfuseConfigCredentials,\n hasLangfuseEnvCredentials,\n hasLangfuseEnvConfig,\n} from '@/langfuse';\nimport {\n resolveLangfuseConfigForSpan,\n resolveTraceIdSeedForSpan,\n} from '@/langfuseRuntimeScope';\nimport { createLangfuseSpanProcessor } from '@/langfuseToolOutputTracing';\nimport { traceIdFromSeed } from '@/langfuseRuntimeContext';\nimport { isPresent } from '@/utils/misc';\n\n/**\n * Per-run seed for deterministic Langfuse trace ids. When a run opts in\n * (`LangfuseConfig.deterministicTraceId`), it executes its stream inside\n * `runWithTraceIdSeed(runId, ...)` from `./langfuseRuntimeContext`, and the\n * IdGenerator below derives the root trace id from that seed instead of a\n * random one. This lets external systems (e.g. a host app recording user\n * feedback after the fact) attach scores or observations to the trace by\n * regenerating the same id from the run/message id; no trace lookup required.\n * With no active seed it falls back to random ids, so default behavior is\n * unchanged.\n */\nclass SeededTraceIdGenerator implements IdGenerator {\n generateTraceId(): string {\n const seed = resolveTraceIdSeedForSpan(context.active());\n return isPresent(seed)\n ? traceIdFromSeed(seed)\n : randomBytes(16).toString('hex');\n }\n\n generateSpanId(): string {\n return randomBytes(8).toString('hex');\n }\n}\n\nlet langfuseTracerProvider: BasicTracerProvider | undefined;\nlet langfuseRoutingSpanProcessor: RoutingLangfuseSpanProcessor | undefined;\nconst contextManagerProbeKey = createContextKey(\n 'langfuse-context-manager-probe'\n);\n\nfunction hasActiveContextManager(): boolean {\n return context.with(\n ROOT_CONTEXT.setValue(contextManagerProbeKey, true),\n () => context.active().getValue(contextManagerProbeKey) === true\n );\n}\n\nexport function ensureOpenTelemetryContextManager(): void {\n if (hasActiveContextManager()) {\n return;\n }\n\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n if (!context.setGlobalContextManager(contextManager)) {\n contextManager.disable();\n }\n}\n\nfunction getLangfuseSpanProcessorParams(\n langfuse?: t.LangfuseConfig\n): LangfuseSpanProcessorParams | undefined {\n if (langfuse?.enabled === false) {\n return undefined;\n }\n if (hasLangfuseConfigCredentials(langfuse)) {\n return {\n publicKey: langfuse.publicKey,\n secretKey: langfuse.secretKey,\n ...(isPresent(langfuse.baseUrl) ? { baseUrl: langfuse.baseUrl } : {}),\n };\n }\n if (hasLangfuseEnvConfig()) {\n const baseUrl =\n langfuse?.baseUrl ??\n process.env.LANGFUSE_BASE_URL ??\n process.env.LANGFUSE_BASEURL;\n return {\n publicKey: process.env.LANGFUSE_PUBLIC_KEY as string,\n secretKey: process.env.LANGFUSE_SECRET_KEY as string,\n ...(isPresent(baseUrl) ? { baseUrl } : {}),\n };\n }\n if (isPresent(langfuse?.baseUrl) && hasLangfuseEnvCredentials()) {\n return {\n publicKey: process.env.LANGFUSE_PUBLIC_KEY as string,\n secretKey: process.env.LANGFUSE_SECRET_KEY as string,\n baseUrl: langfuse.baseUrl,\n };\n }\n return undefined;\n}\n\nfunction hashCacheKeyValue(value: string | undefined): string | undefined {\n return isPresent(value)\n ? createHash('sha256').update(value, 'utf8').digest('hex')\n : undefined;\n}\n\nfunction getLangfuseTracerProviderKey(\n params: LangfuseSpanProcessorParams,\n langfuse?: t.LangfuseConfig\n): string {\n return JSON.stringify({\n publicKey: params.publicKey,\n secretKeyHash: hashCacheKeyValue(params.secretKey),\n baseUrl: params.baseUrl,\n environment: params.environment,\n toolOutputTracing: langfuse?.toolOutputTracing,\n });\n}\n\nclass RoutingLangfuseSpanProcessor implements SpanProcessor {\n // Processors live for the process lifetime. LibreChat tenant Langfuse\n // destinations are expected to be a bounded admin-managed set, and shutdown\n // drains every cached processor when the provider is disposed.\n private readonly processors = new Map<string, SpanProcessor>();\n private readonly spanProcessors = new WeakMap<object, SpanProcessor>();\n\n ensureProcessor(langfuse?: t.LangfuseConfig): SpanProcessor | undefined {\n const params = getLangfuseSpanProcessorParams(langfuse);\n if (params == null) {\n return undefined;\n }\n\n const processorKey = getLangfuseTracerProviderKey(params, langfuse);\n const existing = this.processors.get(processorKey);\n if (existing != null) {\n return existing;\n }\n\n const processor = createLangfuseSpanProcessor(params, langfuse);\n this.processors.set(processorKey, processor);\n return processor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n const langfuse = resolveLangfuseConfigForSpan(parentContext);\n const processor = this.ensureProcessor(langfuse);\n if (processor == null) {\n return;\n }\n\n const librechatTraceAttributes = createLibreChatTraceAttributes(\n langfuse?.librechatTraceAttributes ?? {}\n );\n if (Object.keys(librechatTraceAttributes).length > 0) {\n span.setAttributes(librechatTraceAttributes);\n }\n\n this.spanProcessors.set(span, processor);\n processor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n this.spanProcessors.get(span)?.onEnd(span);\n }\n\n async forceFlush(): Promise<void> {\n await Promise.all(\n Array.from(this.processors.values(), (processor) =>\n processor.forceFlush()\n )\n );\n }\n\n async shutdown(): Promise<void> {\n await Promise.all(\n Array.from(this.processors.values(), (processor) => processor.shutdown())\n );\n }\n}\n\nexport function initializeLangfuseTracing(\n langfuse?: t.LangfuseConfig\n): BasicTracerProvider | undefined {\n const params = getLangfuseSpanProcessorParams(langfuse);\n if (params == null) {\n return undefined;\n }\n\n if (langfuseTracerProvider != null) {\n langfuseRoutingSpanProcessor?.ensureProcessor(langfuse);\n return langfuseTracerProvider;\n }\n\n ensureOpenTelemetryContextManager();\n langfuseRoutingSpanProcessor = new RoutingLangfuseSpanProcessor();\n langfuseRoutingSpanProcessor.ensureProcessor(langfuse);\n langfuseTracerProvider = new BasicTracerProvider({\n spanProcessors: [langfuseRoutingSpanProcessor],\n idGenerator: new SeededTraceIdGenerator(),\n });\n\n setLangfuseTracerProvider(langfuseTracerProvider);\n return langfuseTracerProvider;\n}\n\nexport function initializeLangfuseTracingFromEnv():\n | BasicTracerProvider\n | undefined {\n return initializeLangfuseTracing();\n}\n\ninitializeLangfuseTracingFromEnv();\n"],"mappings":"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"}
|
package/dist/esm/langfuse.mjs
CHANGED
|
@@ -1,18 +1,54 @@
|
|
|
1
|
-
import { isPresent } from "./utils/misc.mjs";
|
|
1
|
+
import { isPresent, parseBooleanEnv } from "./utils/misc.mjs";
|
|
2
|
+
import { resolveLangfuseConfigForSpan, resolveTraceIdSeedForSpan, withLangfuseRuntimeScope } from "./langfuseRuntimeScope.mjs";
|
|
2
3
|
import { CallbackHandler } from "@langfuse/langchain";
|
|
4
|
+
import { context } from "@opentelemetry/api";
|
|
3
5
|
import { getLangfuseTracerProvider, propagateAttributes } from "@langfuse/tracing";
|
|
4
6
|
//#region src/langfuse.ts
|
|
5
7
|
const TRACE_METADATA_MAX_LENGTH = 200;
|
|
6
8
|
const LANGFUSE_FORCE_FLUSH_ON_DISPOSE = "LANGFUSE_FORCE_FLUSH_ON_DISPOSE";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
9
|
+
var ScopedLangfuseCallbackHandler = class extends CallbackHandler {
|
|
10
|
+
langfuse;
|
|
11
|
+
traceIdSeed;
|
|
12
|
+
constructor(params) {
|
|
13
|
+
const { langfuse, traceIdSeed, ...handlerParams } = params ?? {};
|
|
14
|
+
super(handlerParams);
|
|
15
|
+
this.langfuse = langfuse;
|
|
16
|
+
this.traceIdSeed = traceIdSeed;
|
|
17
|
+
}
|
|
18
|
+
getDeterministicTraceSeed() {
|
|
19
|
+
return this.langfuse?.deterministicTraceId === true ? this.traceIdSeed : void 0;
|
|
20
|
+
}
|
|
21
|
+
withRuntimeContext(action) {
|
|
22
|
+
const activeContext = context.active();
|
|
23
|
+
const langfuse = resolveLangfuseConfigForSpan(activeContext) ?? this.langfuse;
|
|
24
|
+
const seed = this.getDeterministicTraceSeed();
|
|
25
|
+
return withLangfuseRuntimeScope({
|
|
26
|
+
langfuse,
|
|
27
|
+
traceIdSeed: resolveTraceIdSeedForSpan(activeContext) ?? seed
|
|
28
|
+
}, action);
|
|
29
|
+
}
|
|
30
|
+
handleChainStart(...args) {
|
|
31
|
+
return this.withRuntimeContext(() => super.handleChainStart(...args));
|
|
32
|
+
}
|
|
33
|
+
handleAgentAction(...args) {
|
|
34
|
+
return this.withRuntimeContext(() => super.handleAgentAction(...args));
|
|
35
|
+
}
|
|
36
|
+
handleGenerationStart(...args) {
|
|
37
|
+
return this.withRuntimeContext(() => super.handleGenerationStart(...args));
|
|
38
|
+
}
|
|
39
|
+
handleChatModelStart(...args) {
|
|
40
|
+
return this.withRuntimeContext(() => super.handleChatModelStart(...args));
|
|
41
|
+
}
|
|
42
|
+
handleLLMStart(...args) {
|
|
43
|
+
return this.withRuntimeContext(() => super.handleLLMStart(...args));
|
|
44
|
+
}
|
|
45
|
+
handleToolStart(...args) {
|
|
46
|
+
return this.withRuntimeContext(() => super.handleToolStart(...args));
|
|
47
|
+
}
|
|
48
|
+
handleRetrieverStart(...args) {
|
|
49
|
+
return this.withRuntimeContext(() => super.handleRetrieverStart(...args));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
16
52
|
function hasLangfuseConfigCredentials(langfuse) {
|
|
17
53
|
return langfuse != null && isPresent(langfuse.publicKey) && isPresent(langfuse.secretKey);
|
|
18
54
|
}
|
|
@@ -29,6 +65,19 @@ function createTraceMetadata(metadata) {
|
|
|
29
65
|
}
|
|
30
66
|
return traceMetadata;
|
|
31
67
|
}
|
|
68
|
+
function createLibreChatTraceAttributes(attributes) {
|
|
69
|
+
const librechatTraceAttributes = {};
|
|
70
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
71
|
+
if (value == null || key.trim() === "") continue;
|
|
72
|
+
if (typeof value === "string") {
|
|
73
|
+
if (value.trim() === "" || value.length > TRACE_METADATA_MAX_LENGTH) continue;
|
|
74
|
+
librechatTraceAttributes[key] = value;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
librechatTraceAttributes[key] = value;
|
|
78
|
+
}
|
|
79
|
+
return librechatTraceAttributes;
|
|
80
|
+
}
|
|
32
81
|
function createLangfuseTraceMetadata({ messageId, parentMessageId, agentId, agentName }) {
|
|
33
82
|
return createTraceMetadata({
|
|
34
83
|
messageId,
|
|
@@ -62,13 +111,15 @@ function shouldCreateLangfuseHandler(langfuse) {
|
|
|
62
111
|
if (langfuse?.enabled === false) return false;
|
|
63
112
|
return hasLangfuseEnvConfig() || hasLangfuseConfigCredentials(langfuse) || hasLangfuseConfigBaseUrl(langfuse) && hasLangfuseEnvCredentials();
|
|
64
113
|
}
|
|
65
|
-
function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tags }) {
|
|
114
|
+
function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tags, traceIdSeed }) {
|
|
66
115
|
if (!shouldCreateLangfuseHandler(langfuse)) return;
|
|
67
|
-
return new
|
|
116
|
+
return new ScopedLangfuseCallbackHandler({
|
|
68
117
|
userId,
|
|
69
118
|
sessionId,
|
|
70
119
|
traceMetadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),
|
|
71
|
-
tags: mergeLangfuseTags(tags, langfuse?.tags)
|
|
120
|
+
tags: mergeLangfuseTags(tags, langfuse?.tags),
|
|
121
|
+
langfuse,
|
|
122
|
+
traceIdSeed
|
|
72
123
|
});
|
|
73
124
|
}
|
|
74
125
|
function createPropagateAttributeParams({ langfuse, userId, sessionId, traceMetadata, traceName, tags }) {
|
|
@@ -92,6 +143,6 @@ async function disposeLangfuseHandler(value) {
|
|
|
92
143
|
await getLangfuseTracerProvider().forceFlush?.();
|
|
93
144
|
}
|
|
94
145
|
//#endregion
|
|
95
|
-
export { createLangfuseHandler, createLangfuseTraceMetadata, disposeLangfuseHandler, getLangfuseTraceName, hasLangfuseConfigCredentials, hasLangfuseEnvConfig, hasLangfuseEnvCredentials, isLangfuseCallbackHandler, withLangfuseAttributes };
|
|
146
|
+
export { createLangfuseHandler, createLangfuseTraceMetadata, createLibreChatTraceAttributes, disposeLangfuseHandler, getLangfuseTraceName, hasLangfuseConfigCredentials, hasLangfuseEnvConfig, hasLangfuseEnvCredentials, isLangfuseCallbackHandler, withLangfuseAttributes };
|
|
96
147
|
|
|
97
148
|
//# sourceMappingURL=langfuse.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"langfuse.mjs","names":[],"sources":["../../src/langfuse.ts"],"sourcesContent":["import { CallbackHandler } from '@langfuse/langchain';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type { PropagateAttributesParams } from '@langfuse/tracing';\nimport type * as t from '@/types';\nimport { isPresent } from '@/utils/misc';\n\nconst TRACE_METADATA_MAX_LENGTH = 200;\nconst LANGFUSE_FORCE_FLUSH_ON_DISPOSE = 'LANGFUSE_FORCE_FLUSH_ON_DISPOSE';\n\nexport type LangfuseTraceMetadata = Record<string, string>;\ntype LangfuseMetadata = NonNullable<t.LangfuseConfig['metadata']>;\n\ntype LangfuseHandlerParams = {\n userId?: string;\n sessionId?: string;\n traceMetadata?: LangfuseTraceMetadata;\n tags?: string[];\n};\n\ntype AgentLangfuseHandlerParams = LangfuseHandlerParams & {\n langfuse?: t.LangfuseConfig;\n};\n\ntype LangfuseAttributeParams = AgentLangfuseHandlerParams & {\n traceName?: string;\n};\n\ntype FlushableTracerProvider = {\n forceFlush?: () => Promise<void> | void;\n};\n\nfunction parseBooleanEnv(value?: string): boolean {\n if (value == null) {\n return false;\n }\n return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());\n}\n\nfunction hasLangfuseTracingConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.toolNodeTracing != null || langfuse?.toolOutputTracing != null\n );\n}\n\nfunction hasLangfuseTraceAttributes(langfuse?: t.LangfuseConfig): boolean {\n return (\n Object.keys(createTraceMetadata(langfuse?.metadata ?? {})).length > 0 ||\n (mergeLangfuseTags(undefined, langfuse?.tags)?.length ?? 0) > 0\n );\n}\n\nexport function hasLangfuseConfigCredentials(\n langfuse?: t.LangfuseConfig\n): langfuse is t.LangfuseConfig & {\n publicKey: string;\n secretKey: string;\n} {\n return (\n langfuse != null &&\n isPresent(langfuse.publicKey) &&\n isPresent(langfuse.secretKey)\n );\n}\n\nfunction hasLangfuseConfigBaseUrl(langfuse?: t.LangfuseConfig): boolean {\n return isPresent(langfuse?.baseUrl);\n}\n\nexport function isExplicitLangfuseConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.enabled != null ||\n isPresent(langfuse?.publicKey) ||\n isPresent(langfuse?.secretKey) ||\n isPresent(langfuse?.baseUrl) ||\n hasLangfuseTraceAttributes(langfuse) ||\n hasLangfuseTracingConfig(langfuse)\n );\n}\n\nfunction createTraceMetadata(\n metadata: Record<string, unknown>\n): LangfuseTraceMetadata {\n const traceMetadata: LangfuseTraceMetadata = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (value == null) {\n continue;\n }\n const stringValue = typeof value === 'string' ? value : String(value);\n if (\n stringValue.trim() === '' ||\n stringValue.length > TRACE_METADATA_MAX_LENGTH\n ) {\n continue;\n }\n traceMetadata[key] = stringValue;\n }\n return traceMetadata;\n}\n\nexport function createLangfuseTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n}: {\n messageId?: unknown;\n parentMessageId?: unknown;\n agentId?: unknown;\n agentName?: unknown;\n}): LangfuseTraceMetadata {\n return createTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n });\n}\n\nfunction mergeLangfuseTraceMetadata(\n traceMetadata?: LangfuseTraceMetadata,\n metadata?: LangfuseMetadata\n): LangfuseTraceMetadata | undefined {\n const merged = createTraceMetadata({\n ...(metadata ?? {}),\n ...(traceMetadata ?? {}),\n });\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\nfunction mergeLangfuseTags(\n tags?: string[],\n configTags?: string[]\n): string[] | undefined {\n const merged = [...(tags ?? []), ...(configTags ?? [])].filter(\n (tag) => tag.trim() !== ''\n );\n return merged.length > 0 ? [...new Set(merged)] : undefined;\n}\n\nexport function getLangfuseTraceName(\n traceMetadata?: LangfuseTraceMetadata,\n fallback: string = 'LibreChat Agent'\n): string {\n const agentName = traceMetadata?.agentName;\n return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;\n}\n\nexport function hasLangfuseEnvConfig(): boolean {\n return hasLangfuseEnvCredentials();\n}\n\nexport function hasLangfuseEnvCredentials(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nexport function shouldCreateLangfuseHandler(\n langfuse?: t.LangfuseConfig\n): boolean {\n if (langfuse?.enabled === false) {\n return false;\n }\n return (\n hasLangfuseEnvConfig() ||\n hasLangfuseConfigCredentials(langfuse) ||\n (hasLangfuseConfigBaseUrl(langfuse) && hasLangfuseEnvCredentials())\n );\n}\n\nexport function createLegacyLangfuseHandler(\n params: LangfuseHandlerParams\n): CallbackHandler {\n return new CallbackHandler(params);\n}\n\nexport function createLangfuseHandler({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n tags,\n}: AgentLangfuseHandlerParams): CallbackHandler | undefined {\n if (!shouldCreateLangfuseHandler(langfuse)) {\n return undefined;\n }\n return new CallbackHandler({\n userId,\n sessionId,\n traceMetadata: mergeLangfuseTraceMetadata(\n traceMetadata,\n langfuse?.metadata\n ),\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n });\n}\n\nfunction createPropagateAttributeParams({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n traceName,\n tags,\n}: LangfuseAttributeParams): PropagateAttributesParams {\n return {\n userId,\n sessionId,\n traceName,\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n metadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),\n };\n}\n\nexport function withLangfuseAttributes<T>(\n params: LangfuseAttributeParams,\n action: () => T\n): T {\n if (!shouldCreateLangfuseHandler(params.langfuse)) {\n return action();\n }\n return propagateAttributes(createPropagateAttributeParams(params), action);\n}\n\nexport function hasExplicitLangfuseConfig(\n contexts: Iterable<{ langfuse?: t.LangfuseConfig }>\n): boolean {\n for (const context of contexts) {\n if (isExplicitLangfuseConfig(context.langfuse)) {\n return true;\n }\n }\n return false;\n}\n\nexport function isLangfuseCallbackHandler(value: unknown): boolean {\n return value instanceof CallbackHandler;\n}\n\nexport async function disposeLangfuseHandler(value: unknown): Promise<void> {\n if (\n value == null ||\n !parseBooleanEnv(process.env[LANGFUSE_FORCE_FLUSH_ON_DISPOSE])\n ) {\n return;\n }\n const provider = getLangfuseTracerProvider() as FlushableTracerProvider;\n await provider.forceFlush?.();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"langfuse.mjs","names":["otelContext"],"sources":["../../src/langfuse.ts"],"sourcesContent":["import { CallbackHandler } from '@langfuse/langchain';\nimport { context as otelContext } from '@opentelemetry/api';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type { PropagateAttributesParams } from '@langfuse/tracing';\nimport type * as t from '@/types';\nimport {\n resolveLangfuseConfigForSpan,\n resolveTraceIdSeedForSpan,\n withLangfuseRuntimeScope,\n} from '@/langfuseRuntimeScope';\nimport { isPresent, parseBooleanEnv } from '@/utils/misc';\n\nconst TRACE_METADATA_MAX_LENGTH = 200;\nconst LANGFUSE_FORCE_FLUSH_ON_DISPOSE = 'LANGFUSE_FORCE_FLUSH_ON_DISPOSE';\n\nexport type LangfuseTraceMetadata = Record<string, string>;\nexport type LangfuseTraceAttributes = Record<string, string | number | boolean>;\ntype LangfuseMetadata = NonNullable<t.LangfuseConfig['metadata']>;\ntype LangfuseConfigTraceAttributes = NonNullable<\n t.LangfuseConfig['librechatTraceAttributes']\n>;\n\ntype LangfuseHandlerParams = {\n userId?: string;\n sessionId?: string;\n traceMetadata?: LangfuseTraceMetadata;\n tags?: string[];\n traceIdSeed?: string;\n};\n\ntype AgentLangfuseHandlerParams = LangfuseHandlerParams & {\n langfuse?: t.LangfuseConfig;\n};\n\ntype LangfuseAttributeParams = AgentLangfuseHandlerParams & {\n traceName?: string;\n};\n\ntype FlushableTracerProvider = {\n forceFlush?: () => Promise<void> | void;\n};\n\nclass ScopedLangfuseCallbackHandler extends CallbackHandler {\n private readonly langfuse?: t.LangfuseConfig;\n private readonly traceIdSeed?: string;\n\n constructor(params?: AgentLangfuseHandlerParams) {\n const { langfuse, traceIdSeed, ...handlerParams } = params ?? {};\n super(handlerParams);\n this.langfuse = langfuse;\n this.traceIdSeed = traceIdSeed;\n }\n\n private getDeterministicTraceSeed(): string | undefined {\n return this.langfuse?.deterministicTraceId === true\n ? this.traceIdSeed\n : undefined;\n }\n\n private withRuntimeContext<T>(action: () => T): T {\n const activeContext = otelContext.active();\n const langfuse =\n resolveLangfuseConfigForSpan(activeContext) ?? this.langfuse;\n const seed = this.getDeterministicTraceSeed();\n return withLangfuseRuntimeScope(\n {\n langfuse,\n traceIdSeed: resolveTraceIdSeedForSpan(activeContext) ?? seed,\n },\n action\n );\n }\n\n // LangChain may invoke callback handlers outside the caller's OTEL context.\n // Re-enter tenant scope only for callbacks that start Langfuse observations;\n // end/error/token callbacks use spans already bound to a processor at start.\n override handleChainStart(\n ...args: Parameters<CallbackHandler['handleChainStart']>\n ): ReturnType<CallbackHandler['handleChainStart']> {\n return this.withRuntimeContext(() => super.handleChainStart(...args));\n }\n\n override handleAgentAction(\n ...args: Parameters<CallbackHandler['handleAgentAction']>\n ): ReturnType<CallbackHandler['handleAgentAction']> {\n return this.withRuntimeContext(() => super.handleAgentAction(...args));\n }\n\n override handleGenerationStart(\n ...args: Parameters<CallbackHandler['handleGenerationStart']>\n ): ReturnType<CallbackHandler['handleGenerationStart']> {\n return this.withRuntimeContext(() => super.handleGenerationStart(...args));\n }\n\n override handleChatModelStart(\n ...args: Parameters<CallbackHandler['handleChatModelStart']>\n ): ReturnType<CallbackHandler['handleChatModelStart']> {\n return this.withRuntimeContext(() => super.handleChatModelStart(...args));\n }\n\n override handleLLMStart(\n ...args: Parameters<CallbackHandler['handleLLMStart']>\n ): ReturnType<CallbackHandler['handleLLMStart']> {\n return this.withRuntimeContext(() => super.handleLLMStart(...args));\n }\n\n override handleToolStart(\n ...args: Parameters<CallbackHandler['handleToolStart']>\n ): ReturnType<CallbackHandler['handleToolStart']> {\n return this.withRuntimeContext(() => super.handleToolStart(...args));\n }\n\n override handleRetrieverStart(\n ...args: Parameters<CallbackHandler['handleRetrieverStart']>\n ): ReturnType<CallbackHandler['handleRetrieverStart']> {\n return this.withRuntimeContext(() => super.handleRetrieverStart(...args));\n }\n}\n\nfunction hasLangfuseTracingConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.toolNodeTracing != null || langfuse?.toolOutputTracing != null\n );\n}\n\nfunction hasLangfuseTraceAttributes(langfuse?: t.LangfuseConfig): boolean {\n return (\n Object.keys(createTraceMetadata(langfuse?.metadata ?? {})).length > 0 ||\n Object.keys(\n createLibreChatTraceAttributes(langfuse?.librechatTraceAttributes ?? {})\n ).length > 0 ||\n (mergeLangfuseTags(undefined, langfuse?.tags)?.length ?? 0) > 0\n );\n}\n\nexport function hasLangfuseConfigCredentials(\n langfuse?: t.LangfuseConfig\n): langfuse is t.LangfuseConfig & {\n publicKey: string;\n secretKey: string;\n} {\n return (\n langfuse != null &&\n isPresent(langfuse.publicKey) &&\n isPresent(langfuse.secretKey)\n );\n}\n\nfunction hasLangfuseConfigBaseUrl(langfuse?: t.LangfuseConfig): boolean {\n return isPresent(langfuse?.baseUrl);\n}\n\nexport function isExplicitLangfuseConfig(langfuse?: t.LangfuseConfig): boolean {\n return (\n langfuse?.enabled != null ||\n isPresent(langfuse?.publicKey) ||\n isPresent(langfuse?.secretKey) ||\n isPresent(langfuse?.baseUrl) ||\n hasLangfuseTraceAttributes(langfuse) ||\n hasLangfuseTracingConfig(langfuse)\n );\n}\n\nfunction createTraceMetadata(\n metadata: Record<string, unknown>\n): LangfuseTraceMetadata {\n const traceMetadata: LangfuseTraceMetadata = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (value == null) {\n continue;\n }\n const stringValue = typeof value === 'string' ? value : String(value);\n if (\n stringValue.trim() === '' ||\n stringValue.length > TRACE_METADATA_MAX_LENGTH\n ) {\n continue;\n }\n traceMetadata[key] = stringValue;\n }\n return traceMetadata;\n}\n\nexport function createLibreChatTraceAttributes(\n attributes: LangfuseConfigTraceAttributes\n): LangfuseTraceAttributes {\n const librechatTraceAttributes: LangfuseTraceAttributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value == null || key.trim() === '') {\n continue;\n }\n if (typeof value === 'string') {\n if (value.trim() === '' || value.length > TRACE_METADATA_MAX_LENGTH) {\n continue;\n }\n librechatTraceAttributes[key] = value;\n continue;\n }\n librechatTraceAttributes[key] = value;\n }\n return librechatTraceAttributes;\n}\n\nexport function createLangfuseTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n}: {\n messageId?: unknown;\n parentMessageId?: unknown;\n agentId?: unknown;\n agentName?: unknown;\n}): LangfuseTraceMetadata {\n return createTraceMetadata({\n messageId,\n parentMessageId,\n agentId,\n agentName,\n });\n}\n\nfunction mergeLangfuseTraceMetadata(\n traceMetadata?: LangfuseTraceMetadata,\n metadata?: LangfuseMetadata\n): LangfuseTraceMetadata | undefined {\n const merged = createTraceMetadata({\n ...(metadata ?? {}),\n ...(traceMetadata ?? {}),\n });\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\nfunction mergeLangfuseTags(\n tags?: string[],\n configTags?: string[]\n): string[] | undefined {\n const merged = [...(tags ?? []), ...(configTags ?? [])].filter(\n (tag) => tag.trim() !== ''\n );\n return merged.length > 0 ? [...new Set(merged)] : undefined;\n}\n\nexport function getLangfuseTraceName(\n traceMetadata?: LangfuseTraceMetadata,\n fallback: string = 'LibreChat Agent'\n): string {\n const agentName = traceMetadata?.agentName;\n return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;\n}\n\nexport function hasLangfuseEnvConfig(): boolean {\n return hasLangfuseEnvCredentials();\n}\n\nexport function hasLangfuseEnvCredentials(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nexport function shouldCreateLangfuseHandler(\n langfuse?: t.LangfuseConfig\n): boolean {\n if (langfuse?.enabled === false) {\n return false;\n }\n return (\n hasLangfuseEnvConfig() ||\n hasLangfuseConfigCredentials(langfuse) ||\n (hasLangfuseConfigBaseUrl(langfuse) && hasLangfuseEnvCredentials())\n );\n}\n\nexport function createLegacyLangfuseHandler(\n params: LangfuseHandlerParams\n): CallbackHandler {\n return new ScopedLangfuseCallbackHandler(params);\n}\n\nexport function createLangfuseHandler({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n tags,\n traceIdSeed,\n}: AgentLangfuseHandlerParams): CallbackHandler | undefined {\n if (!shouldCreateLangfuseHandler(langfuse)) {\n return undefined;\n }\n return new ScopedLangfuseCallbackHandler({\n userId,\n sessionId,\n traceMetadata: mergeLangfuseTraceMetadata(\n traceMetadata,\n langfuse?.metadata\n ),\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n langfuse,\n traceIdSeed,\n });\n}\n\nfunction createPropagateAttributeParams({\n langfuse,\n userId,\n sessionId,\n traceMetadata,\n traceName,\n tags,\n}: LangfuseAttributeParams): PropagateAttributesParams {\n return {\n userId,\n sessionId,\n traceName,\n tags: mergeLangfuseTags(tags, langfuse?.tags),\n metadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),\n };\n}\n\nexport function withLangfuseAttributes<T>(\n params: LangfuseAttributeParams,\n action: () => T\n): T {\n if (!shouldCreateLangfuseHandler(params.langfuse)) {\n return action();\n }\n return propagateAttributes(createPropagateAttributeParams(params), action);\n}\n\nexport function hasExplicitLangfuseConfig(\n contexts: Iterable<{ langfuse?: t.LangfuseConfig }>\n): boolean {\n for (const context of contexts) {\n if (isExplicitLangfuseConfig(context.langfuse)) {\n return true;\n }\n }\n return false;\n}\n\nexport function isLangfuseCallbackHandler(value: unknown): boolean {\n return value instanceof CallbackHandler;\n}\n\nexport async function disposeLangfuseHandler(value: unknown): Promise<void> {\n if (\n value == null ||\n !parseBooleanEnv(process.env[LANGFUSE_FORCE_FLUSH_ON_DISPOSE])\n ) {\n return;\n }\n const provider = getLangfuseTracerProvider() as FlushableTracerProvider;\n await provider.forceFlush?.();\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { parseBooleanEnv } from "./utils/misc.mjs";
|
|
2
|
+
//#region src/langfuseConfig.ts
|
|
3
|
+
const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = "[tool output redacted]";
|
|
4
|
+
function isPresent(value) {
|
|
5
|
+
return typeof value === "string" && value.trim() !== "";
|
|
6
|
+
}
|
|
7
|
+
function normalizeToolName(name) {
|
|
8
|
+
return name.trim().toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
function normalizeToolNames(names) {
|
|
11
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const name of names ?? []) if (isPresent(name)) normalized.add(normalizeToolName(name));
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
function parseToolNames(value) {
|
|
16
|
+
if (!isPresent(value)) return;
|
|
17
|
+
return value.split(",").map((name) => name.trim()).filter((name) => name !== "");
|
|
18
|
+
}
|
|
19
|
+
function getEnvToolOutputTracingEnabled() {
|
|
20
|
+
const traceToolOutputs = parseBooleanEnv(process.env.LANGFUSE_TRACE_TOOL_OUTPUTS);
|
|
21
|
+
if (traceToolOutputs != null) return traceToolOutputs;
|
|
22
|
+
const redactToolOutputs = parseBooleanEnv(process.env.LANGFUSE_REDACT_TOOL_OUTPUTS);
|
|
23
|
+
if (redactToolOutputs != null) return !redactToolOutputs;
|
|
24
|
+
return parseBooleanEnv(process.env.LANGFUSE_TOOL_OUTPUT_TRACING_ENABLED);
|
|
25
|
+
}
|
|
26
|
+
function getEnvRedactedToolNames() {
|
|
27
|
+
return parseToolNames(process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAMES) ?? parseToolNames(process.env.LANGFUSE_REDACT_TOOL_NAMES);
|
|
28
|
+
}
|
|
29
|
+
function getEnvRedactionText() {
|
|
30
|
+
return isPresent(process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT) ? process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT : void 0;
|
|
31
|
+
}
|
|
32
|
+
function getEnvToolNameMatchMode() {
|
|
33
|
+
const mode = (process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAME_MATCH_MODE ?? process.env.LANGFUSE_REDACT_TOOL_NAME_MATCH_MODE)?.trim().toLowerCase();
|
|
34
|
+
if (mode === "exact" || mode === "partial") return mode;
|
|
35
|
+
}
|
|
36
|
+
function hasEnvToolOutputTracingConfig() {
|
|
37
|
+
return getEnvToolOutputTracingEnabled() != null || getEnvRedactedToolNames() != null || getEnvRedactionText() != null || getEnvToolNameMatchMode() != null;
|
|
38
|
+
}
|
|
39
|
+
function resolveToolOutputTracingEnabled(runConfig, agentConfig) {
|
|
40
|
+
return agentConfig?.enabled ?? runConfig?.enabled ?? getEnvToolOutputTracingEnabled() ?? true;
|
|
41
|
+
}
|
|
42
|
+
function resolveRedactedToolNames(runConfig, agentConfig) {
|
|
43
|
+
return normalizeToolNames([
|
|
44
|
+
...getEnvRedactedToolNames() ?? [],
|
|
45
|
+
...runConfig?.redactedToolNames ?? [],
|
|
46
|
+
...agentConfig?.redactedToolNames ?? []
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
function resolveToolNameMatchMode(runConfig, agentConfig) {
|
|
50
|
+
return [
|
|
51
|
+
getEnvToolNameMatchMode(),
|
|
52
|
+
runConfig?.redactedToolNameMatchMode,
|
|
53
|
+
agentConfig?.redactedToolNameMatchMode
|
|
54
|
+
].includes("partial") ? "partial" : "exact";
|
|
55
|
+
}
|
|
56
|
+
function hasToolOutputTracingConfig(runLangfuse, agentLangfuse) {
|
|
57
|
+
return runLangfuse?.toolOutputTracing != null || agentLangfuse?.toolOutputTracing != null || hasEnvToolOutputTracingConfig();
|
|
58
|
+
}
|
|
59
|
+
function resolveToolOutputTracingConfig(runLangfuse, agentLangfuse) {
|
|
60
|
+
const runConfig = runLangfuse?.toolOutputTracing;
|
|
61
|
+
const agentConfig = agentLangfuse?.toolOutputTracing;
|
|
62
|
+
return {
|
|
63
|
+
enabled: resolveToolOutputTracingEnabled(runConfig, agentConfig),
|
|
64
|
+
redactedToolNames: resolveRedactedToolNames(runConfig, agentConfig),
|
|
65
|
+
redactedToolNameMatchMode: resolveToolNameMatchMode(runConfig, agentConfig),
|
|
66
|
+
redactionText: agentConfig?.redactionText ?? runConfig?.redactionText ?? getEnvRedactionText() ?? "[tool output redacted]"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function resolveLangfuseConfig(runLangfuse, agentLangfuse) {
|
|
70
|
+
if (runLangfuse == null) return agentLangfuse;
|
|
71
|
+
if (agentLangfuse == null) return runLangfuse;
|
|
72
|
+
const toolNodeTracing = runLangfuse.toolNodeTracing != null || agentLangfuse.toolNodeTracing != null ? {
|
|
73
|
+
...runLangfuse.toolNodeTracing,
|
|
74
|
+
...agentLangfuse.toolNodeTracing
|
|
75
|
+
} : void 0;
|
|
76
|
+
const toolOutputTracing = runLangfuse.toolOutputTracing != null || agentLangfuse.toolOutputTracing != null ? {
|
|
77
|
+
...runLangfuse.toolOutputTracing,
|
|
78
|
+
...agentLangfuse.toolOutputTracing
|
|
79
|
+
} : void 0;
|
|
80
|
+
const metadata = runLangfuse.metadata != null || agentLangfuse.metadata != null ? {
|
|
81
|
+
...runLangfuse.metadata,
|
|
82
|
+
...agentLangfuse.metadata
|
|
83
|
+
} : void 0;
|
|
84
|
+
const librechatTraceAttributes = runLangfuse.librechatTraceAttributes != null || agentLangfuse.librechatTraceAttributes != null ? {
|
|
85
|
+
...runLangfuse.librechatTraceAttributes,
|
|
86
|
+
...agentLangfuse.librechatTraceAttributes
|
|
87
|
+
} : void 0;
|
|
88
|
+
const tags = runLangfuse.tags != null || agentLangfuse.tags != null ? [...new Set([...runLangfuse.tags ?? [], ...agentLangfuse.tags ?? []])] : void 0;
|
|
89
|
+
return {
|
|
90
|
+
...runLangfuse,
|
|
91
|
+
...agentLangfuse,
|
|
92
|
+
...metadata != null ? { metadata } : {},
|
|
93
|
+
...librechatTraceAttributes != null ? { librechatTraceAttributes } : {},
|
|
94
|
+
...tags != null ? { tags } : {},
|
|
95
|
+
...toolNodeTracing != null ? { toolNodeTracing } : {},
|
|
96
|
+
...toolOutputTracing != null ? { toolOutputTracing } : {}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT, hasToolOutputTracingConfig, normalizeToolName, resolveLangfuseConfig, resolveToolOutputTracingConfig };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=langfuseConfig.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langfuseConfig.mjs","names":[],"sources":["../../src/langfuseConfig.ts"],"sourcesContent":["import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';\nimport type * as t from '@/types';\nimport { parseBooleanEnv } from '@/utils/misc';\n\nexport const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';\n\nfunction isPresent(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nexport function normalizeToolName(name: string): string {\n return name.trim().toLowerCase();\n}\n\nfunction normalizeToolNames(names: string[] | undefined): Set<string> {\n const normalized = new Set<string>();\n for (const name of names ?? []) {\n if (isPresent(name)) {\n normalized.add(normalizeToolName(name));\n }\n }\n return normalized;\n}\n\nfunction parseToolNames(value: string | undefined): string[] | undefined {\n if (!isPresent(value)) {\n return undefined;\n }\n\n return value\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name !== '');\n}\n\nfunction getEnvToolOutputTracingEnabled(): boolean | undefined {\n const traceToolOutputs = parseBooleanEnv(\n process.env.LANGFUSE_TRACE_TOOL_OUTPUTS\n );\n if (traceToolOutputs != null) {\n return traceToolOutputs;\n }\n\n const redactToolOutputs = parseBooleanEnv(\n process.env.LANGFUSE_REDACT_TOOL_OUTPUTS\n );\n if (redactToolOutputs != null) {\n return !redactToolOutputs;\n }\n\n return parseBooleanEnv(process.env.LANGFUSE_TOOL_OUTPUT_TRACING_ENABLED);\n}\n\nfunction getEnvRedactedToolNames(): string[] | undefined {\n return (\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAMES) ??\n parseToolNames(process.env.LANGFUSE_REDACT_TOOL_NAMES)\n );\n}\n\nfunction getEnvRedactionText(): string | undefined {\n return isPresent(process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT)\n ? process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT\n : undefined;\n}\n\nfunction getEnvToolNameMatchMode(): 'exact' | 'partial' | undefined {\n const mode = (\n process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAME_MATCH_MODE ??\n process.env.LANGFUSE_REDACT_TOOL_NAME_MATCH_MODE\n )\n ?.trim()\n .toLowerCase();\n if (mode === 'exact' || mode === 'partial') {\n return mode;\n }\n return undefined;\n}\n\nfunction hasEnvToolOutputTracingConfig(): boolean {\n return (\n getEnvToolOutputTracingEnabled() != null ||\n getEnvRedactedToolNames() != null ||\n getEnvRedactionText() != null ||\n getEnvToolNameMatchMode() != null\n );\n}\n\nfunction resolveToolOutputTracingEnabled(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): boolean {\n return (\n agentConfig?.enabled ??\n runConfig?.enabled ??\n getEnvToolOutputTracingEnabled() ??\n true\n );\n}\n\nfunction resolveRedactedToolNames(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): Set<string> {\n return normalizeToolNames([\n ...(getEnvRedactedToolNames() ?? []),\n ...(runConfig?.redactedToolNames ?? []),\n ...(agentConfig?.redactedToolNames ?? []),\n ]);\n}\n\nfunction resolveToolNameMatchMode(\n runConfig?: t.LangfuseToolOutputTracingConfig,\n agentConfig?: t.LangfuseToolOutputTracingConfig\n): 'exact' | 'partial' {\n const modes = [\n getEnvToolNameMatchMode(),\n runConfig?.redactedToolNameMatchMode,\n agentConfig?.redactedToolNameMatchMode,\n ];\n return modes.includes('partial') ? 'partial' : 'exact';\n}\n\nexport function hasToolOutputTracingConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): boolean {\n return (\n runLangfuse?.toolOutputTracing != null ||\n agentLangfuse?.toolOutputTracing != null ||\n hasEnvToolOutputTracingConfig()\n );\n}\n\nexport function resolveToolOutputTracingConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): ResolvedLangfuseToolOutputTracingConfig {\n const runConfig = runLangfuse?.toolOutputTracing;\n const agentConfig = agentLangfuse?.toolOutputTracing;\n\n return {\n enabled: resolveToolOutputTracingEnabled(runConfig, agentConfig),\n redactedToolNames: resolveRedactedToolNames(runConfig, agentConfig),\n redactedToolNameMatchMode: resolveToolNameMatchMode(runConfig, agentConfig),\n redactionText:\n agentConfig?.redactionText ??\n runConfig?.redactionText ??\n getEnvRedactionText() ??\n LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,\n };\n}\n\nexport function resolveLangfuseConfig(\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): t.LangfuseConfig | undefined {\n if (runLangfuse == null) {\n return agentLangfuse;\n }\n if (agentLangfuse == null) {\n return runLangfuse;\n }\n\n const toolNodeTracing =\n runLangfuse.toolNodeTracing != null || agentLangfuse.toolNodeTracing != null\n ? {\n ...runLangfuse.toolNodeTracing,\n ...agentLangfuse.toolNodeTracing,\n }\n : undefined;\n const toolOutputTracing =\n runLangfuse.toolOutputTracing != null ||\n agentLangfuse.toolOutputTracing != null\n ? {\n ...runLangfuse.toolOutputTracing,\n ...agentLangfuse.toolOutputTracing,\n }\n : undefined;\n const metadata =\n runLangfuse.metadata != null || agentLangfuse.metadata != null\n ? {\n ...runLangfuse.metadata,\n ...agentLangfuse.metadata,\n }\n : undefined;\n const librechatTraceAttributes =\n runLangfuse.librechatTraceAttributes != null ||\n agentLangfuse.librechatTraceAttributes != null\n ? {\n ...runLangfuse.librechatTraceAttributes,\n ...agentLangfuse.librechatTraceAttributes,\n }\n : undefined;\n const tags =\n runLangfuse.tags != null || agentLangfuse.tags != null\n ? [\n ...new Set([\n ...(runLangfuse.tags ?? []),\n ...(agentLangfuse.tags ?? []),\n ]),\n ]\n : undefined;\n\n return {\n ...runLangfuse,\n ...agentLangfuse,\n ...(metadata != null ? { metadata } : {}),\n ...(librechatTraceAttributes != null ? { librechatTraceAttributes } : {}),\n ...(tags != null ? { tags } : {}),\n ...(toolNodeTracing != null ? { toolNodeTracing } : {}),\n ...(toolOutputTracing != null ? { toolOutputTracing } : {}),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
//#region src/langfuseRuntimeContext.ts
|
|
4
|
+
const langfuseRuntimeContextStore = new AsyncLocalStorage();
|
|
5
|
+
function hasText(value) {
|
|
6
|
+
return value != null && value.trim() !== "";
|
|
7
|
+
}
|
|
8
|
+
function hasLangfuseRuntimeContextValue(context) {
|
|
9
|
+
return context.langfuse != null || hasText(context.traceIdSeed) || context.toolOutputTracing != null;
|
|
10
|
+
}
|
|
11
|
+
/** sha256(seed) -> first 32 hex chars; matches `@langfuse/tracing` `createTraceId`. */
|
|
12
|
+
function traceIdFromSeed(seed) {
|
|
13
|
+
return createHash("sha256").update(seed, "utf8").digest("hex").slice(0, 32);
|
|
14
|
+
}
|
|
15
|
+
function getLangfuseRuntimeContext() {
|
|
16
|
+
return langfuseRuntimeContextStore.getStore();
|
|
17
|
+
}
|
|
18
|
+
function getLangfuseRuntimeConfig() {
|
|
19
|
+
return getLangfuseRuntimeContext()?.langfuse;
|
|
20
|
+
}
|
|
21
|
+
function getTraceIdSeed() {
|
|
22
|
+
return getLangfuseRuntimeContext()?.traceIdSeed;
|
|
23
|
+
}
|
|
24
|
+
function getLangfuseRuntimeToolOutputTracingConfig() {
|
|
25
|
+
return getLangfuseRuntimeContext()?.toolOutputTracing;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Runs `fn` with a merged Langfuse runtime context. Undefined fields inherit
|
|
29
|
+
* from the parent scope; callers intentionally cannot clear parent values by
|
|
30
|
+
* passing `undefined`.
|
|
31
|
+
*/
|
|
32
|
+
function runWithLangfuseRuntimeContext(context, fn) {
|
|
33
|
+
const next = {
|
|
34
|
+
...getLangfuseRuntimeContext() ?? {},
|
|
35
|
+
...context.langfuse !== void 0 ? { langfuse: context.langfuse } : {},
|
|
36
|
+
...hasText(context.traceIdSeed) ? { traceIdSeed: context.traceIdSeed } : {},
|
|
37
|
+
...context.toolOutputTracing !== void 0 ? { toolOutputTracing: context.toolOutputTracing } : {}
|
|
38
|
+
};
|
|
39
|
+
return hasLangfuseRuntimeContextValue(next) ? langfuseRuntimeContextStore.run(next, fn) : fn();
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { getLangfuseRuntimeConfig, getLangfuseRuntimeToolOutputTracingConfig, getTraceIdSeed, hasLangfuseRuntimeContextValue, runWithLangfuseRuntimeContext, traceIdFromSeed };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=langfuseRuntimeContext.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langfuseRuntimeContext.mjs","names":[],"sources":["../../src/langfuseRuntimeContext.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type * as t from '@/types';\n\nexport type ResolvedLangfuseToolOutputTracingConfig = {\n enabled: boolean;\n redactedToolNames: Set<string>;\n redactedToolNameMatchMode: 'exact' | 'partial';\n redactionText: string;\n};\n\nexport type LangfuseRuntimeContext = {\n langfuse?: t.LangfuseConfig;\n traceIdSeed?: string;\n toolOutputTracing?: ResolvedLangfuseToolOutputTracingConfig;\n};\n\nconst langfuseRuntimeContextStore =\n new AsyncLocalStorage<LangfuseRuntimeContext>();\n\nfunction hasText(value: string | undefined): value is string {\n return value != null && value.trim() !== '';\n}\n\nexport function hasLangfuseRuntimeContextValue(\n context: LangfuseRuntimeContext\n): boolean {\n return (\n context.langfuse != null ||\n hasText(context.traceIdSeed) ||\n context.toolOutputTracing != null\n );\n}\n\n/** sha256(seed) -> first 32 hex chars; matches `@langfuse/tracing` `createTraceId`. */\nexport function traceIdFromSeed(seed: string): string {\n return createHash('sha256').update(seed, 'utf8').digest('hex').slice(0, 32);\n}\n\nexport function getLangfuseRuntimeContext():\n | LangfuseRuntimeContext\n | undefined {\n return langfuseRuntimeContextStore.getStore();\n}\n\nexport function getLangfuseRuntimeConfig(): t.LangfuseConfig | undefined {\n return getLangfuseRuntimeContext()?.langfuse;\n}\n\nexport function getTraceIdSeed(): string | undefined {\n return getLangfuseRuntimeContext()?.traceIdSeed;\n}\n\nexport function getLangfuseRuntimeToolOutputTracingConfig():\n | ResolvedLangfuseToolOutputTracingConfig\n | undefined {\n return getLangfuseRuntimeContext()?.toolOutputTracing;\n}\n\n/**\n * Runs `fn` with a merged Langfuse runtime context. Undefined fields inherit\n * from the parent scope; callers intentionally cannot clear parent values by\n * passing `undefined`.\n */\nexport function runWithLangfuseRuntimeContext<T>(\n context: LangfuseRuntimeContext,\n fn: () => T\n): T {\n const current = getLangfuseRuntimeContext();\n const next = {\n ...(current ?? {}),\n ...(context.langfuse !== undefined ? { langfuse: context.langfuse } : {}),\n ...(hasText(context.traceIdSeed)\n ? { traceIdSeed: context.traceIdSeed }\n : {}),\n ...(context.toolOutputTracing !== undefined\n ? { toolOutputTracing: context.toolOutputTracing }\n : {}),\n };\n\n return hasLangfuseRuntimeContextValue(next)\n ? langfuseRuntimeContextStore.run(next, fn)\n : fn();\n}\n\nexport function runWithTraceIdSeed<T>(\n seed: string | undefined,\n fn: () => T\n): T {\n return hasText(seed)\n ? runWithLangfuseRuntimeContext({ traceIdSeed: seed }, fn)\n : fn();\n}\n"],"mappings":";;;AAiBA,MAAM,8BACJ,IAAI,kBAA0C;AAEhD,SAAS,QAAQ,OAA4C;CAC3D,OAAO,SAAS,QAAQ,MAAM,KAAK,MAAM;AAC3C;AAEA,SAAgB,+BACd,SACS;CACT,OACE,QAAQ,YAAY,QACpB,QAAQ,QAAQ,WAAW,KAC3B,QAAQ,qBAAqB;AAEjC;;AAGA,SAAgB,gBAAgB,MAAsB;CACpD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;AAC5E;AAEA,SAAgB,4BAEF;CACZ,OAAO,4BAA4B,SAAS;AAC9C;AAEA,SAAgB,2BAAyD;CACvE,OAAO,0BAA0B,CAAC,EAAE;AACtC;AAEA,SAAgB,iBAAqC;CACnD,OAAO,0BAA0B,CAAC,EAAE;AACtC;AAEA,SAAgB,4CAEF;CACZ,OAAO,0BAA0B,CAAC,EAAE;AACtC;;;;;;AAOA,SAAgB,8BACd,SACA,IACG;CAEH,MAAM,OAAO;EACX,GAFc,0BAEJ,KAAK,CAAC;EAChB,GAAI,QAAQ,aAAa,KAAA,IAAY,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;EACvE,GAAI,QAAQ,QAAQ,WAAW,IAC3B,EAAE,aAAa,QAAQ,YAAY,IACnC,CAAC;EACL,GAAI,QAAQ,sBAAsB,KAAA,IAC9B,EAAE,mBAAmB,QAAQ,kBAAkB,IAC/C,CAAC;CACP;CAEA,OAAO,+BAA+B,IAAI,IACtC,4BAA4B,IAAI,MAAM,EAAE,IACxC,GAAG;AACT"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getLangfuseRuntimeConfig, getLangfuseRuntimeToolOutputTracingConfig, getTraceIdSeed, hasLangfuseRuntimeContextValue, runWithLangfuseRuntimeContext } from "./langfuseRuntimeContext.mjs";
|
|
2
|
+
import { hasToolOutputTracingConfig, resolveLangfuseConfig, resolveToolOutputTracingConfig } from "./langfuseConfig.mjs";
|
|
3
|
+
import { context, createContextKey } from "@opentelemetry/api";
|
|
4
|
+
//#region src/langfuseRuntimeScope.ts
|
|
5
|
+
const langfuseToolOutputTracingConfigKey = createContextKey("librechat.langfuse.tool-output-tracing");
|
|
6
|
+
const langfuseConfigKey = createContextKey("librechat.langfuse.config");
|
|
7
|
+
const langfuseTraceIdSeedKey = createContextKey("librechat.langfuse.trace-id-seed");
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function hasText(value) {
|
|
12
|
+
return value != null && value.trim() !== "";
|
|
13
|
+
}
|
|
14
|
+
function getOtelLangfuseConfig(activeContext) {
|
|
15
|
+
const value = activeContext.getValue(langfuseConfigKey);
|
|
16
|
+
return isRecord(value) ? value : void 0;
|
|
17
|
+
}
|
|
18
|
+
function getOtelTraceIdSeed(activeContext) {
|
|
19
|
+
const value = activeContext.getValue(langfuseTraceIdSeedKey);
|
|
20
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
21
|
+
}
|
|
22
|
+
function getOtelToolOutputTracingConfig(activeContext) {
|
|
23
|
+
const value = activeContext.getValue(langfuseToolOutputTracingConfigKey);
|
|
24
|
+
return isRecord(value) ? value : void 0;
|
|
25
|
+
}
|
|
26
|
+
function resolveLangfuseConfigForSpan(activeContext) {
|
|
27
|
+
return getLangfuseRuntimeConfig() ?? getOtelLangfuseConfig(activeContext);
|
|
28
|
+
}
|
|
29
|
+
function resolveTraceIdSeedForSpan(activeContext) {
|
|
30
|
+
return getTraceIdSeed() ?? getOtelTraceIdSeed(activeContext);
|
|
31
|
+
}
|
|
32
|
+
function resolveToolOutputTracingConfigForSpan(activeContext) {
|
|
33
|
+
return getLangfuseRuntimeToolOutputTracingConfig() ?? getOtelToolOutputTracingConfig(activeContext);
|
|
34
|
+
}
|
|
35
|
+
function withLangfuseRuntimeScope(scope, action) {
|
|
36
|
+
if (!hasLangfuseRuntimeContextValue(scope)) return action();
|
|
37
|
+
let activeContext = context.active();
|
|
38
|
+
if (scope.langfuse != null) activeContext = activeContext.setValue(langfuseConfigKey, scope.langfuse);
|
|
39
|
+
if (scope.toolOutputTracing != null) activeContext = activeContext.setValue(langfuseToolOutputTracingConfigKey, scope.toolOutputTracing);
|
|
40
|
+
if (hasText(scope.traceIdSeed)) activeContext = activeContext.setValue(langfuseTraceIdSeedKey, scope.traceIdSeed);
|
|
41
|
+
return runWithLangfuseRuntimeContext(scope, () => context.with(activeContext, action));
|
|
42
|
+
}
|
|
43
|
+
function resolveLangfuseRuntimeScope({ runLangfuse, langfuseOverlay, traceIdSeed }) {
|
|
44
|
+
return {
|
|
45
|
+
langfuse: resolveLangfuseConfig(runLangfuse, langfuseOverlay),
|
|
46
|
+
traceIdSeed,
|
|
47
|
+
toolOutputTracing: !hasToolOutputTracingConfig(runLangfuse, langfuseOverlay) ? void 0 : resolveToolOutputTracingConfig(runLangfuse, langfuseOverlay)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { resolveLangfuseConfigForSpan, resolveLangfuseRuntimeScope, resolveToolOutputTracingConfigForSpan, resolveTraceIdSeedForSpan, withLangfuseRuntimeScope };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=langfuseRuntimeScope.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langfuseRuntimeScope.mjs","names":[],"sources":["../../src/langfuseRuntimeScope.ts"],"sourcesContent":["import { context, createContextKey } from '@opentelemetry/api';\nimport type { Context } from '@opentelemetry/api';\nimport type {\n LangfuseRuntimeContext,\n ResolvedLangfuseToolOutputTracingConfig,\n} from '@/langfuseRuntimeContext';\nimport type * as t from '@/types';\nimport {\n getLangfuseRuntimeConfig,\n getLangfuseRuntimeToolOutputTracingConfig,\n getTraceIdSeed,\n hasLangfuseRuntimeContextValue,\n runWithLangfuseRuntimeContext,\n} from '@/langfuseRuntimeContext';\nimport {\n hasToolOutputTracingConfig,\n resolveLangfuseConfig,\n resolveToolOutputTracingConfig,\n} from '@/langfuseConfig';\n\nexport type LangfuseRuntimeScope = LangfuseRuntimeContext;\n\nexport type ResolveLangfuseRuntimeScopeParams = {\n runLangfuse?: t.LangfuseConfig;\n langfuseOverlay?: t.LangfuseConfig;\n traceIdSeed?: string;\n};\n\nconst langfuseToolOutputTracingConfigKey = createContextKey(\n 'librechat.langfuse.tool-output-tracing'\n);\nconst langfuseConfigKey = createContextKey('librechat.langfuse.config');\nconst langfuseTraceIdSeedKey = createContextKey(\n 'librechat.langfuse.trace-id-seed'\n);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction hasText(value: string | undefined): value is string {\n return value != null && value.trim() !== '';\n}\n\nexport function getOtelLangfuseConfig(\n activeContext: Context\n): t.LangfuseConfig | undefined {\n const value = activeContext.getValue(langfuseConfigKey);\n return isRecord(value) ? (value as t.LangfuseConfig) : undefined;\n}\n\nexport function getOtelTraceIdSeed(activeContext: Context): string | undefined {\n const value = activeContext.getValue(langfuseTraceIdSeedKey);\n return typeof value === 'string' && value.trim() !== '' ? value : undefined;\n}\n\nexport function getOtelToolOutputTracingConfig(\n activeContext: Context\n): ResolvedLangfuseToolOutputTracingConfig | undefined {\n const value = activeContext.getValue(langfuseToolOutputTracingConfigKey);\n return isRecord(value)\n ? (value as ResolvedLangfuseToolOutputTracingConfig)\n : undefined;\n}\n\nexport function resolveLangfuseConfigForSpan(\n activeContext: Context\n): t.LangfuseConfig | undefined {\n return getLangfuseRuntimeConfig() ?? getOtelLangfuseConfig(activeContext);\n}\n\nexport function resolveTraceIdSeedForSpan(\n activeContext: Context\n): string | undefined {\n return getTraceIdSeed() ?? getOtelTraceIdSeed(activeContext);\n}\n\nexport function resolveToolOutputTracingConfigForSpan(\n activeContext: Context\n): ResolvedLangfuseToolOutputTracingConfig | undefined {\n return (\n getLangfuseRuntimeToolOutputTracingConfig() ??\n getOtelToolOutputTracingConfig(activeContext)\n );\n}\n\nexport function withLangfuseRuntimeScope<T>(\n scope: LangfuseRuntimeScope,\n action: () => T\n): T {\n if (!hasLangfuseRuntimeContextValue(scope)) {\n return action();\n }\n\n let activeContext = context.active();\n if (scope.langfuse != null) {\n activeContext = activeContext.setValue(langfuseConfigKey, scope.langfuse);\n }\n if (scope.toolOutputTracing != null) {\n activeContext = activeContext.setValue(\n langfuseToolOutputTracingConfigKey,\n scope.toolOutputTracing\n );\n }\n if (hasText(scope.traceIdSeed)) {\n activeContext = activeContext.setValue(\n langfuseTraceIdSeedKey,\n scope.traceIdSeed\n );\n }\n\n // Span processors receive the OTel parent context in `onStart`, while\n // LangChain callback handlers may run outside that context and need ALS.\n // The trace id generator reads the seed from ALS or OTel context so SDK\n // callbacks that preserve only one of those contexts still keep trace/score\n // cohesion.\n return runWithLangfuseRuntimeContext(scope, () =>\n context.with(activeContext, action)\n );\n}\n\nexport function resolveLangfuseRuntimeScope({\n runLangfuse,\n langfuseOverlay,\n traceIdSeed,\n}: ResolveLangfuseRuntimeScopeParams): LangfuseRuntimeScope {\n const langfuse = resolveLangfuseConfig(runLangfuse, langfuseOverlay);\n const toolOutputTracing = !hasToolOutputTracingConfig(\n runLangfuse,\n langfuseOverlay\n )\n ? undefined\n : resolveToolOutputTracingConfig(runLangfuse, langfuseOverlay);\n return { langfuse, traceIdSeed, toolOutputTracing };\n}\n"],"mappings":";;;;AA4BA,MAAM,qCAAqC,iBACzC,wCACF;AACA,MAAM,oBAAoB,iBAAiB,2BAA2B;AACtE,MAAM,yBAAyB,iBAC7B,kCACF;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,SAAS,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC3E;AAEA,SAAS,QAAQ,OAA4C;CAC3D,OAAO,SAAS,QAAQ,MAAM,KAAK,MAAM;AAC3C;AAEA,SAAgB,sBACd,eAC8B;CAC9B,MAAM,QAAQ,cAAc,SAAS,iBAAiB;CACtD,OAAO,SAAS,KAAK,IAAK,QAA6B,KAAA;AACzD;AAEA,SAAgB,mBAAmB,eAA4C;CAC7E,MAAM,QAAQ,cAAc,SAAS,sBAAsB;CAC3D,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,KAAK,QAAQ,KAAA;AACpE;AAEA,SAAgB,+BACd,eACqD;CACrD,MAAM,QAAQ,cAAc,SAAS,kCAAkC;CACvE,OAAO,SAAS,KAAK,IAChB,QACD,KAAA;AACN;AAEA,SAAgB,6BACd,eAC8B;CAC9B,OAAO,yBAAyB,KAAK,sBAAsB,aAAa;AAC1E;AAEA,SAAgB,0BACd,eACoB;CACpB,OAAO,eAAe,KAAK,mBAAmB,aAAa;AAC7D;AAEA,SAAgB,sCACd,eACqD;CACrD,OACE,0CAA0C,KAC1C,+BAA+B,aAAa;AAEhD;AAEA,SAAgB,yBACd,OACA,QACG;CACH,IAAI,CAAC,+BAA+B,KAAK,GACvC,OAAO,OAAO;CAGhB,IAAI,gBAAgB,QAAQ,OAAO;CACnC,IAAI,MAAM,YAAY,MACpB,gBAAgB,cAAc,SAAS,mBAAmB,MAAM,QAAQ;CAE1E,IAAI,MAAM,qBAAqB,MAC7B,gBAAgB,cAAc,SAC5B,oCACA,MAAM,iBACR;CAEF,IAAI,QAAQ,MAAM,WAAW,GAC3B,gBAAgB,cAAc,SAC5B,wBACA,MAAM,WACR;CAQF,OAAO,8BAA8B,aACnC,QAAQ,KAAK,eAAe,MAAM,CACpC;AACF;AAEA,SAAgB,4BAA4B,EAC1C,aACA,iBACA,eAC0D;CAQ1D,OAAO;EAAE,UAPQ,sBAAsB,aAAa,eAOpC;EAAG;EAAa,mBANN,CAAC,2BACzB,aACA,eACF,IACI,KAAA,IACA,+BAA+B,aAAa,eAAe;CACb;AACpD"}
|