@librechat/agents 3.8.9 → 3.9.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 +3 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +4 -2
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +3 -2
- package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -1
- package/dist/cjs/messages/handoffCue.cjs +27 -0
- package/dist/cjs/messages/handoffCue.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/esm/graphs/Graph.mjs +3 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +4 -2
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +3 -2
- package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/handoffCue.mjs +25 -1
- package/dist/esm/messages/handoffCue.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/types/llm/prepareProviderRequest.d.ts +1 -1
- package/dist/types/messages/handoffCue.d.ts +11 -0
- package/package.json +2 -2
- package/src/graphs/Graph.ts +10 -4
- package/src/graphs/MultiAgentGraph.ts +23 -10
- package/src/llm/prepareProviderRequest.ts +6 -4
- package/src/messages/handoffCue.ts +51 -0
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { stampSyntheticProviderMessage } from "./provenance.mjs";
|
|
2
2
|
import { HumanMessage } from "@langchain/core/messages";
|
|
3
3
|
//#region src/messages/handoffCue.ts
|
|
4
|
+
const HANDOFF_CUE_MESSAGE_ID = "__handoff_cue_message_id";
|
|
5
|
+
const INSTRUCTIONLESS_HANDOFF_CUE = "Continue as the receiving agent using the preceding user request and context.";
|
|
6
|
+
function withInstructionlessHandoffCue(config, tail) {
|
|
7
|
+
return {
|
|
8
|
+
...config,
|
|
9
|
+
metadata: {
|
|
10
|
+
...config?.metadata,
|
|
11
|
+
[HANDOFF_CUE_MESSAGE_ID]: tail?.getType() === "ai" ? tail.id ?? null : null
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function appendInstructionlessHandoffCue(messages, config) {
|
|
16
|
+
const tail = messages.at(-1);
|
|
17
|
+
const sourceId = config?.metadata?.[HANDOFF_CUE_MESSAGE_ID];
|
|
18
|
+
if (typeof sourceId !== "string" || sourceId === "" || tail?.getType() !== "ai" || tail.id !== sourceId) return messages;
|
|
19
|
+
return [...messages, stampSyntheticProviderMessage(new HumanMessage({
|
|
20
|
+
content: INSTRUCTIONLESS_HANDOFF_CUE,
|
|
21
|
+
additional_kwargs: {
|
|
22
|
+
role: "user",
|
|
23
|
+
isMeta: true,
|
|
24
|
+
source: "routing"
|
|
25
|
+
}
|
|
26
|
+
}))];
|
|
27
|
+
}
|
|
4
28
|
const PREDECESSOR_HANDOFF_CUE = "[The assistant message above is the completed output of a previous agent. Respond now according to your own role and instructions.]";
|
|
5
29
|
function appendPredecessorHandoffCue(messages, isRunProduced) {
|
|
6
30
|
const last = messages.at(-1);
|
|
@@ -21,6 +45,6 @@ function removePredecessorHandoffCue(messages) {
|
|
|
21
45
|
return messages.slice(0, -1);
|
|
22
46
|
}
|
|
23
47
|
//#endregion
|
|
24
|
-
export { PREDECESSOR_HANDOFF_CUE, appendPredecessorHandoffCue, removePredecessorHandoffCue };
|
|
48
|
+
export { INSTRUCTIONLESS_HANDOFF_CUE, PREDECESSOR_HANDOFF_CUE, appendInstructionlessHandoffCue, appendPredecessorHandoffCue, removePredecessorHandoffCue, withInstructionlessHandoffCue };
|
|
25
49
|
|
|
26
50
|
//# sourceMappingURL=handoffCue.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handoffCue.mjs","names":[],"sources":["../../../src/messages/handoffCue.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"handoffCue.mjs","names":[],"sources":["../../../src/messages/handoffCue.ts"],"mappings":";;;AAMA,MAAM,yBAAyB;AAC/B,MAAa,8BACX;AAKF,SAAgB,8BACd,QACA,MACgB;CAChB,OAAO;EACL,GAAG;EACH,UAAU;GACR,GAAG,QAAQ;IACV,yBACC,MAAM,QAAQ,MAAM,OAAQ,KAAK,MAAM,OAAQ;EACnD;CACF;AACF;AAMA,SAAgB,gCACd,UACA,QACe;CACf,MAAM,OAAO,SAAS,GAAG,EAAE;CAC3B,MAAM,WAAW,QAAQ,WAAW;CACpC,IACE,OAAO,aAAa,YACpB,aAAa,MACb,MAAM,QAAQ,MAAM,QACpB,KAAK,OAAO,UAEZ,OAAO;CAET,OAAO,CACL,GAAG,UACH,8BACE,IAAI,aAAa;EACf,SAAS;EACT,mBAAmB;GAAE,MAAM;GAAQ,QAAQ;GAAM,QAAQ;EAAU;CACrE,CAAC,CACH,CACF;AACF;AASA,MAAa,0BACX;AAyBF,SAAgB,4BACd,UACA,eACe;CACf,MAAM,OAAO,SAAS,GAAG,EAAE;CAC3B,IAAI,QAAQ,QAAQ,KAAK,QAAQ,MAAM,MACrC,OAAO;CAET,IAAI,iBAAiB,QAAQ,CAAC,cAAc,IAAI,GAC9C,OAAO;CAET,OAAO,CACL,GAAG,UACH,8BACE,IAAI,aAAa;EACf,SAAS;EACT,mBAAmB;GAAE,MAAM;GAAQ,QAAQ;GAAM,QAAQ;EAAU;CACrE,CAAC,CACH,CACF;AACF;AASA,SAAgB,4BACd,UACe;CACf,MAAM,OAAO,SAAS,GAAG,EAAE;CAC3B,IACE,QAAQ,QACR,KAAK,QAAQ,MAAM,WACnB,KAAK,kBAAkB,WAAW,aAClC,KAAK,YAAA,uIAEL,OAAO;CAET,OAAO,SAAS,MAAM,GAAG,EAAE;AAC7B"}
|
|
@@ -5,6 +5,7 @@ import "./toolHistoryProjection.mjs";
|
|
|
5
5
|
import "./core.mjs";
|
|
6
6
|
import "./fading.mjs";
|
|
7
7
|
import "./injected.mjs";
|
|
8
|
+
import "./handoffCue.mjs";
|
|
8
9
|
import "./ids.mjs";
|
|
9
10
|
import "./contextPruningSettings.mjs";
|
|
10
11
|
import "./contextPruning.mjs";
|
|
@@ -15,7 +16,6 @@ import "./format.mjs";
|
|
|
15
16
|
import "./anthropicToolCache.mjs";
|
|
16
17
|
import "./tools.mjs";
|
|
17
18
|
import "./alternation.mjs";
|
|
18
|
-
import "./handoffCue.mjs";
|
|
19
19
|
import "./reducer.mjs";
|
|
20
20
|
import "./recency.mjs";
|
|
21
21
|
import "./assistantPhase.mjs";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
2
2
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
3
3
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
4
|
-
import type * as t from '@/types';
|
|
5
4
|
import type { ToolHistoryPreparation } from '@/messages/toolHistoryProjection';
|
|
5
|
+
import type * as t from '@/types';
|
|
6
6
|
declare const preparedProviderRequestBrand: unique symbol;
|
|
7
7
|
export type ProviderMessageProjectionMode = 'chat-messages' | 'openai-responses';
|
|
8
8
|
export interface ProviderPayloadMeasurement {
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
1
2
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
3
|
+
export declare const INSTRUCTIONLESS_HANDOFF_CUE = "Continue as the receiving agent using the preceding user request and context.";
|
|
4
|
+
/** Scope transport-only grounding to this recipient's incoming assistant turn.
|
|
5
|
+
* Every agent entry resets the marker, including direct edges and cycles. The
|
|
6
|
+
* reducer has already assigned the source message's id, including on replay. */
|
|
7
|
+
export declare function withInstructionlessHandoffCue(config: RunnableConfig | undefined, tail?: BaseMessage): RunnableConfig;
|
|
8
|
+
/** Only provider projections get this cue. Matching the incoming turn's id
|
|
9
|
+
* prevents it leaking into later tool iterations or deliberate assistant
|
|
10
|
+
* prefill. It is independent of transport provider and run-produced ids, so
|
|
11
|
+
* gateways and checkpoint replays follow the same handoff contract. */
|
|
12
|
+
export declare function appendInstructionlessHandoffCue(messages: BaseMessage[], config?: RunnableConfig): BaseMessage[];
|
|
2
13
|
/**
|
|
3
14
|
* Bracketed-meta convention, like the handoff path's
|
|
4
15
|
* `[Processed tool result and transferring to …]` bridge. The wording makes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.1",
|
|
4
4
|
"reova": {
|
|
5
5
|
"enabled": true,
|
|
6
6
|
"endpoint": "https://telemetry.reo.dev/data"
|
|
@@ -359,7 +359,7 @@
|
|
|
359
359
|
"mongodb": "^6.21.0",
|
|
360
360
|
"mongodb-memory-server-core": "^11.2.0",
|
|
361
361
|
"prettier": "^3.6.2",
|
|
362
|
-
"ts-jest": "^29.4.
|
|
362
|
+
"ts-jest": "^29.4.12",
|
|
363
363
|
"tsc-alias": "^1.8.10",
|
|
364
364
|
"tsdown": "^0.22.2",
|
|
365
365
|
"tslib": "^2.6.3",
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
convertInjectedMessages,
|
|
71
71
|
coalesceAdjacentUserTurns,
|
|
72
72
|
appendPredecessorHandoffCue,
|
|
73
|
+
appendInstructionlessHandoffCue,
|
|
73
74
|
stampSyntheticProviderMessage,
|
|
74
75
|
} from '@/messages';
|
|
75
76
|
import {
|
|
@@ -3721,11 +3722,16 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3721
3722
|
* Applied HERE for the primary so the cue is part of the MEASURED
|
|
3722
3723
|
* payload — the pre-invoke projection and overflow guard run on this
|
|
3723
3724
|
* stage's output, and a post-measure append could push a just-fits
|
|
3724
|
-
* prompt over budget unreported (#346 round 2).
|
|
3725
|
-
*
|
|
3726
|
-
*
|
|
3727
|
-
*
|
|
3725
|
+
* prompt over budget unreported (#346 round 2). Instructionless tool
|
|
3726
|
+
* handoffs are grounded for every transport. Only the direct-edge
|
|
3727
|
+
* predecessor cue is re-keyed per SERVING provider: tolerant
|
|
3728
|
+
* fallbacks strip it, while Claude fallbacks add it.
|
|
3728
3729
|
*/
|
|
3730
|
+
const beforeHandoffCue = transformed;
|
|
3731
|
+
transformed = trackProviderMessageOrigins(
|
|
3732
|
+
beforeHandoffCue,
|
|
3733
|
+
appendInstructionlessHandoffCue(beforeHandoffCue, callConfig)
|
|
3734
|
+
);
|
|
3729
3735
|
if (isAnthropicLike(provider, clientOptions as { model?: string })) {
|
|
3730
3736
|
const before = transformed;
|
|
3731
3737
|
transformed = trackProviderMessageOrigins(
|
|
@@ -28,12 +28,13 @@ import {
|
|
|
28
28
|
setProviderMessageProvenance,
|
|
29
29
|
stampSyntheticProviderMessage,
|
|
30
30
|
} from '@/messages/provenance';
|
|
31
|
-
import { serializeToolContentBounded } from '@/utils/toolContent';
|
|
32
|
-
import { Constants, MULTI_AGENT_GRAPH_RUN_NAME } from '@/common';
|
|
33
31
|
import {
|
|
34
32
|
calculateMaxToolResultChars,
|
|
35
33
|
HARD_MAX_TOOL_RESULT_CHARS,
|
|
36
34
|
} from '@/utils/truncation';
|
|
35
|
+
import { withInstructionlessHandoffCue } from '@/messages/handoffCue';
|
|
36
|
+
import { serializeToolContentBounded } from '@/utils/toolContent';
|
|
37
|
+
import { Constants, MULTI_AGENT_GRAPH_RUN_NAME } from '@/common';
|
|
37
38
|
import { StandardGraph } from './Graph';
|
|
38
39
|
|
|
39
40
|
/** Pattern to extract instructions from transfer ToolMessage content */
|
|
@@ -1354,12 +1355,6 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1354
1355
|
this.memberRecursionLimit == null
|
|
1355
1356
|
? config
|
|
1356
1357
|
: { ...config, recursionLimit: this.memberRecursionLimit };
|
|
1357
|
-
const memberConfig = withActiveAgentMetadata(
|
|
1358
|
-
recursionLimitedConfig,
|
|
1359
|
-
agentId,
|
|
1360
|
-
agentContext?.name
|
|
1361
|
-
);
|
|
1362
|
-
|
|
1363
1358
|
/**
|
|
1364
1359
|
* Check if this agent is receiving a handoff.
|
|
1365
1360
|
* If so, filter out the transfer messages and inject instructions as preamble.
|
|
@@ -1371,6 +1366,19 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1371
1366
|
agentId
|
|
1372
1367
|
);
|
|
1373
1368
|
|
|
1369
|
+
const memberConfig = withInstructionlessHandoffCue(
|
|
1370
|
+
withActiveAgentMetadata(
|
|
1371
|
+
recursionLimitedConfig,
|
|
1372
|
+
agentId,
|
|
1373
|
+
agentContext?.name
|
|
1374
|
+
),
|
|
1375
|
+
handoffContext != null &&
|
|
1376
|
+
(handoffContext.instructions == null ||
|
|
1377
|
+
handoffContext.instructions === '')
|
|
1378
|
+
? handoffContext.filteredMessages.at(-1)
|
|
1379
|
+
: undefined
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1374
1382
|
if (
|
|
1375
1383
|
handoffContext?.sourceAgentName != null &&
|
|
1376
1384
|
handoffContext.sourceAgentName !== ''
|
|
@@ -1582,7 +1590,9 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1582
1590
|
}
|
|
1583
1591
|
|
|
1584
1592
|
const startingNodes =
|
|
1585
|
-
summarizeOnlyAgentId != null
|
|
1593
|
+
summarizeOnlyAgentId != null
|
|
1594
|
+
? [summarizeOnlyAgentId]
|
|
1595
|
+
: this.startingNodes;
|
|
1586
1596
|
for (const startNode of startingNodes) {
|
|
1587
1597
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1588
1598
|
/** @ts-ignore */
|
|
@@ -1634,7 +1644,10 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1634
1644
|
this.resolveMaxRoutingPromptChars(destination);
|
|
1635
1645
|
|
|
1636
1646
|
if (typeof prompt === 'function') {
|
|
1637
|
-
const resolvedPrompt = await prompt(
|
|
1647
|
+
const resolvedPrompt = await prompt(
|
|
1648
|
+
state.messages,
|
|
1649
|
+
this.startIndex
|
|
1650
|
+
);
|
|
1638
1651
|
promptText =
|
|
1639
1652
|
resolvedPrompt == null
|
|
1640
1653
|
? undefined
|
|
@@ -6,8 +6,8 @@ import type {
|
|
|
6
6
|
} from '@langchain/core/messages';
|
|
7
7
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
8
8
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
9
|
-
import type * as t from '@/types';
|
|
10
9
|
import type { ToolHistoryPreparation } from '@/messages/toolHistoryProjection';
|
|
10
|
+
import type * as t from '@/types';
|
|
11
11
|
import {
|
|
12
12
|
projectCacheControlledToolOutputsToText,
|
|
13
13
|
projectComputerCallOutputsToText,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
coalesceAdjacentUserTurns,
|
|
23
23
|
appendPredecessorHandoffCue,
|
|
24
|
+
appendInstructionlessHandoffCue,
|
|
24
25
|
removePredecessorHandoffCue,
|
|
25
26
|
} from '@/messages';
|
|
26
27
|
import {
|
|
@@ -28,12 +29,12 @@ import {
|
|
|
28
29
|
stripBedrockCacheControl,
|
|
29
30
|
cloneMessage,
|
|
30
31
|
} from '@/messages/cache';
|
|
32
|
+
import { createToolHistoryPreparation } from '@/messages/toolHistoryProjection';
|
|
31
33
|
import { isAnthropicLike, isGoogleLike, isOpenAILike } from '@/utils/llm';
|
|
32
34
|
import { annotateMessagesForLLM } from '@/tools/toolOutputReferences';
|
|
33
35
|
import { providerRequiresStrictAlternation } from '@/llm/providers';
|
|
34
36
|
import { getProviderFamily } from '@/llm/providerRegistry';
|
|
35
37
|
import { Providers } from '@/common';
|
|
36
|
-
import { createToolHistoryPreparation } from '@/messages/toolHistoryProjection';
|
|
37
38
|
|
|
38
39
|
const preparedProviderRequestBrand = Symbol('PreparedProviderRequest');
|
|
39
40
|
const OMITTED_ATTACHMENT_TEXT =
|
|
@@ -555,16 +556,17 @@ export function prepareProviderRequest({
|
|
|
555
556
|
const annotated = annotateMessagesForLLM(projected, registry, runId);
|
|
556
557
|
const isRunProduced = context?.isRunProducedMessage;
|
|
557
558
|
const modelId = resolveServingModelId(model);
|
|
559
|
+
const handoffGrounded = appendInstructionlessHandoffCue(annotated, config);
|
|
558
560
|
const cued = isAnthropicLike(provider, {
|
|
559
561
|
model: modelId,
|
|
560
562
|
})
|
|
561
563
|
? appendPredecessorHandoffCue(
|
|
562
|
-
|
|
564
|
+
handoffGrounded,
|
|
563
565
|
isRunProduced == null
|
|
564
566
|
? undefined
|
|
565
567
|
: (message): boolean => isRunProduced.call(context, message)
|
|
566
568
|
)
|
|
567
|
-
: removePredecessorHandoffCue(
|
|
569
|
+
: removePredecessorHandoffCue(handoffGrounded);
|
|
568
570
|
const preparedMessages = providerRequiresStrictAlternation(provider)
|
|
569
571
|
? coalesceAdjacentUserTurns(cued)
|
|
570
572
|
: cued;
|
|
@@ -1,8 +1,59 @@
|
|
|
1
1
|
// src/messages/handoffCue.ts
|
|
2
2
|
import { HumanMessage } from '@langchain/core/messages';
|
|
3
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
3
4
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
4
5
|
import { stampSyntheticProviderMessage } from './provenance';
|
|
5
6
|
|
|
7
|
+
const HANDOFF_CUE_MESSAGE_ID = '__handoff_cue_message_id';
|
|
8
|
+
export const INSTRUCTIONLESS_HANDOFF_CUE =
|
|
9
|
+
'Continue as the receiving agent using the preceding user request and context.';
|
|
10
|
+
|
|
11
|
+
/** Scope transport-only grounding to this recipient's incoming assistant turn.
|
|
12
|
+
* Every agent entry resets the marker, including direct edges and cycles. The
|
|
13
|
+
* reducer has already assigned the source message's id, including on replay. */
|
|
14
|
+
export function withInstructionlessHandoffCue(
|
|
15
|
+
config: RunnableConfig | undefined,
|
|
16
|
+
tail?: BaseMessage
|
|
17
|
+
): RunnableConfig {
|
|
18
|
+
return {
|
|
19
|
+
...config,
|
|
20
|
+
metadata: {
|
|
21
|
+
...config?.metadata,
|
|
22
|
+
[HANDOFF_CUE_MESSAGE_ID]:
|
|
23
|
+
tail?.getType() === 'ai' ? (tail.id ?? null) : null,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Only provider projections get this cue. Matching the incoming turn's id
|
|
29
|
+
* prevents it leaking into later tool iterations or deliberate assistant
|
|
30
|
+
* prefill. It is independent of transport provider and run-produced ids, so
|
|
31
|
+
* gateways and checkpoint replays follow the same handoff contract. */
|
|
32
|
+
export function appendInstructionlessHandoffCue(
|
|
33
|
+
messages: BaseMessage[],
|
|
34
|
+
config?: RunnableConfig
|
|
35
|
+
): BaseMessage[] {
|
|
36
|
+
const tail = messages.at(-1);
|
|
37
|
+
const sourceId = config?.metadata?.[HANDOFF_CUE_MESSAGE_ID];
|
|
38
|
+
if (
|
|
39
|
+
typeof sourceId !== 'string' ||
|
|
40
|
+
sourceId === '' ||
|
|
41
|
+
tail?.getType() !== 'ai' ||
|
|
42
|
+
tail.id !== sourceId
|
|
43
|
+
) {
|
|
44
|
+
return messages;
|
|
45
|
+
}
|
|
46
|
+
return [
|
|
47
|
+
...messages,
|
|
48
|
+
stampSyntheticProviderMessage(
|
|
49
|
+
new HumanMessage({
|
|
50
|
+
content: INSTRUCTIONLESS_HANDOFF_CUE,
|
|
51
|
+
additional_kwargs: { role: 'user', isMeta: true, source: 'routing' },
|
|
52
|
+
})
|
|
53
|
+
),
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
6
57
|
/**
|
|
7
58
|
* Bracketed-meta convention, like the handoff path's
|
|
8
59
|
* `[Processed tool result and transferring to …]` bridge. The wording makes
|