@librechat/agents 3.6.7 → 3.6.9
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/common/constants.cjs +14 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +18 -6
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +6 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +2 -0
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +46 -15
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseOperation.cjs +6 -0
- package/dist/cjs/langfuseOperation.cjs.map +1 -0
- package/dist/cjs/langfuseRuntimeContext.cjs +3 -1
- package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -1
- package/dist/cjs/langfuseRuntimeScope.cjs +11 -1
- package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -1
- package/dist/cjs/langfuseSpanRegistry.cjs +35 -0
- package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +1 -6
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +58 -10
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +8 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +8 -1
- package/dist/cjs/run.cjs +59 -25
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/tools/ToolNode.cjs +2 -2
- package/dist/cjs/tools/search/search.cjs +19 -9
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +2 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +3 -3
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/common/constants.mjs +8 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +18 -6
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +6 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +4 -2
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +47 -16
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseOperation.mjs +6 -0
- package/dist/esm/langfuseOperation.mjs.map +1 -0
- package/dist/esm/langfuseRuntimeContext.mjs +3 -2
- package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
- package/dist/esm/langfuseRuntimeScope.mjs +12 -3
- package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
- package/dist/esm/langfuseSpanRegistry.mjs +34 -1
- package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +1 -6
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +58 -10
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +8 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/run.mjs +59 -25
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/tools/ToolNode.mjs +2 -2
- package/dist/esm/tools/search/search.mjs +19 -9
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +2 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +3 -3
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/common/constants.d.ts +8 -0
- package/dist/types/graphs/Graph.d.ts +8 -3
- package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
- package/dist/types/langfuse.d.ts +10 -1
- package/dist/types/langfuseOperation.d.ts +2 -0
- package/dist/types/langfuseRuntimeContext.d.ts +2 -0
- package/dist/types/langfuseRuntimeScope.d.ts +3 -1
- package/dist/types/langfuseSpanRegistry.d.ts +11 -1
- package/dist/types/langfuseTraceShaping.d.ts +8 -6
- package/dist/types/run.d.ts +4 -3
- package/dist/types/tools/search/types.d.ts +15 -0
- package/dist/types/types/activityLabel.d.ts +4 -3
- package/dist/types/types/reasoningLabel.d.ts +1 -1
- package/package.json +5 -5
- package/src/common/constants.ts +9 -0
- package/src/graphs/Graph.ts +125 -103
- package/src/graphs/MultiAgentGraph.ts +6 -2
- package/src/instrumentation.ts +12 -0
- package/src/langfuse.ts +83 -17
- package/src/langfuseOperation.ts +2 -0
- package/src/langfuseRuntimeContext.ts +6 -0
- package/src/langfuseRuntimeScope.ts +31 -1
- package/src/langfuseSpanRegistry.ts +78 -2
- package/src/langfuseToolOutputTracing.ts +0 -2
- package/src/langfuseTraceShaping.ts +120 -26
- package/src/llm/openai/index.ts +34 -3
- package/src/run.ts +118 -49
- package/src/tools/search/search.ts +25 -8
- package/src/tools/search/tool.ts +2 -0
- package/src/tools/search/types.ts +16 -0
- package/src/tools/subagent/SubagentExecutor.ts +8 -4
- package/src/types/activityLabel.ts +4 -3
- package/src/types/reasoningLabel.ts +1 -1
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import type * as t from '@/types';
|
|
8
8
|
import {
|
|
9
9
|
getLangfuseRuntimeConfig,
|
|
10
|
+
getLangfuseRuntimeContext,
|
|
10
11
|
getLangfuseRuntimeToolOutputTracingConfig,
|
|
11
12
|
getLangfuseScopeAgentId,
|
|
12
13
|
getLangfuseScopeRunId,
|
|
@@ -27,6 +28,7 @@ export type ResolveLangfuseRuntimeScopeParams = {
|
|
|
27
28
|
runLangfuse?: t.LangfuseConfig;
|
|
28
29
|
langfuseOverlay?: t.LangfuseConfig;
|
|
29
30
|
traceIdSeed?: string;
|
|
31
|
+
traceAnchor?: object;
|
|
30
32
|
runId?: string;
|
|
31
33
|
agentId?: string;
|
|
32
34
|
};
|
|
@@ -46,6 +48,9 @@ const langfuseConfigKey = createContextKey('librechat.langfuse.config');
|
|
|
46
48
|
const langfuseTraceIdSeedKey = createContextKey(
|
|
47
49
|
'librechat.langfuse.trace-id-seed'
|
|
48
50
|
);
|
|
51
|
+
const langfuseTraceAnchorKey = createContextKey(
|
|
52
|
+
'librechat.langfuse.trace-anchor'
|
|
53
|
+
);
|
|
49
54
|
const langfuseScopeRunIdKey = createContextKey('librechat.langfuse.run-id');
|
|
50
55
|
const langfuseScopeAgentIdKey = createContextKey('librechat.langfuse.agent-id');
|
|
51
56
|
|
|
@@ -69,6 +74,17 @@ export function getOtelTraceIdSeed(activeContext: Context): string | undefined {
|
|
|
69
74
|
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
export function resolveLangfuseTraceAnchor(
|
|
78
|
+
activeContext: Context
|
|
79
|
+
): object | undefined {
|
|
80
|
+
const runtimeAnchor = getLangfuseRuntimeContext()?.traceAnchor;
|
|
81
|
+
if (runtimeAnchor != null) {
|
|
82
|
+
return runtimeAnchor;
|
|
83
|
+
}
|
|
84
|
+
const value = activeContext.getValue(langfuseTraceAnchorKey);
|
|
85
|
+
return typeof value === 'object' && value != null ? value : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
export function getOtelToolOutputTracingConfig(
|
|
73
89
|
activeContext: Context
|
|
74
90
|
): ResolvedLangfuseToolOutputTracingConfig | undefined {
|
|
@@ -166,6 +182,12 @@ export function withLangfuseRuntimeScope<T>(
|
|
|
166
182
|
hasText(scope.traceIdSeed) ? scope.traceIdSeed : undefined,
|
|
167
183
|
replace
|
|
168
184
|
);
|
|
185
|
+
activeContext = setOrClearContextValue(
|
|
186
|
+
activeContext,
|
|
187
|
+
langfuseTraceAnchorKey,
|
|
188
|
+
scope.traceAnchor,
|
|
189
|
+
replace
|
|
190
|
+
);
|
|
169
191
|
activeContext = setOrClearContextValue(
|
|
170
192
|
activeContext,
|
|
171
193
|
langfuseScopeRunIdKey,
|
|
@@ -194,6 +216,7 @@ export function resolveLangfuseRuntimeScope({
|
|
|
194
216
|
runLangfuse,
|
|
195
217
|
langfuseOverlay,
|
|
196
218
|
traceIdSeed,
|
|
219
|
+
traceAnchor,
|
|
197
220
|
runId,
|
|
198
221
|
agentId,
|
|
199
222
|
}: ResolveLangfuseRuntimeScopeParams): LangfuseRuntimeScope {
|
|
@@ -204,5 +227,12 @@ export function resolveLangfuseRuntimeScope({
|
|
|
204
227
|
)
|
|
205
228
|
? undefined
|
|
206
229
|
: resolveToolOutputTracingConfig(runLangfuse, langfuseOverlay);
|
|
207
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
langfuse,
|
|
232
|
+
traceIdSeed,
|
|
233
|
+
traceAnchor,
|
|
234
|
+
toolOutputTracing,
|
|
235
|
+
runId,
|
|
236
|
+
agentId,
|
|
237
|
+
};
|
|
208
238
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { context, trace } from '@opentelemetry/api';
|
|
2
3
|
import type { LangfuseSpanProcessorParams } from '@langfuse/otel';
|
|
3
|
-
import type { Span } from '@opentelemetry/api';
|
|
4
|
+
import type { Span, SpanContext } from '@opentelemetry/api';
|
|
4
5
|
import type * as t from '@/types';
|
|
5
6
|
import {
|
|
6
7
|
hasLangfuseConfigCredentials,
|
|
@@ -16,7 +17,7 @@ import { isPresent } from '@/utils/misc';
|
|
|
16
17
|
*
|
|
17
18
|
* - Foreign spans (a host's own OpenTelemetry instrumentation, e.g. HTTP
|
|
18
19
|
* server spans on the global provider) are never exported to Langfuse —
|
|
19
|
-
* inheriting one orphans the trace root (its root
|
|
20
|
+
* inheriting one orphans the trace root (its root-observation input/output
|
|
20
21
|
* shaping is skipped), collapses concurrent runs inside one request
|
|
21
22
|
* context into a single merged trace, and bypasses the seeded
|
|
22
23
|
* deterministic trace id generator.
|
|
@@ -30,6 +31,19 @@ import { isPresent } from '@/utils/misc';
|
|
|
30
31
|
*/
|
|
31
32
|
const managedSpanDestinations = new WeakMap<Span, string>();
|
|
32
33
|
|
|
34
|
+
type AnchoredSpan = {
|
|
35
|
+
destinationKey: string;
|
|
36
|
+
spanContext: SpanContext;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type TraceAnchorState = {
|
|
40
|
+
spans: Map<string, AnchoredSpan>;
|
|
41
|
+
spanIds: Set<string>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const ROOT_TRACE_ANCHOR = '';
|
|
45
|
+
const traceAnchorStates = new WeakMap<object, TraceAnchorState>();
|
|
46
|
+
|
|
33
47
|
export function registerLangfuseManagedSpan(
|
|
34
48
|
span: Span,
|
|
35
49
|
destinationKey: string
|
|
@@ -43,6 +57,68 @@ export function getLangfuseManagedSpanDestination(
|
|
|
43
57
|
return managedSpanDestinations.get(span);
|
|
44
58
|
}
|
|
45
59
|
|
|
60
|
+
/** Captures the first exported observation for a run and for each agent lane. */
|
|
61
|
+
export function registerLangfuseTraceAnchorSpan(
|
|
62
|
+
anchor: object,
|
|
63
|
+
span: Span,
|
|
64
|
+
destinationKey: string,
|
|
65
|
+
agentId?: string
|
|
66
|
+
): void {
|
|
67
|
+
let state = traceAnchorStates.get(anchor);
|
|
68
|
+
if (state == null) {
|
|
69
|
+
state = { spans: new Map<string, AnchoredSpan>(), spanIds: new Set() };
|
|
70
|
+
traceAnchorStates.set(anchor, state);
|
|
71
|
+
}
|
|
72
|
+
const spanContext = span.spanContext();
|
|
73
|
+
state.spanIds.add(spanContext.spanId);
|
|
74
|
+
const key = agentId ?? ROOT_TRACE_ANCHOR;
|
|
75
|
+
if (state.spans.has(key)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
state.spans.set(key, { destinationKey, spanContext });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the most specific safe parent available for auxiliary work. An
|
|
83
|
+
* active observation wins so labels emitted inside a tool or reasoning step
|
|
84
|
+
* stay beside their source; otherwise the run root anchors the label in the
|
|
85
|
+
* same trace. An agent lane is the fallback when its overlay exports to a
|
|
86
|
+
* different destination. Cross-destination parents are never returned.
|
|
87
|
+
*/
|
|
88
|
+
export function resolveLangfuseTraceAnchorParent(
|
|
89
|
+
anchor: object | undefined,
|
|
90
|
+
destinationKey: string | undefined,
|
|
91
|
+
agentId?: string
|
|
92
|
+
): SpanContext | undefined {
|
|
93
|
+
if (anchor == null || destinationKey == null) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const state = traceAnchorStates.get(anchor);
|
|
97
|
+
if (state == null) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const agentSpan = agentId == null ? undefined : state.spans.get(agentId);
|
|
101
|
+
const rootSpan = state.spans.get(ROOT_TRACE_ANCHOR);
|
|
102
|
+
let anchoredSpan = rootSpan;
|
|
103
|
+
if (anchoredSpan?.destinationKey !== destinationKey) {
|
|
104
|
+
anchoredSpan = agentSpan;
|
|
105
|
+
}
|
|
106
|
+
if (anchoredSpan?.destinationKey !== destinationKey) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const activeSpan = trace.getSpan(context.active());
|
|
111
|
+
if (
|
|
112
|
+
activeSpan != null &&
|
|
113
|
+
getLangfuseManagedSpanDestination(activeSpan) === destinationKey &&
|
|
114
|
+
activeSpan.spanContext().traceId === anchoredSpan.spanContext.traceId &&
|
|
115
|
+
state.spanIds.has(activeSpan.spanContext().spanId)
|
|
116
|
+
) {
|
|
117
|
+
return activeSpan.spanContext();
|
|
118
|
+
}
|
|
119
|
+
return anchoredSpan.spanContext;
|
|
120
|
+
}
|
|
121
|
+
|
|
46
122
|
function resolveLangfuseEnvironment(
|
|
47
123
|
langfuse?: t.LangfuseConfig
|
|
48
124
|
): string | undefined {
|
|
@@ -779,8 +779,6 @@ export function redactLangfuseSpanToolOutputs(
|
|
|
779
779
|
for (const key of [
|
|
780
780
|
LangfuseOtelSpanAttributes.OBSERVATION_INPUT,
|
|
781
781
|
LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,
|
|
782
|
-
LangfuseOtelSpanAttributes.TRACE_INPUT,
|
|
783
|
-
LangfuseOtelSpanAttributes.TRACE_OUTPUT,
|
|
784
782
|
]) {
|
|
785
783
|
redactAttribute(attributes, key, config);
|
|
786
784
|
}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { LangfuseOtelSpanAttributes } from '@langfuse/tracing';
|
|
2
2
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Constants,
|
|
5
|
+
STANDARD_GRAPH_RUN_NAME,
|
|
6
|
+
MULTI_AGENT_GRAPH_RUN_NAME,
|
|
7
|
+
AGENT_MODEL_CALL_RUN_NAME,
|
|
8
|
+
ACTIVITY_LABEL_RUN_NAME,
|
|
9
|
+
REASONING_LABEL_RUN_NAME,
|
|
10
|
+
ACTIVITY_PHASE_RUN_NAME,
|
|
11
|
+
ACTIVITY_PHASE_LABEL_RUN_NAME,
|
|
12
|
+
} from '@/common';
|
|
13
|
+
import { LANGFUSE_OPERATION_METADATA_KEY } from '@/langfuseOperation';
|
|
4
14
|
|
|
5
15
|
const LANGGRAPH_START_NODE = '__start__';
|
|
16
|
+
const LANGGRAPH_RUN_NAME = 'LangGraph';
|
|
6
17
|
const ANONYMOUS_LAMBDA_NAME = 'RunnableLambda';
|
|
18
|
+
const RUNNABLE_SEQUENCE_NAME = 'RunnableSequence';
|
|
7
19
|
const LANGGRAPH_AGENT_NODE_PREFIX = 'agent=';
|
|
8
20
|
const LANGGRAPH_TOOL_NODE_PREFIX = 'tools=';
|
|
9
21
|
const AGENT_NODE_SPAN_NAME = 'agent';
|
|
@@ -14,11 +26,15 @@ const CHAIN_OBSERVATION_TYPE = 'chain';
|
|
|
14
26
|
const TOOL_OBSERVATION_TYPE = 'tool';
|
|
15
27
|
const AGENT_TRACE_TAG = 'agent';
|
|
16
28
|
const TITLE_TRACE_TAG = 'title';
|
|
29
|
+
const ACTIVITY_LABEL_TRACE_TAG = 'activity-label';
|
|
30
|
+
const REASONING_LABEL_TRACE_TAG = 'reasoning-label';
|
|
17
31
|
const ACTIVITY_PHASE_TRACE_TAG = 'activity-phase';
|
|
18
|
-
const ACTIVITY_PHASE_ROOT_NAME = 'summarize-activity-phase';
|
|
19
32
|
const EPHEMERAL_AGENT_SENDER_SEPARATOR = '___';
|
|
20
33
|
const EPHEMERAL_AGENT_INDEX_SEPARATOR = '____';
|
|
34
|
+
const DEPRECATED_TRACE_INPUT_ATTRIBUTE = 'langfuse.trace.input';
|
|
35
|
+
const DEPRECATED_TRACE_OUTPUT_ATTRIBUTE = 'langfuse.trace.output';
|
|
21
36
|
const OBSERVATION_METADATA_LANGGRAPH_NODE = `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.langgraph_node`;
|
|
37
|
+
const OBSERVATION_METADATA_OPERATION = `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.${LANGFUSE_OPERATION_METADATA_KEY}`;
|
|
22
38
|
|
|
23
39
|
type MutableSpan = ReadableSpan & {
|
|
24
40
|
name: string;
|
|
@@ -192,9 +208,7 @@ function getMessageToolCalls(
|
|
|
192
208
|
*/
|
|
193
209
|
/** Tool-result ids present in the serialized state — ToolNode's
|
|
194
210
|
* `!toolMessageIds.has(id)` execution filter, mirrored for the span. */
|
|
195
|
-
function getToolResultIds(
|
|
196
|
-
messages: Record<string, unknown>[]
|
|
197
|
-
): Set<string> {
|
|
211
|
+
function getToolResultIds(messages: Record<string, unknown>[]): Set<string> {
|
|
198
212
|
const ids = new Set<string>();
|
|
199
213
|
for (const message of messages) {
|
|
200
214
|
if (getMessageRole(message) !== 'tool') {
|
|
@@ -217,7 +231,9 @@ function getMessageInvalidToolCalls(
|
|
|
217
231
|
): SerializedToolCall[] {
|
|
218
232
|
const rawCalls =
|
|
219
233
|
message.invalid_tool_calls ??
|
|
220
|
-
(isRecord(message.kwargs)
|
|
234
|
+
(isRecord(message.kwargs)
|
|
235
|
+
? message.kwargs.invalid_tool_calls
|
|
236
|
+
: undefined) ??
|
|
221
237
|
(isRecord(message.data) ? message.data.invalid_tool_calls : undefined);
|
|
222
238
|
if (!Array.isArray(rawCalls)) {
|
|
223
239
|
return [];
|
|
@@ -345,7 +361,80 @@ function shapeAgentNodeSpan(span: MutableSpan): void {
|
|
|
345
361
|
ROOT_OBSERVATION_TYPE;
|
|
346
362
|
}
|
|
347
363
|
|
|
348
|
-
function
|
|
364
|
+
function shapeGraphSpan(span: MutableSpan): void {
|
|
365
|
+
if (span.name === LANGGRAPH_RUN_NAME) {
|
|
366
|
+
span.name = STANDARD_GRAPH_RUN_NAME;
|
|
367
|
+
}
|
|
368
|
+
span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] =
|
|
369
|
+
ROOT_OBSERVATION_TYPE;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function isAgentModelCallSpan(span: MutableSpan): boolean {
|
|
373
|
+
if (
|
|
374
|
+
span.name !== RUNNABLE_SEQUENCE_NAME &&
|
|
375
|
+
span.name !== AGENT_MODEL_CALL_RUN_NAME
|
|
376
|
+
) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
const node = span.attributes[OBSERVATION_METADATA_LANGGRAPH_NODE];
|
|
380
|
+
return (
|
|
381
|
+
typeof node === 'string' && node.startsWith(LANGGRAPH_AGENT_NODE_PREFIX)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function shapeAgentModelCallSpan(span: MutableSpan): void {
|
|
386
|
+
span.name = AGENT_MODEL_CALL_RUN_NAME;
|
|
387
|
+
span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] =
|
|
388
|
+
CHAIN_OBSERVATION_TYPE;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function shapeActivityPhaseSpan(span: MutableSpan): void {
|
|
392
|
+
span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] =
|
|
393
|
+
CHAIN_OBSERVATION_TYPE;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function shapeGenerationSpan(span: MutableSpan): void {
|
|
397
|
+
if (
|
|
398
|
+
hasTraceTag(span, ACTIVITY_LABEL_TRACE_TAG) &&
|
|
399
|
+
span.attributes[OBSERVATION_METADATA_OPERATION] === ACTIVITY_LABEL_RUN_NAME
|
|
400
|
+
) {
|
|
401
|
+
span.name = ACTIVITY_LABEL_RUN_NAME;
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (
|
|
405
|
+
hasTraceTag(span, REASONING_LABEL_TRACE_TAG) &&
|
|
406
|
+
span.attributes[OBSERVATION_METADATA_OPERATION] === REASONING_LABEL_RUN_NAME
|
|
407
|
+
) {
|
|
408
|
+
span.name = REASONING_LABEL_RUN_NAME;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (
|
|
412
|
+
hasTraceTag(span, ACTIVITY_PHASE_TRACE_TAG) &&
|
|
413
|
+
span.attributes[OBSERVATION_METADATA_OPERATION] ===
|
|
414
|
+
ACTIVITY_PHASE_LABEL_RUN_NAME
|
|
415
|
+
) {
|
|
416
|
+
span.name = ACTIVITY_PHASE_LABEL_RUN_NAME;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
span.name = GENERATION_SPAN_NAME;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function isGraphSpan(span: MutableSpan): boolean {
|
|
423
|
+
if (
|
|
424
|
+
span.name !== LANGGRAPH_RUN_NAME &&
|
|
425
|
+
span.name !== STANDARD_GRAPH_RUN_NAME &&
|
|
426
|
+
span.name !== MULTI_AGENT_GRAPH_RUN_NAME
|
|
427
|
+
) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
return (
|
|
431
|
+
isRootSpan(span) ||
|
|
432
|
+
typeof span.attributes[OBSERVATION_METADATA_LANGGRAPH_NODE] === 'string' ||
|
|
433
|
+
hasTraceTag(span, AGENT_TRACE_TAG)
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function shapeConversationPayload(span: MutableSpan): void {
|
|
349
438
|
const inputKey = LangfuseOtelSpanAttributes.OBSERVATION_INPUT;
|
|
350
439
|
const outputKey = LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT;
|
|
351
440
|
const question = findLastMessageText(
|
|
@@ -361,8 +450,7 @@ function shapeRootSpan(span: MutableSpan): void {
|
|
|
361
450
|
* observation carrying its own prompt: reducing its observation input
|
|
362
451
|
* would discard the SystemMessage from the one place it is traced.
|
|
363
452
|
* Chain/agent roots keep the full reduction; their child generations
|
|
364
|
-
* still record the complete prompt.
|
|
365
|
-
* either way, so the trace list keeps showing question and answer. */
|
|
453
|
+
* still record the complete prompt. */
|
|
366
454
|
if (!isGenerationSpan(span)) {
|
|
367
455
|
if (question != null) {
|
|
368
456
|
span.attributes[inputKey] = question;
|
|
@@ -371,14 +459,6 @@ function shapeRootSpan(span: MutableSpan): void {
|
|
|
371
459
|
span.attributes[outputKey] = answer;
|
|
372
460
|
}
|
|
373
461
|
}
|
|
374
|
-
const traceInput = question ?? span.attributes[inputKey];
|
|
375
|
-
const traceOutput = answer ?? span.attributes[outputKey];
|
|
376
|
-
if (traceInput != null) {
|
|
377
|
-
span.attributes[LangfuseOtelSpanAttributes.TRACE_INPUT] = traceInput;
|
|
378
|
-
}
|
|
379
|
-
if (traceOutput != null) {
|
|
380
|
-
span.attributes[LangfuseOtelSpanAttributes.TRACE_OUTPUT] = traceOutput;
|
|
381
|
-
}
|
|
382
462
|
}
|
|
383
463
|
|
|
384
464
|
function isGenerationSpan(span: MutableSpan): boolean {
|
|
@@ -477,7 +557,7 @@ function shapeRootObservationType(span: MutableSpan): void {
|
|
|
477
557
|
}
|
|
478
558
|
if (
|
|
479
559
|
hasTraceTag(span, TITLE_TRACE_TAG) ||
|
|
480
|
-
(span.name ===
|
|
560
|
+
(span.name === ACTIVITY_PHASE_RUN_NAME &&
|
|
481
561
|
hasTraceTag(span, ACTIVITY_PHASE_TRACE_TAG))
|
|
482
562
|
) {
|
|
483
563
|
span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] =
|
|
@@ -496,32 +576,46 @@ function shapeRootObservationType(span: MutableSpan): void {
|
|
|
496
576
|
* (`provider__model`) — strip it so switching models doesn't break
|
|
497
577
|
* name-based logic (item 1). The outer workflow node is named with the
|
|
498
578
|
* bare agent id; ephemeral ids reduce to their stable sender name.
|
|
579
|
+
* - Graph and prompt-to-model framework names become stable SDK operations:
|
|
580
|
+
* `AgentGraph` / `MultiAgentGraph` and `AgentModelCall`.
|
|
499
581
|
* - LLM generation spans keep the provider client class name (`ChatOpenAI`,
|
|
500
|
-
* `AzureChatOpenAI`, …); rename
|
|
501
|
-
*
|
|
502
|
-
* generation's model attribute
|
|
582
|
+
* `AzureChatOpenAI`, …); rename ordinary calls to a provider-agnostic `llm`
|
|
583
|
+
* and label calls by their activity/reasoning role. The model stays on the
|
|
584
|
+
* generation's model attribute.
|
|
503
585
|
* - Agent nodes become `agent` observations, while tool-dispatch nodes become
|
|
504
586
|
* stable `chain` observations whose input is scoped to the pending calls.
|
|
505
587
|
* Individual child calls remain `tool` observations (items 3 & 4).
|
|
506
588
|
* - Agent trace roots become `agent` observations, while title and activity
|
|
507
|
-
* summary
|
|
508
|
-
* reduced to the user question and assistant response when
|
|
509
|
-
* available (item 2).
|
|
589
|
+
* summary operations become `chain` observations. Root-observation
|
|
590
|
+
* input/output are reduced to the user question and assistant response when
|
|
591
|
+
* chat messages are available (item 2).
|
|
510
592
|
*/
|
|
511
593
|
export function shapeLangfuseSpan(span: ReadableSpan): void {
|
|
512
594
|
const mutable = span as MutableSpan;
|
|
595
|
+
delete mutable.attributes[DEPRECATED_TRACE_INPUT_ATTRIBUTE];
|
|
596
|
+
delete mutable.attributes[DEPRECATED_TRACE_OUTPUT_ATTRIBUTE];
|
|
597
|
+
const isGraphObservation = isGraphSpan(mutable);
|
|
513
598
|
if (mutable.name.startsWith(LANGGRAPH_AGENT_NODE_PREFIX)) {
|
|
514
599
|
shapeAgentNodeSpan(mutable);
|
|
515
600
|
} else if (mutable.name.startsWith(LANGGRAPH_TOOL_NODE_PREFIX)) {
|
|
516
601
|
shapeToolNodeSpan(mutable);
|
|
602
|
+
} else if (isGraphObservation) {
|
|
603
|
+
shapeGraphSpan(mutable);
|
|
604
|
+
} else if (isAgentModelCallSpan(mutable)) {
|
|
605
|
+
shapeAgentModelCallSpan(mutable);
|
|
606
|
+
} else if (mutable.name === ACTIVITY_PHASE_RUN_NAME) {
|
|
607
|
+
shapeActivityPhaseSpan(mutable);
|
|
517
608
|
} else if (isGenerationSpan(mutable)) {
|
|
518
|
-
mutable
|
|
609
|
+
shapeGenerationSpan(mutable);
|
|
519
610
|
} else {
|
|
520
611
|
shapeEphemeralAgentNodeSpan(mutable);
|
|
521
612
|
}
|
|
522
613
|
if (!isRootSpan(span)) {
|
|
614
|
+
if (isGraphObservation) {
|
|
615
|
+
shapeConversationPayload(mutable);
|
|
616
|
+
}
|
|
523
617
|
return;
|
|
524
618
|
}
|
|
525
619
|
shapeRootObservationType(mutable);
|
|
526
|
-
|
|
620
|
+
shapeConversationPayload(mutable);
|
|
527
621
|
}
|
package/src/llm/openai/index.ts
CHANGED
|
@@ -58,14 +58,14 @@ import {
|
|
|
58
58
|
STREAMED_TOOL_CALL_ADAPTER_METADATA_KEY,
|
|
59
59
|
OPENAI_CHAT_SEQUENTIAL_STREAMED_TOOL_CALL_ADAPTER,
|
|
60
60
|
} from '@/tools/streamedToolCallSeals';
|
|
61
|
-
import { isReasoningModel, _convertMessagesToOpenAIParams } from './utils';
|
|
62
|
-
import { INTENT_ARG, isIntentLabelProperty } from '@/tools/intentArg';
|
|
63
|
-
import { smoothStream, resolveStreamDelay } from '@/llm/stream/smoother';
|
|
64
61
|
import {
|
|
65
62
|
hasReasoningKwargs,
|
|
66
63
|
hasToolCallChunks,
|
|
67
64
|
getReasoningKwargsText,
|
|
68
65
|
} from '@/llm/stream/chunkAdapters';
|
|
66
|
+
import { isReasoningModel, _convertMessagesToOpenAIParams } from './utils';
|
|
67
|
+
import { smoothStream, resolveStreamDelay } from '@/llm/stream/smoother';
|
|
68
|
+
import { INTENT_ARG, isIntentLabelProperty } from '@/tools/intentArg';
|
|
69
69
|
import { dropRepeatedScalarMetadata } from './streamMetadata';
|
|
70
70
|
|
|
71
71
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
@@ -919,6 +919,33 @@ function attachResponsesReplayPosition(
|
|
|
919
919
|
chunk.message.lc_kwargs.additional_kwargs = additionalKwargs;
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
+
function getResponsesStreamError(
|
|
923
|
+
event: OpenAIClient.Responses.ResponseStreamEvent
|
|
924
|
+
): Error | undefined {
|
|
925
|
+
if (event.type === 'error') {
|
|
926
|
+
return new OpenAIClient.APIError(
|
|
927
|
+
undefined,
|
|
928
|
+
event,
|
|
929
|
+
event.message,
|
|
930
|
+
undefined
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
if (event.type !== 'response.failed') {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (event.response.error != null) {
|
|
937
|
+
return new OpenAIClient.APIError(
|
|
938
|
+
undefined,
|
|
939
|
+
event.response.error,
|
|
940
|
+
event.response.error.message,
|
|
941
|
+
undefined
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
return new OpenAIClient.OpenAIError(
|
|
945
|
+
`Response ${event.response.id} failed without error details.`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
922
949
|
async function* convertLibreChatResponsesStream(
|
|
923
950
|
stream: AsyncIterable<OpenAIClient.Responses.ResponseStreamEvent>,
|
|
924
951
|
options: ResponsesStreamChunkOptions,
|
|
@@ -929,6 +956,10 @@ async function* convertLibreChatResponsesStream(
|
|
|
929
956
|
try {
|
|
930
957
|
for await (const event of stream) {
|
|
931
958
|
options.signal?.throwIfAborted();
|
|
959
|
+
const streamError = getResponsesStreamError(event);
|
|
960
|
+
if (streamError != null) {
|
|
961
|
+
throw streamError;
|
|
962
|
+
}
|
|
932
963
|
const convertedChunk =
|
|
933
964
|
convertResponsesDeltaToChatGenerationChunk(event) ??
|
|
934
965
|
convertDroppedResponsesReplayOutput(event);
|