@librechat/agents 3.1.89 → 3.1.91
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 +9 -5
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +53 -14
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +14 -7
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +234 -0
- package/dist/cjs/langfuse.cjs.map +1 -0
- package/dist/cjs/llm/anthropic/index.cjs +8 -2
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +34 -0
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +34 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/run.cjs +44 -27
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +10 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +10 -9
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +35 -11
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +8 -5
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs +380 -0
- package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs.map +1 -0
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +997 -0
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -0
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +575 -0
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -0
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs +165 -0
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +17 -5
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +110 -6
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +9 -5
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +53 -14
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +14 -7
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +226 -0
- package/dist/esm/langfuse.mjs.map +1 -0
- package/dist/esm/llm/anthropic/index.mjs +9 -3
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -2
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/run.mjs +44 -27
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +10 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +11 -10
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +29 -12
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +8 -5
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs +378 -0
- package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs.map +1 -0
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +994 -0
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -0
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +566 -0
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -0
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs +155 -0
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -0
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +17 -6
- package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +111 -7
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +4 -1
- package/dist/types/graphs/Graph.d.ts +6 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/langfuse.d.ts +48 -0
- package/dist/types/llm/anthropic/index.d.ts +3 -1
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
- package/dist/types/tools/BashExecutor.d.ts +3 -3
- package/dist/types/tools/CodeExecutor.d.ts +10 -3
- package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
- package/dist/types/tools/cloudflare/CloudflareBridgeRuntime.d.ts +23 -0
- package/dist/types/tools/cloudflare/CloudflareProgrammaticToolCalling.d.ts +4 -0
- package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +21 -0
- package/dist/types/tools/cloudflare/CloudflareSandboxTools.d.ts +22 -0
- package/dist/types/tools/cloudflare/index.d.ts +4 -0
- package/dist/types/tools/local/LocalExecutionEngine.d.ts +1 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
- package/dist/types/types/graph.d.ts +8 -0
- package/dist/types/types/tools.d.ts +120 -5
- package/package.json +4 -4
- package/src/__tests__/stream.eagerEventExecution.test.ts +66 -0
- package/src/agents/AgentContext.ts +13 -3
- package/src/graphs/Graph.ts +60 -16
- package/src/hooks/__tests__/executeHooks.test.ts +38 -0
- package/src/hooks/executeHooks.ts +27 -7
- package/src/index.ts +1 -0
- package/src/langfuse.ts +358 -0
- package/src/llm/anthropic/index.ts +27 -3
- package/src/llm/anthropic/llm.spec.ts +60 -1
- package/src/llm/anthropic/utils/message_inputs.ts +46 -0
- package/src/run.ts +60 -38
- package/src/specs/langfuse-config.test.ts +57 -0
- package/src/specs/langfuse-metadata.test.ts +19 -1
- package/src/stream.ts +13 -3
- package/src/tools/BashExecutor.ts +21 -10
- package/src/tools/BashProgrammaticToolCalling.ts +21 -9
- package/src/tools/CodeExecutor.ts +55 -12
- package/src/tools/CodeSessionFileSummary.ts +80 -0
- package/src/tools/ProgrammaticToolCalling.ts +25 -12
- package/src/tools/ToolNode.ts +8 -5
- package/src/tools/__tests__/BashExecutor.test.ts +9 -0
- package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +537 -0
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
- package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
- package/src/tools/__tests__/subagentHooks.test.ts +237 -0
- package/src/tools/cloudflare/CloudflareBridgeRuntime.ts +480 -0
- package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +1162 -0
- package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +744 -0
- package/src/tools/cloudflare/CloudflareSandboxTools.ts +225 -0
- package/src/tools/cloudflare/index.ts +4 -0
- package/src/tools/local/LocalExecutionEngine.ts +20 -4
- package/src/tools/local/resolveLocalExecutionTools.ts +169 -7
- package/src/tools/subagent/SubagentExecutor.ts +514 -36
- package/src/types/graph.ts +9 -0
- package/src/types/tools.ts +143 -5
package/src/langfuse.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { CallbackHandler } from '@langfuse/langchain';
|
|
2
|
+
import { LangfuseSpanProcessor } from '@langfuse/otel';
|
|
3
|
+
import {
|
|
4
|
+
createObservationAttributes,
|
|
5
|
+
createTraceAttributes,
|
|
6
|
+
} from '@langfuse/tracing';
|
|
7
|
+
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
8
|
+
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
|
|
9
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
10
|
+
import type { Serialized } from '@langchain/core/load/serializable';
|
|
11
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
12
|
+
import type { LLMResult } from '@langchain/core/outputs';
|
|
13
|
+
import type { Span } from '@opentelemetry/api';
|
|
14
|
+
import type * as t from '@/types';
|
|
15
|
+
import { isPresent } from '@/utils/misc';
|
|
16
|
+
|
|
17
|
+
type TraceMetadata = Record<string, unknown>;
|
|
18
|
+
|
|
19
|
+
type LangfuseHandlerParams = {
|
|
20
|
+
userId?: string;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
traceMetadata?: TraceMetadata;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type AgentLangfuseHandlerParams = LangfuseHandlerParams & {
|
|
26
|
+
langfuse?: t.LangfuseConfig;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ResolvedLangfuseConfig = t.LangfuseConfig & {
|
|
30
|
+
enabled: true;
|
|
31
|
+
publicKey: string;
|
|
32
|
+
secretKey: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function getModelName(serialized: Serialized): string {
|
|
36
|
+
const serializedRecord = serialized as unknown as Record<string, unknown>;
|
|
37
|
+
const kwargs = serializedRecord.kwargs as Record<string, unknown> | undefined;
|
|
38
|
+
const modelName =
|
|
39
|
+
kwargs?.model ??
|
|
40
|
+
kwargs?.model_name ??
|
|
41
|
+
kwargs?.modelName ??
|
|
42
|
+
kwargs?.model_id ??
|
|
43
|
+
kwargs?.modelId ??
|
|
44
|
+
serializedRecord.name;
|
|
45
|
+
|
|
46
|
+
if (typeof modelName === 'string' && modelName.trim() !== '') {
|
|
47
|
+
return modelName;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (Array.isArray(serializedRecord.id) && serializedRecord.id.length > 0) {
|
|
51
|
+
return String(serializedRecord.id[serializedRecord.id.length - 1]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return 'ChatModel';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getModelParameters(
|
|
58
|
+
extraParams?: Record<string, unknown>
|
|
59
|
+
): Record<string, string | number> {
|
|
60
|
+
const invocationParams = extraParams?.invocation_params;
|
|
61
|
+
const params =
|
|
62
|
+
invocationParams != null && typeof invocationParams === 'object'
|
|
63
|
+
? (invocationParams as Record<string, unknown>)
|
|
64
|
+
: (extraParams ?? {});
|
|
65
|
+
|
|
66
|
+
return Object.fromEntries(
|
|
67
|
+
Object.entries(params).filter(([, value]) => {
|
|
68
|
+
return typeof value === 'string' || typeof value === 'number';
|
|
69
|
+
})
|
|
70
|
+
) as Record<string, string | number>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getOutput(output: LLMResult): unknown {
|
|
74
|
+
return output.generations.map((generation) =>
|
|
75
|
+
generation.map((item) => {
|
|
76
|
+
if ('message' in item && item.message != null) {
|
|
77
|
+
return (item.message as { content?: unknown }).content;
|
|
78
|
+
}
|
|
79
|
+
return item.text;
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getUsageDetails(
|
|
85
|
+
output: LLMResult
|
|
86
|
+
): Record<string, number> | undefined {
|
|
87
|
+
const llmOutput = output.llmOutput as Record<string, unknown> | undefined;
|
|
88
|
+
const usage = llmOutput?.tokenUsage ?? llmOutput?.usage;
|
|
89
|
+
if (usage == null || typeof usage !== 'object') {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const usageEntries = Object.entries(usage as Record<string, unknown>).filter(
|
|
94
|
+
([, value]) => typeof value === 'number'
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return usageEntries.length > 0
|
|
98
|
+
? (Object.fromEntries(usageEntries) as Record<string, number>)
|
|
99
|
+
: undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getTraceName(traceMetadata?: TraceMetadata): string {
|
|
103
|
+
const agentName = traceMetadata?.agentName;
|
|
104
|
+
return typeof agentName === 'string' && agentName.trim() !== ''
|
|
105
|
+
? `LibreChat Agent: ${agentName}`
|
|
106
|
+
: 'LibreChat Agent';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class LangfuseAgentCallbackHandler extends BaseCallbackHandler {
|
|
110
|
+
name = 'librechat_langfuse_agent_handler';
|
|
111
|
+
|
|
112
|
+
private readonly provider: BasicTracerProvider;
|
|
113
|
+
private readonly processor: LangfuseSpanProcessor;
|
|
114
|
+
private readonly userId?: string;
|
|
115
|
+
private readonly sessionId?: string;
|
|
116
|
+
private readonly traceMetadata?: TraceMetadata;
|
|
117
|
+
private readonly spans = new Map<string, Span>();
|
|
118
|
+
|
|
119
|
+
constructor({
|
|
120
|
+
langfuse,
|
|
121
|
+
userId,
|
|
122
|
+
sessionId,
|
|
123
|
+
traceMetadata,
|
|
124
|
+
}: LangfuseHandlerParams & { langfuse: ResolvedLangfuseConfig }) {
|
|
125
|
+
super();
|
|
126
|
+
this.userId = userId;
|
|
127
|
+
this.sessionId = sessionId;
|
|
128
|
+
this.traceMetadata = traceMetadata;
|
|
129
|
+
this.processor = new LangfuseSpanProcessor({
|
|
130
|
+
publicKey: langfuse.publicKey,
|
|
131
|
+
secretKey: langfuse.secretKey,
|
|
132
|
+
...(isPresent(langfuse.baseUrl) ? { baseUrl: langfuse.baseUrl } : {}),
|
|
133
|
+
environment:
|
|
134
|
+
process.env.LANGFUSE_TRACING_ENVIRONMENT ??
|
|
135
|
+
process.env.NODE_ENV ??
|
|
136
|
+
'development',
|
|
137
|
+
exportMode: 'immediate',
|
|
138
|
+
});
|
|
139
|
+
this.provider = new BasicTracerProvider({
|
|
140
|
+
spanProcessors: [this.processor],
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private startGenerationSpan({
|
|
145
|
+
llm,
|
|
146
|
+
input,
|
|
147
|
+
runId,
|
|
148
|
+
extraParams,
|
|
149
|
+
metadata,
|
|
150
|
+
name,
|
|
151
|
+
}: {
|
|
152
|
+
llm: Serialized;
|
|
153
|
+
input: unknown;
|
|
154
|
+
runId: string;
|
|
155
|
+
extraParams?: Record<string, unknown>;
|
|
156
|
+
metadata?: Record<string, unknown>;
|
|
157
|
+
name?: string;
|
|
158
|
+
}): void {
|
|
159
|
+
if (this.spans.has(runId)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const tracer = this.provider.getTracer('librechat-agents-langfuse');
|
|
164
|
+
const spanName =
|
|
165
|
+
typeof name === 'string' && name.trim() !== '' ? name : getModelName(llm);
|
|
166
|
+
const span = tracer.startSpan(spanName, {
|
|
167
|
+
attributes: {
|
|
168
|
+
...createTraceAttributes({
|
|
169
|
+
name: getTraceName(this.traceMetadata),
|
|
170
|
+
userId: this.userId,
|
|
171
|
+
sessionId: this.sessionId,
|
|
172
|
+
metadata: this.traceMetadata,
|
|
173
|
+
}),
|
|
174
|
+
...createObservationAttributes('generation', {
|
|
175
|
+
input,
|
|
176
|
+
model: getModelName(llm),
|
|
177
|
+
modelParameters: getModelParameters(extraParams),
|
|
178
|
+
metadata: {
|
|
179
|
+
...metadata,
|
|
180
|
+
...this.traceMetadata,
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
this.spans.set(runId, span);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async handleChatModelStart(
|
|
189
|
+
llm: Serialized,
|
|
190
|
+
messages: BaseMessage[][],
|
|
191
|
+
runId: string,
|
|
192
|
+
_parentRunId?: string,
|
|
193
|
+
extraParams?: Record<string, unknown>,
|
|
194
|
+
_tags?: string[],
|
|
195
|
+
metadata?: Record<string, unknown>,
|
|
196
|
+
name?: string
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
this.startGenerationSpan({
|
|
199
|
+
llm,
|
|
200
|
+
input: messages,
|
|
201
|
+
runId,
|
|
202
|
+
extraParams,
|
|
203
|
+
metadata,
|
|
204
|
+
name,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async handleLLMStart(
|
|
209
|
+
llm: Serialized,
|
|
210
|
+
prompts: string[],
|
|
211
|
+
runId: string,
|
|
212
|
+
_parentRunId?: string,
|
|
213
|
+
extraParams?: Record<string, unknown>,
|
|
214
|
+
_tags?: string[],
|
|
215
|
+
metadata?: Record<string, unknown>,
|
|
216
|
+
name?: string
|
|
217
|
+
): Promise<void> {
|
|
218
|
+
this.startGenerationSpan({
|
|
219
|
+
llm,
|
|
220
|
+
input: prompts,
|
|
221
|
+
runId,
|
|
222
|
+
extraParams,
|
|
223
|
+
metadata,
|
|
224
|
+
name,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async handleLLMEnd(output: LLMResult, runId: string): Promise<void> {
|
|
229
|
+
const span = this.spans.get(runId);
|
|
230
|
+
if (!span) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
span.setAttributes(
|
|
235
|
+
createObservationAttributes('generation', {
|
|
236
|
+
output: getOutput(output),
|
|
237
|
+
usageDetails: getUsageDetails(output),
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
span.end();
|
|
241
|
+
this.spans.delete(runId);
|
|
242
|
+
await this.flush();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async handleLLMError(err: unknown, runId: string): Promise<void> {
|
|
246
|
+
const span = this.spans.get(runId);
|
|
247
|
+
if (!span) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
252
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
253
|
+
span.setAttributes(
|
|
254
|
+
createObservationAttributes('generation', {
|
|
255
|
+
level: 'ERROR',
|
|
256
|
+
statusMessage: message,
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
span.end();
|
|
260
|
+
this.spans.delete(runId);
|
|
261
|
+
await this.flush();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private async flush(): Promise<void> {
|
|
265
|
+
try {
|
|
266
|
+
await this.provider.forceFlush();
|
|
267
|
+
} catch (error) {
|
|
268
|
+
process.emitWarning(
|
|
269
|
+
`[LangfuseAgentCallbackHandler] Failed to flush Langfuse spans: ${
|
|
270
|
+
error instanceof Error ? error.message : String(error)
|
|
271
|
+
}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async dispose(): Promise<void> {
|
|
277
|
+
for (const span of this.spans.values()) {
|
|
278
|
+
span.end();
|
|
279
|
+
}
|
|
280
|
+
this.spans.clear();
|
|
281
|
+
await this.flush();
|
|
282
|
+
try {
|
|
283
|
+
await this.provider.shutdown();
|
|
284
|
+
} catch (error) {
|
|
285
|
+
process.emitWarning(
|
|
286
|
+
`[LangfuseAgentCallbackHandler] Failed to shut down Langfuse provider: ${
|
|
287
|
+
error instanceof Error ? error.message : String(error)
|
|
288
|
+
}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function hasRequiredLangfuseConfig(
|
|
295
|
+
langfuse?: t.LangfuseConfig
|
|
296
|
+
): langfuse is ResolvedLangfuseConfig {
|
|
297
|
+
return (
|
|
298
|
+
langfuse?.enabled === true &&
|
|
299
|
+
isPresent(langfuse.publicKey) &&
|
|
300
|
+
isPresent(langfuse.secretKey)
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function createLegacyLangfuseHandler(
|
|
305
|
+
params: LangfuseHandlerParams
|
|
306
|
+
): CallbackHandler {
|
|
307
|
+
return new CallbackHandler(params);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function createLangfuseHandler({
|
|
311
|
+
langfuse,
|
|
312
|
+
userId,
|
|
313
|
+
sessionId,
|
|
314
|
+
traceMetadata,
|
|
315
|
+
}: AgentLangfuseHandlerParams): LangfuseAgentCallbackHandler | undefined {
|
|
316
|
+
if (!hasRequiredLangfuseConfig(langfuse)) {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return new LangfuseAgentCallbackHandler({
|
|
321
|
+
langfuse,
|
|
322
|
+
userId,
|
|
323
|
+
sessionId,
|
|
324
|
+
traceMetadata,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function hasExplicitLangfuseConfig(
|
|
329
|
+
contexts: Iterable<{ langfuse?: t.LangfuseConfig }>
|
|
330
|
+
): boolean {
|
|
331
|
+
for (const context of contexts) {
|
|
332
|
+
if (context.langfuse != null) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function hasLangfuseEnvConfig(): boolean {
|
|
340
|
+
return (
|
|
341
|
+
isPresent(process.env.LANGFUSE_SECRET_KEY) &&
|
|
342
|
+
isPresent(process.env.LANGFUSE_PUBLIC_KEY) &&
|
|
343
|
+
isPresent(process.env.LANGFUSE_BASE_URL)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function isLangfuseCallbackHandler(value: unknown): boolean {
|
|
348
|
+
return (
|
|
349
|
+
value instanceof CallbackHandler ||
|
|
350
|
+
value instanceof LangfuseAgentCallbackHandler
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export async function disposeLangfuseHandler(value: unknown): Promise<void> {
|
|
355
|
+
if (value instanceof LangfuseAgentCallbackHandler) {
|
|
356
|
+
await value.dispose();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -17,9 +17,13 @@ import type {
|
|
|
17
17
|
ChatAnthropicToolType,
|
|
18
18
|
AnthropicMCPServerURLDefinition,
|
|
19
19
|
AnthropicContextManagementConfigParam,
|
|
20
|
+
AnthropicRequestOptions,
|
|
20
21
|
} from '@/llm/anthropic/types';
|
|
21
22
|
import { _makeMessageChunkFromAnthropicEvent } from './utils/message_outputs';
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
_convertMessagesToAnthropicPayload,
|
|
25
|
+
stripUnsupportedAssistantPrefill,
|
|
26
|
+
} from './utils/message_inputs';
|
|
23
27
|
import { handleToolChoice } from './utils/tools';
|
|
24
28
|
|
|
25
29
|
const DEFAULT_STREAM_DELAY = 25;
|
|
@@ -591,6 +595,26 @@ export class CustomAnthropic extends ChatAnthropicMessages {
|
|
|
591
595
|
});
|
|
592
596
|
}
|
|
593
597
|
|
|
598
|
+
protected override async createStreamWithRetry(
|
|
599
|
+
request: AnthropicStreamingMessageCreateParams,
|
|
600
|
+
options?: AnthropicRequestOptions
|
|
601
|
+
): ReturnType<ChatAnthropicMessages['createStreamWithRetry']> {
|
|
602
|
+
return super.createStreamWithRetry(
|
|
603
|
+
stripUnsupportedAssistantPrefill(request),
|
|
604
|
+
options
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
protected override async completionWithRetry(
|
|
609
|
+
request: AnthropicMessageCreateParams,
|
|
610
|
+
options: AnthropicRequestOptions
|
|
611
|
+
): ReturnType<ChatAnthropicMessages['completionWithRetry']> {
|
|
612
|
+
return super.completionWithRetry(
|
|
613
|
+
stripUnsupportedAssistantPrefill(request),
|
|
614
|
+
options
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
594
618
|
async *_streamResponseChunks(
|
|
595
619
|
messages: BaseMessage[],
|
|
596
620
|
options: this['ParsedCallOptions'],
|
|
@@ -599,11 +623,11 @@ export class CustomAnthropic extends ChatAnthropicMessages {
|
|
|
599
623
|
this.resetTokenEvents();
|
|
600
624
|
const params = this.invocationParams(options);
|
|
601
625
|
const formattedMessages = _convertMessagesToAnthropicPayload(messages);
|
|
602
|
-
const payload = {
|
|
626
|
+
const payload = stripUnsupportedAssistantPrefill({
|
|
603
627
|
...params,
|
|
604
628
|
...formattedMessages,
|
|
605
629
|
stream: true,
|
|
606
|
-
} as const;
|
|
630
|
+
} as const);
|
|
607
631
|
const coerceContentToString =
|
|
608
632
|
!_toolsInParams(payload) &&
|
|
609
633
|
!_documentsInParams(payload) &&
|
|
@@ -64,7 +64,11 @@ import type {
|
|
|
64
64
|
ToolEndEvent,
|
|
65
65
|
TPayload,
|
|
66
66
|
} from '@/types';
|
|
67
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
_convertMessagesToAnthropicPayload,
|
|
69
|
+
modelDisallowsAssistantPrefill,
|
|
70
|
+
stripUnsupportedAssistantPrefill,
|
|
71
|
+
} from './utils/message_inputs';
|
|
68
72
|
import {
|
|
69
73
|
_makeMessageChunkFromAnthropicEvent,
|
|
70
74
|
getAnthropicUsageMetadata,
|
|
@@ -2637,6 +2641,61 @@ describe('Anthropic Reasoning with contentBlocks', () => {
|
|
|
2637
2641
|
});
|
|
2638
2642
|
});
|
|
2639
2643
|
|
|
2644
|
+
describe('Claude assistant prefill compatibility', () => {
|
|
2645
|
+
test.each([
|
|
2646
|
+
'claude-sonnet-4-6',
|
|
2647
|
+
'claude-sonnet-4-6@20260217',
|
|
2648
|
+
'claude-opus-4-7',
|
|
2649
|
+
'claude-opus-4-10',
|
|
2650
|
+
'global.anthropic.claude-opus-4-6-v1:0',
|
|
2651
|
+
'anthropic/claude-sonnet-4.6',
|
|
2652
|
+
'anthropic/claude-sonnet-4.12',
|
|
2653
|
+
])('detects %s as not supporting assistant prefill', (model) => {
|
|
2654
|
+
expect(modelDisallowsAssistantPrefill(model)).toBe(true);
|
|
2655
|
+
});
|
|
2656
|
+
|
|
2657
|
+
test.each([
|
|
2658
|
+
'claude-sonnet-4-5-20250929',
|
|
2659
|
+
'claude-opus-4-20250514',
|
|
2660
|
+
'anthropic.claude-opus-4-20250514-v1:0',
|
|
2661
|
+
'gpt-5.4',
|
|
2662
|
+
])('leaves %s prefill support unchanged', (model) => {
|
|
2663
|
+
expect(modelDisallowsAssistantPrefill(model)).toBe(false);
|
|
2664
|
+
});
|
|
2665
|
+
|
|
2666
|
+
test('strips trailing assistant messages for Claude 4.6+ requests', () => {
|
|
2667
|
+
const request = {
|
|
2668
|
+
model: 'claude-opus-4-6',
|
|
2669
|
+
max_tokens: 100,
|
|
2670
|
+
messages: [
|
|
2671
|
+
{ role: 'user' as const, content: 'What changed?' },
|
|
2672
|
+
{ role: 'assistant' as const, content: 'Draft prefill' },
|
|
2673
|
+
{ role: 'assistant' as const, content: 'Another prefill' },
|
|
2674
|
+
],
|
|
2675
|
+
};
|
|
2676
|
+
|
|
2677
|
+
const sanitized = stripUnsupportedAssistantPrefill(request);
|
|
2678
|
+
|
|
2679
|
+
expect(sanitized).not.toBe(request);
|
|
2680
|
+
expect(sanitized.messages).toEqual([
|
|
2681
|
+
{ role: 'user', content: 'What changed?' },
|
|
2682
|
+
]);
|
|
2683
|
+
});
|
|
2684
|
+
|
|
2685
|
+
test('does not strip assistant messages for older Claude models', () => {
|
|
2686
|
+
const request = {
|
|
2687
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
2688
|
+
max_tokens: 100,
|
|
2689
|
+
messages: [
|
|
2690
|
+
{ role: 'user' as const, content: 'Write JSON only.' },
|
|
2691
|
+
{ role: 'assistant' as const, content: '{' },
|
|
2692
|
+
],
|
|
2693
|
+
};
|
|
2694
|
+
|
|
2695
|
+
expect(stripUnsupportedAssistantPrefill(request)).toBe(request);
|
|
2696
|
+
});
|
|
2697
|
+
});
|
|
2698
|
+
|
|
2640
2699
|
const opus46Model = 'claude-opus-4-6';
|
|
2641
2700
|
|
|
2642
2701
|
describe('Opus 4.6', () => {
|
|
@@ -49,6 +49,10 @@ type GoogleFunctionCallBlock = MessageContentComplex & {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
const ANTHROPIC_EMPTY_TEXT_PLACEHOLDER = '_';
|
|
52
|
+
const CLAUDE_4_RELEASE_DATE_MODEL_PATTERN =
|
|
53
|
+
/claude-(?:opus|sonnet|haiku)-4-\d{8}(?:[-.@]|$)/i;
|
|
54
|
+
const CLAUDE_4_MINOR_MODEL_PATTERN =
|
|
55
|
+
/claude-(?:opus|sonnet|haiku)-4[-.](\d+)(?:[-.@]|$)/i;
|
|
52
56
|
|
|
53
57
|
function _formatImage(imageUrl: string) {
|
|
54
58
|
const parsed = parseBase64DataUrl({ dataUrl: imageUrl });
|
|
@@ -796,6 +800,48 @@ export function _convertMessagesToAnthropicPayload(
|
|
|
796
800
|
} as AnthropicMessageCreateParams;
|
|
797
801
|
}
|
|
798
802
|
|
|
803
|
+
export function modelDisallowsAssistantPrefill(model?: string): boolean {
|
|
804
|
+
const modelId = model ?? '';
|
|
805
|
+
if (CLAUDE_4_RELEASE_DATE_MODEL_PATTERN.test(modelId)) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const match = CLAUDE_4_MINOR_MODEL_PATTERN.exec(modelId);
|
|
810
|
+
if (!match) {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
return Number(match[1]) >= 6;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
export function stripUnsupportedAssistantPrefill<
|
|
817
|
+
T extends Pick<AnthropicMessageCreateParams, 'messages'> & { model?: string },
|
|
818
|
+
>(request: T): T {
|
|
819
|
+
if (!modelDisallowsAssistantPrefill(request.model)) {
|
|
820
|
+
return request;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const messages = request.messages;
|
|
824
|
+
if (
|
|
825
|
+
messages.length <= 1 ||
|
|
826
|
+
messages[messages.length - 1]?.role !== 'assistant'
|
|
827
|
+
) {
|
|
828
|
+
return request;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const nextMessages = [...messages];
|
|
832
|
+
while (
|
|
833
|
+
nextMessages.length > 1 &&
|
|
834
|
+
nextMessages[nextMessages.length - 1]?.role === 'assistant'
|
|
835
|
+
) {
|
|
836
|
+
nextMessages.pop();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return {
|
|
840
|
+
...request,
|
|
841
|
+
messages: nextMessages,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
799
845
|
function mergeMessages(messages: AnthropicMessageCreateParams['messages']) {
|
|
800
846
|
if (messages.length <= 1) {
|
|
801
847
|
return messages;
|
package/src/run.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/run.ts
|
|
2
2
|
import './instrumentation';
|
|
3
|
-
import { CallbackHandler } from '@langfuse/langchain';
|
|
4
3
|
import { PromptTemplate } from '@langchain/core/prompts';
|
|
5
4
|
import { RunnableLambda } from '@langchain/core/runnables';
|
|
6
5
|
import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
|
|
@@ -31,7 +30,14 @@ import { initializeModel } from '@/llm/init';
|
|
|
31
30
|
import { HandlerRegistry } from '@/events';
|
|
32
31
|
import { executeHooks } from '@/hooks';
|
|
33
32
|
import { isOpenAILike } from '@/utils/llm';
|
|
34
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
createLegacyLangfuseHandler,
|
|
35
|
+
createLangfuseHandler,
|
|
36
|
+
disposeLangfuseHandler,
|
|
37
|
+
hasExplicitLangfuseConfig,
|
|
38
|
+
hasLangfuseEnvConfig,
|
|
39
|
+
isLangfuseCallbackHandler,
|
|
40
|
+
} from '@/langfuse';
|
|
35
41
|
import type { HookRegistry } from '@/hooks';
|
|
36
42
|
|
|
37
43
|
export const defaultOmitOptions = new Set([
|
|
@@ -607,9 +613,8 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
607
613
|
.concat(customHandler);
|
|
608
614
|
|
|
609
615
|
if (
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
isPresent(process.env.LANGFUSE_BASE_URL)
|
|
616
|
+
hasLangfuseEnvConfig() &&
|
|
617
|
+
!hasExplicitLangfuseConfig(this.Graph.agentContexts.values())
|
|
613
618
|
) {
|
|
614
619
|
const userId = config.configurable?.user_id;
|
|
615
620
|
const sessionId = config.configurable?.thread_id;
|
|
@@ -621,7 +626,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
621
626
|
parentMessageId: config.configurable?.requestBody?.parentMessageId,
|
|
622
627
|
agentName: primaryContext?.name,
|
|
623
628
|
};
|
|
624
|
-
const handler =
|
|
629
|
+
const handler = createLegacyLangfuseHandler({
|
|
625
630
|
userId,
|
|
626
631
|
sessionId,
|
|
627
632
|
traceMetadata,
|
|
@@ -1134,12 +1139,8 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1134
1139
|
titleMethod = TitleMethod.COMPLETION,
|
|
1135
1140
|
titlePromptTemplate,
|
|
1136
1141
|
}: t.RunTitleOptions): Promise<{ language?: string; title?: string }> {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
isPresent(process.env.LANGFUSE_SECRET_KEY) &&
|
|
1140
|
-
isPresent(process.env.LANGFUSE_PUBLIC_KEY) &&
|
|
1141
|
-
isPresent(process.env.LANGFUSE_BASE_URL)
|
|
1142
|
-
) {
|
|
1142
|
+
let titleLangfuseHandler: unknown;
|
|
1143
|
+
if (chainOptions != null) {
|
|
1143
1144
|
const userId = chainOptions.configurable?.user_id;
|
|
1144
1145
|
const sessionId = chainOptions.configurable?.thread_id;
|
|
1145
1146
|
const titleContext = this.Graph?.agentContexts.get(
|
|
@@ -1149,14 +1150,31 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1149
1150
|
messageId: 'title-' + this.id,
|
|
1150
1151
|
agentName: titleContext?.name,
|
|
1151
1152
|
};
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1153
|
+
const hasExplicitLangfuse =
|
|
1154
|
+
this.Graph != null &&
|
|
1155
|
+
hasExplicitLangfuseConfig(this.Graph.agentContexts.values());
|
|
1156
|
+
if (titleContext?.langfuse != null) {
|
|
1157
|
+
titleLangfuseHandler = createLangfuseHandler({
|
|
1158
|
+
langfuse: titleContext.langfuse,
|
|
1159
|
+
userId,
|
|
1160
|
+
sessionId,
|
|
1161
|
+
traceMetadata,
|
|
1162
|
+
});
|
|
1163
|
+
} else if (hasLangfuseEnvConfig() && !hasExplicitLangfuse) {
|
|
1164
|
+
titleLangfuseHandler = createLegacyLangfuseHandler({
|
|
1165
|
+
userId,
|
|
1166
|
+
sessionId,
|
|
1167
|
+
traceMetadata,
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
if (titleLangfuseHandler != null) {
|
|
1172
|
+
chainOptions.callbacks = (
|
|
1173
|
+
(chainOptions.callbacks as t.ProvidedCallbacks) ?? []
|
|
1174
|
+
).concat([
|
|
1175
|
+
titleLangfuseHandler as NonNullable<t.ProvidedCallbacks>[number],
|
|
1176
|
+
]);
|
|
1177
|
+
}
|
|
1160
1178
|
}
|
|
1161
1179
|
|
|
1162
1180
|
const convoTemplate = PromptTemplate.fromTemplate(
|
|
@@ -1221,24 +1239,28 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1221
1239
|
});
|
|
1222
1240
|
|
|
1223
1241
|
try {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
+
try {
|
|
1243
|
+
return await fullChain.invoke(
|
|
1244
|
+
{ input: inputText, output: response },
|
|
1245
|
+
invokeConfig
|
|
1246
|
+
);
|
|
1247
|
+
} catch (_e) {
|
|
1248
|
+
// Fallback: strip callbacks to avoid EventStream tracer errors in certain environments
|
|
1249
|
+
// but preserve Langfuse tracing if it exists.
|
|
1250
|
+
const langfuseHandler = (
|
|
1251
|
+
invokeConfig.callbacks as t.ProvidedCallbacks
|
|
1252
|
+
)?.find(isLangfuseCallbackHandler);
|
|
1253
|
+
const { callbacks: _cb, ...rest } = invokeConfig;
|
|
1254
|
+
const safeConfig = Object.assign({}, rest, {
|
|
1255
|
+
callbacks: langfuseHandler ? [langfuseHandler] : [],
|
|
1256
|
+
});
|
|
1257
|
+
return await fullChain.invoke(
|
|
1258
|
+
{ input: inputText, output: response },
|
|
1259
|
+
safeConfig as Partial<RunnableConfig>
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
} finally {
|
|
1263
|
+
await disposeLangfuseHandler(titleLangfuseHandler);
|
|
1242
1264
|
}
|
|
1243
1265
|
}
|
|
1244
1266
|
}
|