@librechat/agents 3.2.35 → 3.2.37
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/agents/AgentContext.cjs +75 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/agents/projection.cjs +25 -0
- package/dist/cjs/agents/projection.cjs.map +1 -0
- package/dist/cjs/graphs/Graph.cjs +10 -26
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +16 -5
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +7 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +118 -7
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +44 -4
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +7 -0
- package/dist/cjs/messages/budget.cjs +23 -0
- package/dist/cjs/messages/budget.cjs.map +1 -0
- package/dist/cjs/messages/cache.cjs +184 -0
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -0
- package/dist/cjs/summarization/node.cjs +1 -1
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/search/format.cjs +91 -2
- package/dist/cjs/tools/search/format.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +4 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +28 -14
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +76 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/agents/projection.mjs +25 -0
- package/dist/esm/agents/projection.mjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +9 -25
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +16 -5
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +7 -0
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +118 -7
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +44 -4
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -2
- package/dist/esm/messages/budget.mjs +23 -0
- package/dist/esm/messages/budget.mjs.map +1 -0
- package/dist/esm/messages/cache.mjs +182 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -0
- package/dist/esm/summarization/node.mjs +2 -2
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/search/format.mjs +91 -2
- package/dist/esm/tools/search/format.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +4 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +28 -14
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +30 -1
- package/dist/types/agents/projection.d.ts +26 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/messages/budget.d.ts +11 -0
- package/dist/types/messages/cache.d.ts +47 -0
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/tools/search/format.d.ts +4 -1
- package/dist/types/tools/search/types.d.ts +7 -0
- package/dist/types/types/graph.d.ts +2 -0
- package/package.json +2 -1
- package/src/agents/AgentContext.ts +105 -4
- package/src/agents/__tests__/AgentContext.test.ts +232 -9
- package/src/agents/__tests__/projection.test.ts +73 -0
- package/src/agents/projection.ts +46 -0
- package/src/graphs/Graph.ts +66 -65
- package/src/index.ts +3 -0
- package/src/langfuse.ts +38 -4
- package/src/langfuseToolOutputTracing.ts +18 -0
- package/src/llm/anthropic/utils/cross-provider-reasoning.test.ts +317 -0
- package/src/llm/anthropic/utils/message_inputs.ts +209 -19
- package/src/llm/anthropic/utils/stripPrefillCache.test.ts +111 -0
- package/src/llm/bedrock/utils/cross-provider-reasoning.test.ts +131 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +129 -0
- package/src/llm/bedrock/utils/message_inputs.ts +81 -4
- package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +103 -0
- package/src/messages/budget.ts +32 -0
- package/src/messages/cache.tail.test.ts +340 -0
- package/src/messages/cache.ts +267 -1
- package/src/messages/index.ts +1 -0
- package/src/messages/tailCacheConversion.test.ts +161 -0
- package/src/scripts/bench-prompt-cache.ts +479 -0
- package/src/specs/langfuse-config.test.ts +69 -2
- package/src/specs/langfuse-metadata.test.ts +44 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +6 -0
- package/src/summarization/node.ts +2 -2
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +50 -0
- package/src/tools/search/format.test.ts +242 -0
- package/src/tools/search/format.ts +122 -5
- package/src/tools/search/tool.ts +5 -1
- package/src/tools/search/types.ts +7 -0
- package/src/tools/toolOutputReferences.ts +34 -20
- package/src/types/graph.ts +2 -0
package/dist/esm/langfuse.mjs
CHANGED
|
@@ -37,6 +37,17 @@ function createLangfuseTraceMetadata({ messageId, parentMessageId, agentId, agen
|
|
|
37
37
|
agentName
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
+
function mergeLangfuseTraceMetadata(traceMetadata, metadata) {
|
|
41
|
+
const merged = createTraceMetadata({
|
|
42
|
+
...metadata ?? {},
|
|
43
|
+
...traceMetadata ?? {}
|
|
44
|
+
});
|
|
45
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
46
|
+
}
|
|
47
|
+
function mergeLangfuseTags(tags, configTags) {
|
|
48
|
+
const merged = [...tags ?? [], ...configTags ?? []].filter((tag) => tag.trim() !== "");
|
|
49
|
+
return merged.length > 0 ? [...new Set(merged)] : void 0;
|
|
50
|
+
}
|
|
40
51
|
function getLangfuseTraceName(traceMetadata, fallback = "LibreChat Agent") {
|
|
41
52
|
const agentName = traceMetadata?.agentName;
|
|
42
53
|
return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;
|
|
@@ -56,17 +67,17 @@ function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tag
|
|
|
56
67
|
return new CallbackHandler({
|
|
57
68
|
userId,
|
|
58
69
|
sessionId,
|
|
59
|
-
traceMetadata,
|
|
60
|
-
tags
|
|
70
|
+
traceMetadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata),
|
|
71
|
+
tags: mergeLangfuseTags(tags, langfuse?.tags)
|
|
61
72
|
});
|
|
62
73
|
}
|
|
63
|
-
function createPropagateAttributeParams({ userId, sessionId, traceMetadata, traceName, tags }) {
|
|
74
|
+
function createPropagateAttributeParams({ langfuse, userId, sessionId, traceMetadata, traceName, tags }) {
|
|
64
75
|
return {
|
|
65
76
|
userId,
|
|
66
77
|
sessionId,
|
|
67
78
|
traceName,
|
|
68
|
-
tags,
|
|
69
|
-
metadata: traceMetadata
|
|
79
|
+
tags: mergeLangfuseTags(tags, langfuse?.tags),
|
|
80
|
+
metadata: mergeLangfuseTraceMetadata(traceMetadata, langfuse?.metadata)
|
|
70
81
|
};
|
|
71
82
|
}
|
|
72
83
|
function withLangfuseAttributes(params, action) {
|
|
@@ -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>;\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\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 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\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,\n tags,\n });\n}\n\nfunction createPropagateAttributeParams({\n userId,\n sessionId,\n traceMetadata,\n traceName,\n tags,\n}: LangfuseAttributeParams): PropagateAttributesParams {\n return {\n userId,\n sessionId,\n traceName,\n tags,\n metadata: traceMetadata,\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":";;;;AASA,MAAM,4BAA4B;AAClC,MAAM,kCAAkC;
|
|
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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"}
|
|
@@ -327,9 +327,16 @@ function resolveLangfuseConfig(runLangfuse, agentLangfuse) {
|
|
|
327
327
|
...runLangfuse.toolOutputTracing,
|
|
328
328
|
...agentLangfuse.toolOutputTracing
|
|
329
329
|
} : void 0;
|
|
330
|
+
const metadata = runLangfuse.metadata != null || agentLangfuse.metadata != null ? {
|
|
331
|
+
...runLangfuse.metadata,
|
|
332
|
+
...agentLangfuse.metadata
|
|
333
|
+
} : void 0;
|
|
334
|
+
const tags = runLangfuse.tags != null || agentLangfuse.tags != null ? [...new Set([...runLangfuse.tags ?? [], ...agentLangfuse.tags ?? []])] : void 0;
|
|
330
335
|
return {
|
|
331
336
|
...runLangfuse,
|
|
332
337
|
...agentLangfuse,
|
|
338
|
+
...metadata != null ? { metadata } : {},
|
|
339
|
+
...tags != null ? { tags } : {},
|
|
333
340
|
...toolNodeTracing != null ? { toolNodeTracing } : {},
|
|
334
341
|
...toolOutputTracing != null ? { toolOutputTracing } : {}
|
|
335
342
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"langfuseToolOutputTracing.mjs","names":[],"sources":["../../src/langfuseToolOutputTracing.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport { context, createContextKey } from '@opentelemetry/api';\nimport { LangfuseOtelSpanAttributes } from '@langfuse/tracing';\nimport type {\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';\n\nexport const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';\n\nconst langfuseToolOutputTracingConfigKey = createContextKey(\n 'librechat.langfuse.tool-output-tracing'\n);\nconst langfuseConfigKey = createContextKey('librechat.langfuse.config');\nconst toolOutputTracingStorage =\n new AsyncLocalStorage<ResolvedLangfuseToolOutputTracingConfig>();\nconst langfuseConfigStorage = new AsyncLocalStorage<t.LangfuseConfig>();\nconst LANGGRAPH_TOOL_NODE_PREFIX = 'tools=';\n\nconst CHAT_ROLES = new Set([\n 'assistant',\n 'developer',\n 'human',\n 'system',\n 'user',\n]);\n\nexport type ResolvedLangfuseToolOutputTracingConfig = {\n enabled: boolean;\n redactedToolNames: Set<string>;\n redactedToolNameMatchMode: 'exact' | 'partial';\n redactionText: string;\n};\n\ntype SpanWithAttributes = ReadableSpan & {\n attributes: Record<string, unknown>;\n};\n\ntype RedactionResult = {\n value: unknown;\n changed: boolean;\n};\n\ntype RedactionContext = {\n toolNamesByCallId: Map<string, string>;\n};\n\nconst TOOL_OUTPUT_FIELD_KEYS = ['content', 'artifact'];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isPresent(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction parseBoolean(value: string | undefined): boolean | undefined {\n if (value == null) {\n return undefined;\n }\n\n const normalized = value.trim().toLowerCase();\n if (['1', 'true', 'yes', 'on'].includes(normalized)) {\n return true;\n }\n if (['0', 'false', 'no', 'off'].includes(normalized)) {\n return false;\n }\n\n return undefined;\n}\n\nfunction 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 = parseBoolean(\n process.env.LANGFUSE_TRACE_TOOL_OUTPUTS\n );\n if (traceToolOutputs != null) {\n return traceToolOutputs;\n }\n\n const redactToolOutputs = parseBoolean(\n process.env.LANGFUSE_REDACT_TOOL_OUTPUTS\n );\n if (redactToolOutputs != null) {\n return !redactToolOutputs;\n }\n\n return parseBoolean(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 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:\n agentConfig?.enabled ??\n runConfig?.enabled ??\n getEnvToolOutputTracingEnabled() ??\n true,\n redactedToolNames: normalizeToolNames(\n agentConfig?.redactedToolNames ??\n runConfig?.redactedToolNames ??\n getEnvRedactedToolNames()\n ),\n redactedToolNameMatchMode:\n agentConfig?.redactedToolNameMatchMode ??\n runConfig?.redactedToolNameMatchMode ??\n getEnvToolNameMatchMode() ??\n 'exact',\n redactionText:\n agentConfig?.redactionText ??\n runConfig?.redactionText ??\n getEnvRedactionText() ??\n LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,\n };\n}\n\nfunction shouldApplyToolOutputRedaction(\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n return config.enabled === false || config.redactedToolNames.size > 0;\n}\n\nfunction toolNameMatches(\n toolName: string | undefined,\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n if (!isPresent(toolName)) {\n return false;\n }\n\n const normalizedToolName = normalizeToolName(toolName);\n if (config.redactedToolNameMatchMode === 'partial') {\n for (const redactedToolName of config.redactedToolNames) {\n if (normalizedToolName.includes(redactedToolName)) {\n return true;\n }\n }\n return false;\n }\n\n return config.redactedToolNames.has(normalizedToolName);\n}\n\nfunction shouldRedactTool(\n toolName: string | undefined,\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n return config.enabled === false || toolNameMatches(toolName, config);\n}\n\nfunction getStringField(\n value: Record<string, unknown>,\n key: string\n): string | undefined {\n const field = value[key];\n return typeof field === 'string' ? field : undefined;\n}\n\nfunction getNestedStringField(\n value: Record<string, unknown>,\n objectKey: string,\n fieldKey: string\n): string | undefined {\n const nested = value[objectKey];\n if (!isRecord(nested)) {\n return undefined;\n }\n return getStringField(nested, fieldKey);\n}\n\nfunction getSerializedToolCallId(\n value: Record<string, unknown>\n): string | undefined {\n return (\n getStringField(value, 'tool_call_id') ??\n getNestedStringField(value, 'kwargs', 'tool_call_id') ??\n getNestedStringField(value, 'additional_kwargs', 'tool_call_id') ??\n getNestedStringField(value, 'data', 'tool_call_id') ??\n (typeof value.id === 'string' ? value.id : undefined)\n );\n}\n\nfunction getSerializedToolName(\n value: Record<string, unknown>,\n redactionContext?: RedactionContext\n): string | undefined {\n const role = getStringField(value, 'role');\n const explicitName =\n getStringField(value, 'name') ??\n getStringField(value, 'tool_name') ??\n getNestedStringField(value, 'function', 'name') ??\n getNestedStringField(value, 'kwargs', 'name') ??\n getNestedStringField(value, 'additional_kwargs', 'name') ??\n getNestedStringField(value, 'data', 'name') ??\n (role != null && role.toLowerCase() !== 'tool' ? role : undefined);\n\n if (explicitName != null) {\n return explicitName;\n }\n\n const toolCallId = getSerializedToolCallId(value);\n return toolCallId != null\n ? redactionContext?.toolNamesByCallId.get(toolCallId)\n : undefined;\n}\n\nfunction hasToolMessageIdentity(value: Record<string, unknown>): boolean {\n const type = getStringField(value, 'type') ?? getStringField(value, '_type');\n if (type === 'tool' || type === 'tool_message') {\n return true;\n }\n\n const id = value.id;\n if (\n Array.isArray(id) &&\n id.some((part) => typeof part === 'string' && part.includes('ToolMessage'))\n ) {\n return true;\n }\n\n if (\n 'tool_call_id' in value ||\n getNestedStringField(value, 'kwargs', 'tool_call_id') != null ||\n getNestedStringField(value, 'additional_kwargs', 'tool_call_id') != null\n ) {\n return true;\n }\n\n const role = getStringField(value, 'role');\n return (\n role != null &&\n !CHAT_ROLES.has(role.toLowerCase()) &&\n ('content' in value || isRecord(value.kwargs) || isRecord(value.data))\n );\n}\n\nfunction redactToolContentFields(\n value: Record<string, unknown>,\n config: ResolvedLangfuseToolOutputTracingConfig\n): Record<string, unknown> {\n const next = { ...value };\n\n for (const outputKey of TOOL_OUTPUT_FIELD_KEYS) {\n if (outputKey in next) {\n next[outputKey] = config.redactionText;\n }\n }\n\n for (const nestedKey of ['kwargs', 'data', 'additional_kwargs']) {\n const nested = next[nestedKey];\n if (!isRecord(nested)) {\n continue;\n }\n const nextNested = { ...nested };\n let changed = false;\n for (const outputKey of TOOL_OUTPUT_FIELD_KEYS) {\n if (outputKey in nextNested) {\n nextNested[outputKey] = config.redactionText;\n changed = true;\n }\n }\n if (changed) {\n next[nestedKey] = nextNested;\n }\n }\n\n return next;\n}\n\nfunction collectToolCallNames(\n value: unknown,\n redactionContext: RedactionContext\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n collectToolCallNames(item, redactionContext);\n }\n return;\n }\n\n if (!isRecord(value)) {\n return;\n }\n\n const toolCallId = getSerializedToolCallId(value);\n const toolName = getSerializedToolName(value);\n if (toolCallId != null && toolName != null) {\n redactionContext.toolNamesByCallId.set(toolCallId, toolName);\n }\n\n for (const child of Object.values(value)) {\n collectToolCallNames(child, redactionContext);\n }\n}\n\nfunction redactValue(\n value: unknown,\n config: ResolvedLangfuseToolOutputTracingConfig,\n redactionContext: RedactionContext\n): RedactionResult {\n if (Array.isArray(value)) {\n let changed = false;\n const next: unknown[] = [];\n for (const item of value) {\n const result = redactValue(item, config, redactionContext);\n if (result.changed) {\n changed = true;\n }\n next.push(result.value);\n }\n return changed ? { value: next, changed } : { value, changed };\n }\n\n if (!isRecord(value)) {\n return { value, changed: false };\n }\n\n const toolName = getSerializedToolName(value, redactionContext);\n if (hasToolMessageIdentity(value) && shouldRedactTool(toolName, config)) {\n return {\n value: redactToolContentFields(value, config),\n changed: true,\n };\n }\n\n let changed = false;\n const next: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n const result = redactValue(child, config, redactionContext);\n if (result.changed) {\n changed = true;\n }\n next[key] = result.value;\n }\n\n return changed ? { value: next, changed } : { value, changed };\n}\n\nfunction redactSerializedValue(\n value: unknown,\n config: ResolvedLangfuseToolOutputTracingConfig\n): RedactionResult {\n const redactionContext: RedactionContext = {\n toolNamesByCallId: new Map(),\n };\n if (typeof value !== 'string') {\n collectToolCallNames(value, redactionContext);\n return redactValue(value, config, redactionContext);\n }\n\n const trimmed = value.trim();\n if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n return { value, changed: false };\n }\n\n try {\n const parsed = JSON.parse(value) as unknown;\n collectToolCallNames(parsed, redactionContext);\n const result = redactValue(parsed, config, redactionContext);\n return result.changed\n ? { value: JSON.stringify(result.value), changed: true }\n : { value, changed: false };\n } catch {\n return { value, changed: false };\n }\n}\n\nfunction redactAttribute(\n attributes: Record<string, unknown>,\n key: string,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n if (!(key in attributes)) {\n return;\n }\n\n const result = redactSerializedValue(attributes[key], config);\n if (result.changed) {\n attributes[key] = result.value;\n }\n}\n\nfunction isToolObservation(attributes: Record<string, unknown>): boolean {\n const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];\n return typeof type === 'string' && type.toLowerCase() === 'tool';\n}\n\nfunction classifyLangGraphToolNodeSpan(\n attributes: Record<string, unknown>\n): void {\n const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];\n if (typeof type !== 'string' || type.toLowerCase() !== 'span') {\n return;\n }\n\n const langGraphNode =\n attributes[\n `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.langgraph_node`\n ];\n if (\n typeof langGraphNode === 'string' &&\n langGraphNode.startsWith(LANGGRAPH_TOOL_NODE_PREFIX)\n ) {\n attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] = 'tool';\n }\n}\n\nfunction redactToolObservationOutput(\n span: ReadableSpan,\n attributes: Record<string, unknown>,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n if (\n !(\n isToolObservation(attributes) &&\n shouldRedactTool(span.name, config) &&\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT in attributes\n )\n ) {\n return;\n }\n\n attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] =\n config.redactionText;\n}\n\nexport function redactLangfuseSpanToolOutputs(\n span: ReadableSpan,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n const attributes = (span as SpanWithAttributes).attributes;\n classifyLangGraphToolNodeSpan(attributes);\n\n if (!shouldApplyToolOutputRedaction(config)) {\n return;\n }\n\n redactToolObservationOutput(span, attributes, config);\n\n for (const key of [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n ]) {\n redactAttribute(attributes, key, config);\n }\n}\n\nfunction getContextToolOutputTracingConfig(\n activeContext: Context\n): ResolvedLangfuseToolOutputTracingConfig | undefined {\n const asyncConfig = toolOutputTracingStorage.getStore();\n if (asyncConfig != null) {\n return asyncConfig;\n }\n\n const value = activeContext.getValue(langfuseToolOutputTracingConfigKey);\n return isRecord(value)\n ? (value as ResolvedLangfuseToolOutputTracingConfig)\n : undefined;\n}\n\nexport function getContextLangfuseConfig(\n activeContext: Context\n): t.LangfuseConfig | undefined {\n const asyncConfig = langfuseConfigStorage.getStore();\n if (asyncConfig != null) {\n return asyncConfig;\n }\n\n const value = activeContext.getValue(langfuseConfigKey);\n return isRecord(value) ? (value as t.LangfuseConfig) : undefined;\n}\n\nclass ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {\n private readonly processor: LangfuseSpanProcessor;\n private readonly fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig;\n private readonly spanConfigs = new WeakMap<\n object,\n ResolvedLangfuseToolOutputTracingConfig\n >();\n\n constructor(\n params?: LangfuseSpanProcessorParams,\n fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig\n ) {\n this.processor = new LangfuseSpanProcessor(params);\n this.fallbackConfig = fallbackConfig;\n }\n\n onStart(span: Span, parentContext: Context): void {\n const config =\n getContextToolOutputTracingConfig(parentContext) ?? this.fallbackConfig;\n if (config != null) {\n this.spanConfigs.set(span, config);\n }\n this.processor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n const config =\n this.spanConfigs.get(span) ??\n toolOutputTracingStorage.getStore() ??\n this.fallbackConfig ??\n resolveToolOutputTracingConfig();\n redactLangfuseSpanToolOutputs(span, config);\n this.processor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.processor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.processor.shutdown();\n }\n}\n\nexport function createLangfuseSpanProcessor(\n params?: LangfuseSpanProcessorParams,\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): SpanProcessor {\n const fallbackConfig =\n runLangfuse != null || agentLangfuse != null\n ? resolveToolOutputTracingConfig(runLangfuse, agentLangfuse)\n : undefined;\n return new ToolOutputRedactingLangfuseSpanProcessor(params, fallbackConfig);\n}\n\nexport function withLangfuseToolOutputTracingConfig<T>(\n runLangfuse: t.LangfuseConfig | undefined,\n action: () => T,\n agentLangfuse?: t.LangfuseConfig\n): T {\n const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);\n const hasNoToolOutputConfig =\n runLangfuse?.toolOutputTracing == null &&\n agentLangfuse?.toolOutputTracing == null;\n\n if (langfuse == null && hasNoToolOutputConfig) {\n return action();\n }\n\n const config = hasNoToolOutputConfig\n ? undefined\n : resolveToolOutputTracingConfig(runLangfuse, agentLangfuse);\n let activeContext = context.active();\n if (langfuse != null) {\n activeContext = activeContext.setValue(langfuseConfigKey, langfuse);\n }\n if (config != null) {\n activeContext = activeContext.setValue(\n langfuseToolOutputTracingConfigKey,\n config\n );\n }\n\n const runWithContext = (): T => context.with(activeContext, action);\n const runWithToolOutputConfig = (): T =>\n config != null\n ? toolOutputTracingStorage.run(config, runWithContext)\n : runWithContext();\n\n return langfuse != null\n ? langfuseConfigStorage.run(langfuse, runWithToolOutputConfig)\n : runWithToolOutputConfig();\n}\n\nfunction hasLangfuseEnvKeys(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nfunction hasLangfuseConfigKeys(langfuse?: t.LangfuseConfig): boolean {\n if (langfuse == null) {\n return false;\n }\n return isPresent(langfuse.secretKey) && isPresent(langfuse.publicKey);\n}\n\nexport function shouldTraceToolNodeForLangfuse({\n runLangfuse,\n agentLangfuse,\n}: {\n runLangfuse?: t.LangfuseConfig;\n agentLangfuse?: t.LangfuseConfig;\n}): boolean {\n const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);\n if (langfuse?.enabled === false) {\n return false;\n }\n\n const explicit = langfuse?.toolNodeTracing?.enabled;\n if (explicit != null) {\n return (\n explicit && (hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys())\n );\n }\n\n return hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys();\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\n return {\n ...runLangfuse,\n ...agentLangfuse,\n ...(toolNodeTracing != null ? { toolNodeTracing } : {}),\n ...(toolOutputTracing != null ? { toolOutputTracing } : {}),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"langfuseToolOutputTracing.mjs","names":[],"sources":["../../src/langfuseToolOutputTracing.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport { context, createContextKey } from '@opentelemetry/api';\nimport { LangfuseOtelSpanAttributes } from '@langfuse/tracing';\nimport type {\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';\n\nexport const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';\n\nconst langfuseToolOutputTracingConfigKey = createContextKey(\n 'librechat.langfuse.tool-output-tracing'\n);\nconst langfuseConfigKey = createContextKey('librechat.langfuse.config');\nconst toolOutputTracingStorage =\n new AsyncLocalStorage<ResolvedLangfuseToolOutputTracingConfig>();\nconst langfuseConfigStorage = new AsyncLocalStorage<t.LangfuseConfig>();\nconst LANGGRAPH_TOOL_NODE_PREFIX = 'tools=';\n\nconst CHAT_ROLES = new Set([\n 'assistant',\n 'developer',\n 'human',\n 'system',\n 'user',\n]);\n\nexport type ResolvedLangfuseToolOutputTracingConfig = {\n enabled: boolean;\n redactedToolNames: Set<string>;\n redactedToolNameMatchMode: 'exact' | 'partial';\n redactionText: string;\n};\n\ntype SpanWithAttributes = ReadableSpan & {\n attributes: Record<string, unknown>;\n};\n\ntype RedactionResult = {\n value: unknown;\n changed: boolean;\n};\n\ntype RedactionContext = {\n toolNamesByCallId: Map<string, string>;\n};\n\nconst TOOL_OUTPUT_FIELD_KEYS = ['content', 'artifact'];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isPresent(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction parseBoolean(value: string | undefined): boolean | undefined {\n if (value == null) {\n return undefined;\n }\n\n const normalized = value.trim().toLowerCase();\n if (['1', 'true', 'yes', 'on'].includes(normalized)) {\n return true;\n }\n if (['0', 'false', 'no', 'off'].includes(normalized)) {\n return false;\n }\n\n return undefined;\n}\n\nfunction 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 = parseBoolean(\n process.env.LANGFUSE_TRACE_TOOL_OUTPUTS\n );\n if (traceToolOutputs != null) {\n return traceToolOutputs;\n }\n\n const redactToolOutputs = parseBoolean(\n process.env.LANGFUSE_REDACT_TOOL_OUTPUTS\n );\n if (redactToolOutputs != null) {\n return !redactToolOutputs;\n }\n\n return parseBoolean(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 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:\n agentConfig?.enabled ??\n runConfig?.enabled ??\n getEnvToolOutputTracingEnabled() ??\n true,\n redactedToolNames: normalizeToolNames(\n agentConfig?.redactedToolNames ??\n runConfig?.redactedToolNames ??\n getEnvRedactedToolNames()\n ),\n redactedToolNameMatchMode:\n agentConfig?.redactedToolNameMatchMode ??\n runConfig?.redactedToolNameMatchMode ??\n getEnvToolNameMatchMode() ??\n 'exact',\n redactionText:\n agentConfig?.redactionText ??\n runConfig?.redactionText ??\n getEnvRedactionText() ??\n LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,\n };\n}\n\nfunction shouldApplyToolOutputRedaction(\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n return config.enabled === false || config.redactedToolNames.size > 0;\n}\n\nfunction toolNameMatches(\n toolName: string | undefined,\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n if (!isPresent(toolName)) {\n return false;\n }\n\n const normalizedToolName = normalizeToolName(toolName);\n if (config.redactedToolNameMatchMode === 'partial') {\n for (const redactedToolName of config.redactedToolNames) {\n if (normalizedToolName.includes(redactedToolName)) {\n return true;\n }\n }\n return false;\n }\n\n return config.redactedToolNames.has(normalizedToolName);\n}\n\nfunction shouldRedactTool(\n toolName: string | undefined,\n config: ResolvedLangfuseToolOutputTracingConfig\n): boolean {\n return config.enabled === false || toolNameMatches(toolName, config);\n}\n\nfunction getStringField(\n value: Record<string, unknown>,\n key: string\n): string | undefined {\n const field = value[key];\n return typeof field === 'string' ? field : undefined;\n}\n\nfunction getNestedStringField(\n value: Record<string, unknown>,\n objectKey: string,\n fieldKey: string\n): string | undefined {\n const nested = value[objectKey];\n if (!isRecord(nested)) {\n return undefined;\n }\n return getStringField(nested, fieldKey);\n}\n\nfunction getSerializedToolCallId(\n value: Record<string, unknown>\n): string | undefined {\n return (\n getStringField(value, 'tool_call_id') ??\n getNestedStringField(value, 'kwargs', 'tool_call_id') ??\n getNestedStringField(value, 'additional_kwargs', 'tool_call_id') ??\n getNestedStringField(value, 'data', 'tool_call_id') ??\n (typeof value.id === 'string' ? value.id : undefined)\n );\n}\n\nfunction getSerializedToolName(\n value: Record<string, unknown>,\n redactionContext?: RedactionContext\n): string | undefined {\n const role = getStringField(value, 'role');\n const explicitName =\n getStringField(value, 'name') ??\n getStringField(value, 'tool_name') ??\n getNestedStringField(value, 'function', 'name') ??\n getNestedStringField(value, 'kwargs', 'name') ??\n getNestedStringField(value, 'additional_kwargs', 'name') ??\n getNestedStringField(value, 'data', 'name') ??\n (role != null && role.toLowerCase() !== 'tool' ? role : undefined);\n\n if (explicitName != null) {\n return explicitName;\n }\n\n const toolCallId = getSerializedToolCallId(value);\n return toolCallId != null\n ? redactionContext?.toolNamesByCallId.get(toolCallId)\n : undefined;\n}\n\nfunction hasToolMessageIdentity(value: Record<string, unknown>): boolean {\n const type = getStringField(value, 'type') ?? getStringField(value, '_type');\n if (type === 'tool' || type === 'tool_message') {\n return true;\n }\n\n const id = value.id;\n if (\n Array.isArray(id) &&\n id.some((part) => typeof part === 'string' && part.includes('ToolMessage'))\n ) {\n return true;\n }\n\n if (\n 'tool_call_id' in value ||\n getNestedStringField(value, 'kwargs', 'tool_call_id') != null ||\n getNestedStringField(value, 'additional_kwargs', 'tool_call_id') != null\n ) {\n return true;\n }\n\n const role = getStringField(value, 'role');\n return (\n role != null &&\n !CHAT_ROLES.has(role.toLowerCase()) &&\n ('content' in value || isRecord(value.kwargs) || isRecord(value.data))\n );\n}\n\nfunction redactToolContentFields(\n value: Record<string, unknown>,\n config: ResolvedLangfuseToolOutputTracingConfig\n): Record<string, unknown> {\n const next = { ...value };\n\n for (const outputKey of TOOL_OUTPUT_FIELD_KEYS) {\n if (outputKey in next) {\n next[outputKey] = config.redactionText;\n }\n }\n\n for (const nestedKey of ['kwargs', 'data', 'additional_kwargs']) {\n const nested = next[nestedKey];\n if (!isRecord(nested)) {\n continue;\n }\n const nextNested = { ...nested };\n let changed = false;\n for (const outputKey of TOOL_OUTPUT_FIELD_KEYS) {\n if (outputKey in nextNested) {\n nextNested[outputKey] = config.redactionText;\n changed = true;\n }\n }\n if (changed) {\n next[nestedKey] = nextNested;\n }\n }\n\n return next;\n}\n\nfunction collectToolCallNames(\n value: unknown,\n redactionContext: RedactionContext\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n collectToolCallNames(item, redactionContext);\n }\n return;\n }\n\n if (!isRecord(value)) {\n return;\n }\n\n const toolCallId = getSerializedToolCallId(value);\n const toolName = getSerializedToolName(value);\n if (toolCallId != null && toolName != null) {\n redactionContext.toolNamesByCallId.set(toolCallId, toolName);\n }\n\n for (const child of Object.values(value)) {\n collectToolCallNames(child, redactionContext);\n }\n}\n\nfunction redactValue(\n value: unknown,\n config: ResolvedLangfuseToolOutputTracingConfig,\n redactionContext: RedactionContext\n): RedactionResult {\n if (Array.isArray(value)) {\n let changed = false;\n const next: unknown[] = [];\n for (const item of value) {\n const result = redactValue(item, config, redactionContext);\n if (result.changed) {\n changed = true;\n }\n next.push(result.value);\n }\n return changed ? { value: next, changed } : { value, changed };\n }\n\n if (!isRecord(value)) {\n return { value, changed: false };\n }\n\n const toolName = getSerializedToolName(value, redactionContext);\n if (hasToolMessageIdentity(value) && shouldRedactTool(toolName, config)) {\n return {\n value: redactToolContentFields(value, config),\n changed: true,\n };\n }\n\n let changed = false;\n const next: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n const result = redactValue(child, config, redactionContext);\n if (result.changed) {\n changed = true;\n }\n next[key] = result.value;\n }\n\n return changed ? { value: next, changed } : { value, changed };\n}\n\nfunction redactSerializedValue(\n value: unknown,\n config: ResolvedLangfuseToolOutputTracingConfig\n): RedactionResult {\n const redactionContext: RedactionContext = {\n toolNamesByCallId: new Map(),\n };\n if (typeof value !== 'string') {\n collectToolCallNames(value, redactionContext);\n return redactValue(value, config, redactionContext);\n }\n\n const trimmed = value.trim();\n if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n return { value, changed: false };\n }\n\n try {\n const parsed = JSON.parse(value) as unknown;\n collectToolCallNames(parsed, redactionContext);\n const result = redactValue(parsed, config, redactionContext);\n return result.changed\n ? { value: JSON.stringify(result.value), changed: true }\n : { value, changed: false };\n } catch {\n return { value, changed: false };\n }\n}\n\nfunction redactAttribute(\n attributes: Record<string, unknown>,\n key: string,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n if (!(key in attributes)) {\n return;\n }\n\n const result = redactSerializedValue(attributes[key], config);\n if (result.changed) {\n attributes[key] = result.value;\n }\n}\n\nfunction isToolObservation(attributes: Record<string, unknown>): boolean {\n const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];\n return typeof type === 'string' && type.toLowerCase() === 'tool';\n}\n\nfunction classifyLangGraphToolNodeSpan(\n attributes: Record<string, unknown>\n): void {\n const type = attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];\n if (typeof type !== 'string' || type.toLowerCase() !== 'span') {\n return;\n }\n\n const langGraphNode =\n attributes[\n `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.langgraph_node`\n ];\n if (\n typeof langGraphNode === 'string' &&\n langGraphNode.startsWith(LANGGRAPH_TOOL_NODE_PREFIX)\n ) {\n attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] = 'tool';\n }\n}\n\nfunction redactToolObservationOutput(\n span: ReadableSpan,\n attributes: Record<string, unknown>,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n if (\n !(\n isToolObservation(attributes) &&\n shouldRedactTool(span.name, config) &&\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT in attributes\n )\n ) {\n return;\n }\n\n attributes[LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT] =\n config.redactionText;\n}\n\nexport function redactLangfuseSpanToolOutputs(\n span: ReadableSpan,\n config: ResolvedLangfuseToolOutputTracingConfig\n): void {\n const attributes = (span as SpanWithAttributes).attributes;\n classifyLangGraphToolNodeSpan(attributes);\n\n if (!shouldApplyToolOutputRedaction(config)) {\n return;\n }\n\n redactToolObservationOutput(span, attributes, config);\n\n for (const key of [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n ]) {\n redactAttribute(attributes, key, config);\n }\n}\n\nfunction getContextToolOutputTracingConfig(\n activeContext: Context\n): ResolvedLangfuseToolOutputTracingConfig | undefined {\n const asyncConfig = toolOutputTracingStorage.getStore();\n if (asyncConfig != null) {\n return asyncConfig;\n }\n\n const value = activeContext.getValue(langfuseToolOutputTracingConfigKey);\n return isRecord(value)\n ? (value as ResolvedLangfuseToolOutputTracingConfig)\n : undefined;\n}\n\nexport function getContextLangfuseConfig(\n activeContext: Context\n): t.LangfuseConfig | undefined {\n const asyncConfig = langfuseConfigStorage.getStore();\n if (asyncConfig != null) {\n return asyncConfig;\n }\n\n const value = activeContext.getValue(langfuseConfigKey);\n return isRecord(value) ? (value as t.LangfuseConfig) : undefined;\n}\n\nclass ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {\n private readonly processor: LangfuseSpanProcessor;\n private readonly fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig;\n private readonly spanConfigs = new WeakMap<\n object,\n ResolvedLangfuseToolOutputTracingConfig\n >();\n\n constructor(\n params?: LangfuseSpanProcessorParams,\n fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig\n ) {\n this.processor = new LangfuseSpanProcessor(params);\n this.fallbackConfig = fallbackConfig;\n }\n\n onStart(span: Span, parentContext: Context): void {\n const config =\n getContextToolOutputTracingConfig(parentContext) ?? this.fallbackConfig;\n if (config != null) {\n this.spanConfigs.set(span, config);\n }\n this.processor.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n const config =\n this.spanConfigs.get(span) ??\n toolOutputTracingStorage.getStore() ??\n this.fallbackConfig ??\n resolveToolOutputTracingConfig();\n redactLangfuseSpanToolOutputs(span, config);\n this.processor.onEnd(span);\n }\n\n forceFlush(): Promise<void> {\n return this.processor.forceFlush();\n }\n\n shutdown(): Promise<void> {\n return this.processor.shutdown();\n }\n}\n\nexport function createLangfuseSpanProcessor(\n params?: LangfuseSpanProcessorParams,\n runLangfuse?: t.LangfuseConfig,\n agentLangfuse?: t.LangfuseConfig\n): SpanProcessor {\n const fallbackConfig =\n runLangfuse != null || agentLangfuse != null\n ? resolveToolOutputTracingConfig(runLangfuse, agentLangfuse)\n : undefined;\n return new ToolOutputRedactingLangfuseSpanProcessor(params, fallbackConfig);\n}\n\nexport function withLangfuseToolOutputTracingConfig<T>(\n runLangfuse: t.LangfuseConfig | undefined,\n action: () => T,\n agentLangfuse?: t.LangfuseConfig\n): T {\n const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);\n const hasNoToolOutputConfig =\n runLangfuse?.toolOutputTracing == null &&\n agentLangfuse?.toolOutputTracing == null;\n\n if (langfuse == null && hasNoToolOutputConfig) {\n return action();\n }\n\n const config = hasNoToolOutputConfig\n ? undefined\n : resolveToolOutputTracingConfig(runLangfuse, agentLangfuse);\n let activeContext = context.active();\n if (langfuse != null) {\n activeContext = activeContext.setValue(langfuseConfigKey, langfuse);\n }\n if (config != null) {\n activeContext = activeContext.setValue(\n langfuseToolOutputTracingConfigKey,\n config\n );\n }\n\n const runWithContext = (): T => context.with(activeContext, action);\n const runWithToolOutputConfig = (): T =>\n config != null\n ? toolOutputTracingStorage.run(config, runWithContext)\n : runWithContext();\n\n return langfuse != null\n ? langfuseConfigStorage.run(langfuse, runWithToolOutputConfig)\n : runWithToolOutputConfig();\n}\n\nfunction hasLangfuseEnvKeys(): boolean {\n return (\n isPresent(process.env.LANGFUSE_SECRET_KEY) &&\n isPresent(process.env.LANGFUSE_PUBLIC_KEY)\n );\n}\n\nfunction hasLangfuseConfigKeys(langfuse?: t.LangfuseConfig): boolean {\n if (langfuse == null) {\n return false;\n }\n return isPresent(langfuse.secretKey) && isPresent(langfuse.publicKey);\n}\n\nexport function shouldTraceToolNodeForLangfuse({\n runLangfuse,\n agentLangfuse,\n}: {\n runLangfuse?: t.LangfuseConfig;\n agentLangfuse?: t.LangfuseConfig;\n}): boolean {\n const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);\n if (langfuse?.enabled === false) {\n return false;\n }\n\n const explicit = langfuse?.toolNodeTracing?.enabled;\n if (explicit != null) {\n return (\n explicit && (hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys())\n );\n }\n\n return hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys();\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 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 ...(tags != null ? { tags } : {}),\n ...(toolNodeTracing != null ? { toolNodeTracing } : {}),\n ...(toolOutputTracing != null ? { toolOutputTracing } : {}),\n };\n}\n"],"mappings":"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"}
|
|
@@ -48,6 +48,34 @@ function normalizeAnthropicToolCallId(id) {
|
|
|
48
48
|
const prefixMaxLength = ANTHROPIC_TOOL_USE_ID_MAX_LENGTH - ANTHROPIC_TOOL_USE_ID_HASH_LENGTH - 1;
|
|
49
49
|
return `${sanitized.slice(0, prefixMaxLength)}_${hash}`;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Lift any `cache_control` off the inner blocks of a tool result onto the
|
|
53
|
+
* `tool_result` block itself. Anthropic documents the top-level
|
|
54
|
+
* `messages.content` block as the cacheable position and does not document
|
|
55
|
+
* caching of sub-content blocks; the API currently honors a nested marker, but
|
|
56
|
+
* anchoring on the documented position keeps the single tail breakpoint robust
|
|
57
|
+
* (and mirrors the Bedrock cachePoint hoist). The first marker found wins; it is
|
|
58
|
+
* stripped from every inner block so exactly one survives, on the outer block.
|
|
59
|
+
*/
|
|
60
|
+
function hoistToolResultCacheControl(content) {
|
|
61
|
+
if (!Array.isArray(content)) return {
|
|
62
|
+
content,
|
|
63
|
+
cacheControl: void 0
|
|
64
|
+
};
|
|
65
|
+
let cacheControl;
|
|
66
|
+
return {
|
|
67
|
+
content: content.map((block) => {
|
|
68
|
+
if ("cache_control" in block) {
|
|
69
|
+
cacheControl ??= block.cache_control;
|
|
70
|
+
const clone = { ...block };
|
|
71
|
+
delete clone.cache_control;
|
|
72
|
+
return clone;
|
|
73
|
+
}
|
|
74
|
+
return block;
|
|
75
|
+
}),
|
|
76
|
+
cacheControl
|
|
77
|
+
};
|
|
78
|
+
}
|
|
51
79
|
function _ensureMessageContents(messages) {
|
|
52
80
|
const updatedMsgs = [];
|
|
53
81
|
for (const message of messages) if (message._getType() === "tool") if (typeof message.content === "string") {
|
|
@@ -63,10 +91,14 @@ function _ensureMessageContents(messages) {
|
|
|
63
91
|
tool_use_id: normalizeAnthropicToolCallId(message.tool_call_id)
|
|
64
92
|
}] }));
|
|
65
93
|
} else {
|
|
66
|
-
const
|
|
94
|
+
const { content: hoistedContent, cacheControl } = message.content != null ? hoistToolResultCacheControl(_formatContent(message)) : {
|
|
95
|
+
content: void 0,
|
|
96
|
+
cacheControl: void 0
|
|
97
|
+
};
|
|
67
98
|
updatedMsgs.push(new HumanMessage({ content: [{
|
|
68
99
|
type: "tool_result",
|
|
69
|
-
...
|
|
100
|
+
...hoistedContent != null ? { content: hoistedContent } : {},
|
|
101
|
+
...cacheControl != null ? { cache_control: cacheControl } : {},
|
|
70
102
|
tool_use_id: normalizeAnthropicToolCallId(message.tool_call_id)
|
|
71
103
|
}] }));
|
|
72
104
|
}
|
|
@@ -206,6 +238,18 @@ function _formatContent(message) {
|
|
|
206
238
|
"web_search_result"
|
|
207
239
|
];
|
|
208
240
|
const textTypes = ["text", "text_delta"];
|
|
241
|
+
/**
|
|
242
|
+
* Reasoning blocks emitted by other providers — Bedrock's `reasoning_content`,
|
|
243
|
+
* Google's `reasoning`, and LibreChat's `think`. Their signatures are
|
|
244
|
+
* provider-specific and cannot be validated by Anthropic, so on a
|
|
245
|
+
* cross-provider handoff (e.g. Bedrock → Anthropic) we drop them rather than
|
|
246
|
+
* forwarding an unusable block. The receiving model produces its own thinking.
|
|
247
|
+
*/
|
|
248
|
+
const foreignReasoningTypes = [
|
|
249
|
+
"reasoning_content",
|
|
250
|
+
"reasoning",
|
|
251
|
+
"think"
|
|
252
|
+
];
|
|
209
253
|
const { content } = message;
|
|
210
254
|
if (typeof content === "string") return content;
|
|
211
255
|
else {
|
|
@@ -273,6 +317,8 @@ function _formatContent(message) {
|
|
|
273
317
|
};
|
|
274
318
|
else if (contentPart.type === "thinking") {
|
|
275
319
|
const thinkingPart = contentPart;
|
|
320
|
+
const signature = thinkingPart.signature;
|
|
321
|
+
if (isAIMessage(message) && (signature == null || signature === "")) return null;
|
|
276
322
|
return {
|
|
277
323
|
type: "thinking",
|
|
278
324
|
thinking: thinkingPart.thinking,
|
|
@@ -350,7 +396,8 @@ function _formatContent(message) {
|
|
|
350
396
|
name: correspondingToolCall.name,
|
|
351
397
|
input: functionCallPart.functionCall.args
|
|
352
398
|
};
|
|
353
|
-
} else
|
|
399
|
+
} else if (isAIMessage(message) && foreignReasoningTypes.some((t) => t === contentPart.type)) return null;
|
|
400
|
+
else {
|
|
354
401
|
console.error("Unsupported content part:", JSON.stringify(contentPart, null, 2));
|
|
355
402
|
throw new Error("Unsupported message content format");
|
|
356
403
|
}
|
|
@@ -398,11 +445,18 @@ function _convertMessagesToAnthropicPayload(messages) {
|
|
|
398
445
|
}, ...clientToolCalls.map(_convertLangChainToolCallToAnthropic)]
|
|
399
446
|
};
|
|
400
447
|
} else {
|
|
401
|
-
const
|
|
402
|
-
|
|
448
|
+
const formattedContent = _formatContent(message);
|
|
449
|
+
const formattedBlocks = Array.isArray(formattedContent) ? formattedContent : [];
|
|
450
|
+
const representedToolIds = new Set(formattedBlocks.filter((block) => block != null && (block.type === "tool_use" || block.type === "server_tool_use")).map((block) => block.id));
|
|
451
|
+
const unrepresentedToolCalls = toolCalls.filter((toolCall) => !(toolCall.id?.startsWith("srvtoolu_") ?? false) && !representedToolIds.has(toolCall.id));
|
|
452
|
+
if (unrepresentedToolCalls.length === 0) return {
|
|
453
|
+
role,
|
|
454
|
+
content: formattedContent
|
|
455
|
+
};
|
|
456
|
+
const existingBlocks = formattedBlocks.filter((block) => !(block != null && block.type === "text" && "text" in block && block.text === ANTHROPIC_EMPTY_TEXT_PLACEHOLDER));
|
|
403
457
|
return {
|
|
404
458
|
role,
|
|
405
|
-
content:
|
|
459
|
+
content: [...existingBlocks, ...unrepresentedToolCalls.map(_convertLangChainToolCallToAnthropic)]
|
|
406
460
|
};
|
|
407
461
|
}
|
|
408
462
|
else return {
|
|
@@ -420,15 +474,72 @@ function modelDisallowsAssistantPrefill(model) {
|
|
|
420
474
|
if (!match) return false;
|
|
421
475
|
return Number(match[1]) >= 6;
|
|
422
476
|
}
|
|
477
|
+
function messagesHaveCacheControl(messages) {
|
|
478
|
+
return messages.some((message) => Array.isArray(message.content) && message.content.some((block) => "cache_control" in block));
|
|
479
|
+
}
|
|
480
|
+
/** Anthropic rejects cache_control on these reasoning blocks. */
|
|
481
|
+
const NON_CACHEABLE_PAYLOAD_BLOCK_TYPES = new Set(["thinking", "redacted_thinking"]);
|
|
482
|
+
/**
|
|
483
|
+
* Place one ephemeral `cache_control` on the last cacheable block of the final
|
|
484
|
+
* message of an already-converted Anthropic payload. Used to re-anchor the tail
|
|
485
|
+
* breakpoint after a trailing assistant prefill is stripped. Operates on the
|
|
486
|
+
* post-conversion payload, where blocks the converter drops (foreign reasoning,
|
|
487
|
+
* input_json_delta) are already gone — only native thinking blocks must be
|
|
488
|
+
* skipped. Returns a new array only when it actually places a marker.
|
|
489
|
+
*/
|
|
490
|
+
function reanchorTailCacheControl(messages) {
|
|
491
|
+
if (messages.length === 0) return messages;
|
|
492
|
+
const lastIndex = messages.length - 1;
|
|
493
|
+
const tail = messages[lastIndex];
|
|
494
|
+
const content = tail.content;
|
|
495
|
+
if (typeof content === "string") {
|
|
496
|
+
if (content.trim() === "") return messages;
|
|
497
|
+
const next = [...messages];
|
|
498
|
+
next[lastIndex] = {
|
|
499
|
+
...tail,
|
|
500
|
+
content: [{
|
|
501
|
+
type: "text",
|
|
502
|
+
text: content,
|
|
503
|
+
cache_control: { type: "ephemeral" }
|
|
504
|
+
}]
|
|
505
|
+
};
|
|
506
|
+
return next;
|
|
507
|
+
}
|
|
508
|
+
if (!Array.isArray(content)) return messages;
|
|
509
|
+
let anchor = -1;
|
|
510
|
+
for (let i = 0; i < content.length; i++) {
|
|
511
|
+
const type = content[i].type;
|
|
512
|
+
if (type == null || NON_CACHEABLE_PAYLOAD_BLOCK_TYPES.has(type)) continue;
|
|
513
|
+
if (type === "text" && (content[i].text ?? "").trim() === "") continue;
|
|
514
|
+
anchor = i;
|
|
515
|
+
}
|
|
516
|
+
if (anchor < 0) return messages;
|
|
517
|
+
const next = [...messages];
|
|
518
|
+
next[lastIndex] = {
|
|
519
|
+
...tail,
|
|
520
|
+
content: content.map((block, i) => i === anchor ? {
|
|
521
|
+
...block,
|
|
522
|
+
cache_control: { type: "ephemeral" }
|
|
523
|
+
} : block)
|
|
524
|
+
};
|
|
525
|
+
return next;
|
|
526
|
+
}
|
|
423
527
|
function stripUnsupportedAssistantPrefill(request) {
|
|
424
528
|
if (!modelDisallowsAssistantPrefill(request.model)) return request;
|
|
425
529
|
const messages = request.messages;
|
|
426
530
|
if (messages.length <= 1 || messages[messages.length - 1]?.role !== "assistant") return request;
|
|
427
531
|
const nextMessages = [...messages];
|
|
428
532
|
while (nextMessages.length > 1 && nextMessages[nextMessages.length - 1]?.role === "assistant") nextMessages.pop();
|
|
533
|
+
/**
|
|
534
|
+
* If a single tail prompt-cache breakpoint rode the stripped assistant
|
|
535
|
+
* prefill, the survivors may now carry no `cache_control` at all, dropping
|
|
536
|
+
* message caching for this request. Re-anchor the breakpoint on the new tail
|
|
537
|
+
* (only when one was actually lost, so caching-off requests stay untouched).
|
|
538
|
+
*/
|
|
539
|
+
const reanchored = messagesHaveCacheControl(messages) && !messagesHaveCacheControl(nextMessages) ? reanchorTailCacheControl(nextMessages) : nextMessages;
|
|
429
540
|
return {
|
|
430
541
|
...request,
|
|
431
|
-
messages:
|
|
542
|
+
messages: reanchored
|
|
432
543
|
};
|
|
433
544
|
}
|
|
434
545
|
function mergeMessages(messages) {
|