@librechat/agents 3.1.90 → 3.1.92
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 +48 -14
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +2 -7
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +285 -0
- package/dist/cjs/langfuse.cjs.map +1 -0
- package/dist/cjs/main.cjs +25 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/run.cjs +75 -44
- 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/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/utils/callbacks.cjs +27 -0
- package/dist/cjs/utils/callbacks.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +9 -5
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +48 -14
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +2 -7
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +275 -0
- package/dist/esm/langfuse.mjs.map +1 -0
- package/dist/esm/main.mjs +5 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/run.mjs +75 -44
- 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/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/utils/callbacks.mjs +24 -0
- package/dist/esm/utils/callbacks.mjs.map +1 -0
- 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 +57 -0
- 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/types/graph.d.ts +8 -0
- package/dist/types/types/run.d.ts +2 -2
- package/dist/types/types/tools.d.ts +118 -2
- package/dist/types/utils/callbacks.d.ts +5 -0
- 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 +57 -16
- package/src/index.ts +1 -0
- package/src/instrumentation.ts +2 -7
- package/src/langfuse.ts +441 -0
- package/src/run.ts +105 -59
- package/src/specs/langfuse-callbacks.test.ts +75 -0
- package/src/specs/langfuse-config.test.ts +114 -0
- package/src/specs/langfuse-metadata.test.ts +19 -1
- package/src/stream.ts +13 -3
- package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +537 -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/types/graph.ts +9 -0
- package/src/types/run.ts +2 -7
- package/src/types/tools.ts +141 -2
- package/src/utils/callbacks.ts +39 -0
package/src/langfuse.ts
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { CallbackHandler } from '@langfuse/langchain';
|
|
2
|
+
import { isDefaultExportSpan, LangfuseSpanProcessor } from '@langfuse/otel';
|
|
3
|
+
import {
|
|
4
|
+
LangfuseOtelSpanAttributes,
|
|
5
|
+
createObservationAttributes,
|
|
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 { Attributes, Span } from '@opentelemetry/api';
|
|
14
|
+
import type * as t from '@/types';
|
|
15
|
+
import { isPresent } from '@/utils/misc';
|
|
16
|
+
|
|
17
|
+
const TRACE_METADATA_MAX_LENGTH = 200;
|
|
18
|
+
const LANGFUSE_TRACER_NAME = 'langfuse-sdk';
|
|
19
|
+
|
|
20
|
+
export type LangfuseTraceMetadata = Record<string, string>;
|
|
21
|
+
|
|
22
|
+
type LangfuseHandlerParams = {
|
|
23
|
+
userId?: string;
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
traceMetadata?: LangfuseTraceMetadata;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type AgentLangfuseHandlerParams = LangfuseHandlerParams & {
|
|
30
|
+
langfuse?: t.LangfuseConfig;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ResolvedLangfuseConfig = t.LangfuseConfig & {
|
|
34
|
+
enabled: true;
|
|
35
|
+
publicKey: string;
|
|
36
|
+
secretKey: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function getEnvLangfuseBaseUrl(): string | undefined {
|
|
40
|
+
return process.env.LANGFUSE_BASE_URL ?? process.env.LANGFUSE_BASEURL;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createTraceMetadata(
|
|
44
|
+
metadata: Record<string, unknown>
|
|
45
|
+
): LangfuseTraceMetadata {
|
|
46
|
+
const traceMetadata: LangfuseTraceMetadata = {};
|
|
47
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
48
|
+
if (value == null) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const stringValue = typeof value === 'string' ? value : String(value);
|
|
52
|
+
if (
|
|
53
|
+
stringValue.trim() === '' ||
|
|
54
|
+
stringValue.length > TRACE_METADATA_MAX_LENGTH
|
|
55
|
+
) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
traceMetadata[key] = stringValue;
|
|
59
|
+
}
|
|
60
|
+
return traceMetadata;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createLangfuseTraceMetadata({
|
|
64
|
+
messageId,
|
|
65
|
+
parentMessageId,
|
|
66
|
+
agentId,
|
|
67
|
+
agentName,
|
|
68
|
+
}: {
|
|
69
|
+
messageId?: unknown;
|
|
70
|
+
parentMessageId?: unknown;
|
|
71
|
+
agentId?: unknown;
|
|
72
|
+
agentName?: unknown;
|
|
73
|
+
}): LangfuseTraceMetadata {
|
|
74
|
+
return createTraceMetadata({
|
|
75
|
+
messageId,
|
|
76
|
+
parentMessageId,
|
|
77
|
+
agentId,
|
|
78
|
+
agentName,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getModelName(serialized: Serialized): string {
|
|
83
|
+
const serializedRecord = serialized as unknown as Record<string, unknown>;
|
|
84
|
+
const kwargs = serializedRecord.kwargs as Record<string, unknown> | undefined;
|
|
85
|
+
const modelName =
|
|
86
|
+
kwargs?.model ??
|
|
87
|
+
kwargs?.model_name ??
|
|
88
|
+
kwargs?.modelName ??
|
|
89
|
+
kwargs?.model_id ??
|
|
90
|
+
kwargs?.modelId ??
|
|
91
|
+
serializedRecord.name;
|
|
92
|
+
|
|
93
|
+
if (typeof modelName === 'string' && modelName.trim() !== '') {
|
|
94
|
+
return modelName;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (Array.isArray(serializedRecord.id) && serializedRecord.id.length > 0) {
|
|
98
|
+
return String(serializedRecord.id[serializedRecord.id.length - 1]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return 'ChatModel';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getModelParameters(
|
|
105
|
+
extraParams?: Record<string, unknown>
|
|
106
|
+
): Record<string, string | number> {
|
|
107
|
+
const invocationParams = extraParams?.invocation_params;
|
|
108
|
+
const params =
|
|
109
|
+
invocationParams != null && typeof invocationParams === 'object'
|
|
110
|
+
? (invocationParams as Record<string, unknown>)
|
|
111
|
+
: (extraParams ?? {});
|
|
112
|
+
|
|
113
|
+
return Object.fromEntries(
|
|
114
|
+
Object.entries(params).filter(([, value]) => {
|
|
115
|
+
return typeof value === 'string' || typeof value === 'number';
|
|
116
|
+
})
|
|
117
|
+
) as Record<string, string | number>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getOutput(output: LLMResult): unknown {
|
|
121
|
+
return output.generations.map((generation) =>
|
|
122
|
+
generation.map((item) => {
|
|
123
|
+
if ('message' in item && item.message != null) {
|
|
124
|
+
return (item.message as { content?: unknown }).content;
|
|
125
|
+
}
|
|
126
|
+
return item.text;
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getUsageDetails(
|
|
132
|
+
output: LLMResult
|
|
133
|
+
): Record<string, number> | undefined {
|
|
134
|
+
const llmOutput = output.llmOutput as Record<string, unknown> | undefined;
|
|
135
|
+
const usage = llmOutput?.tokenUsage ?? llmOutput?.usage;
|
|
136
|
+
if (usage == null || typeof usage !== 'object') {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const usageEntries = Object.entries(usage as Record<string, unknown>).filter(
|
|
141
|
+
([, value]) => typeof value === 'number'
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return usageEntries.length > 0
|
|
145
|
+
? (Object.fromEntries(usageEntries) as Record<string, number>)
|
|
146
|
+
: undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getLangfuseTraceName(
|
|
150
|
+
traceMetadata?: LangfuseTraceMetadata,
|
|
151
|
+
fallback: string = 'LibreChat Agent'
|
|
152
|
+
): string {
|
|
153
|
+
const agentName = traceMetadata?.agentName;
|
|
154
|
+
return isPresent(agentName) ? `${fallback}: ${agentName}` : fallback;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getTraceAttributes({
|
|
158
|
+
userId,
|
|
159
|
+
sessionId,
|
|
160
|
+
traceMetadata,
|
|
161
|
+
tags,
|
|
162
|
+
}: LangfuseHandlerParams): Attributes {
|
|
163
|
+
const attributes: Attributes = {
|
|
164
|
+
[LangfuseOtelSpanAttributes.TRACE_NAME]:
|
|
165
|
+
getLangfuseTraceName(traceMetadata),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (isPresent(userId)) {
|
|
169
|
+
attributes[LangfuseOtelSpanAttributes.TRACE_USER_ID] = userId;
|
|
170
|
+
}
|
|
171
|
+
if (isPresent(sessionId)) {
|
|
172
|
+
attributes[LangfuseOtelSpanAttributes.TRACE_SESSION_ID] = sessionId;
|
|
173
|
+
}
|
|
174
|
+
if (tags != null && tags.length > 0) {
|
|
175
|
+
attributes[LangfuseOtelSpanAttributes.TRACE_TAGS] = tags;
|
|
176
|
+
}
|
|
177
|
+
for (const [key, value] of Object.entries(traceMetadata ?? {})) {
|
|
178
|
+
attributes[`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`] = value;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return attributes;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export class LangfuseAgentCallbackHandler extends BaseCallbackHandler {
|
|
185
|
+
name = 'librechat_langfuse_agent_handler';
|
|
186
|
+
|
|
187
|
+
private readonly provider: BasicTracerProvider;
|
|
188
|
+
private readonly processor: LangfuseSpanProcessor;
|
|
189
|
+
private readonly userId?: string;
|
|
190
|
+
private readonly sessionId?: string;
|
|
191
|
+
private readonly traceMetadata?: LangfuseTraceMetadata;
|
|
192
|
+
private readonly tags?: string[];
|
|
193
|
+
private readonly spans = new Map<string, Span>();
|
|
194
|
+
|
|
195
|
+
constructor({
|
|
196
|
+
langfuse,
|
|
197
|
+
userId,
|
|
198
|
+
sessionId,
|
|
199
|
+
traceMetadata,
|
|
200
|
+
tags,
|
|
201
|
+
}: LangfuseHandlerParams & { langfuse: ResolvedLangfuseConfig }) {
|
|
202
|
+
super();
|
|
203
|
+
this.userId = userId;
|
|
204
|
+
this.sessionId = sessionId;
|
|
205
|
+
this.traceMetadata = traceMetadata;
|
|
206
|
+
this.tags = tags;
|
|
207
|
+
this.processor = new LangfuseSpanProcessor({
|
|
208
|
+
publicKey: langfuse.publicKey,
|
|
209
|
+
secretKey: langfuse.secretKey,
|
|
210
|
+
...(isPresent(langfuse.baseUrl) ? { baseUrl: langfuse.baseUrl } : {}),
|
|
211
|
+
environment:
|
|
212
|
+
process.env.LANGFUSE_TRACING_ENVIRONMENT ??
|
|
213
|
+
process.env.NODE_ENV ??
|
|
214
|
+
'development',
|
|
215
|
+
exportMode: 'immediate',
|
|
216
|
+
shouldExportSpan: ({ otelSpan }): boolean =>
|
|
217
|
+
isDefaultExportSpan(otelSpan) ||
|
|
218
|
+
otelSpan.instrumentationScope.name === LANGFUSE_TRACER_NAME,
|
|
219
|
+
});
|
|
220
|
+
this.provider = new BasicTracerProvider({
|
|
221
|
+
spanProcessors: [this.processor],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private startGenerationSpan({
|
|
226
|
+
llm,
|
|
227
|
+
input,
|
|
228
|
+
runId,
|
|
229
|
+
extraParams,
|
|
230
|
+
metadata,
|
|
231
|
+
name,
|
|
232
|
+
}: {
|
|
233
|
+
llm: Serialized;
|
|
234
|
+
input: unknown;
|
|
235
|
+
runId: string;
|
|
236
|
+
extraParams?: Record<string, unknown>;
|
|
237
|
+
metadata?: Record<string, unknown>;
|
|
238
|
+
name?: string;
|
|
239
|
+
}): void {
|
|
240
|
+
if (this.spans.has(runId)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const tracer = this.provider.getTracer(LANGFUSE_TRACER_NAME);
|
|
245
|
+
const spanName =
|
|
246
|
+
typeof name === 'string' && name.trim() !== '' ? name : getModelName(llm);
|
|
247
|
+
const span = tracer.startSpan(spanName, {
|
|
248
|
+
attributes: {
|
|
249
|
+
...getTraceAttributes({
|
|
250
|
+
userId: this.userId,
|
|
251
|
+
sessionId: this.sessionId,
|
|
252
|
+
traceMetadata: this.traceMetadata,
|
|
253
|
+
tags: this.tags,
|
|
254
|
+
}),
|
|
255
|
+
...createObservationAttributes('generation', {
|
|
256
|
+
input,
|
|
257
|
+
model: getModelName(llm),
|
|
258
|
+
modelParameters: getModelParameters(extraParams),
|
|
259
|
+
metadata: {
|
|
260
|
+
...metadata,
|
|
261
|
+
...this.traceMetadata,
|
|
262
|
+
},
|
|
263
|
+
}),
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
this.spans.set(runId, span);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async handleChatModelStart(
|
|
270
|
+
llm: Serialized,
|
|
271
|
+
messages: BaseMessage[][],
|
|
272
|
+
runId: string,
|
|
273
|
+
_parentRunId?: string,
|
|
274
|
+
extraParams?: Record<string, unknown>,
|
|
275
|
+
_tags?: string[],
|
|
276
|
+
metadata?: Record<string, unknown>,
|
|
277
|
+
name?: string
|
|
278
|
+
): Promise<void> {
|
|
279
|
+
this.startGenerationSpan({
|
|
280
|
+
llm,
|
|
281
|
+
input: messages,
|
|
282
|
+
runId,
|
|
283
|
+
extraParams,
|
|
284
|
+
metadata,
|
|
285
|
+
name,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async handleLLMStart(
|
|
290
|
+
llm: Serialized,
|
|
291
|
+
prompts: string[],
|
|
292
|
+
runId: string,
|
|
293
|
+
_parentRunId?: string,
|
|
294
|
+
extraParams?: Record<string, unknown>,
|
|
295
|
+
_tags?: string[],
|
|
296
|
+
metadata?: Record<string, unknown>,
|
|
297
|
+
name?: string
|
|
298
|
+
): Promise<void> {
|
|
299
|
+
this.startGenerationSpan({
|
|
300
|
+
llm,
|
|
301
|
+
input: prompts,
|
|
302
|
+
runId,
|
|
303
|
+
extraParams,
|
|
304
|
+
metadata,
|
|
305
|
+
name,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async handleLLMEnd(output: LLMResult, runId: string): Promise<void> {
|
|
310
|
+
const span = this.spans.get(runId);
|
|
311
|
+
if (!span) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
span.setAttributes(
|
|
316
|
+
createObservationAttributes('generation', {
|
|
317
|
+
output: getOutput(output),
|
|
318
|
+
usageDetails: getUsageDetails(output),
|
|
319
|
+
})
|
|
320
|
+
);
|
|
321
|
+
span.end();
|
|
322
|
+
this.spans.delete(runId);
|
|
323
|
+
await this.flush();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async handleLLMError(err: unknown, runId: string): Promise<void> {
|
|
327
|
+
const span = this.spans.get(runId);
|
|
328
|
+
if (!span) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
333
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
334
|
+
span.setAttributes(
|
|
335
|
+
createObservationAttributes('generation', {
|
|
336
|
+
level: 'ERROR',
|
|
337
|
+
statusMessage: message,
|
|
338
|
+
})
|
|
339
|
+
);
|
|
340
|
+
span.end();
|
|
341
|
+
this.spans.delete(runId);
|
|
342
|
+
await this.flush();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private async flush(): Promise<void> {
|
|
346
|
+
try {
|
|
347
|
+
await this.provider.forceFlush();
|
|
348
|
+
} catch (error) {
|
|
349
|
+
process.emitWarning(
|
|
350
|
+
`[LangfuseAgentCallbackHandler] Failed to flush Langfuse spans: ${
|
|
351
|
+
error instanceof Error ? error.message : String(error)
|
|
352
|
+
}`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async dispose(): Promise<void> {
|
|
358
|
+
for (const span of this.spans.values()) {
|
|
359
|
+
span.end();
|
|
360
|
+
}
|
|
361
|
+
this.spans.clear();
|
|
362
|
+
await this.flush();
|
|
363
|
+
try {
|
|
364
|
+
await this.provider.shutdown();
|
|
365
|
+
} catch (error) {
|
|
366
|
+
process.emitWarning(
|
|
367
|
+
`[LangfuseAgentCallbackHandler] Failed to shut down Langfuse provider: ${
|
|
368
|
+
error instanceof Error ? error.message : String(error)
|
|
369
|
+
}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function hasRequiredLangfuseConfig(
|
|
376
|
+
langfuse?: t.LangfuseConfig
|
|
377
|
+
): langfuse is ResolvedLangfuseConfig {
|
|
378
|
+
return (
|
|
379
|
+
langfuse?.enabled === true &&
|
|
380
|
+
isPresent(langfuse.publicKey) &&
|
|
381
|
+
isPresent(langfuse.secretKey)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function createLegacyLangfuseHandler(
|
|
386
|
+
params: LangfuseHandlerParams
|
|
387
|
+
): CallbackHandler {
|
|
388
|
+
return new CallbackHandler(params);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function createLangfuseHandler({
|
|
392
|
+
langfuse,
|
|
393
|
+
userId,
|
|
394
|
+
sessionId,
|
|
395
|
+
traceMetadata,
|
|
396
|
+
tags,
|
|
397
|
+
}: AgentLangfuseHandlerParams): LangfuseAgentCallbackHandler | undefined {
|
|
398
|
+
if (!hasRequiredLangfuseConfig(langfuse)) {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return new LangfuseAgentCallbackHandler({
|
|
403
|
+
langfuse,
|
|
404
|
+
userId,
|
|
405
|
+
sessionId,
|
|
406
|
+
traceMetadata,
|
|
407
|
+
tags,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function hasExplicitLangfuseConfig(
|
|
412
|
+
contexts: Iterable<{ langfuse?: t.LangfuseConfig }>
|
|
413
|
+
): boolean {
|
|
414
|
+
for (const context of contexts) {
|
|
415
|
+
if (context.langfuse != null) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function hasLangfuseEnvConfig(): boolean {
|
|
423
|
+
return (
|
|
424
|
+
isPresent(process.env.LANGFUSE_SECRET_KEY) &&
|
|
425
|
+
isPresent(process.env.LANGFUSE_PUBLIC_KEY) &&
|
|
426
|
+
isPresent(getEnvLangfuseBaseUrl())
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function isLangfuseCallbackHandler(value: unknown): boolean {
|
|
431
|
+
return (
|
|
432
|
+
value instanceof CallbackHandler ||
|
|
433
|
+
value instanceof LangfuseAgentCallbackHandler
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export async function disposeLangfuseHandler(value: unknown): Promise<void> {
|
|
438
|
+
if (value instanceof LangfuseAgentCallbackHandler) {
|
|
439
|
+
await value.dispose();
|
|
440
|
+
}
|
|
441
|
+
}
|
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,21 @@ 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
|
+
appendCallbacks,
|
|
35
|
+
findCallback,
|
|
36
|
+
type CallbackEntry,
|
|
37
|
+
} from '@/utils/callbacks';
|
|
38
|
+
import {
|
|
39
|
+
createLegacyLangfuseHandler,
|
|
40
|
+
createLangfuseTraceMetadata,
|
|
41
|
+
createLangfuseHandler,
|
|
42
|
+
disposeLangfuseHandler,
|
|
43
|
+
getLangfuseTraceName,
|
|
44
|
+
hasExplicitLangfuseConfig,
|
|
45
|
+
hasLangfuseEnvConfig,
|
|
46
|
+
isLangfuseCallbackHandler,
|
|
47
|
+
} from '@/langfuse';
|
|
35
48
|
import type { HookRegistry } from '@/hooks';
|
|
36
49
|
|
|
37
50
|
export const defaultOmitOptions = new Set([
|
|
@@ -592,43 +605,48 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
592
605
|
/** Custom event callback to intercept and handle custom events */
|
|
593
606
|
const customEventCallback = this.createCustomEventCallback();
|
|
594
607
|
|
|
595
|
-
const baseCallbacks = (config.callbacks as t.ProvidedCallbacks) ?? [];
|
|
596
608
|
const streamCallbacks = streamOptions?.callbacks
|
|
597
609
|
? this.getCallbacks(streamOptions.callbacks)
|
|
598
|
-
:
|
|
610
|
+
: undefined;
|
|
599
611
|
|
|
600
612
|
const customHandler = BaseCallbackHandler.fromMethods({
|
|
601
613
|
[Callback.CUSTOM_EVENT]: customEventCallback,
|
|
602
614
|
});
|
|
603
615
|
customHandler.awaitHandlers = true;
|
|
604
616
|
|
|
605
|
-
config.callbacks =
|
|
606
|
-
.
|
|
607
|
-
|
|
617
|
+
config.callbacks = appendCallbacks(
|
|
618
|
+
config.callbacks,
|
|
619
|
+
streamCallbacks ? [streamCallbacks, customHandler] : [customHandler]
|
|
620
|
+
);
|
|
608
621
|
|
|
609
622
|
if (
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
isPresent(process.env.LANGFUSE_BASE_URL)
|
|
623
|
+
hasLangfuseEnvConfig() &&
|
|
624
|
+
!hasExplicitLangfuseConfig(this.Graph.agentContexts.values())
|
|
613
625
|
) {
|
|
614
|
-
const userId =
|
|
615
|
-
|
|
626
|
+
const userId =
|
|
627
|
+
typeof config.configurable?.user_id === 'string'
|
|
628
|
+
? config.configurable.user_id
|
|
629
|
+
: undefined;
|
|
630
|
+
const sessionId =
|
|
631
|
+
typeof config.configurable?.thread_id === 'string'
|
|
632
|
+
? config.configurable.thread_id
|
|
633
|
+
: undefined;
|
|
616
634
|
const primaryContext = this.Graph.agentContexts.get(
|
|
617
635
|
this.Graph.defaultAgentId
|
|
618
636
|
);
|
|
619
|
-
const traceMetadata = {
|
|
637
|
+
const traceMetadata = createLangfuseTraceMetadata({
|
|
620
638
|
messageId: this.id,
|
|
621
639
|
parentMessageId: config.configurable?.requestBody?.parentMessageId,
|
|
622
640
|
agentName: primaryContext?.name,
|
|
623
|
-
};
|
|
624
|
-
const handler =
|
|
641
|
+
});
|
|
642
|
+
const handler = createLegacyLangfuseHandler({
|
|
625
643
|
userId,
|
|
626
644
|
sessionId,
|
|
627
645
|
traceMetadata,
|
|
646
|
+
tags: ['librechat', 'agent'],
|
|
628
647
|
});
|
|
629
|
-
config.
|
|
630
|
-
|
|
631
|
-
).concat([handler]);
|
|
648
|
+
config.runName = config.runName ?? getLangfuseTraceName(traceMetadata);
|
|
649
|
+
config.callbacks = appendCallbacks(config.callbacks, [handler]);
|
|
632
650
|
}
|
|
633
651
|
|
|
634
652
|
if (!this.id) {
|
|
@@ -1134,29 +1152,51 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1134
1152
|
titleMethod = TitleMethod.COMPLETION,
|
|
1135
1153
|
titlePromptTemplate,
|
|
1136
1154
|
}: t.RunTitleOptions): Promise<{ language?: string; title?: string }> {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1155
|
+
let titleLangfuseHandler: CallbackEntry | undefined;
|
|
1156
|
+
const titleContext =
|
|
1157
|
+
this.Graph == null
|
|
1158
|
+
? undefined
|
|
1159
|
+
: this.Graph.agentContexts.get(this.Graph.defaultAgentId);
|
|
1160
|
+
const traceMetadata = createLangfuseTraceMetadata({
|
|
1161
|
+
messageId: 'title-' + this.id,
|
|
1162
|
+
agentName: titleContext?.name,
|
|
1163
|
+
});
|
|
1164
|
+
const titleRunName = getLangfuseTraceName(traceMetadata, 'LibreChat Title');
|
|
1165
|
+
|
|
1166
|
+
if (chainOptions != null) {
|
|
1167
|
+
const userId =
|
|
1168
|
+
typeof chainOptions.configurable?.user_id === 'string'
|
|
1169
|
+
? chainOptions.configurable.user_id
|
|
1170
|
+
: undefined;
|
|
1171
|
+
const sessionId =
|
|
1172
|
+
typeof chainOptions.configurable?.thread_id === 'string'
|
|
1173
|
+
? chainOptions.configurable.thread_id
|
|
1174
|
+
: undefined;
|
|
1175
|
+
const hasExplicitLangfuse =
|
|
1176
|
+
this.Graph != null &&
|
|
1177
|
+
hasExplicitLangfuseConfig(this.Graph.agentContexts.values());
|
|
1178
|
+
if (titleContext?.langfuse != null) {
|
|
1179
|
+
titleLangfuseHandler = createLangfuseHandler({
|
|
1180
|
+
langfuse: titleContext.langfuse,
|
|
1181
|
+
userId,
|
|
1182
|
+
sessionId,
|
|
1183
|
+
traceMetadata,
|
|
1184
|
+
tags: ['librechat', 'title'],
|
|
1185
|
+
});
|
|
1186
|
+
} else if (hasLangfuseEnvConfig() && !hasExplicitLangfuse) {
|
|
1187
|
+
titleLangfuseHandler = createLegacyLangfuseHandler({
|
|
1188
|
+
userId,
|
|
1189
|
+
sessionId,
|
|
1190
|
+
traceMetadata,
|
|
1191
|
+
tags: ['librechat', 'title'],
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
if (titleLangfuseHandler != null) {
|
|
1196
|
+
chainOptions.callbacks = appendCallbacks(chainOptions.callbacks, [
|
|
1197
|
+
titleLangfuseHandler,
|
|
1198
|
+
]);
|
|
1199
|
+
}
|
|
1160
1200
|
}
|
|
1161
1201
|
|
|
1162
1202
|
const convoTemplate = PromptTemplate.fromTemplate(
|
|
@@ -1218,27 +1258,33 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1218
1258
|
const invokeConfig = Object.assign({}, chainOptions, {
|
|
1219
1259
|
run_id: this.id,
|
|
1220
1260
|
runId: this.id,
|
|
1261
|
+
runName: chainOptions?.runName ?? titleRunName,
|
|
1221
1262
|
});
|
|
1222
1263
|
|
|
1223
1264
|
try {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
callbacks:
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1265
|
+
try {
|
|
1266
|
+
return await fullChain.invoke(
|
|
1267
|
+
{ input: inputText, output: response },
|
|
1268
|
+
invokeConfig
|
|
1269
|
+
);
|
|
1270
|
+
} catch (_e) {
|
|
1271
|
+
// Fallback: strip callbacks to avoid EventStream tracer errors in certain environments
|
|
1272
|
+
// but preserve Langfuse tracing if it exists.
|
|
1273
|
+
const langfuseHandler = findCallback(
|
|
1274
|
+
invokeConfig.callbacks,
|
|
1275
|
+
isLangfuseCallbackHandler
|
|
1276
|
+
);
|
|
1277
|
+
const { callbacks: _cb, ...rest } = invokeConfig;
|
|
1278
|
+
const safeConfig = Object.assign({}, rest, {
|
|
1279
|
+
callbacks: langfuseHandler ? [langfuseHandler] : [],
|
|
1280
|
+
});
|
|
1281
|
+
return await fullChain.invoke(
|
|
1282
|
+
{ input: inputText, output: response },
|
|
1283
|
+
safeConfig as Partial<RunnableConfig>
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
} finally {
|
|
1287
|
+
await disposeLangfuseHandler(titleLangfuseHandler);
|
|
1242
1288
|
}
|
|
1243
1289
|
}
|
|
1244
1290
|
}
|