@librechat/agents 3.1.71-dev.0 → 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 +7 -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/tools/ToolNode.cjs +84 -55
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +182 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +7 -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/tools/ToolNode.mjs +85 -56
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +182 -1
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +9 -2
- package/dist/types/llm/invoke.d.ts +9 -0
- package/dist/types/tools/ToolNode.d.ts +11 -13
- package/dist/types/tools/toolOutputReferences.d.ts +31 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/messages.d.ts +26 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +8 -1
- package/src/llm/invoke.test.ts +442 -0
- package/src/llm/invoke.ts +23 -2
- package/src/tools/ToolNode.ts +94 -81
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +98 -55
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +419 -0
- package/src/tools/toolOutputReferences.ts +223 -0
- package/src/types/index.ts +1 -0
- package/src/types/messages.ts +27 -0
|
@@ -10,7 +10,7 @@ import { RunnableCallable } from '../utils/run.mjs';
|
|
|
10
10
|
import 'ai-tokenizer';
|
|
11
11
|
import 'zod-to-json-schema';
|
|
12
12
|
import { executeHooks } from '../hooks/executeHooks.mjs';
|
|
13
|
-
import { ToolOutputReferenceRegistry,
|
|
13
|
+
import { ToolOutputReferenceRegistry, buildReferenceKey } from './toolOutputReferences.mjs';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Helper to check if a value is a Send object
|
|
@@ -300,13 +300,17 @@ class ToolNode extends RunnableCallable {
|
|
|
300
300
|
const isError = toolMsg.status === 'error';
|
|
301
301
|
if (isError) {
|
|
302
302
|
/**
|
|
303
|
-
* Error ToolMessages bypass registration
|
|
304
|
-
*
|
|
305
|
-
*
|
|
303
|
+
* Error ToolMessages bypass registration but still stamp the
|
|
304
|
+
* unresolved-refs hint into `additional_kwargs` so the lazy
|
|
305
|
+
* annotation transform surfaces it to the LLM, letting the
|
|
306
|
+
* model self-correct when its reference key caused the
|
|
307
|
+
* failure. Persisted `content` stays clean.
|
|
306
308
|
*/
|
|
307
|
-
if (unresolvedRefs.length > 0
|
|
308
|
-
|
|
309
|
-
|
|
309
|
+
if (unresolvedRefs.length > 0) {
|
|
310
|
+
toolMsg.additional_kwargs = {
|
|
311
|
+
...toolMsg.additional_kwargs,
|
|
312
|
+
_unresolvedRefs: unresolvedRefs,
|
|
313
|
+
};
|
|
310
314
|
}
|
|
311
315
|
return toolMsg;
|
|
312
316
|
}
|
|
@@ -314,7 +318,14 @@ class ToolNode extends RunnableCallable {
|
|
|
314
318
|
if (typeof toolMsg.content === 'string') {
|
|
315
319
|
const rawContent = toolMsg.content;
|
|
316
320
|
const llmContent = truncateToolResultContent(rawContent, this.maxToolResultChars);
|
|
317
|
-
toolMsg.content =
|
|
321
|
+
toolMsg.content = llmContent;
|
|
322
|
+
const refMeta = this.recordOutputReference(runId, rawContent, refKey, unresolvedRefs);
|
|
323
|
+
if (refMeta != null) {
|
|
324
|
+
toolMsg.additional_kwargs = {
|
|
325
|
+
...toolMsg.additional_kwargs,
|
|
326
|
+
...refMeta,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
318
329
|
}
|
|
319
330
|
else {
|
|
320
331
|
/**
|
|
@@ -322,22 +333,16 @@ class ToolNode extends RunnableCallable {
|
|
|
322
333
|
* image). Known limitation: we cannot register under a
|
|
323
334
|
* reference key because there's no canonical serialized
|
|
324
335
|
* form. Warn once per tool per run when the caller
|
|
325
|
-
* intended to register.
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* paths already emit. Prepended as a leading text block
|
|
330
|
-
* to keep the original content ordering intact.
|
|
336
|
+
* intended to register. The unresolved-refs hint is still
|
|
337
|
+
* stamped as metadata; the lazy transform prepends a text
|
|
338
|
+
* block at request time so the LLM gets the self-correction
|
|
339
|
+
* signal.
|
|
331
340
|
*/
|
|
332
|
-
if (unresolvedRefs.length > 0
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
341
|
+
if (unresolvedRefs.length > 0) {
|
|
342
|
+
toolMsg.additional_kwargs = {
|
|
343
|
+
...toolMsg.additional_kwargs,
|
|
344
|
+
_unresolvedRefs: unresolvedRefs,
|
|
336
345
|
};
|
|
337
|
-
toolMsg.content = [
|
|
338
|
-
warningBlock,
|
|
339
|
-
...toolMsg.content,
|
|
340
|
-
];
|
|
341
346
|
}
|
|
342
347
|
if (refKey != null &&
|
|
343
348
|
this.toolOutputRegistry.claimWarnOnce(runId, call.name)) {
|
|
@@ -351,12 +356,15 @@ class ToolNode extends RunnableCallable {
|
|
|
351
356
|
}
|
|
352
357
|
const rawContent = typeof output === 'string' ? output : JSON.stringify(output);
|
|
353
358
|
const truncated = truncateToolResultContent(rawContent, this.maxToolResultChars);
|
|
354
|
-
const
|
|
359
|
+
const refMeta = this.recordOutputReference(runId, rawContent, refKey, unresolvedRefs);
|
|
355
360
|
return new ToolMessage({
|
|
356
361
|
status: 'success',
|
|
357
362
|
name: tool.name,
|
|
358
|
-
content,
|
|
363
|
+
content: truncated,
|
|
359
364
|
tool_call_id: call.id,
|
|
365
|
+
...(refMeta != null && {
|
|
366
|
+
additional_kwargs: refMeta,
|
|
367
|
+
}),
|
|
360
368
|
});
|
|
361
369
|
}
|
|
362
370
|
catch (_e) {
|
|
@@ -400,51 +408,64 @@ class ToolNode extends RunnableCallable {
|
|
|
400
408
|
});
|
|
401
409
|
}
|
|
402
410
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
411
|
+
const errorContent = `Error: ${e.message}\n Please fix your mistakes.`;
|
|
412
|
+
const refMeta = unresolvedRefs.length > 0
|
|
413
|
+
? this.recordOutputReference(runId, errorContent, undefined, unresolvedRefs)
|
|
414
|
+
: undefined;
|
|
407
415
|
return new ToolMessage({
|
|
408
416
|
status: 'error',
|
|
409
417
|
content: errorContent,
|
|
410
418
|
name: call.name,
|
|
411
419
|
tool_call_id: call.id ?? '',
|
|
420
|
+
...(refMeta != null && {
|
|
421
|
+
additional_kwargs: refMeta,
|
|
422
|
+
}),
|
|
412
423
|
});
|
|
413
424
|
}
|
|
414
425
|
}
|
|
415
426
|
/**
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
427
|
+
* Registers the full, raw output under `refKey` (when provided) and
|
|
428
|
+
* builds the per-message ref metadata stamped onto the resulting
|
|
429
|
+
* `ToolMessage.additional_kwargs`. The metadata is read at LLM-
|
|
430
|
+
* request time by `annotateMessagesForLLM` to produce a transient
|
|
431
|
+
* annotated copy of the message — the persisted `content` itself
|
|
432
|
+
* stays clean.
|
|
419
433
|
*
|
|
420
|
-
* @param llmContent The content string the LLM will see. This is
|
|
421
|
-
* the already-truncated, post-hook view; the annotation is
|
|
422
|
-
* applied on top of it.
|
|
423
434
|
* @param registryContent The full, untruncated output to store in
|
|
424
435
|
* the registry so `{{tool<i>turn<n>}}` substitutions deliver the
|
|
425
436
|
* complete payload. Ignored when `refKey` is undefined.
|
|
426
437
|
* @param refKey Precomputed `tool<i>turn<n>` key, or undefined when
|
|
427
438
|
* the output is not to be registered (errors, disabled feature,
|
|
428
439
|
* unavailable batch/turn).
|
|
429
|
-
* @param unresolved Placeholder keys that did not resolve;
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
* so parallel `invoke()` calls on the same ToolNode cannot race on
|
|
434
|
-
* the shared turn field.
|
|
440
|
+
* @param unresolved Placeholder keys that did not resolve; surfaced
|
|
441
|
+
* to the LLM lazily so it can self-correct.
|
|
442
|
+
* @returns A `ToolMessageRefMetadata` object when there is anything
|
|
443
|
+
* to stamp, otherwise `undefined`.
|
|
435
444
|
*/
|
|
436
|
-
|
|
445
|
+
recordOutputReference(runId, registryContent, refKey, unresolved) {
|
|
437
446
|
if (this.toolOutputRegistry != null && refKey != null) {
|
|
438
447
|
this.toolOutputRegistry.set(runId, refKey, registryContent);
|
|
439
448
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
449
|
+
if (refKey == null && unresolved.length === 0)
|
|
450
|
+
return undefined;
|
|
451
|
+
const meta = {};
|
|
452
|
+
if (refKey != null) {
|
|
453
|
+
meta._refKey = refKey;
|
|
454
|
+
/**
|
|
455
|
+
* Stamp the registry scope alongside the key so the lazy
|
|
456
|
+
* annotation transform can look up the right bucket. Anonymous
|
|
457
|
+
* invocations get a synthetic per-batch scope (`\0anon-<n>`)
|
|
458
|
+
* that `attemptInvoke` cannot derive from
|
|
459
|
+
* `config.configurable.run_id` — without this, anonymous-run
|
|
460
|
+
* refs would silently fail registry lookup and the LLM would
|
|
461
|
+
* never see `[ref: …]` markers for outputs that were registered.
|
|
462
|
+
*/
|
|
463
|
+
if (runId != null)
|
|
464
|
+
meta._refScope = runId;
|
|
465
|
+
}
|
|
466
|
+
if (unresolved.length > 0)
|
|
467
|
+
meta._unresolvedRefs = unresolved;
|
|
468
|
+
return meta;
|
|
448
469
|
}
|
|
449
470
|
/**
|
|
450
471
|
* Builds code session context for injection into event-driven tool calls.
|
|
@@ -779,19 +800,24 @@ class ToolNode extends RunnableCallable {
|
|
|
779
800
|
if (result.status === 'error') {
|
|
780
801
|
contentString = `Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`;
|
|
781
802
|
/**
|
|
782
|
-
* Error results bypass registration
|
|
783
|
-
*
|
|
784
|
-
*
|
|
803
|
+
* Error results bypass registration but stamp the
|
|
804
|
+
* unresolved-refs hint into `additional_kwargs` so the lazy
|
|
805
|
+
* annotation transform surfaces it to the LLM at request
|
|
806
|
+
* time, letting the model self-correct when its reference
|
|
807
|
+
* key caused the failure. Persisted `content` stays clean.
|
|
785
808
|
*/
|
|
786
809
|
const unresolved = unresolvedByCallId.get(result.toolCallId) ?? [];
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
810
|
+
const errorRefMeta = unresolved.length > 0
|
|
811
|
+
? this.recordOutputReference(registryRunId, contentString, undefined, unresolved)
|
|
812
|
+
: undefined;
|
|
790
813
|
toolMessage = new ToolMessage({
|
|
791
814
|
status: 'error',
|
|
792
815
|
content: contentString,
|
|
793
816
|
name: toolName,
|
|
794
817
|
tool_call_id: result.toolCallId,
|
|
818
|
+
...(errorRefMeta != null && {
|
|
819
|
+
additional_kwargs: errorRefMeta,
|
|
820
|
+
}),
|
|
795
821
|
});
|
|
796
822
|
if (hasFailureHook) {
|
|
797
823
|
await executeHooks({
|
|
@@ -853,13 +879,16 @@ class ToolNode extends RunnableCallable {
|
|
|
853
879
|
turn != null
|
|
854
880
|
? buildReferenceKey(batchIndex, turn)
|
|
855
881
|
: undefined;
|
|
856
|
-
|
|
882
|
+
const successRefMeta = this.recordOutputReference(registryRunId, registryRaw, refKey, unresolved);
|
|
857
883
|
toolMessage = new ToolMessage({
|
|
858
884
|
status: 'success',
|
|
859
885
|
name: toolName,
|
|
860
886
|
content: contentString,
|
|
861
887
|
artifact: result.artifact,
|
|
862
888
|
tool_call_id: result.toolCallId,
|
|
889
|
+
...(successRefMeta != null && {
|
|
890
|
+
additional_kwargs: successRefMeta,
|
|
891
|
+
}),
|
|
863
892
|
});
|
|
864
893
|
}
|
|
865
894
|
this.dispatchStepCompleted(result.toolCallId, toolName, request?.args ?? {}, contentString, config, request?.turn);
|