@librechat/agents 3.4.3 → 3.4.5
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/hitl/askUserQuestions.cjs +66 -0
- package/dist/cjs/hitl/askUserQuestions.cjs.map +1 -0
- package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs +46 -0
- package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs.map +1 -0
- package/dist/cjs/hitl/index.cjs +2 -0
- 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 +11 -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/types/hitl.cjs +13 -0
- package/dist/cjs/types/hitl.cjs.map +1 -0
- 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/hitl/askUserQuestions.mjs +66 -0
- package/dist/esm/hitl/askUserQuestions.mjs.map +1 -0
- package/dist/esm/hitl/askUserQuestionsInterrupt.mjs +43 -0
- package/dist/esm/hitl/askUserQuestionsInterrupt.mjs.map +1 -0
- package/dist/esm/hitl/index.mjs +2 -0
- 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 +5 -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/types/hitl.mjs +13 -0
- package/dist/esm/types/hitl.mjs.map +1 -0
- package/dist/esm/utils/callbacks.mjs +8 -1
- package/dist/esm/utils/callbacks.mjs.map +1 -1
- package/dist/types/hitl/askUserQuestions.d.ts +24 -0
- package/dist/types/hitl/askUserQuestionsInterrupt.d.ts +11 -0
- package/dist/types/hitl/index.d.ts +2 -0
- 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/hitl.d.ts +31 -2
- 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 +2 -1
- package/src/graphs/Graph.ts +33 -9
- package/src/graphs/__tests__/Graph.reasoning.test.ts +57 -0
- package/src/hitl/askUserQuestions.ts +126 -0
- package/src/hitl/askUserQuestionsInterrupt.ts +115 -0
- package/src/hitl/index.ts +6 -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/specs/ask-user-questions.live.test.ts +185 -0
- package/src/specs/ask-user-questions.test.ts +293 -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/hitl.ts +36 -2
- package/src/types/index.ts +1 -0
- package/src/types/stream.ts +9 -0
- package/src/utils/callbacks.ts +21 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { tool } from '@langchain/core/tools';
|
|
3
|
+
import { describe, expect, it } from '@jest/globals';
|
|
4
|
+
import { AIMessage, ToolMessage } from '@langchain/core/messages';
|
|
5
|
+
import {
|
|
6
|
+
END,
|
|
7
|
+
START,
|
|
8
|
+
Command,
|
|
9
|
+
StateGraph,
|
|
10
|
+
MemorySaver,
|
|
11
|
+
isInterrupted,
|
|
12
|
+
MessagesAnnotation,
|
|
13
|
+
} from '@langchain/langgraph';
|
|
14
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
15
|
+
import type * as t from '@/types';
|
|
16
|
+
import {
|
|
17
|
+
ASK_USER_QUESTION_ID_PATTERN,
|
|
18
|
+
askUserQuestions,
|
|
19
|
+
isAskUserQuestionsInterrupt,
|
|
20
|
+
MAX_ASK_USER_QUESTIONS,
|
|
21
|
+
} from '@/hitl';
|
|
22
|
+
import { ToolNode } from '@/tools/ToolNode';
|
|
23
|
+
|
|
24
|
+
type MessagesUpdate = { messages: BaseMessage[] };
|
|
25
|
+
const questions = [
|
|
26
|
+
{
|
|
27
|
+
id: 'metric',
|
|
28
|
+
header: 'Metric',
|
|
29
|
+
question: 'Which performance cost should be analyzed?',
|
|
30
|
+
options: [
|
|
31
|
+
{ label: 'ClickHouse workload', value: 'workload' },
|
|
32
|
+
{ label: 'Website experience', value: 'website' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'window',
|
|
37
|
+
header: 'Window',
|
|
38
|
+
question: 'Which time window should be used?',
|
|
39
|
+
options: [
|
|
40
|
+
{ label: 'Last 24 hours', value: '24h' },
|
|
41
|
+
{ label: 'Last 7 days', value: '7d' },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
] satisfies t.AskUserQuestionBatchItem[];
|
|
45
|
+
|
|
46
|
+
const questionSchema = z.object({
|
|
47
|
+
id: z.string().regex(ASK_USER_QUESTION_ID_PATTERN),
|
|
48
|
+
header: z.string().optional(),
|
|
49
|
+
question: z.string(),
|
|
50
|
+
options: z
|
|
51
|
+
.array(z.object({ label: z.string(), value: z.string() }))
|
|
52
|
+
.optional(),
|
|
53
|
+
});
|
|
54
|
+
const askUserQuestionsSchema = z.object({
|
|
55
|
+
questions: z.array(questionSchema).min(1).max(MAX_ASK_USER_QUESTIONS),
|
|
56
|
+
});
|
|
57
|
+
type AskUserQuestionsInput = z.infer<typeof askUserQuestionsSchema>;
|
|
58
|
+
|
|
59
|
+
function buildGraph() {
|
|
60
|
+
const askTool = tool(
|
|
61
|
+
async (input: AskUserQuestionsInput, config) => {
|
|
62
|
+
const resolution = askUserQuestions(input, {
|
|
63
|
+
toolCallId: config.toolCall?.id,
|
|
64
|
+
});
|
|
65
|
+
return JSON.stringify(resolution);
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'ask_user_question',
|
|
69
|
+
description: 'Ask several related questions in one interaction.',
|
|
70
|
+
schema: askUserQuestionsSchema,
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
const node = new ToolNode({
|
|
74
|
+
tools: [askTool],
|
|
75
|
+
directToolNames: new Set(['ask_user_question']),
|
|
76
|
+
interruptingToolNames: new Set(['ask_user_question']),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return new StateGraph(MessagesAnnotation)
|
|
80
|
+
.addNode(
|
|
81
|
+
'agent',
|
|
82
|
+
(): MessagesUpdate => ({
|
|
83
|
+
messages: [
|
|
84
|
+
new AIMessage({
|
|
85
|
+
content: '',
|
|
86
|
+
tool_calls: [
|
|
87
|
+
{
|
|
88
|
+
id: 'batched-ask-call',
|
|
89
|
+
name: 'ask_user_question',
|
|
90
|
+
args: { questions },
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
}),
|
|
94
|
+
],
|
|
95
|
+
})
|
|
96
|
+
)
|
|
97
|
+
.addNode('tools', node)
|
|
98
|
+
.addEdge(START, 'agent')
|
|
99
|
+
.addEdge('agent', 'tools')
|
|
100
|
+
.addEdge('tools', END)
|
|
101
|
+
.compile({
|
|
102
|
+
checkpointer: new MemorySaver(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe('askUserQuestions', () => {
|
|
107
|
+
it('pauses once for a batch and resumes with keyed answers', async () => {
|
|
108
|
+
const graph = buildGraph();
|
|
109
|
+
const config = { configurable: { thread_id: 'batched-questions' } };
|
|
110
|
+
|
|
111
|
+
const first = await graph.invoke({ messages: [] }, config);
|
|
112
|
+
expect(isInterrupted<t.HumanInterruptPayload>(first)).toBe(true);
|
|
113
|
+
if (!isInterrupted<t.HumanInterruptPayload>(first)) {
|
|
114
|
+
throw new Error('expected batched question interrupt');
|
|
115
|
+
}
|
|
116
|
+
expect(first.__interrupt__).toHaveLength(1);
|
|
117
|
+
const payload = first.__interrupt__[0].value;
|
|
118
|
+
expect(isAskUserQuestionsInterrupt(payload)).toBe(true);
|
|
119
|
+
expect(payload).toMatchObject({
|
|
120
|
+
type: 'ask_user_question',
|
|
121
|
+
tool_call_id: 'batched-ask-call',
|
|
122
|
+
question: { question: questions[0].question },
|
|
123
|
+
questions,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const answers: t.AskUserQuestionsResolution = {
|
|
127
|
+
answers: { metric: 'workload', window: '7d' },
|
|
128
|
+
};
|
|
129
|
+
const second = (await graph.invoke(
|
|
130
|
+
new Command({ resume: answers }),
|
|
131
|
+
config
|
|
132
|
+
)) as MessagesUpdate;
|
|
133
|
+
const result = second.messages.find(
|
|
134
|
+
(message): message is ToolMessage =>
|
|
135
|
+
message.getType() === 'tool' &&
|
|
136
|
+
(message as ToolMessage).name === 'ask_user_question'
|
|
137
|
+
);
|
|
138
|
+
expect(result).toBeDefined();
|
|
139
|
+
expect(JSON.parse(String(result!.content))).toEqual(answers);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('rejects duplicate question ids before raising an interrupt', () => {
|
|
143
|
+
const request: t.AskUserQuestionsRequest = {
|
|
144
|
+
questions: [questions[0], { ...questions[1], id: questions[0].id }],
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
expect(() => askUserQuestions(request)).toThrow(
|
|
148
|
+
'requires unique question ids'
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('distinguishes singular ask payloads from batched payloads', () => {
|
|
153
|
+
const sparseOptions: unknown[] = [];
|
|
154
|
+
sparseOptions.length = 2;
|
|
155
|
+
|
|
156
|
+
expect(
|
|
157
|
+
isAskUserQuestionsInterrupt({
|
|
158
|
+
type: 'ask_user_question',
|
|
159
|
+
question: { question: 'Proceed?' },
|
|
160
|
+
})
|
|
161
|
+
).toBe(false);
|
|
162
|
+
expect(
|
|
163
|
+
isAskUserQuestionsInterrupt({
|
|
164
|
+
type: 'ask_user_question',
|
|
165
|
+
question: { question: 'Proceed?' },
|
|
166
|
+
questions: [],
|
|
167
|
+
})
|
|
168
|
+
).toBe(false);
|
|
169
|
+
expect(
|
|
170
|
+
isAskUserQuestionsInterrupt({
|
|
171
|
+
type: 'ask_user_question',
|
|
172
|
+
question: { question: 'Proceed?' },
|
|
173
|
+
questions: [null],
|
|
174
|
+
})
|
|
175
|
+
).toBe(false);
|
|
176
|
+
expect(
|
|
177
|
+
isAskUserQuestionsInterrupt({
|
|
178
|
+
type: 'ask_user_question',
|
|
179
|
+
question: { question: 'Proceed?' },
|
|
180
|
+
questions: [{ id: '__proto__', question: 'Unsafe key?' }],
|
|
181
|
+
})
|
|
182
|
+
).toBe(false);
|
|
183
|
+
expect(
|
|
184
|
+
isAskUserQuestionsInterrupt({
|
|
185
|
+
type: 'ask_user_question',
|
|
186
|
+
question: { question: 'Proceed?' },
|
|
187
|
+
questions: Array.from(
|
|
188
|
+
{ length: MAX_ASK_USER_QUESTIONS + 1 },
|
|
189
|
+
(_, index) => ({
|
|
190
|
+
id: `question-${index}`,
|
|
191
|
+
question: `Question ${index}?`,
|
|
192
|
+
})
|
|
193
|
+
),
|
|
194
|
+
})
|
|
195
|
+
).toBe(false);
|
|
196
|
+
expect(
|
|
197
|
+
isAskUserQuestionsInterrupt({
|
|
198
|
+
type: 'ask_user_question',
|
|
199
|
+
question: { question: 'Proceed?' },
|
|
200
|
+
questions: [
|
|
201
|
+
{
|
|
202
|
+
id: 'choice',
|
|
203
|
+
question: 'Choose?',
|
|
204
|
+
options: [{ label: 'Missing value' }],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
})
|
|
208
|
+
).toBe(false);
|
|
209
|
+
expect(
|
|
210
|
+
isAskUserQuestionsInterrupt({
|
|
211
|
+
type: 'ask_user_question',
|
|
212
|
+
question: { question: 'Proceed?' },
|
|
213
|
+
questions: [
|
|
214
|
+
{
|
|
215
|
+
id: 'choice',
|
|
216
|
+
question: 'Choose?',
|
|
217
|
+
options: sparseOptions,
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
})
|
|
221
|
+
).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('returns a tool error when a resumed batch omits an answer', async () => {
|
|
225
|
+
const graph = buildGraph();
|
|
226
|
+
const config = { configurable: { thread_id: 'incomplete-answers' } };
|
|
227
|
+
|
|
228
|
+
await graph.invoke({ messages: [] }, config);
|
|
229
|
+
const resumed = (await graph.invoke(
|
|
230
|
+
new Command({ resume: { answers: { metric: 'workload' } } }),
|
|
231
|
+
config
|
|
232
|
+
)) as MessagesUpdate;
|
|
233
|
+
const result = resumed.messages.find(
|
|
234
|
+
(message): message is ToolMessage =>
|
|
235
|
+
message.getType() === 'tool' &&
|
|
236
|
+
(message as ToolMessage).name === 'ask_user_question'
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
expect(result?.status).toBe('error');
|
|
240
|
+
expect(String(result?.content)).toContain(
|
|
241
|
+
'requires a string answer for question id "window"'
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('rejects empty question ids before raising an interrupt', () => {
|
|
246
|
+
const request: t.AskUserQuestionsRequest = {
|
|
247
|
+
questions: [{ ...questions[0], id: ' ' }],
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
expect(() => askUserQuestions(request)).toThrow(
|
|
251
|
+
'requires each question id to match'
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('rejects unsafe answer-map keys before raising an interrupt', () => {
|
|
256
|
+
const request: t.AskUserQuestionsRequest = {
|
|
257
|
+
questions: [{ ...questions[0], id: '__proto__' }],
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
expect(() => askUserQuestions(request)).toThrow(
|
|
261
|
+
'requires each question id to match'
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('rejects sparse option arrays before raising an interrupt', () => {
|
|
266
|
+
const sparseOptions: t.AskUserQuestionOption[] = [];
|
|
267
|
+
sparseOptions.length = 2;
|
|
268
|
+
const request: t.AskUserQuestionsRequest = {
|
|
269
|
+
questions: [{ ...questions[0], options: sparseOptions }],
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
expect(() => askUserQuestions(request)).toThrow(
|
|
273
|
+
'requires each question and option to have valid string fields'
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('rejects batches larger than four questions', () => {
|
|
278
|
+
expect(MAX_ASK_USER_QUESTIONS).toBe(4);
|
|
279
|
+
const request: t.AskUserQuestionsRequest = {
|
|
280
|
+
questions: [
|
|
281
|
+
questions[0],
|
|
282
|
+
questions[1],
|
|
283
|
+
{ ...questions[0], id: 'third' },
|
|
284
|
+
{ ...questions[0], id: 'fourth' },
|
|
285
|
+
{ ...questions[0], id: 'fifth' },
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
expect(() => askUserQuestions(request)).toThrow(
|
|
290
|
+
'accepts at most 4 questions'
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
});
|
package/src/stream.ts
CHANGED
|
@@ -3,8 +3,19 @@ import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
|
|
|
3
3
|
import type { ChatOpenAIReasoningSummary } from '@langchain/openai';
|
|
4
4
|
import type { AIMessageChunk } from '@langchain/core/messages';
|
|
5
5
|
import type { AgentContext } from '@/agents/AgentContext';
|
|
6
|
+
import type { RunBreakerScope } from '@/llm/streamLimits';
|
|
6
7
|
import type { StandardGraph } from '@/graphs';
|
|
7
8
|
import type * as t from '@/types';
|
|
9
|
+
import {
|
|
10
|
+
claimStreamLimitCharge,
|
|
11
|
+
combineCompleteToolCalls,
|
|
12
|
+
enforceCompleteToolCallArgLimit,
|
|
13
|
+
enforceStreamedToolCallArgLimit,
|
|
14
|
+
enforceStreamDeltaEventLimit,
|
|
15
|
+
requiresStreamLimitAccounting,
|
|
16
|
+
StreamLimitExceededError,
|
|
17
|
+
STREAM_LIMIT_EPOCH_KEY,
|
|
18
|
+
} from '@/llm/streamLimits';
|
|
8
19
|
import {
|
|
9
20
|
getStreamedToolCallSeal,
|
|
10
21
|
getStreamedToolCallAdapter,
|
|
@@ -21,6 +32,10 @@ import {
|
|
|
21
32
|
CODE_EXECUTION_TOOLS,
|
|
22
33
|
LOCAL_CODING_BUNDLE_NAMES,
|
|
23
34
|
} from '@/common';
|
|
35
|
+
import {
|
|
36
|
+
getMessageCreationContentMetadata,
|
|
37
|
+
splitAssistantTextContentByPhase,
|
|
38
|
+
} from '@/messages/assistantPhase';
|
|
24
39
|
import {
|
|
25
40
|
buildToolExecutionRequestPlan,
|
|
26
41
|
coerceRecordArgs,
|
|
@@ -39,17 +54,6 @@ import {
|
|
|
39
54
|
calculateMaxToolResultChars,
|
|
40
55
|
truncateToolResultContent,
|
|
41
56
|
} from '@/utils/truncation';
|
|
42
|
-
import type { RunBreakerScope } from '@/llm/streamLimits';
|
|
43
|
-
import {
|
|
44
|
-
claimStreamLimitCharge,
|
|
45
|
-
combineCompleteToolCalls,
|
|
46
|
-
enforceCompleteToolCallArgLimit,
|
|
47
|
-
enforceStreamedToolCallArgLimit,
|
|
48
|
-
enforceStreamDeltaEventLimit,
|
|
49
|
-
requiresStreamLimitAccounting,
|
|
50
|
-
StreamLimitExceededError,
|
|
51
|
-
STREAM_LIMIT_EPOCH_KEY,
|
|
52
|
-
} from '@/llm/streamLimits';
|
|
53
57
|
import { resolveToolOutcome, outcomeFieldsFromResult } from '@/tools/intentArg';
|
|
54
58
|
import { TOOL_OUTPUT_REF_PATTERN } from '@/tools/toolOutputReferences';
|
|
55
59
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
@@ -521,10 +525,14 @@ function shouldStartFreshMessageStepAfterGoogleServerSideTool({
|
|
|
521
525
|
async function dispatchMessageCreationStep({
|
|
522
526
|
graph,
|
|
523
527
|
stepKey,
|
|
528
|
+
content,
|
|
529
|
+
contentType,
|
|
524
530
|
metadata,
|
|
525
531
|
}: {
|
|
526
532
|
graph: StandardGraph;
|
|
527
533
|
stepKey: string;
|
|
534
|
+
content?: string | t.MessageContentComplex[];
|
|
535
|
+
contentType?: ContentTypes.TEXT | ContentTypes.THINK;
|
|
528
536
|
metadata?: Record<string, unknown>;
|
|
529
537
|
}): Promise<string> {
|
|
530
538
|
const messageId = getMessageId(stepKey, graph, true) ?? '';
|
|
@@ -534,6 +542,7 @@ async function dispatchMessageCreationStep({
|
|
|
534
542
|
type: StepTypes.MESSAGE_CREATION,
|
|
535
543
|
message_creation: {
|
|
536
544
|
message_id: messageId,
|
|
545
|
+
...getMessageCreationContentMetadata(content, contentType),
|
|
537
546
|
},
|
|
538
547
|
},
|
|
539
548
|
metadata
|
|
@@ -555,6 +564,7 @@ async function dispatchMessageContentParts({
|
|
|
555
564
|
const currentStepId = await dispatchMessageCreationStep({
|
|
556
565
|
graph,
|
|
557
566
|
stepKey,
|
|
567
|
+
content: [contentPart],
|
|
558
568
|
metadata,
|
|
559
569
|
});
|
|
560
570
|
if (isGoogleServerSideToolContentPart(contentPart)) {
|
|
@@ -587,6 +597,8 @@ async function dispatchReasoningContentParts({
|
|
|
587
597
|
const currentStepId = await dispatchMessageCreationStep({
|
|
588
598
|
graph,
|
|
589
599
|
stepKey,
|
|
600
|
+
content,
|
|
601
|
+
contentType: ContentTypes.THINK,
|
|
590
602
|
metadata,
|
|
591
603
|
});
|
|
592
604
|
await graph.dispatchReasoningDelta(
|
|
@@ -1811,14 +1823,53 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
1811
1823
|
return;
|
|
1812
1824
|
}
|
|
1813
1825
|
|
|
1826
|
+
if (Array.isArray(content) && content.every(isTextContentPart)) {
|
|
1827
|
+
const contentGroups = splitAssistantTextContentByPhase(content);
|
|
1828
|
+
const currentStepId = graph.stepKeyIds?.get(stepKey)?.at(-1);
|
|
1829
|
+
const currentStep =
|
|
1830
|
+
currentStepId == null ? undefined : graph.getRunStep(currentStepId);
|
|
1831
|
+
const currentPhase =
|
|
1832
|
+
currentStep?.stepDetails.type === StepTypes.MESSAGE_CREATION
|
|
1833
|
+
? currentStep.stepDetails.message_creation.phase
|
|
1834
|
+
: undefined;
|
|
1835
|
+
const nextPhase = getMessageCreationContentMetadata(
|
|
1836
|
+
contentGroups[0]
|
|
1837
|
+
).phase;
|
|
1838
|
+
const phaseChanged =
|
|
1839
|
+
currentPhase != null &&
|
|
1840
|
+
nextPhase != null &&
|
|
1841
|
+
currentPhase !== nextPhase;
|
|
1842
|
+
if (contentGroups.length > 1 || phaseChanged) {
|
|
1843
|
+
for (const contentGroup of contentGroups) {
|
|
1844
|
+
const currentStepId = await dispatchMessageCreationStep({
|
|
1845
|
+
graph,
|
|
1846
|
+
stepKey,
|
|
1847
|
+
content: contentGroup,
|
|
1848
|
+
metadata,
|
|
1849
|
+
});
|
|
1850
|
+
await graph.dispatchMessageDelta(
|
|
1851
|
+
currentStepId,
|
|
1852
|
+
{ content: contentGroup },
|
|
1853
|
+
metadata
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1814
1860
|
const message_id = getMessageId(stepKey, graph) ?? '';
|
|
1815
1861
|
if (message_id) {
|
|
1862
|
+
const fallbackContentType =
|
|
1863
|
+
agentContext.currentTokenType === ContentTypes.TEXT
|
|
1864
|
+
? ContentTypes.TEXT
|
|
1865
|
+
: ContentTypes.THINK;
|
|
1816
1866
|
await graph.dispatchRunStep(
|
|
1817
1867
|
stepKey,
|
|
1818
1868
|
{
|
|
1819
1869
|
type: StepTypes.MESSAGE_CREATION,
|
|
1820
1870
|
message_creation: {
|
|
1821
1871
|
message_id,
|
|
1872
|
+
...getMessageCreationContentMetadata(content, fallbackContentType),
|
|
1822
1873
|
},
|
|
1823
1874
|
},
|
|
1824
1875
|
metadata
|
|
@@ -1835,7 +1886,12 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
1835
1886
|
content,
|
|
1836
1887
|
})
|
|
1837
1888
|
) {
|
|
1838
|
-
stepId = await dispatchMessageCreationStep({
|
|
1889
|
+
stepId = await dispatchMessageCreationStep({
|
|
1890
|
+
graph,
|
|
1891
|
+
stepKey,
|
|
1892
|
+
content,
|
|
1893
|
+
metadata,
|
|
1894
|
+
});
|
|
1839
1895
|
runStep = graph.getRunStep(stepId);
|
|
1840
1896
|
}
|
|
1841
1897
|
if (!runStep) {
|
|
@@ -1906,6 +1962,7 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
1906
1962
|
type: StepTypes.MESSAGE_CREATION,
|
|
1907
1963
|
message_creation: {
|
|
1908
1964
|
message_id,
|
|
1965
|
+
content_type: ContentTypes.TEXT,
|
|
1909
1966
|
},
|
|
1910
1967
|
},
|
|
1911
1968
|
metadata
|