@librechat/agents 3.1.70 → 3.1.71-dev.1
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 +52 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +13 -2
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +9 -2
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/run.cjs +4 -0
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +43 -0
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +482 -45
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +657 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -0
- package/dist/cjs/utils/truncation.cjs +28 -0
- package/dist/cjs/utils/truncation.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +52 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +13 -2
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/prune.mjs +9 -2
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/run.mjs +4 -0
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +42 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +482 -45
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +649 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -0
- package/dist/esm/utils/truncation.mjs +27 -1
- package/dist/esm/utils/truncation.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +28 -0
- package/dist/types/llm/invoke.d.ts +9 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/BashExecutor.d.ts +31 -0
- package/dist/types/tools/ToolNode.d.ts +84 -3
- package/dist/types/tools/toolOutputReferences.d.ts +236 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/messages.d.ts +26 -0
- package/dist/types/types/run.d.ts +9 -1
- package/dist/types/types/tools.d.ts +70 -0
- package/dist/types/utils/truncation.d.ts +21 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +55 -0
- package/src/llm/invoke.test.ts +442 -0
- package/src/llm/invoke.ts +23 -2
- package/src/messages/prune.ts +9 -2
- package/src/run.ts +4 -0
- package/src/specs/prune.test.ts +413 -0
- package/src/tools/BashExecutor.ts +45 -0
- package/src/tools/ToolNode.ts +631 -55
- package/src/tools/__tests__/BashExecutor.test.ts +36 -0
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +1438 -0
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +419 -0
- package/src/tools/__tests__/toolOutputReferences.test.ts +415 -0
- package/src/tools/toolOutputReferences.ts +813 -0
- package/src/types/index.ts +1 -0
- package/src/types/messages.ts +27 -0
- package/src/types/run.ts +9 -1
- package/src/types/tools.ts +71 -0
- package/src/utils/__tests__/truncation.test.ts +66 -0
- package/src/utils/truncation.ts +30 -0
package/src/types/index.ts
CHANGED
package/src/types/messages.ts
CHANGED
|
@@ -2,3 +2,30 @@ import type Anthropic from '@anthropic-ai/sdk';
|
|
|
2
2
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
3
3
|
export type AnthropicMessages = Array<AnthropicMessage | BaseMessage>;
|
|
4
4
|
export type AnthropicMessage = Anthropic.MessageParam;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Per-message ref metadata stamped onto a `ToolMessage` at execution
|
|
8
|
+
* time. Read by `annotateMessagesForLLM` to apply transient annotation
|
|
9
|
+
* to a copy of the message right before it goes on the wire to the
|
|
10
|
+
* provider. Never read after the run-scoped registry has been cleared.
|
|
11
|
+
*
|
|
12
|
+
* Lives in `ToolMessage.additional_kwargs`. LangChain's provider
|
|
13
|
+
* serializers don't transmit `additional_kwargs` to provider APIs, so
|
|
14
|
+
* the metadata never leaks even if you forget to clean it.
|
|
15
|
+
*/
|
|
16
|
+
export interface ToolMessageRefMetadata {
|
|
17
|
+
/** Key under which this message's untruncated output was registered. */
|
|
18
|
+
_refKey?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Registry bucket scope under which `_refKey` was stored. For named
|
|
21
|
+
* runs this equals `config.configurable.run_id`; for anonymous
|
|
22
|
+
* invocations (no `run_id`) ToolNode mints a per-batch synthetic
|
|
23
|
+
* scope (`\0anon-<n>`) so concurrent batches don't collide. Stamping
|
|
24
|
+
* the scope on the message itself lets `annotateMessagesForLLM`
|
|
25
|
+
* recover it without re-deriving from config — which is impossible
|
|
26
|
+
* for the anonymous case, since the scope is internal to ToolNode.
|
|
27
|
+
*/
|
|
28
|
+
_refScope?: string;
|
|
29
|
+
/** Placeholders the model used that could not be resolved this batch. */
|
|
30
|
+
_unresolvedRefs?: string[];
|
|
31
|
+
}
|
package/src/types/run.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type * as s from '@/types/stream';
|
|
|
11
11
|
import type * as e from '@/common/enum';
|
|
12
12
|
import type * as g from '@/types/graph';
|
|
13
13
|
import type * as l from '@/types/llm';
|
|
14
|
-
import type { ToolSessionMap } from '@/types/tools';
|
|
14
|
+
import type { ToolSessionMap, ToolOutputReferencesConfig } from '@/types/tools';
|
|
15
15
|
import type { HookRegistry } from '@/hooks';
|
|
16
16
|
|
|
17
17
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -146,6 +146,14 @@ export type RunConfig = {
|
|
|
146
146
|
* at run start, so ToolNode can inject session_id + files into tool calls.
|
|
147
147
|
*/
|
|
148
148
|
initialSessions?: ToolSessionMap;
|
|
149
|
+
/**
|
|
150
|
+
* Run-scoped tool output reference configuration. When `enabled` is
|
|
151
|
+
* `true`, tool outputs are registered under stable keys
|
|
152
|
+
* (`tool<idx>turn<turn>`) and subsequent tool calls can pipe previous
|
|
153
|
+
* outputs into their arguments via `{{tool<idx>turn<turn>}}`
|
|
154
|
+
* placeholders. Disabled by default so existing runs are unaffected.
|
|
155
|
+
*/
|
|
156
|
+
toolOutputReferences?: ToolOutputReferencesConfig;
|
|
149
157
|
};
|
|
150
158
|
|
|
151
159
|
export type ProvidedCallbacks =
|
package/src/types/tools.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { StructuredToolInterface } from '@langchain/core/tools';
|
|
|
3
3
|
import type { RunnableToolLike } from '@langchain/core/runnables';
|
|
4
4
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
5
5
|
import type { HookRegistry } from '@/hooks';
|
|
6
|
+
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
6
7
|
import type { MessageContentComplex, ToolErrorData } from './stream';
|
|
7
8
|
|
|
8
9
|
/** Replacement type for `import type { ToolCall } from '@langchain/core/messages/tool'` in order to have stringified args typed */
|
|
@@ -62,6 +63,22 @@ export type ToolNodeOptions = {
|
|
|
62
63
|
* When provided, takes precedence over the value computed from maxContextTokens.
|
|
63
64
|
*/
|
|
64
65
|
maxToolResultChars?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Run-scoped tool output reference configuration. When `enabled` is
|
|
68
|
+
* `true`, ToolNode registers successful outputs and substitutes
|
|
69
|
+
* `{{tool<idx>turn<turn>}}` placeholders found in string args.
|
|
70
|
+
*
|
|
71
|
+
* Ignored when `toolOutputRegistry` is also provided (host-supplied
|
|
72
|
+
* registry wins).
|
|
73
|
+
*/
|
|
74
|
+
toolOutputReferences?: ToolOutputReferencesConfig;
|
|
75
|
+
/**
|
|
76
|
+
* Pre-constructed registry instance shared across ToolNodes for the
|
|
77
|
+
* run. Graphs pass the same registry to every ToolNode they compile
|
|
78
|
+
* so cross-agent `{{tool<i>turn<n>}}` substitutions resolve. Takes
|
|
79
|
+
* precedence over `toolOutputReferences` when both are set.
|
|
80
|
+
*/
|
|
81
|
+
toolOutputRegistry?: ToolOutputReferenceRegistry;
|
|
65
82
|
};
|
|
66
83
|
|
|
67
84
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
@@ -234,6 +251,60 @@ export type ToolExecuteResult = {
|
|
|
234
251
|
/** Map of tool names to tool definitions */
|
|
235
252
|
export type LCToolRegistry = Map<string, LCTool>;
|
|
236
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Run-scoped configuration for tool output references.
|
|
256
|
+
*
|
|
257
|
+
* When enabled, each successful tool result is registered under a stable
|
|
258
|
+
* key (`tool<idx>turn<turn>`). Later tool calls can pipe a previous
|
|
259
|
+
* output into their arguments by including the literal placeholder
|
|
260
|
+
* `{{tool<idx>turn<turn>}}` anywhere in a string argument; ToolNode
|
|
261
|
+
* substitutes it with the stored output immediately before invoking
|
|
262
|
+
* the tool.
|
|
263
|
+
*
|
|
264
|
+
* The registry stores the *raw, untruncated* tool output (subject to
|
|
265
|
+
* its own size caps) so a later substitution can pipe the full payload
|
|
266
|
+
* into the next tool even when the LLM only saw a head+tail-truncated
|
|
267
|
+
* preview in `ToolMessage.content`. Size limits are decoupled from the
|
|
268
|
+
* LLM-visible truncation budget and default to 5 MB total.
|
|
269
|
+
*
|
|
270
|
+
* Known limitations:
|
|
271
|
+
* - Tools that return a `ToolMessage` with array-type content
|
|
272
|
+
* (multi-part content blocks such as text + image) are not
|
|
273
|
+
* registered and cannot be cited via `{{tool<i>turn<n>}}`. A
|
|
274
|
+
* warning is logged so the missing reference is visible.
|
|
275
|
+
* - When a `PostToolUse` hook replaces `ToolMessage.content`, the
|
|
276
|
+
* *post-hook* content is what gets stored in the registry (and
|
|
277
|
+
* what the model sees), so `{{…}}` substitutions deliver the
|
|
278
|
+
* hooked output rather than the raw tool return. This matches the
|
|
279
|
+
* hook's "authoritative" role for output shaping.
|
|
280
|
+
*/
|
|
281
|
+
export type ToolOutputReferencesConfig = {
|
|
282
|
+
/** Enable the registry and placeholder substitution. Defaults to `false`. */
|
|
283
|
+
enabled?: boolean;
|
|
284
|
+
/**
|
|
285
|
+
* Maximum characters stored (and substituted) per registered output.
|
|
286
|
+
* Applied to the *raw* output before storage. Defaults to
|
|
287
|
+
* `HARD_MAX_TOOL_RESULT_CHARS` (~400 KB) — matching the
|
|
288
|
+
* LLM-visible tool-result truncation budget, which is also a safe
|
|
289
|
+
* payload size for shell `ARG_MAX` limits when a `{{…}}` expansion
|
|
290
|
+
* gets piped into a bash `command`. Hosts that want to preserve
|
|
291
|
+
* fuller fidelity (for example for non-bash API consumers) can
|
|
292
|
+
* raise this up to `maxTotalSize` (defaults to 5 MB) — be aware
|
|
293
|
+
* that large single-output substitutions may exceed shell
|
|
294
|
+
* argument-size limits on typical Linux/macOS.
|
|
295
|
+
*/
|
|
296
|
+
maxOutputSize?: number;
|
|
297
|
+
/**
|
|
298
|
+
* Hard cap on total characters retained across all registered outputs
|
|
299
|
+
* for the run. When exceeded, the oldest entries are evicted FIFO
|
|
300
|
+
* until the total fits. The effective per-output cap is
|
|
301
|
+
* `min(maxOutputSize, maxTotalSize)` so a single stored output can
|
|
302
|
+
* never exceed the aggregate bound. Defaults to
|
|
303
|
+
* `calculateMaxTotalToolOutputSize(maxOutputSize)` (5 MB).
|
|
304
|
+
*/
|
|
305
|
+
maxTotalSize?: number;
|
|
306
|
+
};
|
|
307
|
+
|
|
237
308
|
export type ProgrammaticCache = { toolMap: ToolMap; toolDefs: LCTool[] };
|
|
238
309
|
|
|
239
310
|
/** Search mode: code_interpreter uses external sandbox, local uses safe substring matching */
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import {
|
|
3
|
+
HARD_MAX_TOOL_RESULT_CHARS,
|
|
4
|
+
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE,
|
|
5
|
+
calculateMaxToolResultChars,
|
|
6
|
+
calculateMaxTotalToolOutputSize,
|
|
7
|
+
} from '@/utils/truncation';
|
|
8
|
+
|
|
9
|
+
describe('truncation helpers', () => {
|
|
10
|
+
describe('calculateMaxToolResultChars', () => {
|
|
11
|
+
it('returns the hard cap when context tokens are missing', () => {
|
|
12
|
+
expect(calculateMaxToolResultChars()).toBe(HARD_MAX_TOOL_RESULT_CHARS);
|
|
13
|
+
expect(calculateMaxToolResultChars(undefined)).toBe(
|
|
14
|
+
HARD_MAX_TOOL_RESULT_CHARS
|
|
15
|
+
);
|
|
16
|
+
expect(calculateMaxToolResultChars(0)).toBe(HARD_MAX_TOOL_RESULT_CHARS);
|
|
17
|
+
expect(calculateMaxToolResultChars(-100)).toBe(
|
|
18
|
+
HARD_MAX_TOOL_RESULT_CHARS
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('computes 30% of context-window characters for normal inputs', () => {
|
|
23
|
+
// 100k tokens * 0.3 = 30k tokens * 4 chars/token = 120k chars
|
|
24
|
+
expect(calculateMaxToolResultChars(100_000)).toBe(120_000);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('clamps to the hard cap for large context windows', () => {
|
|
28
|
+
// 1M tokens * 0.3 * 4 = 1.2M chars, exceeds 400k cap
|
|
29
|
+
expect(calculateMaxToolResultChars(1_000_000)).toBe(
|
|
30
|
+
HARD_MAX_TOOL_RESULT_CHARS
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('calculateMaxTotalToolOutputSize', () => {
|
|
36
|
+
it('returns the absolute hard cap when no per-output is provided', () => {
|
|
37
|
+
expect(calculateMaxTotalToolOutputSize()).toBe(
|
|
38
|
+
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE
|
|
39
|
+
);
|
|
40
|
+
expect(calculateMaxTotalToolOutputSize(0)).toBe(
|
|
41
|
+
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE
|
|
42
|
+
);
|
|
43
|
+
expect(calculateMaxTotalToolOutputSize(-1)).toBe(
|
|
44
|
+
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('doubles the per-output cap by default', () => {
|
|
49
|
+
expect(calculateMaxTotalToolOutputSize(100_000)).toBe(200_000);
|
|
50
|
+
expect(calculateMaxTotalToolOutputSize(1)).toBe(2);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('clamps the doubled value to HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE', () => {
|
|
54
|
+
// 4M * 2 = 8M, exceeds 5M
|
|
55
|
+
expect(calculateMaxTotalToolOutputSize(4_000_000)).toBe(
|
|
56
|
+
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE
|
|
57
|
+
);
|
|
58
|
+
// Right at the boundary: 2.5M * 2 = 5M (no clamp).
|
|
59
|
+
expect(calculateMaxTotalToolOutputSize(2_500_000)).toBe(5_000_000);
|
|
60
|
+
// Just past it: 2_500_001 * 2 = 5_000_002 -> clamped.
|
|
61
|
+
expect(calculateMaxTotalToolOutputSize(2_500_001)).toBe(
|
|
62
|
+
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
package/src/utils/truncation.ts
CHANGED
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export const HARD_MAX_TOOL_RESULT_CHARS = 400_000;
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Absolute hard cap on the aggregate size (characters) of all registered
|
|
17
|
+
* tool outputs kept for `{{tool<i>turn<n>}}` substitution. Set at 5 MB
|
|
18
|
+
* because the registry stores *raw, untruncated* tool output — full
|
|
19
|
+
* fidelity for piping into downstream bash/jq — so the budget needs
|
|
20
|
+
* enough headroom to keep a handful of large responses without
|
|
21
|
+
* ballooning unbounded.
|
|
22
|
+
*/
|
|
23
|
+
export const HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE = 5_000_000;
|
|
24
|
+
|
|
15
25
|
/**
|
|
16
26
|
* Computes the dynamic max tool result size based on the model's context window.
|
|
17
27
|
* Uses 30% of the context window (in estimated characters, ~4 chars/token)
|
|
@@ -32,6 +42,26 @@ export function calculateMaxToolResultChars(
|
|
|
32
42
|
);
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Computes the default aggregate size (characters) for the tool output
|
|
47
|
+
* reference registry based on the per-output budget. Mirrors
|
|
48
|
+
* `calculateMaxToolResultChars`'s shape: a multiple of the per-output
|
|
49
|
+
* cap, clamped to `HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE`.
|
|
50
|
+
*
|
|
51
|
+
* @param maxOutputSize - Per-output maximum characters (e.g., the
|
|
52
|
+
* ToolNode's `maxToolResultChars`). When omitted or non-positive,
|
|
53
|
+
* falls back to the absolute total cap.
|
|
54
|
+
* @returns Maximum total characters retained across the registry.
|
|
55
|
+
*/
|
|
56
|
+
export function calculateMaxTotalToolOutputSize(
|
|
57
|
+
maxOutputSize?: number
|
|
58
|
+
): number {
|
|
59
|
+
if (maxOutputSize == null || maxOutputSize <= 0) {
|
|
60
|
+
return HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE;
|
|
61
|
+
}
|
|
62
|
+
return Math.min(maxOutputSize * 2, HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE);
|
|
63
|
+
}
|
|
64
|
+
|
|
35
65
|
/**
|
|
36
66
|
* Truncates a tool-call input (the arguments/payload of a tool_use block)
|
|
37
67
|
* using head+tail strategy. Returns an object with `_truncated` (the
|