@librechat/agents 3.1.97 → 3.1.99
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 +6 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +16 -5
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +10 -0
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +125 -0
- package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -0
- package/dist/cjs/messages/cache.cjs +17 -9
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +45 -8
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +6 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +6 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +16 -5
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +10 -0
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +122 -0
- package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -0
- package/dist/esm/messages/cache.mjs +17 -9
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +45 -8
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +6 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/types/llm/bedrock/index.d.ts +16 -0
- package/dist/types/llm/bedrock/toolCache.d.ts +4 -0
- package/dist/types/messages/cache.d.ts +2 -2
- package/dist/types/types/llm.d.ts +2 -2
- package/package.json +1 -1
- package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +332 -0
- package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +504 -0
- package/src/graphs/Graph.ts +14 -0
- package/src/langfuseToolOutputTracing.ts +26 -7
- package/src/llm/bedrock/index.ts +32 -1
- package/src/llm/bedrock/llm.spec.ts +154 -1
- package/src/llm/bedrock/toolCache.test.ts +131 -0
- package/src/llm/bedrock/toolCache.ts +191 -0
- package/src/messages/cache.test.ts +97 -38
- package/src/messages/cache.ts +18 -10
- package/src/messages/prune.ts +55 -17
- package/src/specs/langfuse-tool-output-tracing.test.ts +28 -0
- package/src/specs/prune.test.ts +193 -0
- package/src/tools/ToolNode.ts +7 -1
- package/src/tools/__tests__/ToolNode.langfuse.test.ts +6 -0
- package/src/types/llm.ts +2 -2
package/src/specs/prune.test.ts
CHANGED
|
@@ -2382,3 +2382,196 @@ describe('thinking enabled — tail tool_use without a thinking block (issue #11
|
|
|
2382
2382
|
expect(result.thinkingStartIndex).toBeGreaterThanOrEqual(0);
|
|
2383
2383
|
});
|
|
2384
2384
|
});
|
|
2385
|
+
|
|
2386
|
+
describe('thinking enabled — non-Anthropic reasoning_content blocks (issue #191)', () => {
|
|
2387
|
+
it('locates a trailing reasoning_content block even when reasoningType defaults to THINKING (DeepSeek/Qwen)', () => {
|
|
2388
|
+
// DeepSeek-R1 and DashScope/Qwen-thinking route through the non-Bedrock
|
|
2389
|
+
// branch, so the caller passes reasoningType: THINKING — but their blocks
|
|
2390
|
+
// are tagged `reasoning_content` and are not normalized upstream. With a
|
|
2391
|
+
// system prompt at index 0 and an all-AI/tool tail, the consume loop never
|
|
2392
|
+
// pops a human to clear thinkingEndIndex (the issue #116 escape hatch), so
|
|
2393
|
+
// searching only for `thinking` missed the present block and threw a fatal
|
|
2394
|
+
// that permanently bricked the thread. The pruner must find the block by
|
|
2395
|
+
// its actual shape instead.
|
|
2396
|
+
const tokenCounter = createTestTokenCounter();
|
|
2397
|
+
const messages: BaseMessage[] = [
|
|
2398
|
+
new SystemMessage('you are a helpful assistant'),
|
|
2399
|
+
new AIMessage({
|
|
2400
|
+
content: [
|
|
2401
|
+
{
|
|
2402
|
+
type: ContentTypes.REASONING_CONTENT,
|
|
2403
|
+
reasoningText: {
|
|
2404
|
+
text: 'I will fetch the doc',
|
|
2405
|
+
signature: 'sig-new',
|
|
2406
|
+
},
|
|
2407
|
+
},
|
|
2408
|
+
{
|
|
2409
|
+
type: 'tool_use',
|
|
2410
|
+
id: 'tc_get_doc',
|
|
2411
|
+
name: 'get_doc_content',
|
|
2412
|
+
input: { docId: 'abc' },
|
|
2413
|
+
},
|
|
2414
|
+
],
|
|
2415
|
+
tool_calls: [
|
|
2416
|
+
{
|
|
2417
|
+
id: 'tc_get_doc',
|
|
2418
|
+
name: 'get_doc_content',
|
|
2419
|
+
args: { docId: 'abc' },
|
|
2420
|
+
type: 'tool_call',
|
|
2421
|
+
},
|
|
2422
|
+
],
|
|
2423
|
+
}),
|
|
2424
|
+
new ToolMessage({
|
|
2425
|
+
content: 'c'.repeat(6000),
|
|
2426
|
+
tool_call_id: 'tc_get_doc',
|
|
2427
|
+
name: 'get_doc_content',
|
|
2428
|
+
}),
|
|
2429
|
+
];
|
|
2430
|
+
|
|
2431
|
+
const indexTokenCountMap: Record<string, number | undefined> = {};
|
|
2432
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2433
|
+
indexTokenCountMap[i] = tokenCounter(messages[i]);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
let result: ReturnType<typeof realGetMessagesWithinTokenLimit> | undefined;
|
|
2437
|
+
expect(() => {
|
|
2438
|
+
result = realGetMessagesWithinTokenLimit({
|
|
2439
|
+
messages,
|
|
2440
|
+
maxContextTokens: 200,
|
|
2441
|
+
indexTokenCountMap,
|
|
2442
|
+
thinkingEnabled: true,
|
|
2443
|
+
tokenCounter,
|
|
2444
|
+
reasoningType: ContentTypes.THINKING,
|
|
2445
|
+
});
|
|
2446
|
+
}).not.toThrow();
|
|
2447
|
+
|
|
2448
|
+
// thinkingStartIndex is only set when the reasoning block is actually
|
|
2449
|
+
// located — isolating the find fix (B) from the graceful-degradation
|
|
2450
|
+
// safety net (C), which would swallow the throw without finding anything.
|
|
2451
|
+
expect(result!.thinkingStartIndex).toBeGreaterThanOrEqual(0);
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2454
|
+
it('does not throw when a carried-over thinking sequence has no locatable block', () => {
|
|
2455
|
+
// Models a stale runThinkingStartIndex carry-over pointing at an assistant
|
|
2456
|
+
// message that has no reasoning block. The pruner cannot find a block, but
|
|
2457
|
+
// a trailing AI/tool sequence keeps thinkingEndIndex set, so it used to
|
|
2458
|
+
// reach the fatal "no thinking block found" throw. Defense in depth: a
|
|
2459
|
+
// misconfiguration upstream of the pruner must not be able to brick the
|
|
2460
|
+
// thread — degrade to the partially-pruned context instead.
|
|
2461
|
+
const tokenCounter = createTestTokenCounter();
|
|
2462
|
+
const messages: BaseMessage[] = [
|
|
2463
|
+
new HumanMessage('h'.repeat(100)),
|
|
2464
|
+
new AIMessage({
|
|
2465
|
+
content: [{ type: 'text', text: 'a reply with no reasoning block' }],
|
|
2466
|
+
}),
|
|
2467
|
+
new HumanMessage('please read the doc'),
|
|
2468
|
+
new AIMessage({
|
|
2469
|
+
content: [
|
|
2470
|
+
{
|
|
2471
|
+
type: 'tool_use',
|
|
2472
|
+
id: 'tc_get_doc',
|
|
2473
|
+
name: 'get_doc_content',
|
|
2474
|
+
input: { docId: 'abc' },
|
|
2475
|
+
},
|
|
2476
|
+
],
|
|
2477
|
+
tool_calls: [
|
|
2478
|
+
{
|
|
2479
|
+
id: 'tc_get_doc',
|
|
2480
|
+
name: 'get_doc_content',
|
|
2481
|
+
args: { docId: 'abc' },
|
|
2482
|
+
type: 'tool_call',
|
|
2483
|
+
},
|
|
2484
|
+
],
|
|
2485
|
+
}),
|
|
2486
|
+
new ToolMessage({
|
|
2487
|
+
content: 'x'.repeat(150),
|
|
2488
|
+
tool_call_id: 'tc_get_doc',
|
|
2489
|
+
name: 'get_doc_content',
|
|
2490
|
+
}),
|
|
2491
|
+
];
|
|
2492
|
+
|
|
2493
|
+
const indexTokenCountMap: Record<string, number | undefined> = {};
|
|
2494
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2495
|
+
indexTokenCountMap[i] = tokenCounter(messages[i]);
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
let result: ReturnType<typeof realGetMessagesWithinTokenLimit> | undefined;
|
|
2499
|
+
expect(() => {
|
|
2500
|
+
result = realGetMessagesWithinTokenLimit({
|
|
2501
|
+
messages,
|
|
2502
|
+
maxContextTokens: 200,
|
|
2503
|
+
indexTokenCountMap,
|
|
2504
|
+
thinkingEnabled: true,
|
|
2505
|
+
tokenCounter,
|
|
2506
|
+
thinkingStartIndex: 1,
|
|
2507
|
+
reasoningType: ContentTypes.THINKING,
|
|
2508
|
+
});
|
|
2509
|
+
}).not.toThrow();
|
|
2510
|
+
|
|
2511
|
+
expect(result!.context.length).toBeGreaterThan(0);
|
|
2512
|
+
expect(result!.messagesToRefine.length).toBeGreaterThan(0);
|
|
2513
|
+
// The stale carried-over index must NOT be propagated: createPruneMessages
|
|
2514
|
+
// persists it as runThinkingStartIndex, and a stale value would suppress
|
|
2515
|
+
// the trailing scan on later turns and miss a real reasoning block.
|
|
2516
|
+
expect(result!.thinkingStartIndex).toBeUndefined();
|
|
2517
|
+
});
|
|
2518
|
+
|
|
2519
|
+
it('does not match an Anthropic thinking block for a Bedrock (reasoning_content) run', () => {
|
|
2520
|
+
// The cross-type fallback is one-directional: REASONING_CONTENT (Bedrock)
|
|
2521
|
+
// must not match a `thinking` block, since the Bedrock input converter
|
|
2522
|
+
// rejects `thinking` blocks and reattaching one would break the request.
|
|
2523
|
+
const tokenCounter = createTestTokenCounter();
|
|
2524
|
+
const messages: BaseMessage[] = [
|
|
2525
|
+
new SystemMessage('you are a helpful assistant'),
|
|
2526
|
+
new AIMessage({
|
|
2527
|
+
content: [
|
|
2528
|
+
{
|
|
2529
|
+
type: ContentTypes.THINKING,
|
|
2530
|
+
thinking: 'inherited Anthropic-style reasoning',
|
|
2531
|
+
signature: 'sig-anthropic',
|
|
2532
|
+
},
|
|
2533
|
+
{
|
|
2534
|
+
type: 'tool_use',
|
|
2535
|
+
id: 'tc_get_doc',
|
|
2536
|
+
name: 'get_doc_content',
|
|
2537
|
+
input: { docId: 'abc' },
|
|
2538
|
+
},
|
|
2539
|
+
],
|
|
2540
|
+
tool_calls: [
|
|
2541
|
+
{
|
|
2542
|
+
id: 'tc_get_doc',
|
|
2543
|
+
name: 'get_doc_content',
|
|
2544
|
+
args: { docId: 'abc' },
|
|
2545
|
+
type: 'tool_call',
|
|
2546
|
+
},
|
|
2547
|
+
],
|
|
2548
|
+
}),
|
|
2549
|
+
new ToolMessage({
|
|
2550
|
+
content: 'c'.repeat(6000),
|
|
2551
|
+
tool_call_id: 'tc_get_doc',
|
|
2552
|
+
name: 'get_doc_content',
|
|
2553
|
+
}),
|
|
2554
|
+
];
|
|
2555
|
+
|
|
2556
|
+
const indexTokenCountMap: Record<string, number | undefined> = {};
|
|
2557
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2558
|
+
indexTokenCountMap[i] = tokenCounter(messages[i]);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
let result: ReturnType<typeof realGetMessagesWithinTokenLimit> | undefined;
|
|
2562
|
+
expect(() => {
|
|
2563
|
+
result = realGetMessagesWithinTokenLimit({
|
|
2564
|
+
messages,
|
|
2565
|
+
maxContextTokens: 200,
|
|
2566
|
+
indexTokenCountMap,
|
|
2567
|
+
thinkingEnabled: true,
|
|
2568
|
+
tokenCounter,
|
|
2569
|
+
reasoningType: ContentTypes.REASONING_CONTENT,
|
|
2570
|
+
});
|
|
2571
|
+
}).not.toThrow();
|
|
2572
|
+
|
|
2573
|
+
// The thinking block is intentionally not located for a Bedrock run, so no
|
|
2574
|
+
// index is reported and nothing gets reattached.
|
|
2575
|
+
expect(result!.thinkingStartIndex).toBeUndefined();
|
|
2576
|
+
});
|
|
2577
|
+
});
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -103,6 +103,8 @@ type RunToolBatchContext = {
|
|
|
103
103
|
additionalContextsSink?: string[];
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
+
const TOOL_NODE_RUN_NAME = 'tool_batch';
|
|
107
|
+
|
|
106
108
|
/**
|
|
107
109
|
* Per-batch context for `dispatchToolEvents` / `executeViaEvent`.
|
|
108
110
|
* Mirrors {@link RunToolBatchContext} for the event-driven path,
|
|
@@ -500,7 +502,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
500
502
|
toolExecution,
|
|
501
503
|
fileCheckpointer,
|
|
502
504
|
}: t.ToolNodeConstructorParams) {
|
|
503
|
-
super({
|
|
505
|
+
super({
|
|
506
|
+
name: name ?? TOOL_NODE_RUN_NAME,
|
|
507
|
+
tags,
|
|
508
|
+
func: (input, config) => this.run(input, config),
|
|
509
|
+
});
|
|
504
510
|
this.trace = trace ?? this.trace;
|
|
505
511
|
this.runLangfuse = runLangfuse;
|
|
506
512
|
this.agentLangfuse = agentLangfuse;
|
|
@@ -15,6 +15,12 @@ describe('ToolNode Langfuse redaction context', () => {
|
|
|
15
15
|
mockWithLangfuseToolOutputTracingConfig.mockClear();
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
it('uses a stable default run name for tracing', () => {
|
|
19
|
+
const node = new ToolNode({ tools: [] });
|
|
20
|
+
|
|
21
|
+
expect(node.name).toBe('tool_batch');
|
|
22
|
+
});
|
|
23
|
+
|
|
18
24
|
it('scopes ToolNode invocation with run and agent Langfuse config', async () => {
|
|
19
25
|
const runLangfuse = {
|
|
20
26
|
toolOutputTracing: { enabled: true },
|
package/src/types/llm.ts
CHANGED
|
@@ -87,7 +87,7 @@ export type BedrockAnthropicInput = ChatBedrockConverseInput & {
|
|
|
87
87
|
AnthropicReasoning;
|
|
88
88
|
promptCache?: boolean;
|
|
89
89
|
};
|
|
90
|
-
export type BedrockConverseClientOptions =
|
|
90
|
+
export type BedrockConverseClientOptions = BedrockAnthropicInput;
|
|
91
91
|
export type BedrockAnthropicClientOptions = BedrockAnthropicInput;
|
|
92
92
|
export type GoogleClientOptions = GoogleGenerativeAIChatInput & {
|
|
93
93
|
customHeaders?: RequestOptions['customHeaders'];
|
|
@@ -128,7 +128,7 @@ export type ProviderOptionsMap = {
|
|
|
128
128
|
[Providers.MISTRALAI]: MistralAIClientOptions;
|
|
129
129
|
[Providers.MISTRAL]: MistralAIClientOptions;
|
|
130
130
|
[Providers.OPENROUTER]: ChatOpenRouterCallOptions;
|
|
131
|
-
[Providers.BEDROCK]:
|
|
131
|
+
[Providers.BEDROCK]: BedrockAnthropicClientOptions;
|
|
132
132
|
[Providers.XAI]: XAIClientOptions;
|
|
133
133
|
[Providers.MOONSHOT]: OpenAIClientOptions;
|
|
134
134
|
};
|