@librechat/agents 3.2.64 → 3.2.66
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/graphs/Graph.cjs +18 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +15 -3
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +1 -2
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +51 -24
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +73 -9
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +49 -9
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +3 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs +337 -0
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -0
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +8 -0
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +9 -8
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +9 -7
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-scraper.cjs +165 -0
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/crw-search.cjs +105 -0
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +4 -2
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +15 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +18 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +15 -3
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +1 -2
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +51 -24
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +73 -9
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +49 -9
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +3 -2
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/stream_events.mjs +337 -0
- package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -0
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +8 -0
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +9 -8
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +9 -7
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/search/crw-scraper.mjs +163 -0
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/crw-search.mjs +103 -0
- package/dist/esm/tools/search/crw-search.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +4 -2
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +15 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +2 -0
- package/dist/types/langfuseTraceShaping.d.ts +12 -6
- package/dist/types/llm/anthropic/index.d.ts +2 -0
- package/dist/types/llm/anthropic/types.d.ts +2 -0
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -2
- package/dist/types/llm/anthropic/utils/stream_events.d.ts +25 -0
- package/dist/types/tools/BashExecutor.d.ts +7 -6
- package/dist/types/tools/CodeExecutor.d.ts +7 -5
- package/dist/types/tools/search/crw-scraper.d.ts +41 -0
- package/dist/types/tools/search/crw-search.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +88 -3
- package/dist/types/types/graph.d.ts +10 -3
- package/package.json +1 -1
- package/src/graphs/Graph.ts +36 -3
- package/src/instrumentation.ts +20 -0
- package/src/langfuseToolOutputTracing.ts +2 -4
- package/src/langfuseTraceShaping.ts +73 -20
- package/src/llm/anthropic/index.ts +134 -10
- package/src/llm/anthropic/inherited-content-utils.spec.ts +10 -5
- package/src/llm/anthropic/inherited-stream-events.spec.ts +513 -9
- package/src/llm/anthropic/llm.spec.ts +100 -16
- package/src/llm/anthropic/types.ts +3 -0
- package/src/llm/anthropic/utils/cross-provider-server-tools.test.ts +110 -0
- package/src/llm/anthropic/utils/message_inputs.ts +75 -3
- package/src/llm/anthropic/utils/message_outputs.ts +10 -2
- package/src/llm/anthropic/utils/stream_events.ts +471 -0
- package/src/llm/bedrock/utils/cross-provider-server-tools.test.ts +122 -0
- package/src/llm/bedrock/utils/message_inputs.ts +13 -0
- package/src/specs/langfuse-instrumentation.test.ts +64 -0
- package/src/specs/langfuse-routing.integration.test.ts +49 -4
- package/src/specs/langfuse-tool-output-tracing.test.ts +5 -4
- package/src/specs/langfuse-trace-shaping.test.ts +80 -9
- package/src/specs/subagent.test.ts +180 -0
- package/src/tools/BashExecutor.ts +9 -8
- package/src/tools/CodeExecutor.ts +9 -7
- package/src/tools/__tests__/BashExecutor.test.ts +16 -5
- package/src/tools/__tests__/CodeExecutor.stateful.test.ts +17 -6
- package/src/tools/search/crw-scraper.ts +244 -0
- package/src/tools/search/crw-search.ts +167 -0
- package/src/tools/search/crw.test.ts +836 -0
- package/src/tools/search/search.ts +7 -1
- package/src/tools/search/tool.ts +23 -3
- package/src/tools/search/types.ts +103 -3
- package/src/types/graph.ts +10 -3
|
@@ -2,8 +2,8 @@ import type { RunnableConfig } from '@langchain/core/runnables';
|
|
|
2
2
|
import type { Logger as WinstonLogger } from 'winston';
|
|
3
3
|
import type { BaseReranker } from './rerankers';
|
|
4
4
|
import { DATE_RANGE } from './schema';
|
|
5
|
-
export type SearchProvider = 'serper' | 'searxng' | 'tavily' | 'keenable';
|
|
6
|
-
export type ScraperProvider = 'firecrawl' | 'serper' | 'tavily';
|
|
5
|
+
export type SearchProvider = 'serper' | 'searxng' | 'tavily' | 'keenable' | 'crw';
|
|
6
|
+
export type ScraperProvider = 'firecrawl' | 'serper' | 'tavily' | 'crw';
|
|
7
7
|
export type RerankerType = 'infinity' | 'jina' | 'cohere' | 'none';
|
|
8
8
|
export interface Highlight {
|
|
9
9
|
score: number;
|
|
@@ -91,6 +91,48 @@ export interface TavilySearchPayload {
|
|
|
91
91
|
include_favicon?: boolean;
|
|
92
92
|
chunks_per_source?: number;
|
|
93
93
|
}
|
|
94
|
+
export interface CrwSearchOptions {
|
|
95
|
+
/** Max results to request (maps to `limit`; clamped 1..20). */
|
|
96
|
+
maxResults?: number;
|
|
97
|
+
/** Add 'images' to the sources array. */
|
|
98
|
+
includeImages?: boolean;
|
|
99
|
+
timeout?: number;
|
|
100
|
+
}
|
|
101
|
+
export type CrwSearchSource = 'web' | 'images' | 'news';
|
|
102
|
+
export interface CrwSearchPayload {
|
|
103
|
+
query: string;
|
|
104
|
+
limit: number;
|
|
105
|
+
sources?: CrwSearchSource[];
|
|
106
|
+
tbs?: string;
|
|
107
|
+
}
|
|
108
|
+
export interface CrwSearchResult {
|
|
109
|
+
title?: string;
|
|
110
|
+
url?: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
snippet?: string;
|
|
113
|
+
position?: number;
|
|
114
|
+
category?: string;
|
|
115
|
+
}
|
|
116
|
+
export interface CrwImageSearchResult extends CrwSearchResult {
|
|
117
|
+
imageUrl?: string;
|
|
118
|
+
imageFormat?: string;
|
|
119
|
+
}
|
|
120
|
+
export interface CrwNewsSearchResult extends CrwSearchResult {
|
|
121
|
+
publishedDate?: string;
|
|
122
|
+
}
|
|
123
|
+
export interface CrwSearchGroups {
|
|
124
|
+
web?: CrwSearchResult[];
|
|
125
|
+
images?: CrwImageSearchResult[];
|
|
126
|
+
news?: CrwNewsSearchResult[];
|
|
127
|
+
}
|
|
128
|
+
export interface CrwSearchResponse {
|
|
129
|
+
success: boolean;
|
|
130
|
+
data?: (CrwSearchGroups & {
|
|
131
|
+
results?: CrwSearchGroups;
|
|
132
|
+
}) | CrwSearchResult[];
|
|
133
|
+
error?: string;
|
|
134
|
+
error_code?: string;
|
|
135
|
+
}
|
|
94
136
|
export interface SearchConfig {
|
|
95
137
|
searchProvider?: SearchProvider;
|
|
96
138
|
serperApiKey?: string;
|
|
@@ -103,6 +145,9 @@ export interface SearchConfig {
|
|
|
103
145
|
keenableApiKey?: string;
|
|
104
146
|
keenableApiUrl?: string;
|
|
105
147
|
keenableSearchOptions?: KeenableSearchOptions;
|
|
148
|
+
crwApiKey?: string;
|
|
149
|
+
crwApiUrl?: string;
|
|
150
|
+
crwSearchOptions?: CrwSearchOptions;
|
|
106
151
|
}
|
|
107
152
|
export interface KeenableSearchOptions {
|
|
108
153
|
maxResults?: number;
|
|
@@ -224,6 +269,7 @@ export type SafeSearchLevel = 0 | 1 | 2;
|
|
|
224
269
|
export type Logger = WinstonLogger;
|
|
225
270
|
export interface SearchToolConfig extends SearchConfig, ProcessSourcesConfig, FirecrawlConfig {
|
|
226
271
|
tavilyScraperOptions?: TavilyScraperConfig;
|
|
272
|
+
crwScraperOptions?: CrwScraperConfig;
|
|
227
273
|
/** Max chars of highlight content this tool feeds the MODEL per search (the
|
|
228
274
|
* dominant, otherwise-unbounded part of the output). Distinct from
|
|
229
275
|
* `maxContentLength`, which caps scraped/reranked content per source — full
|
|
@@ -255,7 +301,7 @@ export type UsedReferences = {
|
|
|
255
301
|
originalIndex: number;
|
|
256
302
|
reference: MediaReference;
|
|
257
303
|
}[];
|
|
258
|
-
export type AnyScraperResponse = FirecrawlScrapeResponse | SerperScrapeResponse | TavilyScrapeResponse;
|
|
304
|
+
export type AnyScraperResponse = FirecrawlScrapeResponse | SerperScrapeResponse | TavilyScrapeResponse | CrwScrapeResponse;
|
|
259
305
|
/** Base Scraper Interface */
|
|
260
306
|
export interface BaseScraper {
|
|
261
307
|
scrapeUrl(url: string, options?: unknown): Promise<[string, AnyScraperResponse]>;
|
|
@@ -265,6 +311,24 @@ export interface BaseScraper {
|
|
|
265
311
|
}
|
|
266
312
|
/** Firecrawl */
|
|
267
313
|
export type FirecrawlScrapeOptions = Omit<FirecrawlScraperConfig, 'apiKey' | 'apiUrl' | 'version' | 'logger'>;
|
|
314
|
+
export interface CrwScraperConfig {
|
|
315
|
+
apiKey?: string;
|
|
316
|
+
apiUrl?: string;
|
|
317
|
+
formats?: string[];
|
|
318
|
+
timeout?: number;
|
|
319
|
+
logger?: Logger;
|
|
320
|
+
onlyMainContent?: boolean;
|
|
321
|
+
includeTags?: string[];
|
|
322
|
+
excludeTags?: string[];
|
|
323
|
+
waitFor?: number;
|
|
324
|
+
headers?: Record<string, string>;
|
|
325
|
+
renderJs?: boolean | null;
|
|
326
|
+
cssSelector?: string;
|
|
327
|
+
xpath?: string;
|
|
328
|
+
proxy?: string;
|
|
329
|
+
stealth?: boolean;
|
|
330
|
+
}
|
|
331
|
+
export type CrwScrapeOptions = Omit<CrwScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
|
|
268
332
|
export type SerperScrapeOptions = Omit<SerperScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
|
|
269
333
|
export type TavilyScrapeOptions = Omit<TavilyScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
|
|
270
334
|
export interface TavilyExtractPayload {
|
|
@@ -341,6 +405,27 @@ export interface FirecrawlScrapeResponse {
|
|
|
341
405
|
};
|
|
342
406
|
error?: string;
|
|
343
407
|
}
|
|
408
|
+
export interface CrwScrapeData {
|
|
409
|
+
markdown?: string;
|
|
410
|
+
html?: string;
|
|
411
|
+
rawHtml?: string;
|
|
412
|
+
plainText?: string;
|
|
413
|
+
screenshot?: string;
|
|
414
|
+
links?: string[];
|
|
415
|
+
metadata?: ScrapeMetadata;
|
|
416
|
+
}
|
|
417
|
+
export interface CrwScrapeResponse {
|
|
418
|
+
success: boolean;
|
|
419
|
+
data?: CrwScrapeData;
|
|
420
|
+
error?: string;
|
|
421
|
+
error_code?: string;
|
|
422
|
+
}
|
|
423
|
+
export interface CrwRawScrapeResponse extends CrwScrapeData {
|
|
424
|
+
success?: boolean;
|
|
425
|
+
data?: CrwScrapeData;
|
|
426
|
+
error?: string;
|
|
427
|
+
error_code?: string;
|
|
428
|
+
}
|
|
344
429
|
export interface SerperScrapeResponse {
|
|
345
430
|
success: boolean;
|
|
346
431
|
data?: {
|
|
@@ -406,9 +406,9 @@ export type LangfuseToolOutputTracingConfig = {
|
|
|
406
406
|
};
|
|
407
407
|
export type LangfuseToolNodeTracingConfig = {
|
|
408
408
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
409
|
+
* Opts into the internal ToolNode batch observation. Graph tool-dispatch
|
|
410
|
+
* and individual tool observations are exported without this wrapper, so
|
|
411
|
+
* the default is false to avoid a redundant hierarchy level.
|
|
412
412
|
*/
|
|
413
413
|
enabled?: boolean;
|
|
414
414
|
};
|
|
@@ -417,6 +417,13 @@ export interface LangfuseConfig {
|
|
|
417
417
|
publicKey?: string;
|
|
418
418
|
secretKey?: string;
|
|
419
419
|
baseUrl?: string;
|
|
420
|
+
/**
|
|
421
|
+
* Environment identifier attached to exported traces (Langfuse
|
|
422
|
+
* `environment`). When unset, falls back to `LANGFUSE_TRACING_ENVIRONMENT`
|
|
423
|
+
* then `NODE_ENV`, so production traces are not collapsed under the
|
|
424
|
+
* `default` environment.
|
|
425
|
+
*/
|
|
426
|
+
environment?: string;
|
|
420
427
|
metadata?: Record<string, string | number | boolean | null | undefined>;
|
|
421
428
|
/**
|
|
422
429
|
* Internal OTLP span attributes to attach to Langfuse observations before
|
package/package.json
CHANGED
package/src/graphs/Graph.ts
CHANGED
|
@@ -96,6 +96,18 @@ const { AGENT, TOOLS, SUMMARIZE } = GraphNodeKeys;
|
|
|
96
96
|
/** Minimum relative variance before calibrated toolSchemaTokens overrides current value. */
|
|
97
97
|
const CALIBRATION_VARIANCE_THRESHOLD = 0.15;
|
|
98
98
|
|
|
99
|
+
function createToolHandlerRegistry(
|
|
100
|
+
source: HandlerRegistry | undefined
|
|
101
|
+
): HandlerRegistry | undefined {
|
|
102
|
+
const toolHandler = source?.getHandler(GraphEvents.ON_TOOL_EXECUTE);
|
|
103
|
+
if (toolHandler == null) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const registry = new HandlerRegistry();
|
|
107
|
+
registry.register(GraphEvents.ON_TOOL_EXECUTE, toolHandler);
|
|
108
|
+
return registry;
|
|
109
|
+
}
|
|
110
|
+
|
|
99
111
|
/**
|
|
100
112
|
* Start index of the span post-prune formatters can mutate in place: the
|
|
101
113
|
* trailing tool batch plus its owning AI message (artifact formatting touches
|
|
@@ -576,6 +588,8 @@ export abstract class Graph<
|
|
|
576
588
|
/** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
|
|
577
589
|
invokedToolIds?: Set<string>;
|
|
578
590
|
handlerRegistry: HandlerRegistry | undefined;
|
|
591
|
+
/** Host registry retained only for forwarding tools from nested child graphs. */
|
|
592
|
+
protected parentToolHandlerRegistry: HandlerRegistry | undefined;
|
|
579
593
|
/**
|
|
580
594
|
* True when event-driven tool execution can be routed through callbacks even
|
|
581
595
|
* though this graph intentionally does not own the full handler registry.
|
|
@@ -661,6 +675,7 @@ export abstract class Graph<
|
|
|
661
675
|
this.prelimMessageIdsByStepKey = new Map();
|
|
662
676
|
this.invokedToolIds = undefined;
|
|
663
677
|
this.handlerRegistry = undefined;
|
|
678
|
+
this.parentToolHandlerRegistry = undefined;
|
|
664
679
|
this.hookRegistry = undefined;
|
|
665
680
|
this.humanInTheLoop = undefined;
|
|
666
681
|
this.toolOutputReferences = undefined;
|
|
@@ -1130,6 +1145,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1130
1145
|
/* Misc.*/
|
|
1131
1146
|
|
|
1132
1147
|
getRunMessages(): BaseMessage[] | undefined {
|
|
1148
|
+
if (this.messages == null) {
|
|
1149
|
+
return this.cachedRunMessages;
|
|
1150
|
+
}
|
|
1133
1151
|
if (this.messages.length === 0 && this.cachedRunMessages != null) {
|
|
1134
1152
|
return this.cachedRunMessages;
|
|
1135
1153
|
}
|
|
@@ -1137,6 +1155,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1137
1155
|
}
|
|
1138
1156
|
|
|
1139
1157
|
getContentParts(): t.MessageContentComplex[] | undefined {
|
|
1158
|
+
// `messages` can be null/undefined on a graph that has been disposed
|
|
1159
|
+
// (clearHeavyState) but is still reachable via a cache (e.g. RedisJobStore's
|
|
1160
|
+
// WeakRef) during a HITL resume/reconnect. Guard instead of dereferencing null.
|
|
1161
|
+
if (this.messages == null) {
|
|
1162
|
+
return undefined;
|
|
1163
|
+
}
|
|
1140
1164
|
return convertMessagesToContent(this.messages.slice(this.startIndex));
|
|
1141
1165
|
}
|
|
1142
1166
|
|
|
@@ -1169,6 +1193,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1169
1193
|
* Get all run steps, optionally filtered by agent ID
|
|
1170
1194
|
*/
|
|
1171
1195
|
getRunSteps(agentId?: string): t.RunStep[] {
|
|
1196
|
+
// `contentData` can be null/undefined on a disposed-but-cached graph during a
|
|
1197
|
+
// HITL resume/reconnect; without this guard `[...this.contentData]` throws
|
|
1198
|
+
// "this.contentData is not iterable".
|
|
1199
|
+
if (this.contentData == null) {
|
|
1200
|
+
return [];
|
|
1201
|
+
}
|
|
1172
1202
|
if (agentId == null || agentId === '') {
|
|
1173
1203
|
return [...this.contentData];
|
|
1174
1204
|
}
|
|
@@ -2361,7 +2391,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2361
2391
|
);
|
|
2362
2392
|
if (resolvedConfigs.length > 0) {
|
|
2363
2393
|
const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
|
|
2364
|
-
this.handlerRegistry;
|
|
2394
|
+
this.handlerRegistry ?? this.parentToolHandlerRegistry;
|
|
2365
2395
|
const executor = new SubagentExecutor({
|
|
2366
2396
|
configs: new Map(resolvedConfigs.map((c) => [c.type, c])),
|
|
2367
2397
|
parentSignal: this.signal,
|
|
@@ -2378,6 +2408,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2378
2408
|
maxDepth: effectiveSubagentDepth,
|
|
2379
2409
|
createChildGraph: (input): StandardGraph => {
|
|
2380
2410
|
const childGraph = new StandardGraph(input);
|
|
2411
|
+
const toolHandlerRegistry = createToolHandlerRegistry(
|
|
2412
|
+
getParentHandlerRegistry()
|
|
2413
|
+
);
|
|
2381
2414
|
childGraph.hookRegistry = this.hookRegistry;
|
|
2382
2415
|
/**
|
|
2383
2416
|
* Do not propagate `humanInTheLoop` into the child graph yet:
|
|
@@ -2400,9 +2433,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2400
2433
|
// have the executable graphTool, the guard correctly applies.
|
|
2401
2434
|
childGraph.interruptingToolNames = this.interruptingToolNames;
|
|
2402
2435
|
childGraph.toolExecution = this.toolExecution;
|
|
2436
|
+
childGraph.parentToolHandlerRegistry = toolHandlerRegistry;
|
|
2403
2437
|
childGraph.eventToolExecutionAvailable =
|
|
2404
|
-
|
|
2405
|
-
null;
|
|
2438
|
+
toolHandlerRegistry != null;
|
|
2406
2439
|
return childGraph;
|
|
2407
2440
|
},
|
|
2408
2441
|
});
|
package/src/instrumentation.ts
CHANGED
|
@@ -75,17 +75,35 @@ export function ensureOpenTelemetryContextManager(): void {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
function resolveLangfuseEnvironment(
|
|
79
|
+
langfuse?: t.LangfuseConfig
|
|
80
|
+
): string | undefined {
|
|
81
|
+
const candidates = [
|
|
82
|
+
langfuse?.environment,
|
|
83
|
+
process.env.LANGFUSE_TRACING_ENVIRONMENT,
|
|
84
|
+
process.env.NODE_ENV,
|
|
85
|
+
];
|
|
86
|
+
for (const candidate of candidates) {
|
|
87
|
+
if (candidate != null && candidate.trim() !== '') {
|
|
88
|
+
return candidate.trim();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
78
94
|
function getLangfuseSpanProcessorParams(
|
|
79
95
|
langfuse?: t.LangfuseConfig
|
|
80
96
|
): LangfuseSpanProcessorParams | undefined {
|
|
81
97
|
if (langfuse?.enabled === false) {
|
|
82
98
|
return undefined;
|
|
83
99
|
}
|
|
100
|
+
const environment = resolveLangfuseEnvironment(langfuse);
|
|
84
101
|
if (hasLangfuseConfigCredentials(langfuse)) {
|
|
85
102
|
return {
|
|
86
103
|
publicKey: langfuse.publicKey,
|
|
87
104
|
secretKey: langfuse.secretKey,
|
|
88
105
|
...(isPresent(langfuse.baseUrl) ? { baseUrl: langfuse.baseUrl } : {}),
|
|
106
|
+
...(isPresent(environment) ? { environment } : {}),
|
|
89
107
|
};
|
|
90
108
|
}
|
|
91
109
|
if (hasLangfuseEnvConfig()) {
|
|
@@ -97,6 +115,7 @@ function getLangfuseSpanProcessorParams(
|
|
|
97
115
|
publicKey: process.env.LANGFUSE_PUBLIC_KEY as string,
|
|
98
116
|
secretKey: process.env.LANGFUSE_SECRET_KEY as string,
|
|
99
117
|
...(isPresent(baseUrl) ? { baseUrl } : {}),
|
|
118
|
+
...(isPresent(environment) ? { environment } : {}),
|
|
100
119
|
};
|
|
101
120
|
}
|
|
102
121
|
if (isPresent(langfuse?.baseUrl) && hasLangfuseEnvCredentials()) {
|
|
@@ -104,6 +123,7 @@ function getLangfuseSpanProcessorParams(
|
|
|
104
123
|
publicKey: process.env.LANGFUSE_PUBLIC_KEY as string,
|
|
105
124
|
secretKey: process.env.LANGFUSE_SECRET_KEY as string,
|
|
106
125
|
baseUrl: langfuse.baseUrl,
|
|
126
|
+
...(isPresent(environment) ? { environment } : {}),
|
|
107
127
|
};
|
|
108
128
|
}
|
|
109
129
|
return undefined;
|
|
@@ -483,10 +483,8 @@ export function shouldTraceToolNodeForLangfuse({
|
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
const explicit = langfuse?.toolNodeTracing?.enabled;
|
|
486
|
-
if (explicit
|
|
487
|
-
return
|
|
488
|
-
explicit && (hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys())
|
|
489
|
-
);
|
|
486
|
+
if (explicit !== true) {
|
|
487
|
+
return false;
|
|
490
488
|
}
|
|
491
489
|
|
|
492
490
|
return hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys();
|
|
@@ -5,8 +5,13 @@ const LANGGRAPH_START_NODE = '__start__';
|
|
|
5
5
|
const ANONYMOUS_LAMBDA_NAME = 'RunnableLambda';
|
|
6
6
|
const LANGGRAPH_AGENT_NODE_PREFIX = 'agent=';
|
|
7
7
|
const LANGGRAPH_TOOL_NODE_PREFIX = 'tools=';
|
|
8
|
-
const TOOL_BATCH_RUN_NAME = 'tool_batch';
|
|
9
8
|
const AGENT_NODE_SPAN_NAME = 'agent';
|
|
9
|
+
const TOOL_DISPATCH_SPAN_NAME = 'tool-dispatch';
|
|
10
|
+
const GENERATION_SPAN_NAME = 'llm';
|
|
11
|
+
const ROOT_OBSERVATION_TYPE = 'agent';
|
|
12
|
+
const CHAIN_OBSERVATION_TYPE = 'chain';
|
|
13
|
+
const AGENT_TRACE_TAG = 'agent';
|
|
14
|
+
const TITLE_TRACE_TAG = 'title';
|
|
10
15
|
|
|
11
16
|
type MutableSpan = ReadableSpan & {
|
|
12
17
|
name: string;
|
|
@@ -207,6 +212,8 @@ function isRootSpan(span: ReadableSpan): boolean {
|
|
|
207
212
|
* LangGraph plumbing observations that add noise without information:
|
|
208
213
|
* the duplicated `__start__` channel-seed nodes and anonymous
|
|
209
214
|
* `RunnableLambda` pass-throughs (Langfuse team feedback items 4 & 5).
|
|
215
|
+
* Internal ToolNode batch spans are disabled at their source so traced child
|
|
216
|
+
* tools retain an exported parent. Explicitly traced ToolNodes are preserved.
|
|
210
217
|
*/
|
|
211
218
|
export function shouldDropLangfuseSpan(spanName: string): boolean {
|
|
212
219
|
return (
|
|
@@ -216,18 +223,26 @@ export function shouldDropLangfuseSpan(spanName: string): boolean {
|
|
|
216
223
|
|
|
217
224
|
function shapeToolNodeSpan(span: MutableSpan): void {
|
|
218
225
|
const inputKey = LangfuseOtelSpanAttributes.OBSERVATION_INPUT;
|
|
226
|
+
span.name = TOOL_DISPATCH_SPAN_NAME;
|
|
227
|
+
span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] =
|
|
228
|
+
CHAIN_OBSERVATION_TYPE;
|
|
219
229
|
const calls = findPendingToolCalls(
|
|
220
230
|
parseAttributeValue(span.attributes[inputKey])
|
|
221
231
|
);
|
|
222
232
|
if (calls.length === 0) {
|
|
223
233
|
return;
|
|
224
234
|
}
|
|
225
|
-
span.name = [...new Set(calls.map((call) => call.name))].join(', ');
|
|
226
235
|
span.attributes[inputKey] = JSON.stringify(
|
|
227
236
|
calls.map(({ name, args }) => ({ name, args }))
|
|
228
237
|
);
|
|
229
238
|
}
|
|
230
239
|
|
|
240
|
+
function shapeAgentNodeSpan(span: MutableSpan): void {
|
|
241
|
+
span.name = AGENT_NODE_SPAN_NAME;
|
|
242
|
+
span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] =
|
|
243
|
+
ROOT_OBSERVATION_TYPE;
|
|
244
|
+
}
|
|
245
|
+
|
|
231
246
|
function shapeRootSpan(span: MutableSpan): void {
|
|
232
247
|
const inputKey = LangfuseOtelSpanAttributes.OBSERVATION_INPUT;
|
|
233
248
|
const outputKey = LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT;
|
|
@@ -241,11 +256,47 @@ function shapeRootSpan(span: MutableSpan): void {
|
|
|
241
256
|
);
|
|
242
257
|
if (question != null) {
|
|
243
258
|
span.attributes[inputKey] = question;
|
|
244
|
-
span.attributes[LangfuseOtelSpanAttributes.TRACE_INPUT] = question;
|
|
245
259
|
}
|
|
246
260
|
if (answer != null) {
|
|
247
261
|
span.attributes[outputKey] = answer;
|
|
248
|
-
|
|
262
|
+
}
|
|
263
|
+
const traceInput = question ?? span.attributes[inputKey];
|
|
264
|
+
const traceOutput = answer ?? span.attributes[outputKey];
|
|
265
|
+
if (traceInput != null) {
|
|
266
|
+
span.attributes[LangfuseOtelSpanAttributes.TRACE_INPUT] = traceInput;
|
|
267
|
+
}
|
|
268
|
+
if (traceOutput != null) {
|
|
269
|
+
span.attributes[LangfuseOtelSpanAttributes.TRACE_OUTPUT] = traceOutput;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function isGenerationSpan(span: MutableSpan): boolean {
|
|
274
|
+
const type = span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE];
|
|
275
|
+
return typeof type === 'string' && type.toLowerCase() === 'generation';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function hasTraceTag(span: MutableSpan, expectedTag: string): boolean {
|
|
279
|
+
const tags = parseAttributeValue(
|
|
280
|
+
span.attributes[LangfuseOtelSpanAttributes.TRACE_TAGS]
|
|
281
|
+
);
|
|
282
|
+
return (
|
|
283
|
+
Array.isArray(tags) &&
|
|
284
|
+
tags.some((tag) => typeof tag === 'string' && tag === expectedTag)
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function shapeRootObservationType(span: MutableSpan): void {
|
|
289
|
+
if (isGenerationSpan(span)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (hasTraceTag(span, AGENT_TRACE_TAG)) {
|
|
293
|
+
span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] =
|
|
294
|
+
ROOT_OBSERVATION_TYPE;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (hasTraceTag(span, TITLE_TRACE_TAG)) {
|
|
298
|
+
span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_TYPE] =
|
|
299
|
+
CHAIN_OBSERVATION_TYPE;
|
|
249
300
|
}
|
|
250
301
|
}
|
|
251
302
|
|
|
@@ -254,27 +305,29 @@ function shapeRootSpan(span: MutableSpan): void {
|
|
|
254
305
|
* - `agent=<id>` / `tools=<id>` node names carry the ephemeral agent id
|
|
255
306
|
* (`provider__model`) — strip it so switching models doesn't break
|
|
256
307
|
* name-based logic (item 1).
|
|
257
|
-
* -
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
308
|
+
* - LLM generation spans keep the provider client class name (`ChatOpenAI`,
|
|
309
|
+
* `AzureChatOpenAI`, …); rename them to a provider-agnostic `llm` so the
|
|
310
|
+
* name reflects the operation, not the model (the model stays on the
|
|
311
|
+
* generation's model attribute).
|
|
312
|
+
* - Agent nodes become `agent` observations, while tool-dispatch nodes become
|
|
313
|
+
* stable `chain` observations whose input is scoped to the pending calls.
|
|
314
|
+
* Individual child calls remain `tool` observations (items 3 & 4).
|
|
315
|
+
* - Agent trace roots become `agent` observations and title trace roots become
|
|
316
|
+
* `chain` observations. Root and trace input/output are reduced to the user
|
|
317
|
+
* question and assistant response when chat messages are available (item 2).
|
|
263
318
|
*/
|
|
264
319
|
export function shapeLangfuseSpan(span: ReadableSpan): void {
|
|
265
320
|
const mutable = span as MutableSpan;
|
|
266
321
|
if (mutable.name.startsWith(LANGGRAPH_AGENT_NODE_PREFIX)) {
|
|
267
|
-
mutable
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
if (
|
|
271
|
-
mutable.name.startsWith(LANGGRAPH_TOOL_NODE_PREFIX) ||
|
|
272
|
-
mutable.name === TOOL_BATCH_RUN_NAME
|
|
273
|
-
) {
|
|
322
|
+
shapeAgentNodeSpan(mutable);
|
|
323
|
+
} else if (mutable.name.startsWith(LANGGRAPH_TOOL_NODE_PREFIX)) {
|
|
274
324
|
shapeToolNodeSpan(mutable);
|
|
275
|
-
|
|
325
|
+
} else if (isGenerationSpan(mutable)) {
|
|
326
|
+
mutable.name = GENERATION_SPAN_NAME;
|
|
276
327
|
}
|
|
277
|
-
if (isRootSpan(span)) {
|
|
278
|
-
|
|
328
|
+
if (!isRootSpan(span)) {
|
|
329
|
+
return;
|
|
279
330
|
}
|
|
331
|
+
shapeRootObservationType(mutable);
|
|
332
|
+
shapeRootSpan(mutable);
|
|
280
333
|
}
|