@librechat/agents 3.4.2 → 3.4.4
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 +27 -14
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
- package/dist/cjs/instrumentation.cjs +3 -3
- package/dist/cjs/langfuse.cjs +3 -3
- package/dist/cjs/langfuseRuntimeScope.cjs +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +2 -2
- package/dist/cjs/main.cjs +5 -1
- package/dist/cjs/messages/assistantPhase.cjs +59 -0
- package/dist/cjs/messages/assistantPhase.cjs.map +1 -0
- package/dist/cjs/messages/index.cjs +1 -0
- package/dist/cjs/prompts/activityLabel.cjs +76 -0
- package/dist/cjs/prompts/activityLabel.cjs.map +1 -1
- package/dist/cjs/run.cjs +200 -10
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +1 -1
- package/dist/cjs/stream.cjs +45 -8
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +3 -3
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +81 -6
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/callbacks.cjs +8 -0
- package/dist/cjs/utils/callbacks.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +27 -14
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
- package/dist/esm/instrumentation.mjs +3 -3
- package/dist/esm/langfuse.mjs +3 -3
- package/dist/esm/langfuseRuntimeScope.mjs +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +2 -2
- package/dist/esm/main.mjs +3 -2
- package/dist/esm/messages/assistantPhase.mjs +57 -0
- package/dist/esm/messages/assistantPhase.mjs.map +1 -0
- package/dist/esm/messages/index.mjs +1 -0
- package/dist/esm/prompts/activityLabel.mjs +74 -1
- package/dist/esm/prompts/activityLabel.mjs.map +1 -1
- package/dist/esm/run.mjs +202 -12
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +1 -1
- package/dist/esm/stream.mjs +45 -8
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +3 -3
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +81 -6
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/callbacks.mjs +8 -1
- package/dist/esm/utils/callbacks.mjs.map +1 -1
- package/dist/types/messages/assistantPhase.d.ts +22 -0
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/prompts/activityLabel.d.ts +21 -1
- package/dist/types/run.d.ts +15 -2
- package/dist/types/types/activityLabel.d.ts +63 -0
- package/dist/types/types/assistantPhase.d.ts +6 -0
- package/dist/types/types/graph.d.ts +8 -1
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/stream.d.ts +11 -0
- package/dist/types/utils/callbacks.d.ts +1 -0
- package/package.json +3 -3
- package/src/graphs/Graph.ts +33 -9
- package/src/graphs/__tests__/Graph.reasoning.test.ts +57 -0
- package/src/messages/assistantPhase.test.ts +75 -0
- package/src/messages/assistantPhase.ts +91 -0
- package/src/messages/index.ts +1 -0
- package/src/prompts/activityLabel.ts +177 -1
- package/src/run.ts +403 -21
- package/src/specs/activity-label-prompt.test.ts +123 -1
- package/src/specs/activity-phase-label.test.ts +306 -0
- package/src/stream.ts +69 -12
- package/src/tools/__tests__/SubagentExecutor.test.ts +436 -0
- package/src/tools/subagent/SubagentExecutor.ts +160 -8
- package/src/types/activityLabel.ts +65 -0
- package/src/types/assistantPhase.ts +6 -0
- package/src/types/graph.ts +8 -0
- package/src/types/index.ts +1 -0
- package/src/types/stream.ts +9 -0
- package/src/utils/callbacks.ts +21 -0
|
@@ -10,7 +10,14 @@ function findCallback(callbacks, predicate) {
|
|
|
10
10
|
if (callbacks == null) return;
|
|
11
11
|
return (Array.isArray(callbacks) ? callbacks : callbacks.handlers).find(predicate);
|
|
12
12
|
}
|
|
13
|
+
function filterCallbacks(callbacks, predicate) {
|
|
14
|
+
if (callbacks == null) return [];
|
|
15
|
+
if (Array.isArray(callbacks)) return callbacks.filter(predicate);
|
|
16
|
+
const filtered = callbacks.copy();
|
|
17
|
+
for (const handler of [...filtered.handlers]) if (!predicate(handler)) filtered.removeHandler(handler);
|
|
18
|
+
return filtered;
|
|
19
|
+
}
|
|
13
20
|
//#endregion
|
|
14
|
-
export { appendCallbacks, findCallback };
|
|
21
|
+
export { appendCallbacks, filterCallbacks, findCallback };
|
|
15
22
|
|
|
16
23
|
//# sourceMappingURL=callbacks.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callbacks.mjs","names":[],"sources":["../../../src/utils/callbacks.ts"],"sourcesContent":["import { ensureHandler } from '@langchain/core/callbacks/manager';\nimport type {\n BaseCallbackHandler,\n CallbackHandlerMethods,\n} from '@langchain/core/callbacks/base';\nimport type { Callbacks } from '@langchain/core/callbacks/manager';\n\nexport type CallbackEntry = BaseCallbackHandler | CallbackHandlerMethods;\n\nexport function appendCallbacks(\n callbacks: Callbacks | undefined,\n additions: readonly CallbackEntry[]\n): Callbacks {\n if (additions.length === 0) {\n return callbacks ?? [];\n }\n\n if (callbacks == null) {\n return [...additions];\n }\n\n if (Array.isArray(callbacks)) {\n return callbacks.concat(additions);\n }\n\n return callbacks.copy(additions.map(ensureHandler));\n}\n\nexport function findCallback(\n callbacks: Callbacks | undefined,\n predicate: (callback: CallbackEntry) => boolean\n): CallbackEntry | undefined {\n if (callbacks == null) {\n return undefined;\n }\n\n const handlers = Array.isArray(callbacks) ? callbacks : callbacks.handlers;\n return handlers.find(predicate);\n}\n"],"mappings":";;AASA,SAAgB,gBACd,WACA,WACW;CACX,IAAI,UAAU,WAAW,GACvB,OAAO,aAAa,CAAC;CAGvB,IAAI,aAAa,MACf,OAAO,CAAC,GAAG,SAAS;CAGtB,IAAI,MAAM,QAAQ,SAAS,GACzB,OAAO,UAAU,OAAO,SAAS;CAGnC,OAAO,UAAU,KAAK,UAAU,IAAI,aAAa,CAAC;AACpD;AAEA,SAAgB,aACd,WACA,WAC2B;CAC3B,IAAI,aAAa,MACf;CAIF,QADiB,MAAM,QAAQ,SAAS,IAAI,YAAY,UAAU,SAAA,CAClD,KAAK,SAAS;AAChC"}
|
|
1
|
+
{"version":3,"file":"callbacks.mjs","names":[],"sources":["../../../src/utils/callbacks.ts"],"sourcesContent":["import { ensureHandler } from '@langchain/core/callbacks/manager';\nimport type {\n BaseCallbackHandler,\n CallbackHandlerMethods,\n} from '@langchain/core/callbacks/base';\nimport type { Callbacks } from '@langchain/core/callbacks/manager';\n\nexport type CallbackEntry = BaseCallbackHandler | CallbackHandlerMethods;\n\nexport function appendCallbacks(\n callbacks: Callbacks | undefined,\n additions: readonly CallbackEntry[]\n): Callbacks {\n if (additions.length === 0) {\n return callbacks ?? [];\n }\n\n if (callbacks == null) {\n return [...additions];\n }\n\n if (Array.isArray(callbacks)) {\n return callbacks.concat(additions);\n }\n\n return callbacks.copy(additions.map(ensureHandler));\n}\n\nexport function findCallback(\n callbacks: Callbacks | undefined,\n predicate: (callback: CallbackEntry) => boolean\n): CallbackEntry | undefined {\n if (callbacks == null) {\n return undefined;\n }\n\n const handlers = Array.isArray(callbacks) ? callbacks : callbacks.handlers;\n return handlers.find(predicate);\n}\n\nexport function filterCallbacks(\n callbacks: Callbacks | undefined,\n predicate: (callback: CallbackEntry) => boolean\n): Callbacks {\n if (callbacks == null) {\n return [];\n }\n\n if (Array.isArray(callbacks)) {\n return callbacks.filter(predicate);\n }\n\n const filtered = callbacks.copy();\n for (const handler of [...filtered.handlers]) {\n if (!predicate(handler)) {\n filtered.removeHandler(handler);\n }\n }\n return filtered;\n}\n"],"mappings":";;AASA,SAAgB,gBACd,WACA,WACW;CACX,IAAI,UAAU,WAAW,GACvB,OAAO,aAAa,CAAC;CAGvB,IAAI,aAAa,MACf,OAAO,CAAC,GAAG,SAAS;CAGtB,IAAI,MAAM,QAAQ,SAAS,GACzB,OAAO,UAAU,OAAO,SAAS;CAGnC,OAAO,UAAU,KAAK,UAAU,IAAI,aAAa,CAAC;AACpD;AAEA,SAAgB,aACd,WACA,WAC2B;CAC3B,IAAI,aAAa,MACf;CAIF,QADiB,MAAM,QAAQ,SAAS,IAAI,YAAY,UAAU,SAAA,CAClD,KAAK,SAAS;AAChC;AAEA,SAAgB,gBACd,WACA,WACW;CACX,IAAI,aAAa,MACf,OAAO,CAAC;CAGV,IAAI,MAAM,QAAQ,SAAS,GACzB,OAAO,UAAU,OAAO,SAAS;CAGnC,MAAM,WAAW,UAAU,KAAK;CAChC,KAAK,MAAM,WAAW,CAAC,GAAG,SAAS,QAAQ,GACzC,IAAI,CAAC,UAAU,OAAO,GACpB,SAAS,cAAc,OAAO;CAGlC,OAAO;AACT"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AssistantTextPhase } from '@/types/assistantPhase';
|
|
2
|
+
import type { MessageContentComplex } from '@/types/stream';
|
|
3
|
+
import { ContentTypes } from '@/common';
|
|
4
|
+
export type MessageCreationContentMetadata = {
|
|
5
|
+
content_type?: ContentTypes.TEXT | ContentTypes.THINK;
|
|
6
|
+
phase?: AssistantTextPhase;
|
|
7
|
+
};
|
|
8
|
+
/** Reads both provider-native and LangChain standard-content phase fields. */
|
|
9
|
+
export declare function getAssistantTextPhase(contentPart: MessageContentComplex): AssistantTextPhase | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Keeps provider-authored text phases in distinct message-creation steps.
|
|
12
|
+
* Open Responses may return commentary and final-answer blocks in one chunk;
|
|
13
|
+
* collapsing the array into one step would assign the first block's phase to
|
|
14
|
+
* every block and hide the boundary that closes an activity phase.
|
|
15
|
+
*/
|
|
16
|
+
export declare function splitAssistantTextContentByPhase(content: MessageContentComplex[]): MessageContentComplex[][];
|
|
17
|
+
/**
|
|
18
|
+
* Derives additive message-creation metadata before a content delta is
|
|
19
|
+
* dispatched. The fallback covers string-only providers whose semantic lane
|
|
20
|
+
* is tracked by the stream handler rather than represented on a block.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getMessageCreationContentMetadata(content: string | MessageContentComplex[] | undefined, fallbackContentType?: ContentTypes.TEXT | ContentTypes.THINK): MessageCreationContentMetadata;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { ActivityLabelToolEntry, ActivityPhaseEntry } from '@/types/activityLabel';
|
|
1
2
|
import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';
|
|
2
|
-
import type { ActivityLabelToolEntry } from '@/types/activityLabel';
|
|
3
3
|
/**
|
|
4
4
|
* Default system prompt for fast-model activity labeling.
|
|
5
5
|
*
|
|
@@ -9,8 +9,13 @@ import type { ActivityLabelToolEntry } from '@/types/activityLabel';
|
|
|
9
9
|
* "Synthesized version data and curated comparative framework").
|
|
10
10
|
*/
|
|
11
11
|
export declare const ACTIVITY_LABEL_PROMPT = "Write a short label describing what this block of agent activity accomplished. It appears as the header of a collapsed activity group in a chat UI.\n\nRules:\n- 5 to 9 words, past-tense verb first\n- Name the most distinctive subject (file, API, topic); drop articles and filler\n- Describe outcomes, not mechanics; if something failed, say so plainly\n- Output only the label \u2014 no quotes, no punctuation at the end, no preamble\n\nExamples:\n- Searched Node.js release notes and changelogs\n- Compared runtime versions across official sources\n- Fixed failing auth middleware tests\n- Read project config and dependency manifests\n- Attempted database migration, hit permission errors";
|
|
12
|
+
/** Default system prompt for a run-wide parent activity phase. */
|
|
13
|
+
export declare const ACTIVITY_PHASE_LABEL_PROMPT = "Summarize what this phase of an agent run accomplished. The result appears as the header of one collapsed parent group containing several activities.\n\nRules:\n- One line, 8 to 18 words, past tense\n- Lead with the concrete outcome and name the most distinctive subject\n- Synthesize the phase; do not enumerate, count, or restate individual activities\n- Describe failures plainly when they are the phase's material outcome\n- Never mention tool names, calls, arguments, reasoning, commentary, or activity counts\n- Output only the summary \u2014 no quotes, no trailing punctuation, no preamble\n\nExamples:\n- Reconciled authentication behavior and fixed the failing session refresh path\n- Compared deployment options and documented the safest production rollout\n- Investigated database latency but could not confirm the suspected index regression\n\nBad examples:\n- Used three tools to inspect files and run tests\n- Searched code, read configuration, and updated middleware";
|
|
14
|
+
/** Hard ceiling across every activity/context section in one phase request. */
|
|
15
|
+
export declare const ACTIVITY_PHASE_PROMPT_MAX_LENGTH = 12000;
|
|
12
16
|
/** Truncates a serialized value for the label prompt. */
|
|
13
17
|
export declare function truncateForLabel(value: string, maxLength: number): string;
|
|
18
|
+
export declare const ACTIVITY_PHASE_LABEL_MAX_LENGTH = 160;
|
|
14
19
|
export type BuildActivityLabelPromptParams = {
|
|
15
20
|
entries: ActivityLabelToolEntry[];
|
|
16
21
|
charLimit: number;
|
|
@@ -36,3 +41,18 @@ export type BuildActivityLabelPromptParams = {
|
|
|
36
41
|
* for direct testing of redaction and truncation behavior.
|
|
37
42
|
*/
|
|
38
43
|
export declare function buildActivityLabelPrompt({ entries, charLimit, thinkingExcerpts, lastAssistantText, previousLabels, redaction, }: BuildActivityLabelPromptParams): string;
|
|
44
|
+
export type BuildActivityPhaseLabelPromptParams = {
|
|
45
|
+
activities: ActivityPhaseEntry[];
|
|
46
|
+
totalActivityCount?: number;
|
|
47
|
+
charLimit: number;
|
|
48
|
+
assistantContext?: string[];
|
|
49
|
+
redaction?: ResolvedLangfuseToolOutputTracingConfig;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Builds bounded, redaction-aware evidence for a parent activity phase.
|
|
53
|
+
* Committed child labels are preferred; raw tool/reasoning evidence is only
|
|
54
|
+
* used when no child label exists.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildActivityPhaseLabelPrompt({ activities, totalActivityCount, charLimit, assistantContext, redaction, }: BuildActivityPhaseLabelPromptParams): string;
|
|
57
|
+
/** Normalizes a model result for safe single-row persistence and display. */
|
|
58
|
+
export declare function normalizeActivityPhaseLabel(label: string): string;
|
package/dist/types/run.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BaseMessage } from '@langchain/core/messages';
|
|
2
1
|
import { Command } from '@langchain/langgraph';
|
|
2
|
+
import { BaseMessage } from '@langchain/core/messages';
|
|
3
3
|
import type { MessageContentComplex } from '@langchain/core/messages';
|
|
4
4
|
import type { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
|
|
5
5
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
@@ -44,6 +44,10 @@ export declare class Run<_T extends t.BaseGraphState> {
|
|
|
44
44
|
private _interrupt;
|
|
45
45
|
/** Per-run sequence for batch-unique activity-label trace-seed fallbacks. */
|
|
46
46
|
private activityLabelSeq;
|
|
47
|
+
/** Per-run sequence for parent activity-phase trace and invocation ids. */
|
|
48
|
+
private activityPhaseLabelSeq;
|
|
49
|
+
/** Latest user turn used to keep detached phase roots conversation-shaped. */
|
|
50
|
+
private activityPhaseTraceInput?;
|
|
47
51
|
/** Distinguishes sibling forks started from the same explicit checkpoint. */
|
|
48
52
|
private checkpointForkSeq;
|
|
49
53
|
private _haltedReason;
|
|
@@ -238,7 +242,16 @@ export declare class Run<_T extends t.BaseGraphState> {
|
|
|
238
242
|
* comes from `lastAssistantText`, content from reasoning excerpts and
|
|
239
243
|
* tool entries.
|
|
240
244
|
*/
|
|
241
|
-
generateActivityLabel({ provider, clientOptions, entries, thinkingExcerpts, lastAssistantText, previousLabels, prompt, charLimit, chainOptions, traceSeed, agentId, }: t.RunActivityLabelOptions): Promise<{
|
|
245
|
+
generateActivityLabel({ provider, clientOptions, entries, thinkingExcerpts, lastAssistantText, lastAssistantPhase, previousLabels, prompt, charLimit, chainOptions, traceSeed, agentId, }: t.RunActivityLabelOptions): Promise<{
|
|
246
|
+
label?: string;
|
|
247
|
+
}>;
|
|
248
|
+
/**
|
|
249
|
+
* Generates one parent summary for two or more logical activities. The
|
|
250
|
+
* summary model is traced as a dedicated activity-phase agent root in the
|
|
251
|
+
* conversation session, with the model callback recorded as its generation
|
|
252
|
+
* child. No session id means no phase trace, avoiding orphan observations.
|
|
253
|
+
*/
|
|
254
|
+
generateActivityPhaseLabel({ provider, clientOptions, activities, totalActivityCount, assistantContext, closingTextPhase, prompt, charLimit, chainOptions, traceSeed, sourceRunId, sourceTraceId, responseId, phaseIndex, status, agentIds, }: t.RunActivityPhaseLabelOptions): Promise<{
|
|
242
255
|
label?: string;
|
|
243
256
|
}>;
|
|
244
257
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
2
|
+
import type { AssistantTextPhase } from '@/types/assistantPhase';
|
|
2
3
|
import type { ClientOptions } from '@/types/llm';
|
|
3
4
|
import type { Providers } from '@/common';
|
|
4
5
|
/** One tool call's contribution to the label payload (host-assembled). */
|
|
@@ -36,6 +37,11 @@ export type RunActivityLabelOptions = {
|
|
|
36
37
|
thinkingExcerpts?: string[];
|
|
37
38
|
/** Assistant's last text before the block (~200 chars), as intent context. */
|
|
38
39
|
lastAssistantText?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Provider-authored semantic phase for `lastAssistantText`. Hosts can use
|
|
42
|
+
* this to pass commentary as intent while excluding final-answer text.
|
|
43
|
+
*/
|
|
44
|
+
lastAssistantPhase?: AssistantTextPhase;
|
|
39
45
|
/**
|
|
40
46
|
* Headers already committed for earlier batches in this run (run order,
|
|
41
47
|
* most recent last). Continuity context: the prompt shows them so the new
|
|
@@ -59,3 +65,60 @@ export type RunActivityLabelOptions = {
|
|
|
59
65
|
*/
|
|
60
66
|
traceSeed?: string;
|
|
61
67
|
};
|
|
68
|
+
/** One logical activity contributing to a run-wide phase summary. */
|
|
69
|
+
export type ActivityPhaseEntry = {
|
|
70
|
+
/** Already committed child activity label, preferred over raw evidence. */
|
|
71
|
+
label?: string;
|
|
72
|
+
/** Raw fallback for a child label that was disabled, pending, or dropped. */
|
|
73
|
+
entries?: ActivityLabelToolEntry[];
|
|
74
|
+
/** Standalone or tool-attached reasoning excerpts for this activity. */
|
|
75
|
+
thinkingExcerpts?: string[];
|
|
76
|
+
/** Agent lane that produced the activity. */
|
|
77
|
+
agentId?: string;
|
|
78
|
+
/** Failed activities remain useful evidence and still count. */
|
|
79
|
+
status?: 'success' | 'partial' | 'error';
|
|
80
|
+
};
|
|
81
|
+
/** Options for `Run.generateActivityPhaseLabel`. */
|
|
82
|
+
export type RunActivityPhaseLabelOptions = {
|
|
83
|
+
provider: Providers;
|
|
84
|
+
clientOptions?: ClientOptions;
|
|
85
|
+
/**
|
|
86
|
+
* Logical activities in run order. The SDK requires at least two so hosts
|
|
87
|
+
* cannot accidentally spend a model call summarizing a single activity.
|
|
88
|
+
*/
|
|
89
|
+
activities: ActivityPhaseEntry[];
|
|
90
|
+
/** Full count when the host retained only bounded prompt evidence. */
|
|
91
|
+
totalActivityCount?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Bounded assistant commentary emitted inside the phase. Human messages
|
|
94
|
+
* must not be supplied through this low-scrutiny summarization path.
|
|
95
|
+
*/
|
|
96
|
+
assistantContext?: string[];
|
|
97
|
+
/** Semantic phase of the text boundary that closed the activity phase. */
|
|
98
|
+
closingTextPhase?: AssistantTextPhase;
|
|
99
|
+
/** Override for the dedicated phase-label system prompt. */
|
|
100
|
+
prompt?: string;
|
|
101
|
+
/** Per-evidence serialization cap for the prompt. Default 600. */
|
|
102
|
+
charLimit?: number;
|
|
103
|
+
/** LangChain runnable config carrier (signal, callbacks, thread/user ids). */
|
|
104
|
+
chainOptions?: Partial<RunnableConfig> & {
|
|
105
|
+
configurable?: Record<string, unknown>;
|
|
106
|
+
};
|
|
107
|
+
/** Deterministic seed for this phase label trace. */
|
|
108
|
+
traceSeed?: string;
|
|
109
|
+
/** Stable source run identifier recorded on the phase observation. */
|
|
110
|
+
sourceRunId?: string;
|
|
111
|
+
/** Source Langfuse trace id for linking this detached summary trace. */
|
|
112
|
+
sourceTraceId?: string;
|
|
113
|
+
/** Host response/message identifier recorded on the phase observation. */
|
|
114
|
+
responseId?: string;
|
|
115
|
+
/** Zero-based index of the phase within the source run. */
|
|
116
|
+
phaseIndex?: number;
|
|
117
|
+
/** Completion state recorded on the phase observation. */
|
|
118
|
+
status?: 'completed' | 'partial' | 'failed';
|
|
119
|
+
/**
|
|
120
|
+
* Contributing agents. Their Langfuse redaction policies are combined into
|
|
121
|
+
* the strictest union before any phase evidence enters the model or trace.
|
|
122
|
+
*/
|
|
123
|
+
agentIds?: string[];
|
|
124
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic phase attached to assistant text by Open Responses-compatible
|
|
3
|
+
* providers. Commentary is intermediate agent narration; final_answer marks
|
|
4
|
+
* the user-facing answer that closes the preceding activity phase.
|
|
5
|
+
*/
|
|
6
|
+
export type AssistantTextPhase = 'commentary' | 'final_answer';
|
|
@@ -5,7 +5,7 @@ import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
|
|
|
5
5
|
import type { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
6
6
|
import type { GoogleAIToolType } from '@langchain/google-common';
|
|
7
7
|
import type { SummarizationNodeInput, SummarizeCompleteEvent, SummarizationConfig, SummarizeStartEvent, SummarizeDeltaEvent } from '@/types/summarize';
|
|
8
|
-
import type { ToolMap, ToolEndEvent, GenericTool, LCTool, ToolExecuteBatchRequest } from '@/types/tools';
|
|
8
|
+
import type { ToolMap, ToolSessionMap, ToolEndEvent, GenericTool, LCTool, ToolExecuteBatchRequest } from '@/types/tools';
|
|
9
9
|
import type { RunStep, RunStepDeltaEvent, MessageDeltaEvent, ReasoningDeltaEvent } from '@/types/stream';
|
|
10
10
|
import type { TokenCounter, StreamLimits, StreamPreemption, TokenBudgetBreakdown } from '@/types/run';
|
|
11
11
|
import type { Providers, Callback, GraphNodeKeys } from '@/common';
|
|
@@ -680,6 +680,13 @@ export interface AgentInputs {
|
|
|
680
680
|
subagentConfigs?: SubagentConfigEntry[];
|
|
681
681
|
/** Maximum subagent nesting depth. Default 1 means top-level agents can spawn subagents but subagents cannot nest further. */
|
|
682
682
|
maxSubagentDepth?: number;
|
|
683
|
+
/**
|
|
684
|
+
* Initial tool-session state owned by this agent when it runs as a
|
|
685
|
+
* subagent. The executor copies these sessions into the isolated child
|
|
686
|
+
* graph before its first invocation. Top-level runs continue to use
|
|
687
|
+
* `RunConfig.initialSessions`.
|
|
688
|
+
*/
|
|
689
|
+
initialSessions?: ToolSessionMap;
|
|
683
690
|
/**
|
|
684
691
|
* Host-supplied tool instances that must execute IN-PROCESS inside the graph's
|
|
685
692
|
* ToolNode even when the run is event-driven (`toolDefinitions` non-empty). Each
|
|
@@ -3,6 +3,7 @@ import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
|
|
|
3
3
|
import type { LLMResult, Generation } from '@langchain/core/outputs';
|
|
4
4
|
import type { Command } from '@langchain/langgraph';
|
|
5
5
|
import type { AnthropicContentBlock } from '@/llm/anthropic/types';
|
|
6
|
+
import type { AssistantTextPhase } from '@/types/assistantPhase';
|
|
6
7
|
import type { SummarizeCompleteEvent } from '@/types/summarize';
|
|
7
8
|
import type { ToolEndEvent } from '@/types/tools';
|
|
8
9
|
import { StepTypes, ContentTypes, GraphEvents } from '@/common/enum';
|
|
@@ -80,6 +81,10 @@ export type MessageCreationDetails = {
|
|
|
80
81
|
type: StepTypes.MESSAGE_CREATION;
|
|
81
82
|
message_creation: {
|
|
82
83
|
message_id: string;
|
|
84
|
+
/** Content lane announced before its first delta. */
|
|
85
|
+
content_type?: ContentTypes.TEXT | ContentTypes.THINK;
|
|
86
|
+
/** Provider-authored assistant text phase, when available. */
|
|
87
|
+
phase?: AssistantTextPhase;
|
|
83
88
|
};
|
|
84
89
|
};
|
|
85
90
|
export type ToolEndData = {
|
|
@@ -323,6 +328,12 @@ export type MessageContentComplex = (ToolResultContent | ThinkingContentText | S
|
|
|
323
328
|
}) | (Record<string, any> & {
|
|
324
329
|
type?: never;
|
|
325
330
|
})) & {
|
|
331
|
+
/** Open Responses-compatible semantic phase for assistant text. */
|
|
332
|
+
phase?: AssistantTextPhase;
|
|
333
|
+
/** LangChain standard-content form of provider-specific block fields. */
|
|
334
|
+
extras?: {
|
|
335
|
+
phase?: AssistantTextPhase;
|
|
336
|
+
} & Record<string, unknown>;
|
|
326
337
|
tool_call_ids?: string[];
|
|
327
338
|
agentId?: string;
|
|
328
339
|
groupId?: number;
|
|
@@ -3,3 +3,4 @@ import type { Callbacks } from '@langchain/core/callbacks/manager';
|
|
|
3
3
|
export type CallbackEntry = BaseCallbackHandler | CallbackHandlerMethods;
|
|
4
4
|
export declare function appendCallbacks(callbacks: Callbacks | undefined, additions: readonly CallbackEntry[]): Callbacks;
|
|
5
5
|
export declare function findCallback(callbacks: Callbacks | undefined, predicate: (callback: CallbackEntry) => boolean): CallbackEntry | undefined;
|
|
6
|
+
export declare function filterCallbacks(callbacks: Callbacks | undefined, predicate: (callback: CallbackEntry) => boolean): Callbacks;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
4
4
|
"reova": {
|
|
5
5
|
"enabled": true,
|
|
6
6
|
"endpoint": "https://telemetry.reo.dev/data"
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
"uuid": "$uuid",
|
|
218
218
|
"ajv": "6.14.0",
|
|
219
219
|
"@opentelemetry/core": "^2.9.0",
|
|
220
|
-
"js-yaml": "^4.3.
|
|
220
|
+
"js-yaml": "^4.3.1",
|
|
221
221
|
"minimatch": "^10.2.2",
|
|
222
222
|
"test-exclude": "^8.0.0"
|
|
223
223
|
},
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
"dotenv": "^16.4.7",
|
|
252
252
|
"https-proxy-agent": "^7.0.6",
|
|
253
253
|
"mathjs": "^15.2.0",
|
|
254
|
-
"nanoid": "^3.3.
|
|
254
|
+
"nanoid": "^3.3.18",
|
|
255
255
|
"okapibm25": "^1.4.1",
|
|
256
256
|
"openai": "^6.46.0",
|
|
257
257
|
"reova": "^0.4.1",
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -61,6 +61,8 @@ import {
|
|
|
61
61
|
supportsBedrockToolCache,
|
|
62
62
|
isSyntheticProviderContextMessage,
|
|
63
63
|
getMessageId,
|
|
64
|
+
getMessageCreationContentMetadata,
|
|
65
|
+
splitAssistantTextContentByPhase,
|
|
64
66
|
makeIsDeferred,
|
|
65
67
|
partitionAndMarkAnthropicToolCache,
|
|
66
68
|
DEFAULT_RETAIN_RECENT_TURNS,
|
|
@@ -507,18 +509,25 @@ async function dispatchMessageCreationStep({
|
|
|
507
509
|
graph,
|
|
508
510
|
stepKey,
|
|
509
511
|
messageId,
|
|
512
|
+
content,
|
|
513
|
+
contentType,
|
|
510
514
|
metadata,
|
|
511
515
|
}: {
|
|
512
516
|
graph: Graph<t.BaseGraphState>;
|
|
513
517
|
stepKey: string;
|
|
514
518
|
messageId: string;
|
|
519
|
+
content?: string | t.MessageContentComplex[];
|
|
520
|
+
contentType?: ContentTypes.TEXT | ContentTypes.THINK;
|
|
515
521
|
metadata: Record<string, unknown>;
|
|
516
522
|
}): Promise<string> {
|
|
517
523
|
await graph.dispatchRunStep(
|
|
518
524
|
stepKey,
|
|
519
525
|
{
|
|
520
526
|
type: StepTypes.MESSAGE_CREATION,
|
|
521
|
-
message_creation: {
|
|
527
|
+
message_creation: {
|
|
528
|
+
message_id: messageId,
|
|
529
|
+
...getMessageCreationContentMetadata(content, contentType),
|
|
530
|
+
},
|
|
522
531
|
},
|
|
523
532
|
metadata
|
|
524
533
|
);
|
|
@@ -548,6 +557,7 @@ async function dispatchTextMessageContent({
|
|
|
548
557
|
graph,
|
|
549
558
|
stepKey,
|
|
550
559
|
messageId,
|
|
560
|
+
content: [contentPart],
|
|
551
561
|
metadata,
|
|
552
562
|
});
|
|
553
563
|
await graph.dispatchMessageDelta(
|
|
@@ -558,13 +568,24 @@ async function dispatchTextMessageContent({
|
|
|
558
568
|
}
|
|
559
569
|
return true;
|
|
560
570
|
}
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
571
|
+
const contentGroups = Array.isArray(content)
|
|
572
|
+
? splitAssistantTextContentByPhase(content)
|
|
573
|
+
: [content];
|
|
574
|
+
for (const contentGroup of contentGroups) {
|
|
575
|
+
const stepId = await dispatchMessageCreationStep({
|
|
576
|
+
graph,
|
|
577
|
+
stepKey,
|
|
578
|
+
messageId,
|
|
579
|
+
content: contentGroup,
|
|
580
|
+
contentType: ContentTypes.TEXT,
|
|
581
|
+
metadata,
|
|
582
|
+
});
|
|
583
|
+
await graph.dispatchMessageDelta(
|
|
584
|
+
stepId,
|
|
585
|
+
{ content: contentGroup },
|
|
586
|
+
metadata
|
|
587
|
+
);
|
|
588
|
+
}
|
|
568
589
|
return true;
|
|
569
590
|
}
|
|
570
591
|
|
|
@@ -599,7 +620,10 @@ async function dispatchReasoningContent({
|
|
|
599
620
|
stepKey,
|
|
600
621
|
{
|
|
601
622
|
type: StepTypes.MESSAGE_CREATION,
|
|
602
|
-
message_creation: {
|
|
623
|
+
message_creation: {
|
|
624
|
+
message_id: messageId,
|
|
625
|
+
content_type: ContentTypes.THINK,
|
|
626
|
+
},
|
|
603
627
|
},
|
|
604
628
|
metadata
|
|
605
629
|
);
|
|
@@ -275,6 +275,63 @@ describe('StandardGraph final response reasoning fallback', () => {
|
|
|
275
275
|
streamUsage: false,
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
+
it('starts a new message step when streamed assistant phases change', async () => {
|
|
279
|
+
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
280
|
+
const messageDeltas: t.MessageDeltaEvent[] = [];
|
|
281
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
282
|
+
const run = await Run.create<t.IState>({
|
|
283
|
+
runId: 'separate-streamed-assistant-phases',
|
|
284
|
+
graphConfig: {
|
|
285
|
+
type: 'standard',
|
|
286
|
+
llmConfig,
|
|
287
|
+
},
|
|
288
|
+
returnContent: true,
|
|
289
|
+
skipCleanup: true,
|
|
290
|
+
customHandlers: createReasoningHandlers(
|
|
291
|
+
aggregateContent,
|
|
292
|
+
reasoningDeltas,
|
|
293
|
+
messageDeltas
|
|
294
|
+
),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if (!run.Graph) {
|
|
298
|
+
throw new Error('Expected graph to be initialized');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
run.Graph.overrideModel = new StreamingReasoningModel([
|
|
302
|
+
new AIMessageChunk({
|
|
303
|
+
content: [
|
|
304
|
+
{ type: 'text', text: 'Checking the session.', phase: 'commentary' },
|
|
305
|
+
] as never,
|
|
306
|
+
}),
|
|
307
|
+
new AIMessageChunk({
|
|
308
|
+
content: [
|
|
309
|
+
{ type: 'text', text: 'The session is fixed.', phase: 'final_answer' },
|
|
310
|
+
] as never,
|
|
311
|
+
}),
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
await run.processStream(
|
|
315
|
+
{ messages: [new HumanMessage('fix the session')] },
|
|
316
|
+
config
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
expect(new Set(messageDeltas.map((delta) => delta.id)).size).toBe(2);
|
|
320
|
+
expect(contentParts).toEqual([
|
|
321
|
+
{ type: 'text', text: 'Checking the session.' },
|
|
322
|
+
{ type: 'text', text: 'The session is fixed.' },
|
|
323
|
+
]);
|
|
324
|
+
expect(
|
|
325
|
+
run.Graph.getRunSteps()
|
|
326
|
+
.filter((step) => step.stepDetails.type === StepTypes.MESSAGE_CREATION)
|
|
327
|
+
.map((step) =>
|
|
328
|
+
step.stepDetails.type === StepTypes.MESSAGE_CREATION
|
|
329
|
+
? step.stepDetails.message_creation.phase
|
|
330
|
+
: undefined
|
|
331
|
+
)
|
|
332
|
+
).toEqual(['commentary', 'final_answer']);
|
|
333
|
+
});
|
|
334
|
+
|
|
278
335
|
it('emits reasoning_content from invoke-only final responses', async () => {
|
|
279
336
|
const reasoningText = 'Need to inspect the Home Assistant tool state.';
|
|
280
337
|
const reasoningDeltas: t.ReasoningDeltaEvent[] = [];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAssistantTextPhase,
|
|
3
|
+
getMessageCreationContentMetadata,
|
|
4
|
+
splitAssistantTextContentByPhase,
|
|
5
|
+
} from './assistantPhase';
|
|
6
|
+
import { ContentTypes } from '@/common';
|
|
7
|
+
|
|
8
|
+
describe('assistant phase metadata', () => {
|
|
9
|
+
it('reads provider-native Open Responses phase metadata', () => {
|
|
10
|
+
const part = {
|
|
11
|
+
type: ContentTypes.TEXT,
|
|
12
|
+
text: '',
|
|
13
|
+
phase: 'commentary' as const,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
expect(getAssistantTextPhase(part)).toBe('commentary');
|
|
17
|
+
expect(getMessageCreationContentMetadata([part])).toEqual({
|
|
18
|
+
content_type: ContentTypes.TEXT,
|
|
19
|
+
phase: 'commentary',
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('reads LangChain standard-content extras', () => {
|
|
24
|
+
const part = {
|
|
25
|
+
type: ContentTypes.TEXT,
|
|
26
|
+
text: '',
|
|
27
|
+
extras: { phase: 'final_answer' as const },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
expect(getMessageCreationContentMetadata([part])).toEqual({
|
|
31
|
+
content_type: ContentTypes.TEXT,
|
|
32
|
+
phase: 'final_answer',
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('announces reasoning without inventing a text phase', () => {
|
|
37
|
+
expect(
|
|
38
|
+
getMessageCreationContentMetadata([
|
|
39
|
+
{ type: ContentTypes.THINK, think: 'checking' },
|
|
40
|
+
])
|
|
41
|
+
).toEqual({ content_type: ContentTypes.THINK });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('preserves every provider phase in a multi-block text chunk', () => {
|
|
45
|
+
const commentary = {
|
|
46
|
+
type: ContentTypes.TEXT,
|
|
47
|
+
text: 'I will inspect the session path.',
|
|
48
|
+
phase: 'commentary' as const,
|
|
49
|
+
};
|
|
50
|
+
const moreCommentary = {
|
|
51
|
+
type: ContentTypes.TEXT,
|
|
52
|
+
text: 'The refresh branch is stale.',
|
|
53
|
+
phase: 'commentary' as const,
|
|
54
|
+
};
|
|
55
|
+
const finalAnswer = {
|
|
56
|
+
type: ContentTypes.TEXT,
|
|
57
|
+
text: 'The refresh path is fixed.',
|
|
58
|
+
phase: 'final_answer' as const,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const groups = splitAssistantTextContentByPhase([
|
|
62
|
+
commentary,
|
|
63
|
+
moreCommentary,
|
|
64
|
+
finalAnswer,
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
expect(groups).toEqual([[commentary, moreCommentary], [finalAnswer]]);
|
|
68
|
+
expect(
|
|
69
|
+
groups.map((group) => getMessageCreationContentMetadata(group))
|
|
70
|
+
).toEqual([
|
|
71
|
+
{ content_type: ContentTypes.TEXT, phase: 'commentary' },
|
|
72
|
+
{ content_type: ContentTypes.TEXT, phase: 'final_answer' },
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AssistantTextPhase } from '@/types/assistantPhase';
|
|
2
|
+
import type { MessageContentComplex } from '@/types/stream';
|
|
3
|
+
import { ContentTypes } from '@/common';
|
|
4
|
+
|
|
5
|
+
export type MessageCreationContentMetadata = {
|
|
6
|
+
content_type?: ContentTypes.TEXT | ContentTypes.THINK;
|
|
7
|
+
phase?: AssistantTextPhase;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function toAssistantTextPhase(value: unknown): AssistantTextPhase | undefined {
|
|
11
|
+
return value === 'commentary' || value === 'final_answer' ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Reads both provider-native and LangChain standard-content phase fields. */
|
|
15
|
+
export function getAssistantTextPhase(
|
|
16
|
+
contentPart: MessageContentComplex
|
|
17
|
+
): AssistantTextPhase | undefined {
|
|
18
|
+
return (
|
|
19
|
+
toAssistantTextPhase(contentPart.phase) ??
|
|
20
|
+
toAssistantTextPhase(contentPart.extras?.phase)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Keeps provider-authored text phases in distinct message-creation steps.
|
|
26
|
+
* Open Responses may return commentary and final-answer blocks in one chunk;
|
|
27
|
+
* collapsing the array into one step would assign the first block's phase to
|
|
28
|
+
* every block and hide the boundary that closes an activity phase.
|
|
29
|
+
*/
|
|
30
|
+
export function splitAssistantTextContentByPhase(
|
|
31
|
+
content: MessageContentComplex[]
|
|
32
|
+
): MessageContentComplex[][] {
|
|
33
|
+
const groups: MessageContentComplex[][] = [];
|
|
34
|
+
for (const contentPart of content) {
|
|
35
|
+
const phase = getAssistantTextPhase(contentPart);
|
|
36
|
+
const currentGroup = groups.at(-1);
|
|
37
|
+
if (
|
|
38
|
+
currentGroup == null ||
|
|
39
|
+
getAssistantTextPhase(currentGroup[0]) !== phase
|
|
40
|
+
) {
|
|
41
|
+
groups.push([contentPart]);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
currentGroup.push(contentPart);
|
|
45
|
+
}
|
|
46
|
+
return groups;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isTextPart(contentPart: MessageContentComplex): boolean {
|
|
50
|
+
return contentPart.type?.startsWith(ContentTypes.TEXT) ?? false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isReasoningPart(contentPart: MessageContentComplex): boolean {
|
|
54
|
+
return (
|
|
55
|
+
contentPart.type === ContentTypes.THINK ||
|
|
56
|
+
(contentPart.type?.startsWith(ContentTypes.THINKING) ?? false) ||
|
|
57
|
+
(contentPart.type?.startsWith(ContentTypes.REASONING) ?? false) ||
|
|
58
|
+
(contentPart.type?.startsWith(ContentTypes.REASONING_CONTENT) ?? false) ||
|
|
59
|
+
contentPart.type === 'redacted_thinking'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Derives additive message-creation metadata before a content delta is
|
|
65
|
+
* dispatched. The fallback covers string-only providers whose semantic lane
|
|
66
|
+
* is tracked by the stream handler rather than represented on a block.
|
|
67
|
+
*/
|
|
68
|
+
export function getMessageCreationContentMetadata(
|
|
69
|
+
content: string | MessageContentComplex[] | undefined,
|
|
70
|
+
fallbackContentType?: ContentTypes.TEXT | ContentTypes.THINK
|
|
71
|
+
): MessageCreationContentMetadata {
|
|
72
|
+
if (!Array.isArray(content)) {
|
|
73
|
+
return fallbackContentType == null
|
|
74
|
+
? {}
|
|
75
|
+
: { content_type: fallbackContentType };
|
|
76
|
+
}
|
|
77
|
+
const textPart = content.find(isTextPart);
|
|
78
|
+
if (textPart != null) {
|
|
79
|
+
const phase = getAssistantTextPhase(textPart);
|
|
80
|
+
return {
|
|
81
|
+
content_type: ContentTypes.TEXT,
|
|
82
|
+
...(phase == null ? {} : { phase }),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (content.some(isReasoningPart)) {
|
|
86
|
+
return { content_type: ContentTypes.THINK };
|
|
87
|
+
}
|
|
88
|
+
return fallbackContentType == null
|
|
89
|
+
? {}
|
|
90
|
+
: { content_type: fallbackContentType };
|
|
91
|
+
}
|
package/src/messages/index.ts
CHANGED