@librechat/agents 3.3.1 → 3.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/langfuse.cjs +6 -0
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/esm/langfuse.mjs +6 -0
- package/dist/esm/langfuse.mjs.map +1 -1
- package/package.json +16 -12
- package/src/langfuse.ts +15 -0
- package/src/llm/anthropic/llm.spec.ts +8 -0
- package/src/specs/langfuse-callbacks.test.ts +203 -1
package/dist/cjs/langfuse.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const require_misc = require("./utils/misc.cjs");
|
|
2
2
|
const require_langfuseRuntimeScope = require("./langfuseRuntimeScope.cjs");
|
|
3
3
|
let _langchain_core_messages = require("@langchain/core/messages");
|
|
4
|
+
let _langchain_langgraph = require("@langchain/langgraph");
|
|
4
5
|
let _langfuse_langchain = require("@langfuse/langchain");
|
|
5
6
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
6
7
|
let _langfuse_tracing = require("@langfuse/tracing");
|
|
@@ -88,6 +89,11 @@ var ScopedLangfuseCallbackHandler = class extends _langfuse_langchain.CallbackHa
|
|
|
88
89
|
handleChainStart(...args) {
|
|
89
90
|
return this.withRuntimeContext(() => super.handleChainStart(...args));
|
|
90
91
|
}
|
|
92
|
+
handleChainError(...args) {
|
|
93
|
+
const [error, runId, parentRunId] = args;
|
|
94
|
+
if (error != null && parentRunId != null && (0, _langchain_langgraph.isParentCommand)(error)) return super.handleChainEnd({ controlFlow: "ParentCommand" }, runId, parentRunId);
|
|
95
|
+
return super.handleChainError(...args);
|
|
96
|
+
}
|
|
91
97
|
handleAgentAction(...args) {
|
|
92
98
|
return this.withRuntimeContext(() => super.handleAgentAction(...args));
|
|
93
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"langfuse.cjs","names":["AIMessageChunk","AIMessage","CallbackHandler","otelContext","resolveLangfuseConfigForSpan","withLangfuseRuntimeScope","resolveTraceIdSeedForSpan","isPresent","parseBooleanEnv"],"sources":["../../src/langfuse.ts"],"sourcesContent":["import { CallbackHandler } from '@langfuse/langchain';\nimport { context as otelContext } from '@opentelemetry/api';\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type {\n AIMessageChunkFields,\n AIMessageFields,\n UsageMetadata,\n} from '@langchain/core/messages';\nimport type {\n ChatGeneration,\n Generation,\n LLMResult,\n} from '@langchain/core/outputs';\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\ntype BedrockResponseUsage = {\n inputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n};\n\ntype BedrockResponseMetadata = {\n metadata?: {\n usage?: BedrockResponseUsage;\n };\n};\n\nfunction getLangfuseBedrockUsage(\n message: AIMessage | AIMessageChunk\n): UsageMetadata | undefined {\n const usageMetadata = message.usage_metadata;\n const bedrockUsage = (message.response_metadata as BedrockResponseMetadata)\n .metadata?.usage;\n if (\n usageMetadata == null ||\n bedrockUsage == null ||\n usageMetadata.input_tokens !== bedrockUsage.inputTokens\n ) {\n return usageMetadata;\n }\n\n const cacheRead = bedrockUsage.cacheReadInputTokens ?? 0;\n const cacheCreation = bedrockUsage.cacheWriteInputTokens ?? 0;\n if (cacheRead === 0 && cacheCreation === 0) {\n return usageMetadata;\n }\n\n return {\n ...usageMetadata,\n input_tokens: usageMetadata.input_tokens + cacheRead + cacheCreation,\n };\n}\n\nfunction cloneMessageWithUsage(\n message: AIMessage | AIMessageChunk,\n usageMetadata: UsageMetadata\n): AIMessage | AIMessageChunk {\n const fields: AIMessageFields = {\n content: message.content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n id: message.id,\n name: message.name,\n tool_calls: message.tool_calls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: usageMetadata,\n };\n\n if (message instanceof AIMessageChunk) {\n const chunkFields: AIMessageChunkFields = {\n ...fields,\n tool_call_chunks: message.tool_call_chunks,\n };\n return new AIMessageChunk(chunkFields);\n }\n\n return new AIMessage(fields);\n}\n\nfunction normalizeGenerationForLangfuse(generation: Generation): Generation {\n if (!('message' in generation)) {\n return generation;\n }\n\n const message = (generation as ChatGeneration).message;\n if (!(message instanceof AIMessage || message instanceof AIMessageChunk)) {\n return generation;\n }\n\n const usageMetadata = getLangfuseBedrockUsage(message);\n if (usageMetadata == null || usageMetadata === message.usage_metadata) {\n return generation;\n }\n\n const chatGeneration: ChatGeneration = {\n ...(generation as ChatGeneration),\n message: cloneMessageWithUsage(message, usageMetadata),\n };\n return chatGeneration;\n}\n\nfunction normalizeBedrockUsageForLangfuse(output: LLMResult): LLMResult {\n if (output.generations.length === 0) {\n return output;\n }\n\n const listIndex = output.generations.length - 1;\n const generationList = output.generations[listIndex];\n if (generationList.length === 0) {\n return output;\n }\n\n const generationIndex = generationList.length - 1;\n const generation = generationList[generationIndex];\n const normalized = normalizeGenerationForLangfuse(generation);\n if (normalized === generation) {\n return output;\n }\n\n const generations = [...output.generations];\n generations[listIndex] = [...generationList];\n generations[listIndex][generationIndex] = normalized;\n return { ...output, generations };\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 handleLLMEnd(\n output: LLMResult,\n runId: string,\n parentRunId?: string\n ): Promise<void> {\n return super.handleLLMEnd(\n normalizeBedrockUsageForLangfuse(output),\n runId,\n parentRunId\n );\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]) !== true\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.cjs","names":["AIMessageChunk","AIMessage","CallbackHandler","otelContext","resolveLangfuseConfigForSpan","withLangfuseRuntimeScope","resolveTraceIdSeedForSpan","isPresent","parseBooleanEnv"],"sources":["../../src/langfuse.ts"],"sourcesContent":["import { CallbackHandler } from '@langfuse/langchain';\nimport { isParentCommand } from '@langchain/langgraph';\nimport { context as otelContext } from '@opentelemetry/api';\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type {\n AIMessageChunkFields,\n AIMessageFields,\n UsageMetadata,\n} from '@langchain/core/messages';\nimport type {\n ChatGeneration,\n Generation,\n LLMResult,\n} from '@langchain/core/outputs';\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\ntype BedrockResponseUsage = {\n inputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n};\n\ntype BedrockResponseMetadata = {\n metadata?: {\n usage?: BedrockResponseUsage;\n };\n};\n\nfunction getLangfuseBedrockUsage(\n message: AIMessage | AIMessageChunk\n): UsageMetadata | undefined {\n const usageMetadata = message.usage_metadata;\n const bedrockUsage = (message.response_metadata as BedrockResponseMetadata)\n .metadata?.usage;\n if (\n usageMetadata == null ||\n bedrockUsage == null ||\n usageMetadata.input_tokens !== bedrockUsage.inputTokens\n ) {\n return usageMetadata;\n }\n\n const cacheRead = bedrockUsage.cacheReadInputTokens ?? 0;\n const cacheCreation = bedrockUsage.cacheWriteInputTokens ?? 0;\n if (cacheRead === 0 && cacheCreation === 0) {\n return usageMetadata;\n }\n\n return {\n ...usageMetadata,\n input_tokens: usageMetadata.input_tokens + cacheRead + cacheCreation,\n };\n}\n\nfunction cloneMessageWithUsage(\n message: AIMessage | AIMessageChunk,\n usageMetadata: UsageMetadata\n): AIMessage | AIMessageChunk {\n const fields: AIMessageFields = {\n content: message.content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n id: message.id,\n name: message.name,\n tool_calls: message.tool_calls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: usageMetadata,\n };\n\n if (message instanceof AIMessageChunk) {\n const chunkFields: AIMessageChunkFields = {\n ...fields,\n tool_call_chunks: message.tool_call_chunks,\n };\n return new AIMessageChunk(chunkFields);\n }\n\n return new AIMessage(fields);\n}\n\nfunction normalizeGenerationForLangfuse(generation: Generation): Generation {\n if (!('message' in generation)) {\n return generation;\n }\n\n const message = (generation as ChatGeneration).message;\n if (!(message instanceof AIMessage || message instanceof AIMessageChunk)) {\n return generation;\n }\n\n const usageMetadata = getLangfuseBedrockUsage(message);\n if (usageMetadata == null || usageMetadata === message.usage_metadata) {\n return generation;\n }\n\n const chatGeneration: ChatGeneration = {\n ...(generation as ChatGeneration),\n message: cloneMessageWithUsage(message, usageMetadata),\n };\n return chatGeneration;\n}\n\nfunction normalizeBedrockUsageForLangfuse(output: LLMResult): LLMResult {\n if (output.generations.length === 0) {\n return output;\n }\n\n const listIndex = output.generations.length - 1;\n const generationList = output.generations[listIndex];\n if (generationList.length === 0) {\n return output;\n }\n\n const generationIndex = generationList.length - 1;\n const generation = generationList[generationIndex];\n const normalized = normalizeGenerationForLangfuse(generation);\n if (normalized === generation) {\n return output;\n }\n\n const generations = [...output.generations];\n generations[listIndex] = [...generationList];\n generations[listIndex][generationIndex] = normalized;\n return { ...output, generations };\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 handleChainError(\n ...args: Parameters<CallbackHandler['handleChainError']>\n ): ReturnType<CallbackHandler['handleChainError']> {\n const [error, runId, parentRunId] = args;\n if (error != null && parentRunId != null && isParentCommand(error)) {\n return super.handleChainEnd(\n { controlFlow: 'ParentCommand' },\n runId,\n parentRunId\n );\n }\n return super.handleChainError(...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 handleLLMEnd(\n output: LLMResult,\n runId: string,\n parentRunId?: string\n ): Promise<void> {\n return super.handleLLMEnd(\n normalizeBedrockUsageForLangfuse(output),\n runId,\n parentRunId\n );\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]) !== true\n ) {\n return;\n }\n const provider = getLangfuseTracerProvider() as FlushableTracerProvider;\n await provider.forceFlush?.();\n}\n"],"mappings":";;;;;;;;AA2BA,MAAM,4BAA4B;AAClC,MAAM,kCAAkC;AAyCxC,SAAS,wBACP,SAC2B;CAC3B,MAAM,gBAAgB,QAAQ;CAC9B,MAAM,eAAgB,QAAQ,kBAC3B,UAAU;CACb,IACE,iBAAiB,QACjB,gBAAgB,QAChB,cAAc,iBAAiB,aAAa,aAE5C,OAAO;CAGT,MAAM,YAAY,aAAa,wBAAwB;CACvD,MAAM,gBAAgB,aAAa,yBAAyB;CAC5D,IAAI,cAAc,KAAK,kBAAkB,GACvC,OAAO;CAGT,OAAO;EACL,GAAG;EACH,cAAc,cAAc,eAAe,YAAY;CACzD;AACF;AAEA,SAAS,sBACP,SACA,eAC4B;CAC5B,MAAM,SAA0B;EAC9B,SAAS,QAAQ;EACjB,mBAAmB,QAAQ;EAC3B,mBAAmB,QAAQ;EAC3B,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,YAAY,QAAQ;EACpB,oBAAoB,QAAQ;EAC5B,gBAAgB;CAClB;CAEA,IAAI,mBAAmBA,yBAAAA,gBAKrB,OAAO,IAAIA,yBAAAA,eAAe;EAHxB,GAAG;EACH,kBAAkB,QAAQ;CAEQ,CAAC;CAGvC,OAAO,IAAIC,yBAAAA,UAAU,MAAM;AAC7B;AAEA,SAAS,+BAA+B,YAAoC;CAC1E,IAAI,EAAE,aAAa,aACjB,OAAO;CAGT,MAAM,UAAW,WAA8B;CAC/C,IAAI,EAAE,mBAAmBA,yBAAAA,aAAa,mBAAmBD,yBAAAA,iBACvD,OAAO;CAGT,MAAM,gBAAgB,wBAAwB,OAAO;CACrD,IAAI,iBAAiB,QAAQ,kBAAkB,QAAQ,gBACrD,OAAO;CAOT,OAAO;EAHL,GAAI;EACJ,SAAS,sBAAsB,SAAS,aAAa;CAEnC;AACtB;AAEA,SAAS,iCAAiC,QAA8B;CACtE,IAAI,OAAO,YAAY,WAAW,GAChC,OAAO;CAGT,MAAM,YAAY,OAAO,YAAY,SAAS;CAC9C,MAAM,iBAAiB,OAAO,YAAY;CAC1C,IAAI,eAAe,WAAW,GAC5B,OAAO;CAGT,MAAM,kBAAkB,eAAe,SAAS;CAChD,MAAM,aAAa,eAAe;CAClC,MAAM,aAAa,+BAA+B,UAAU;CAC5D,IAAI,eAAe,YACjB,OAAO;CAGT,MAAM,cAAc,CAAC,GAAG,OAAO,WAAW;CAC1C,YAAY,aAAa,CAAC,GAAG,cAAc;CAC3C,YAAY,UAAU,CAAC,mBAAmB;CAC1C,OAAO;EAAE,GAAG;EAAQ;CAAY;AAClC;AAEA,IAAM,gCAAN,cAA4CE,oBAAAA,gBAAgB;CAC1D;CACA;CAEA,YAAY,QAAqC;EAC/C,MAAM,EAAE,UAAU,aAAa,GAAG,kBAAkB,UAAU,CAAC;EAC/D,MAAM,aAAa;EACnB,KAAK,WAAW;EAChB,KAAK,cAAc;CACrB;CAEA,4BAAwD;EACtD,OAAO,KAAK,UAAU,yBAAyB,OAC3C,KAAK,cACL,KAAA;CACN;CAEA,mBAA8B,QAAoB;EAChD,MAAM,gBAAgBC,mBAAAA,QAAY,OAAO;EACzC,MAAM,WACJC,6BAAAA,6BAA6B,aAAa,KAAK,KAAK;EACtD,MAAM,OAAO,KAAK,0BAA0B;EAC5C,OAAOC,6BAAAA,yBACL;GACE;GACA,aAAaC,6BAAAA,0BAA0B,aAAa,KAAK;EAC3D,GACA,MACF;CACF;CAKA,iBACE,GAAG,MAC8C;EACjD,OAAO,KAAK,yBAAyB,MAAM,iBAAiB,GAAG,IAAI,CAAC;CACtE;CAEA,iBACE,GAAG,MAC8C;EACjD,MAAM,CAAC,OAAO,OAAO,eAAe;EACpC,IAAI,SAAS,QAAQ,eAAe,SAAA,GAAA,qBAAA,gBAAA,CAAwB,KAAK,GAC/D,OAAO,MAAM,eACX,EAAE,aAAa,gBAAgB,GAC/B,OACA,WACF;EAEF,OAAO,MAAM,iBAAiB,GAAG,IAAI;CACvC;CAEA,kBACE,GAAG,MAC+C;EAClD,OAAO,KAAK,yBAAyB,MAAM,kBAAkB,GAAG,IAAI,CAAC;CACvE;CAEA,sBACE,GAAG,MACmD;EACtD,OAAO,KAAK,yBAAyB,MAAM,sBAAsB,GAAG,IAAI,CAAC;CAC3E;CAEA,qBACE,GAAG,MACkD;EACrD,OAAO,KAAK,yBAAyB,MAAM,qBAAqB,GAAG,IAAI,CAAC;CAC1E;CAEA,eACE,GAAG,MAC4C;EAC/C,OAAO,KAAK,yBAAyB,MAAM,eAAe,GAAG,IAAI,CAAC;CACpE;CAEA,aACE,QACA,OACA,aACe;EACf,OAAO,MAAM,aACX,iCAAiC,MAAM,GACvC,OACA,WACF;CACF;CAEA,gBACE,GAAG,MAC6C;EAChD,OAAO,KAAK,yBAAyB,MAAM,gBAAgB,GAAG,IAAI,CAAC;CACrE;CAEA,qBACE,GAAG,MACkD;EACrD,OAAO,KAAK,yBAAyB,MAAM,qBAAqB,GAAG,IAAI,CAAC;CAC1E;AACF;AAkBA,SAAgB,6BACd,UAIA;CACA,OACE,YAAY,QACZC,aAAAA,UAAU,SAAS,SAAS,KAC5BA,aAAAA,UAAU,SAAS,SAAS;AAEhC;AAEA,SAAS,yBAAyB,UAAsC;CACtE,OAAOA,aAAAA,UAAU,UAAU,OAAO;AACpC;AAaA,SAAS,oBACP,UACuB;CACvB,MAAM,gBAAuC,CAAC;CAC9C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GAAG;EACnD,IAAI,SAAS,MACX;EAEF,MAAM,cAAc,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;EACpE,IACE,YAAY,KAAK,MAAM,MACvB,YAAY,SAAS,2BAErB;EAEF,cAAc,OAAO;CACvB;CACA,OAAO;AACT;AAEA,SAAgB,+BACd,YACyB;CACzB,MAAM,2BAAoD,CAAC;CAC3D,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAAG;EACrD,IAAI,SAAS,QAAQ,IAAI,KAAK,MAAM,IAClC;EAEF,IAAI,OAAO,UAAU,UAAU;GAC7B,IAAI,MAAM,KAAK,MAAM,MAAM,MAAM,SAAS,2BACxC;GAEF,yBAAyB,OAAO;GAChC;EACF;EACA,yBAAyB,OAAO;CAClC;CACA,OAAO;AACT;AAEA,SAAgB,4BAA4B,EAC1C,WACA,iBACA,SACA,aAMwB;CACxB,OAAO,oBAAoB;EACzB;EACA;EACA;EACA;CACF,CAAC;AACH;AAEA,SAAS,2BACP,eACA,UACmC;CACnC,MAAM,SAAS,oBAAoB;EACjC,GAAI,YAAY,CAAC;EACjB,GAAI,iBAAiB,CAAC;CACxB,CAAC;CACD,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,SAAS,KAAA;AACnD;AAEA,SAAS,kBACP,MACA,YACsB;CACtB,MAAM,SAAS,CAAC,GAAI,QAAQ,CAAC,GAAI,GAAI,cAAc,CAAC,CAAE,CAAC,CAAC,QACrD,QAAQ,IAAI,KAAK,MAAM,EAC1B;CACA,OAAO,OAAO,SAAS,IAAI,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,KAAA;AACpD;AAEA,SAAgB,qBACd,eACA,WAAmB,mBACX;CACR,MAAM,YAAY,eAAe;CACjC,OAAOA,aAAAA,UAAU,SAAS,IAAI,GAAG,SAAS,IAAI,cAAc;AAC9D;AAEA,SAAgB,uBAAgC;CAC9C,OAAO,0BAA0B;AACnC;AAEA,SAAgB,4BAAqC;CACnD,OACEA,aAAAA,UAAU,QAAQ,IAAI,mBAAmB,KACzCA,aAAAA,UAAU,QAAQ,IAAI,mBAAmB;AAE7C;AAEA,SAAgB,4BACd,UACS;CACT,IAAI,UAAU,YAAY,OACxB,OAAO;CAET,OACE,qBAAqB,KACrB,6BAA6B,QAAQ,KACpC,yBAAyB,QAAQ,KAAK,0BAA0B;AAErE;AAQA,SAAgB,sBAAsB,EACpC,UACA,QACA,WACA,eACA,MACA,eAC0D;CAC1D,IAAI,CAAC,4BAA4B,QAAQ,GACvC;CAEF,OAAO,IAAI,8BAA8B;EACvC;EACA;EACA,eAAe,2BACb,eACA,UAAU,QACZ;EACA,MAAM,kBAAkB,MAAM,UAAU,IAAI;EAC5C;EACA;CACF,CAAC;AACH;AAEA,SAAS,+BAA+B,EACtC,UACA,QACA,WACA,eACA,WACA,QACqD;CACrD,OAAO;EACL;EACA;EACA;EACA,MAAM,kBAAkB,MAAM,UAAU,IAAI;EAC5C,UAAU,2BAA2B,eAAe,UAAU,QAAQ;CACxE;AACF;AAEA,SAAgB,uBACd,QACA,QACG;CACH,IAAI,CAAC,4BAA4B,OAAO,QAAQ,GAC9C,OAAO,OAAO;CAEhB,QAAA,GAAA,kBAAA,oBAAA,CAA2B,+BAA+B,MAAM,GAAG,MAAM;AAC3E;AAaA,SAAgB,0BAA0B,OAAyB;CACjE,OAAO,iBAAiBL,oBAAAA;AAC1B;AAEA,eAAsB,uBAAuB,OAA+B;CAC1E,IACE,SAAS,QACTM,aAAAA,gBAAgB,QAAQ,IAAI,gCAAgC,MAAM,MAElE;CAGF,OAAA,GAAA,kBAAA,0BAAA,CAAa,CAAC,CAAC,aAAa;AAC9B"}
|
package/dist/esm/langfuse.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isPresent, parseBooleanEnv } from "./utils/misc.mjs";
|
|
2
2
|
import { resolveLangfuseConfigForSpan, resolveTraceIdSeedForSpan, withLangfuseRuntimeScope } from "./langfuseRuntimeScope.mjs";
|
|
3
3
|
import { AIMessage, AIMessageChunk } from "@langchain/core/messages";
|
|
4
|
+
import { isParentCommand } from "@langchain/langgraph";
|
|
4
5
|
import { CallbackHandler } from "@langfuse/langchain";
|
|
5
6
|
import { context } from "@opentelemetry/api";
|
|
6
7
|
import { getLangfuseTracerProvider, propagateAttributes } from "@langfuse/tracing";
|
|
@@ -88,6 +89,11 @@ var ScopedLangfuseCallbackHandler = class extends CallbackHandler {
|
|
|
88
89
|
handleChainStart(...args) {
|
|
89
90
|
return this.withRuntimeContext(() => super.handleChainStart(...args));
|
|
90
91
|
}
|
|
92
|
+
handleChainError(...args) {
|
|
93
|
+
const [error, runId, parentRunId] = args;
|
|
94
|
+
if (error != null && parentRunId != null && isParentCommand(error)) return super.handleChainEnd({ controlFlow: "ParentCommand" }, runId, parentRunId);
|
|
95
|
+
return super.handleChainError(...args);
|
|
96
|
+
}
|
|
91
97
|
handleAgentAction(...args) {
|
|
92
98
|
return this.withRuntimeContext(() => super.handleAgentAction(...args));
|
|
93
99
|
}
|
|
@@ -1 +1 @@
|
|
|
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 { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type {\n AIMessageChunkFields,\n AIMessageFields,\n UsageMetadata,\n} from '@langchain/core/messages';\nimport type {\n ChatGeneration,\n Generation,\n LLMResult,\n} from '@langchain/core/outputs';\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\ntype BedrockResponseUsage = {\n inputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n};\n\ntype BedrockResponseMetadata = {\n metadata?: {\n usage?: BedrockResponseUsage;\n };\n};\n\nfunction getLangfuseBedrockUsage(\n message: AIMessage | AIMessageChunk\n): UsageMetadata | undefined {\n const usageMetadata = message.usage_metadata;\n const bedrockUsage = (message.response_metadata as BedrockResponseMetadata)\n .metadata?.usage;\n if (\n usageMetadata == null ||\n bedrockUsage == null ||\n usageMetadata.input_tokens !== bedrockUsage.inputTokens\n ) {\n return usageMetadata;\n }\n\n const cacheRead = bedrockUsage.cacheReadInputTokens ?? 0;\n const cacheCreation = bedrockUsage.cacheWriteInputTokens ?? 0;\n if (cacheRead === 0 && cacheCreation === 0) {\n return usageMetadata;\n }\n\n return {\n ...usageMetadata,\n input_tokens: usageMetadata.input_tokens + cacheRead + cacheCreation,\n };\n}\n\nfunction cloneMessageWithUsage(\n message: AIMessage | AIMessageChunk,\n usageMetadata: UsageMetadata\n): AIMessage | AIMessageChunk {\n const fields: AIMessageFields = {\n content: message.content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n id: message.id,\n name: message.name,\n tool_calls: message.tool_calls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: usageMetadata,\n };\n\n if (message instanceof AIMessageChunk) {\n const chunkFields: AIMessageChunkFields = {\n ...fields,\n tool_call_chunks: message.tool_call_chunks,\n };\n return new AIMessageChunk(chunkFields);\n }\n\n return new AIMessage(fields);\n}\n\nfunction normalizeGenerationForLangfuse(generation: Generation): Generation {\n if (!('message' in generation)) {\n return generation;\n }\n\n const message = (generation as ChatGeneration).message;\n if (!(message instanceof AIMessage || message instanceof AIMessageChunk)) {\n return generation;\n }\n\n const usageMetadata = getLangfuseBedrockUsage(message);\n if (usageMetadata == null || usageMetadata === message.usage_metadata) {\n return generation;\n }\n\n const chatGeneration: ChatGeneration = {\n ...(generation as ChatGeneration),\n message: cloneMessageWithUsage(message, usageMetadata),\n };\n return chatGeneration;\n}\n\nfunction normalizeBedrockUsageForLangfuse(output: LLMResult): LLMResult {\n if (output.generations.length === 0) {\n return output;\n }\n\n const listIndex = output.generations.length - 1;\n const generationList = output.generations[listIndex];\n if (generationList.length === 0) {\n return output;\n }\n\n const generationIndex = generationList.length - 1;\n const generation = generationList[generationIndex];\n const normalized = normalizeGenerationForLangfuse(generation);\n if (normalized === generation) {\n return output;\n }\n\n const generations = [...output.generations];\n generations[listIndex] = [...generationList];\n generations[listIndex][generationIndex] = normalized;\n return { ...output, generations };\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 handleLLMEnd(\n output: LLMResult,\n runId: string,\n parentRunId?: string\n ): Promise<void> {\n return super.handleLLMEnd(\n normalizeBedrockUsageForLangfuse(output),\n runId,\n parentRunId\n );\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]) !== true\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 { isParentCommand } from '@langchain/langgraph';\nimport { context as otelContext } from '@opentelemetry/api';\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport {\n getLangfuseTracerProvider,\n propagateAttributes,\n} from '@langfuse/tracing';\nimport type {\n AIMessageChunkFields,\n AIMessageFields,\n UsageMetadata,\n} from '@langchain/core/messages';\nimport type {\n ChatGeneration,\n Generation,\n LLMResult,\n} from '@langchain/core/outputs';\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\ntype BedrockResponseUsage = {\n inputTokens?: number;\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n};\n\ntype BedrockResponseMetadata = {\n metadata?: {\n usage?: BedrockResponseUsage;\n };\n};\n\nfunction getLangfuseBedrockUsage(\n message: AIMessage | AIMessageChunk\n): UsageMetadata | undefined {\n const usageMetadata = message.usage_metadata;\n const bedrockUsage = (message.response_metadata as BedrockResponseMetadata)\n .metadata?.usage;\n if (\n usageMetadata == null ||\n bedrockUsage == null ||\n usageMetadata.input_tokens !== bedrockUsage.inputTokens\n ) {\n return usageMetadata;\n }\n\n const cacheRead = bedrockUsage.cacheReadInputTokens ?? 0;\n const cacheCreation = bedrockUsage.cacheWriteInputTokens ?? 0;\n if (cacheRead === 0 && cacheCreation === 0) {\n return usageMetadata;\n }\n\n return {\n ...usageMetadata,\n input_tokens: usageMetadata.input_tokens + cacheRead + cacheCreation,\n };\n}\n\nfunction cloneMessageWithUsage(\n message: AIMessage | AIMessageChunk,\n usageMetadata: UsageMetadata\n): AIMessage | AIMessageChunk {\n const fields: AIMessageFields = {\n content: message.content,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n id: message.id,\n name: message.name,\n tool_calls: message.tool_calls,\n invalid_tool_calls: message.invalid_tool_calls,\n usage_metadata: usageMetadata,\n };\n\n if (message instanceof AIMessageChunk) {\n const chunkFields: AIMessageChunkFields = {\n ...fields,\n tool_call_chunks: message.tool_call_chunks,\n };\n return new AIMessageChunk(chunkFields);\n }\n\n return new AIMessage(fields);\n}\n\nfunction normalizeGenerationForLangfuse(generation: Generation): Generation {\n if (!('message' in generation)) {\n return generation;\n }\n\n const message = (generation as ChatGeneration).message;\n if (!(message instanceof AIMessage || message instanceof AIMessageChunk)) {\n return generation;\n }\n\n const usageMetadata = getLangfuseBedrockUsage(message);\n if (usageMetadata == null || usageMetadata === message.usage_metadata) {\n return generation;\n }\n\n const chatGeneration: ChatGeneration = {\n ...(generation as ChatGeneration),\n message: cloneMessageWithUsage(message, usageMetadata),\n };\n return chatGeneration;\n}\n\nfunction normalizeBedrockUsageForLangfuse(output: LLMResult): LLMResult {\n if (output.generations.length === 0) {\n return output;\n }\n\n const listIndex = output.generations.length - 1;\n const generationList = output.generations[listIndex];\n if (generationList.length === 0) {\n return output;\n }\n\n const generationIndex = generationList.length - 1;\n const generation = generationList[generationIndex];\n const normalized = normalizeGenerationForLangfuse(generation);\n if (normalized === generation) {\n return output;\n }\n\n const generations = [...output.generations];\n generations[listIndex] = [...generationList];\n generations[listIndex][generationIndex] = normalized;\n return { ...output, generations };\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 handleChainError(\n ...args: Parameters<CallbackHandler['handleChainError']>\n ): ReturnType<CallbackHandler['handleChainError']> {\n const [error, runId, parentRunId] = args;\n if (error != null && parentRunId != null && isParentCommand(error)) {\n return super.handleChainEnd(\n { controlFlow: 'ParentCommand' },\n runId,\n parentRunId\n );\n }\n return super.handleChainError(...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 handleLLMEnd(\n output: LLMResult,\n runId: string,\n parentRunId?: string\n ): Promise<void> {\n return super.handleLLMEnd(\n normalizeBedrockUsageForLangfuse(output),\n runId,\n parentRunId\n );\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]) !== true\n ) {\n return;\n }\n const provider = getLangfuseTracerProvider() as FlushableTracerProvider;\n await provider.forceFlush?.();\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.3",
|
|
4
4
|
"main": "./dist/cjs/main.cjs",
|
|
5
5
|
"module": "./dist/esm/main.mjs",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -198,7 +198,8 @@
|
|
|
198
198
|
"reinstall": "npm run clean && npm ci && rm -rf ./dist && npm run build",
|
|
199
199
|
"re": "bun run clean && bun install && rm -rf ./dist && turbo build",
|
|
200
200
|
"lint": "eslint \"{,!(node_modules|venv)/**/}*.{js,jsx,ts,tsx}\" --fix",
|
|
201
|
-
"format": "prettier --write ."
|
|
201
|
+
"format": "prettier --write .",
|
|
202
|
+
"audit": "npm audit --omit=dev"
|
|
202
203
|
},
|
|
203
204
|
"overrides": {
|
|
204
205
|
"@langchain/openai": "1.5.5",
|
|
@@ -208,14 +209,16 @@
|
|
|
208
209
|
"uuid": "$uuid",
|
|
209
210
|
"ajv": "6.14.0",
|
|
210
211
|
"@opentelemetry/core": "^2.9.0",
|
|
211
|
-
"js-yaml": "^4.3.0"
|
|
212
|
+
"js-yaml": "^4.3.0",
|
|
213
|
+
"minimatch": "^10.2.2",
|
|
214
|
+
"test-exclude": "^8.0.0"
|
|
212
215
|
},
|
|
213
216
|
"dependencies": {
|
|
214
|
-
"@anthropic-ai/sdk": "^0.
|
|
217
|
+
"@anthropic-ai/sdk": "^0.115.0",
|
|
215
218
|
"@aws-sdk/client-bedrock-runtime": "^3.1075.0",
|
|
216
|
-
"@langchain/anthropic": "
|
|
219
|
+
"@langchain/anthropic": "1.5.2",
|
|
217
220
|
"@langchain/aws": "^1.4.2",
|
|
218
|
-
"@langchain/core": "^1.2.
|
|
221
|
+
"@langchain/core": "^1.2.3",
|
|
219
222
|
"@langchain/deepseek": "^1.1.3",
|
|
220
223
|
"@langchain/google-common": "2.2.0",
|
|
221
224
|
"@langchain/google-gauth": "2.2.0",
|
|
@@ -260,18 +263,19 @@
|
|
|
260
263
|
"devDependencies": {
|
|
261
264
|
"@anthropic-ai/sandbox-runtime": "^0.0.67",
|
|
262
265
|
"@anthropic-ai/vertex-sdk": "^0.12.0",
|
|
263
|
-
"@eslint/
|
|
266
|
+
"@eslint/js": "^10.0.1",
|
|
264
267
|
"@langchain/langgraph-checkpoint-mongodb": "^1.4.0",
|
|
265
268
|
"@swc/core": "^1.6.13",
|
|
266
269
|
"@types/jest": "^30.0.0",
|
|
267
270
|
"@types/node": "^24.13.1",
|
|
268
271
|
"@types/node-fetch": "^2.6.13",
|
|
269
272
|
"@types/yargs-parser": "^21.0.3",
|
|
270
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
271
|
-
"@typescript-eslint/parser": "^8.
|
|
272
|
-
"eslint": "^
|
|
273
|
-
"eslint-import-resolver-typescript": "^
|
|
274
|
-
"eslint-plugin-import": "^
|
|
273
|
+
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
|
274
|
+
"@typescript-eslint/parser": "^8.65.0",
|
|
275
|
+
"eslint": "^10.8.0",
|
|
276
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
277
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
278
|
+
"globals": "^17.8.0",
|
|
275
279
|
"husky": "^9.1.7",
|
|
276
280
|
"jest": "^30.2.0",
|
|
277
281
|
"jest-util": "^30.2.0",
|
package/src/langfuse.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CallbackHandler } from '@langfuse/langchain';
|
|
2
|
+
import { isParentCommand } from '@langchain/langgraph';
|
|
2
3
|
import { context as otelContext } from '@opentelemetry/api';
|
|
3
4
|
import { AIMessage, AIMessageChunk } from '@langchain/core/messages';
|
|
4
5
|
import {
|
|
@@ -204,6 +205,20 @@ class ScopedLangfuseCallbackHandler extends CallbackHandler {
|
|
|
204
205
|
return this.withRuntimeContext(() => super.handleChainStart(...args));
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
override handleChainError(
|
|
209
|
+
...args: Parameters<CallbackHandler['handleChainError']>
|
|
210
|
+
): ReturnType<CallbackHandler['handleChainError']> {
|
|
211
|
+
const [error, runId, parentRunId] = args;
|
|
212
|
+
if (error != null && parentRunId != null && isParentCommand(error)) {
|
|
213
|
+
return super.handleChainEnd(
|
|
214
|
+
{ controlFlow: 'ParentCommand' },
|
|
215
|
+
runId,
|
|
216
|
+
parentRunId
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return super.handleChainError(...args);
|
|
220
|
+
}
|
|
221
|
+
|
|
207
222
|
override handleAgentAction(
|
|
208
223
|
...args: Parameters<CallbackHandler['handleAgentAction']>
|
|
209
224
|
): ReturnType<CallbackHandler['handleAgentAction']> {
|
|
@@ -976,6 +976,7 @@ test('Anthropic message_delta usage preserves cumulative input totals', () => {
|
|
|
976
976
|
output_tokens: 375,
|
|
977
977
|
cache_creation_input_tokens: 11,
|
|
978
978
|
cache_read_input_tokens: 13,
|
|
979
|
+
fallback_credit: null,
|
|
979
980
|
server_tool_use: null,
|
|
980
981
|
iterations: null,
|
|
981
982
|
output_tokens_details: null,
|
|
@@ -1018,6 +1019,7 @@ test('Anthropic stream usage does not double-count cumulative input tokens', asy
|
|
|
1018
1019
|
cache_creation: null,
|
|
1019
1020
|
cache_creation_input_tokens: 20,
|
|
1020
1021
|
cache_read_input_tokens: 30,
|
|
1022
|
+
fallback_credit: null,
|
|
1021
1023
|
inference_geo: null,
|
|
1022
1024
|
input_tokens: 243,
|
|
1023
1025
|
iterations: null,
|
|
@@ -1043,6 +1045,7 @@ test('Anthropic stream usage does not double-count cumulative input tokens', asy
|
|
|
1043
1045
|
output_tokens: 375,
|
|
1044
1046
|
cache_creation_input_tokens: 20,
|
|
1045
1047
|
cache_read_input_tokens: 30,
|
|
1048
|
+
fallback_credit: null,
|
|
1046
1049
|
server_tool_use: null,
|
|
1047
1050
|
iterations: null,
|
|
1048
1051
|
output_tokens_details: null,
|
|
@@ -1089,6 +1092,7 @@ test('Anthropic stream usage accepts input first reported at message_delta', asy
|
|
|
1089
1092
|
cache_creation: null,
|
|
1090
1093
|
cache_creation_input_tokens: 0,
|
|
1091
1094
|
cache_read_input_tokens: 0,
|
|
1095
|
+
fallback_credit: null,
|
|
1092
1096
|
inference_geo: null,
|
|
1093
1097
|
input_tokens: 0,
|
|
1094
1098
|
iterations: null,
|
|
@@ -1114,6 +1118,7 @@ test('Anthropic stream usage accepts input first reported at message_delta', asy
|
|
|
1114
1118
|
output_tokens: 42,
|
|
1115
1119
|
cache_creation_input_tokens: 500,
|
|
1116
1120
|
cache_read_input_tokens: 200,
|
|
1121
|
+
fallback_credit: null,
|
|
1117
1122
|
server_tool_use: null,
|
|
1118
1123
|
iterations: null,
|
|
1119
1124
|
output_tokens_details: null,
|
|
@@ -1160,6 +1165,7 @@ test('Anthropic stream usage handles multiple cumulative message_delta events',
|
|
|
1160
1165
|
cache_creation: null,
|
|
1161
1166
|
cache_creation_input_tokens: 0,
|
|
1162
1167
|
cache_read_input_tokens: 0,
|
|
1168
|
+
fallback_credit: null,
|
|
1163
1169
|
inference_geo: null,
|
|
1164
1170
|
input_tokens: 243,
|
|
1165
1171
|
iterations: null,
|
|
@@ -1185,6 +1191,7 @@ test('Anthropic stream usage handles multiple cumulative message_delta events',
|
|
|
1185
1191
|
output_tokens: 100,
|
|
1186
1192
|
cache_creation_input_tokens: 0,
|
|
1187
1193
|
cache_read_input_tokens: 0,
|
|
1194
|
+
fallback_credit: null,
|
|
1188
1195
|
server_tool_use: null,
|
|
1189
1196
|
iterations: null,
|
|
1190
1197
|
output_tokens_details: null,
|
|
@@ -1204,6 +1211,7 @@ test('Anthropic stream usage handles multiple cumulative message_delta events',
|
|
|
1204
1211
|
output_tokens: 375,
|
|
1205
1212
|
cache_creation_input_tokens: 0,
|
|
1206
1213
|
cache_read_input_tokens: 0,
|
|
1214
|
+
fallback_credit: null,
|
|
1207
1215
|
server_tool_use: null,
|
|
1208
1216
|
iterations: null,
|
|
1209
1217
|
output_tokens_details: null,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { HumanMessage } from '@langchain/core/messages';
|
|
2
|
+
import { Command, ParentCommand } from '@langchain/langgraph';
|
|
2
3
|
import { LangfuseOtelSpanAttributes } from '@langfuse/tracing';
|
|
3
4
|
import { CallbackManager } from '@langchain/core/callbacks/manager';
|
|
4
5
|
import { context as otelContext, trace as otelTrace } from '@opentelemetry/api';
|
|
6
|
+
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
5
7
|
import type * as t from '@/types';
|
|
6
8
|
import { handleConverseStreamMetadata } from '@/llm/bedrock/utils/message_outputs';
|
|
7
9
|
import { traceIdFromSeed } from '@/langfuseRuntimeContext';
|
|
8
|
-
import { Providers } from '@/common';
|
|
10
|
+
import { Constants, Providers } from '@/common';
|
|
9
11
|
import { Run } from '@/run';
|
|
10
12
|
|
|
11
13
|
const mockProcessorStarts: Array<{
|
|
@@ -389,6 +391,206 @@ describe('Langfuse callback composition', () => {
|
|
|
389
391
|
});
|
|
390
392
|
});
|
|
391
393
|
|
|
394
|
+
it('ends nested ParentCommand control flow without reporting an error', async () => {
|
|
395
|
+
const { createLangfuseHandler } = await import('@/langfuse');
|
|
396
|
+
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
397
|
+
const langfuse = {
|
|
398
|
+
publicKey: 'pk-test',
|
|
399
|
+
secretKey: 'sk-test',
|
|
400
|
+
};
|
|
401
|
+
initializeLangfuseTracing(langfuse);
|
|
402
|
+
const handler = createLangfuseHandler({ langfuse });
|
|
403
|
+
const runId = 'test-langfuse-parent-command';
|
|
404
|
+
|
|
405
|
+
await handler?.handleChainStart(
|
|
406
|
+
{ lc: 1, type: 'not_implemented', id: ['HandoffChain'] },
|
|
407
|
+
{ input: 'handoff' },
|
|
408
|
+
runId,
|
|
409
|
+
'parent-run'
|
|
410
|
+
);
|
|
411
|
+
await handler?.handleChainError(
|
|
412
|
+
new ParentCommand(
|
|
413
|
+
new Command({ graph: 'source:graph', goto: 'destination' })
|
|
414
|
+
),
|
|
415
|
+
runId,
|
|
416
|
+
'parent-run'
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
expect(mockSpanAttributeSets).not.toContainEqual(
|
|
420
|
+
expect.objectContaining({
|
|
421
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: 'ERROR',
|
|
422
|
+
})
|
|
423
|
+
);
|
|
424
|
+
expect(mockSpanAttributeSets).toContainEqual(
|
|
425
|
+
expect.objectContaining({
|
|
426
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT]: JSON.stringify({
|
|
427
|
+
controlFlow: 'ParentCommand',
|
|
428
|
+
}),
|
|
429
|
+
})
|
|
430
|
+
);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('reports a ParentCommand that escapes the root graph as an error', async () => {
|
|
434
|
+
const { createLangfuseHandler } = await import('@/langfuse');
|
|
435
|
+
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
436
|
+
const langfuse = {
|
|
437
|
+
publicKey: 'pk-root-parent-command',
|
|
438
|
+
secretKey: 'sk-root-parent-command',
|
|
439
|
+
};
|
|
440
|
+
initializeLangfuseTracing(langfuse);
|
|
441
|
+
const handler = createLangfuseHandler({ langfuse });
|
|
442
|
+
const runId = 'test-langfuse-root-parent-command';
|
|
443
|
+
|
|
444
|
+
await handler?.handleChainStart(
|
|
445
|
+
{ lc: 1, type: 'not_implemented', id: ['RootChain'] },
|
|
446
|
+
{ input: 'root handoff' },
|
|
447
|
+
runId
|
|
448
|
+
);
|
|
449
|
+
await handler?.handleChainError(
|
|
450
|
+
new ParentCommand(
|
|
451
|
+
new Command({ graph: Command.PARENT, goto: 'destination' })
|
|
452
|
+
),
|
|
453
|
+
runId
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
expect(mockSpanAttributeSets).toContainEqual(
|
|
457
|
+
expect.objectContaining({
|
|
458
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: 'ERROR',
|
|
459
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE]:
|
|
460
|
+
'ParentCommand',
|
|
461
|
+
})
|
|
462
|
+
);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('continues reporting genuine nested chain failures as errors', async () => {
|
|
466
|
+
const { createLangfuseHandler } = await import('@/langfuse');
|
|
467
|
+
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
468
|
+
const langfuse = {
|
|
469
|
+
publicKey: 'pk-genuine-error',
|
|
470
|
+
secretKey: 'sk-genuine-error',
|
|
471
|
+
};
|
|
472
|
+
initializeLangfuseTracing(langfuse);
|
|
473
|
+
const handler = createLangfuseHandler({ langfuse });
|
|
474
|
+
const runId = 'test-langfuse-genuine-error';
|
|
475
|
+
|
|
476
|
+
await handler?.handleChainStart(
|
|
477
|
+
{ lc: 1, type: 'not_implemented', id: ['FailingChain'] },
|
|
478
|
+
{ input: 'fail' },
|
|
479
|
+
runId,
|
|
480
|
+
'parent-run'
|
|
481
|
+
);
|
|
482
|
+
await handler?.handleChainError(new Error('boom'), runId, 'parent-run');
|
|
483
|
+
|
|
484
|
+
expect(mockSpanAttributeSets).toContainEqual(
|
|
485
|
+
expect.objectContaining({
|
|
486
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: 'ERROR',
|
|
487
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE]: 'Error: boom',
|
|
488
|
+
})
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('delegates null chain errors without throwing during classification', async () => {
|
|
493
|
+
const { createLangfuseHandler } = await import('@/langfuse');
|
|
494
|
+
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
495
|
+
const langfuse = {
|
|
496
|
+
publicKey: 'pk-null-error',
|
|
497
|
+
secretKey: 'sk-null-error',
|
|
498
|
+
};
|
|
499
|
+
initializeLangfuseTracing(langfuse);
|
|
500
|
+
const handler = createLangfuseHandler({ langfuse });
|
|
501
|
+
const runId = 'test-langfuse-null-error';
|
|
502
|
+
|
|
503
|
+
await handler?.handleChainStart(
|
|
504
|
+
{ lc: 1, type: 'not_implemented', id: ['NullErrorChain'] },
|
|
505
|
+
{ input: 'fail' },
|
|
506
|
+
runId,
|
|
507
|
+
'parent-run'
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
await expect(
|
|
511
|
+
handler?.handleChainError(null, runId, 'parent-run')
|
|
512
|
+
).resolves.toBeUndefined();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('traces a completed two-agent handoff without false error spans', async () => {
|
|
516
|
+
const langfuse = {
|
|
517
|
+
publicKey: 'pk-handoff',
|
|
518
|
+
secretKey: 'sk-handoff',
|
|
519
|
+
};
|
|
520
|
+
const run = await Run.create<t.IState>({
|
|
521
|
+
runId: 'test-langfuse-completed-handoff',
|
|
522
|
+
graphConfig: {
|
|
523
|
+
type: 'multi-agent',
|
|
524
|
+
agents: [
|
|
525
|
+
{
|
|
526
|
+
agentId: 'agent_source',
|
|
527
|
+
name: 'Source Agent',
|
|
528
|
+
provider: Providers.OPENAI,
|
|
529
|
+
clientOptions: { model: 'gpt-4' },
|
|
530
|
+
instructions: 'Transfer to the destination agent.',
|
|
531
|
+
tools: [],
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
agentId: 'agent_destination',
|
|
535
|
+
name: 'Destination Agent',
|
|
536
|
+
provider: Providers.OPENAI,
|
|
537
|
+
clientOptions: { model: 'gpt-4' },
|
|
538
|
+
instructions: 'Complete the request.',
|
|
539
|
+
tools: [],
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
edges: [
|
|
543
|
+
{
|
|
544
|
+
from: 'agent_source',
|
|
545
|
+
to: 'agent_destination',
|
|
546
|
+
edgeType: 'handoff',
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
},
|
|
550
|
+
langfuse,
|
|
551
|
+
skipCleanup: true,
|
|
552
|
+
});
|
|
553
|
+
run.Graph?.overrideTestModel(
|
|
554
|
+
['Transferring to destination', 'Destination complete'],
|
|
555
|
+
10,
|
|
556
|
+
[
|
|
557
|
+
{
|
|
558
|
+
id: 'handoff-call',
|
|
559
|
+
name: `${Constants.LC_TRANSFER_TO_}agent_destination`,
|
|
560
|
+
args: {},
|
|
561
|
+
} as ToolCall,
|
|
562
|
+
]
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
await run.processStream(
|
|
566
|
+
{ messages: [new HumanMessage('Please hand this off')] },
|
|
567
|
+
{
|
|
568
|
+
configurable: {
|
|
569
|
+
thread_id: 'test-langfuse-completed-handoff-thread',
|
|
570
|
+
},
|
|
571
|
+
version: 'v2',
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
expect(
|
|
576
|
+
run
|
|
577
|
+
.getRunMessages()
|
|
578
|
+
?.some((message) => message.content === 'Destination complete')
|
|
579
|
+
).toBe(true);
|
|
580
|
+
expect(mockSpanAttributeSets).toContainEqual(
|
|
581
|
+
expect.objectContaining({
|
|
582
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT]: JSON.stringify({
|
|
583
|
+
controlFlow: 'ParentCommand',
|
|
584
|
+
}),
|
|
585
|
+
})
|
|
586
|
+
);
|
|
587
|
+
expect(mockSpanAttributeSets).not.toContainEqual(
|
|
588
|
+
expect.objectContaining({
|
|
589
|
+
[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL]: 'ERROR',
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
});
|
|
593
|
+
|
|
392
594
|
it('exports Bedrock prompt-cache usage buckets to Langfuse', async () => {
|
|
393
595
|
const { createLangfuseHandler } = await import('@/langfuse');
|
|
394
596
|
const { initializeLangfuseTracing } = await import('@/instrumentation');
|