@librechat/agents 2.2.0 → 2.2.2
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/common/enum.cjs +4 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +8 -7
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +19 -8
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/{messages.cjs → messages/core.cjs} +2 -2
- package/dist/cjs/messages/core.cjs.map +1 -0
- package/dist/cjs/messages/format.cjs +313 -0
- package/dist/cjs/messages/format.cjs.map +1 -0
- package/dist/cjs/messages/transformers.cjs +318 -0
- package/dist/cjs/messages/transformers.cjs.map +1 -0
- package/dist/cjs/messages/trimMessagesFactory.cjs +129 -0
- package/dist/cjs/messages/trimMessagesFactory.cjs.map +1 -0
- package/dist/cjs/stream.cjs +13 -0
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +4 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +2 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/{messages.mjs → messages/core.mjs} +2 -2
- package/dist/esm/messages/core.mjs.map +1 -0
- package/dist/esm/messages/format.mjs +306 -0
- package/dist/esm/messages/format.mjs.map +1 -0
- package/dist/esm/messages/transformers.mjs +316 -0
- package/dist/esm/messages/transformers.mjs.map +1 -0
- package/dist/esm/messages/trimMessagesFactory.mjs +127 -0
- package/dist/esm/messages/trimMessagesFactory.mjs.map +1 -0
- package/dist/esm/stream.mjs +13 -0
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +5 -1
- package/dist/types/messages/format.d.ts +111 -0
- package/dist/types/messages/index.d.ts +4 -0
- package/dist/types/messages/transformers.d.ts +320 -0
- package/dist/types/messages/trimMessagesFactory.d.ts +37 -0
- package/dist/types/types/stream.d.ts +9 -1
- package/package.json +1 -1
- package/src/common/enum.ts +4 -0
- package/src/messages/format.ts +433 -0
- package/src/messages/formatAgentMessages.test.ts +628 -0
- package/src/messages/formatMessage.test.ts +277 -0
- package/src/messages/index.ts +4 -0
- package/src/messages/transformers.ts +786 -0
- package/src/messages/trimMessagesFactory.test.ts +331 -0
- package/src/messages/trimMessagesFactory.ts +140 -0
- package/src/stream.ts +15 -1
- package/src/types/stream.ts +10 -1
- package/dist/cjs/messages.cjs.map +0 -1
- package/dist/esm/messages.mjs.map +0 -1
- /package/dist/types/{messages.d.ts → messages/core.d.ts} +0 -0
- /package/src/{messages.ts → messages/core.ts} +0 -0
|
@@ -114,6 +114,14 @@ export interface ExtendedMessageContent {
|
|
|
114
114
|
id?: string;
|
|
115
115
|
name?: string;
|
|
116
116
|
}
|
|
117
|
+
export type AgentUpdate = {
|
|
118
|
+
type: ContentTypes.AGENT_UPDATE;
|
|
119
|
+
agent_update: {
|
|
120
|
+
index: number;
|
|
121
|
+
runId: string;
|
|
122
|
+
agentId: string;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
117
125
|
/**
|
|
118
126
|
* Represents a message delta i.e. any changed fields on a message during
|
|
119
127
|
* streaming.
|
|
@@ -194,7 +202,7 @@ export type BedrockReasoningContentText = {
|
|
|
194
202
|
signature?: string;
|
|
195
203
|
};
|
|
196
204
|
};
|
|
197
|
-
export type MessageContentComplex = (ThinkingContentText | ReasoningContentText | MessageContentText | MessageContentImageUrl | (Record<string, any> & {
|
|
205
|
+
export type MessageContentComplex = (ThinkingContentText | AgentUpdate | ReasoningContentText | MessageContentText | MessageContentImageUrl | (Record<string, any> & {
|
|
198
206
|
type?: 'text' | 'image_url' | 'think' | 'thinking' | string;
|
|
199
207
|
}) | (Record<string, any> & {
|
|
200
208
|
type?: never;
|
package/package.json
CHANGED
package/src/common/enum.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
export enum GraphEvents {
|
|
8
8
|
/* Custom Events */
|
|
9
9
|
|
|
10
|
+
/** [Custom] Agent update event in multi-agent graph/workflow */
|
|
11
|
+
ON_AGENT_UPDATE = 'on_agent_update',
|
|
10
12
|
/** [Custom] Delta event for run steps (message creation and tool calls) */
|
|
11
13
|
ON_RUN_STEP = 'on_run_step',
|
|
12
14
|
/** [Custom] Delta event for run steps (tool calls) */
|
|
@@ -116,6 +118,8 @@ export enum ContentTypes {
|
|
|
116
118
|
THINKING = 'thinking',
|
|
117
119
|
/** Bedrock */
|
|
118
120
|
REASONING_CONTENT = 'reasoning_content',
|
|
121
|
+
/** Multi-Agent Switch */
|
|
122
|
+
AGENT_UPDATE = 'agent_update',
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
export enum ToolCallTypes {
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { ToolMessage, BaseMessage } from '@langchain/core/messages';
|
|
2
|
+
import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
3
|
+
import { MessageContentImageUrl } from '@langchain/core/messages';
|
|
4
|
+
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
5
|
+
import type { MessageContentComplex } from '@/types';
|
|
6
|
+
import { Providers, ContentTypes } from '@/common';
|
|
7
|
+
|
|
8
|
+
interface VisionMessageParams {
|
|
9
|
+
message: {
|
|
10
|
+
role: string;
|
|
11
|
+
content: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
};
|
|
15
|
+
image_urls: MessageContentImageUrl[];
|
|
16
|
+
endpoint?: Providers;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Formats a message to OpenAI Vision API payload format.
|
|
21
|
+
*
|
|
22
|
+
* @param {VisionMessageParams} params - The parameters for formatting.
|
|
23
|
+
* @returns {Object} - The formatted message.
|
|
24
|
+
*/
|
|
25
|
+
export const formatVisionMessage = ({ message, image_urls, endpoint }: VisionMessageParams): {
|
|
26
|
+
role: string;
|
|
27
|
+
content: MessageContentComplex[];
|
|
28
|
+
name?: string;
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
} => {
|
|
31
|
+
// Create a new object to avoid mutating the input
|
|
32
|
+
const result: {
|
|
33
|
+
role: string;
|
|
34
|
+
content: MessageContentComplex[];
|
|
35
|
+
name?: string;
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
} = {
|
|
38
|
+
...message,
|
|
39
|
+
content: [] as MessageContentComplex[]
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (endpoint === Providers.ANTHROPIC) {
|
|
43
|
+
result.content = [
|
|
44
|
+
...image_urls,
|
|
45
|
+
{ type: ContentTypes.TEXT, text: message.content }
|
|
46
|
+
] as MessageContentComplex[];
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
result.content = [
|
|
51
|
+
{ type: ContentTypes.TEXT, text: message.content },
|
|
52
|
+
...image_urls
|
|
53
|
+
] as MessageContentComplex[];
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
interface MessageInput {
|
|
59
|
+
role?: string;
|
|
60
|
+
_name?: string;
|
|
61
|
+
sender?: string;
|
|
62
|
+
text?: string;
|
|
63
|
+
content?: string | MessageContentComplex[];
|
|
64
|
+
image_urls?: MessageContentImageUrl[];
|
|
65
|
+
lc_id?: string[];
|
|
66
|
+
[key: string]: any;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface FormatMessageParams {
|
|
70
|
+
message: MessageInput;
|
|
71
|
+
userName?: string;
|
|
72
|
+
assistantName?: string;
|
|
73
|
+
endpoint?: Providers;
|
|
74
|
+
langChain?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface FormattedMessage {
|
|
78
|
+
role: string;
|
|
79
|
+
content: string | MessageContentComplex[];
|
|
80
|
+
name?: string;
|
|
81
|
+
[key: string]: any;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Formats a message to OpenAI payload format based on the provided options.
|
|
86
|
+
*
|
|
87
|
+
* @param {FormatMessageParams} params - The parameters for formatting.
|
|
88
|
+
* @returns {FormattedMessage | HumanMessage | AIMessage | SystemMessage} - The formatted message.
|
|
89
|
+
*/
|
|
90
|
+
export const formatMessage = ({
|
|
91
|
+
message,
|
|
92
|
+
userName,
|
|
93
|
+
assistantName,
|
|
94
|
+
endpoint,
|
|
95
|
+
langChain = false
|
|
96
|
+
}: FormatMessageParams): FormattedMessage | HumanMessage | AIMessage | SystemMessage => {
|
|
97
|
+
let { role: _role, _name, sender, text, content: _content, lc_id } = message;
|
|
98
|
+
if (lc_id && lc_id[2] && !langChain) {
|
|
99
|
+
const roleMapping: Record<string, string> = {
|
|
100
|
+
SystemMessage: 'system',
|
|
101
|
+
HumanMessage: 'user',
|
|
102
|
+
AIMessage: 'assistant',
|
|
103
|
+
};
|
|
104
|
+
_role = roleMapping[lc_id[2]] || _role;
|
|
105
|
+
}
|
|
106
|
+
const role = _role ?? (sender && sender?.toLowerCase() === 'user' ? 'user' : 'assistant');
|
|
107
|
+
const content = _content ?? text ?? '';
|
|
108
|
+
const formattedMessage: FormattedMessage = {
|
|
109
|
+
role,
|
|
110
|
+
content,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const { image_urls } = message;
|
|
114
|
+
if (Array.isArray(image_urls) && image_urls.length > 0 && role === 'user') {
|
|
115
|
+
return formatVisionMessage({
|
|
116
|
+
message: {
|
|
117
|
+
...formattedMessage,
|
|
118
|
+
content: typeof formattedMessage.content === 'string' ? formattedMessage.content : ''
|
|
119
|
+
},
|
|
120
|
+
image_urls,
|
|
121
|
+
endpoint,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (_name) {
|
|
126
|
+
formattedMessage.name = _name;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (userName && formattedMessage.role === 'user') {
|
|
130
|
+
formattedMessage.name = userName;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (assistantName && formattedMessage.role === 'assistant') {
|
|
134
|
+
formattedMessage.name = assistantName;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (formattedMessage.name) {
|
|
138
|
+
// Conform to API regex: ^[a-zA-Z0-9_-]{1,64}$
|
|
139
|
+
// https://community.openai.com/t/the-format-of-the-name-field-in-the-documentation-is-incorrect/175684/2
|
|
140
|
+
formattedMessage.name = formattedMessage.name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
141
|
+
|
|
142
|
+
if (formattedMessage.name.length > 64) {
|
|
143
|
+
formattedMessage.name = formattedMessage.name.substring(0, 64);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!langChain) {
|
|
148
|
+
return formattedMessage;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (role === 'user') {
|
|
152
|
+
return new HumanMessage(formattedMessage);
|
|
153
|
+
} else if (role === 'assistant') {
|
|
154
|
+
return new AIMessage(formattedMessage);
|
|
155
|
+
} else {
|
|
156
|
+
return new SystemMessage(formattedMessage);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Formats an array of messages for LangChain.
|
|
162
|
+
*
|
|
163
|
+
* @param {Array<MessageInput>} messages - The array of messages to format.
|
|
164
|
+
* @param {Omit<FormatMessageParams, 'message' | 'langChain'>} formatOptions - The options for formatting each message.
|
|
165
|
+
* @returns {Array<HumanMessage | AIMessage | SystemMessage>} - The array of formatted LangChain messages.
|
|
166
|
+
*/
|
|
167
|
+
export const formatLangChainMessages = (
|
|
168
|
+
messages: Array<MessageInput>,
|
|
169
|
+
formatOptions: Omit<FormatMessageParams, 'message' | 'langChain'>
|
|
170
|
+
): Array<HumanMessage | AIMessage | SystemMessage> => {
|
|
171
|
+
return messages.map((msg) => {
|
|
172
|
+
const formatted = formatMessage({ ...formatOptions, message: msg, langChain: true });
|
|
173
|
+
return formatted as HumanMessage | AIMessage | SystemMessage;
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
interface LangChainMessage {
|
|
178
|
+
lc_kwargs?: {
|
|
179
|
+
additional_kwargs?: Record<string, any>;
|
|
180
|
+
[key: string]: any;
|
|
181
|
+
};
|
|
182
|
+
kwargs?: {
|
|
183
|
+
additional_kwargs?: Record<string, any>;
|
|
184
|
+
[key: string]: any;
|
|
185
|
+
};
|
|
186
|
+
[key: string]: any;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Formats a LangChain message object by merging properties from `lc_kwargs` or `kwargs` and `additional_kwargs`.
|
|
191
|
+
*
|
|
192
|
+
* @param {LangChainMessage} message - The message object to format.
|
|
193
|
+
* @returns {Record<string, any>} The formatted LangChain message.
|
|
194
|
+
*/
|
|
195
|
+
export const formatFromLangChain = (message: LangChainMessage): Record<string, any> => {
|
|
196
|
+
const kwargs = message.lc_kwargs ?? message.kwargs ?? {};
|
|
197
|
+
const { additional_kwargs = {}, ...message_kwargs } = kwargs;
|
|
198
|
+
return {
|
|
199
|
+
...message_kwargs,
|
|
200
|
+
...additional_kwargs,
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
interface TMessage {
|
|
205
|
+
role?: string;
|
|
206
|
+
content?: string | Array<{
|
|
207
|
+
type: ContentTypes;
|
|
208
|
+
[ContentTypes.TEXT]?: string;
|
|
209
|
+
text?: string;
|
|
210
|
+
tool_call_ids?: string[];
|
|
211
|
+
[key: string]: any;
|
|
212
|
+
}>;
|
|
213
|
+
[key: string]: any;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
interface ToolCallPart {
|
|
217
|
+
type: ContentTypes.TOOL_CALL;
|
|
218
|
+
tool_call: {
|
|
219
|
+
id: string;
|
|
220
|
+
name: string;
|
|
221
|
+
args: string | Record<string, unknown>;
|
|
222
|
+
output?: string;
|
|
223
|
+
[key: string]: any;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Formats an array of messages for LangChain, handling tool calls and creating ToolMessage instances.
|
|
229
|
+
*
|
|
230
|
+
* @param {Array<Partial<TMessage>>} payload - The array of messages to format.
|
|
231
|
+
* @param {Record<number, number>} [indexTokenCountMap] - Optional map of message indices to token counts.
|
|
232
|
+
* @returns {Object} - Object containing formatted messages and updated indexTokenCountMap if provided.
|
|
233
|
+
*/
|
|
234
|
+
export const formatAgentMessages = (
|
|
235
|
+
payload: Array<Partial<TMessage>>,
|
|
236
|
+
indexTokenCountMap?: Record<number, number>
|
|
237
|
+
): {
|
|
238
|
+
messages: Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>;
|
|
239
|
+
indexTokenCountMap?: Record<number, number>;
|
|
240
|
+
} => {
|
|
241
|
+
const messages: Array<HumanMessage | AIMessage | SystemMessage | ToolMessage> = [];
|
|
242
|
+
// If indexTokenCountMap is provided, create a new map to track the updated indices
|
|
243
|
+
const updatedIndexTokenCountMap: Record<number, number> = {};
|
|
244
|
+
// Keep track of the mapping from original payload indices to result indices
|
|
245
|
+
const indexMapping: Record<number, number[]> = {};
|
|
246
|
+
|
|
247
|
+
for (let i = 0; i < payload.length; i++) {
|
|
248
|
+
const message = payload[i];
|
|
249
|
+
// Q: Store the current length of messages to track where this payload message starts in the result?
|
|
250
|
+
// const startIndex = messages.length;
|
|
251
|
+
if (typeof message.content === 'string') {
|
|
252
|
+
message.content = [{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: message.content }];
|
|
253
|
+
}
|
|
254
|
+
if (message.role !== 'assistant') {
|
|
255
|
+
messages.push(formatMessage({
|
|
256
|
+
message: message as MessageInput,
|
|
257
|
+
langChain: true
|
|
258
|
+
}) as HumanMessage | AIMessage | SystemMessage);
|
|
259
|
+
|
|
260
|
+
// Update the index mapping for this message
|
|
261
|
+
indexMapping[i] = [messages.length - 1];
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// For assistant messages, track the starting index before processing
|
|
266
|
+
const startMessageIndex = messages.length;
|
|
267
|
+
|
|
268
|
+
let currentContent: any[] = [];
|
|
269
|
+
let lastAIMessage: AIMessage | null = null;
|
|
270
|
+
|
|
271
|
+
let hasReasoning = false;
|
|
272
|
+
if (Array.isArray(message.content)) {
|
|
273
|
+
for (const part of message.content) {
|
|
274
|
+
if (part.type === ContentTypes.TEXT && part.tool_call_ids) {
|
|
275
|
+
/*
|
|
276
|
+
If there's pending content, it needs to be aggregated as a single string to prepare for tool calls.
|
|
277
|
+
For Anthropic models, the "tool_calls" field on a message is only respected if content is a string.
|
|
278
|
+
*/
|
|
279
|
+
if (currentContent.length > 0) {
|
|
280
|
+
let content = currentContent.reduce((acc, curr) => {
|
|
281
|
+
if (curr.type === ContentTypes.TEXT) {
|
|
282
|
+
return `${acc}${curr[ContentTypes.TEXT] || ''}\n`;
|
|
283
|
+
}
|
|
284
|
+
return acc;
|
|
285
|
+
}, '');
|
|
286
|
+
content = `${content}\n${part[ContentTypes.TEXT] ?? part.text ?? ''}`.trim();
|
|
287
|
+
lastAIMessage = new AIMessage({ content });
|
|
288
|
+
messages.push(lastAIMessage);
|
|
289
|
+
currentContent = [];
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Create a new AIMessage with this text and prepare for tool calls
|
|
294
|
+
lastAIMessage = new AIMessage({
|
|
295
|
+
content: part.text || '',
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
messages.push(lastAIMessage);
|
|
299
|
+
} else if (part.type === ContentTypes.TOOL_CALL) {
|
|
300
|
+
if (!lastAIMessage) {
|
|
301
|
+
throw new Error('Invalid tool call structure: No preceding AIMessage with tool_call_ids');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Note: `tool_calls` list is defined when constructed by `AIMessage` class, and outputs should be excluded from it
|
|
305
|
+
const { output, args: _args, ...tool_call } = (part.tool_call as any);
|
|
306
|
+
// TODO: investigate; args as dictionary may need to be providers-or-tool-specific
|
|
307
|
+
let args: any = _args;
|
|
308
|
+
try {
|
|
309
|
+
if (typeof _args === 'string') {
|
|
310
|
+
args = JSON.parse(_args);
|
|
311
|
+
}
|
|
312
|
+
} catch (e) {
|
|
313
|
+
if (typeof _args === 'string') {
|
|
314
|
+
args = { input: _args };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
tool_call.args = args;
|
|
319
|
+
if (!lastAIMessage.tool_calls) {
|
|
320
|
+
lastAIMessage.tool_calls = [];
|
|
321
|
+
}
|
|
322
|
+
lastAIMessage.tool_calls.push(tool_call as ToolCall);
|
|
323
|
+
|
|
324
|
+
// Add the corresponding ToolMessage
|
|
325
|
+
messages.push(
|
|
326
|
+
new ToolMessage({
|
|
327
|
+
tool_call_id: tool_call.id,
|
|
328
|
+
name: tool_call.name,
|
|
329
|
+
content: output || '',
|
|
330
|
+
}),
|
|
331
|
+
);
|
|
332
|
+
} else if (part.type === ContentTypes.THINK) {
|
|
333
|
+
hasReasoning = true;
|
|
334
|
+
continue;
|
|
335
|
+
} else if (part.type === ContentTypes.ERROR || part.type === ContentTypes.AGENT_UPDATE) {
|
|
336
|
+
continue;
|
|
337
|
+
} else {
|
|
338
|
+
currentContent.push(part);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (hasReasoning && currentContent.length > 0) {
|
|
344
|
+
const content = currentContent
|
|
345
|
+
.reduce((acc, curr) => {
|
|
346
|
+
if (curr.type === ContentTypes.TEXT) {
|
|
347
|
+
return `${acc}${curr[ContentTypes.TEXT] || ''}\n`;
|
|
348
|
+
}
|
|
349
|
+
return acc;
|
|
350
|
+
}, '')
|
|
351
|
+
.trim();
|
|
352
|
+
|
|
353
|
+
if (content) {
|
|
354
|
+
messages.push(new AIMessage({ content }));
|
|
355
|
+
}
|
|
356
|
+
} else if (currentContent.length > 0) {
|
|
357
|
+
messages.push(new AIMessage({ content: currentContent }));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Update the index mapping for this assistant message
|
|
361
|
+
// Store all indices that were created from this original message
|
|
362
|
+
const endMessageIndex = messages.length;
|
|
363
|
+
const resultIndices = [];
|
|
364
|
+
for (let j = startMessageIndex; j < endMessageIndex; j++) {
|
|
365
|
+
resultIndices.push(j);
|
|
366
|
+
}
|
|
367
|
+
indexMapping[i] = resultIndices;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Update the token count map if it was provided
|
|
371
|
+
if (indexTokenCountMap) {
|
|
372
|
+
for (let originalIndex = 0; originalIndex < payload.length; originalIndex++) {
|
|
373
|
+
const resultIndices = indexMapping[originalIndex] || [];
|
|
374
|
+
const tokenCount = indexTokenCountMap[originalIndex];
|
|
375
|
+
|
|
376
|
+
if (tokenCount !== undefined) {
|
|
377
|
+
if (resultIndices.length === 1) {
|
|
378
|
+
// Simple 1:1 mapping
|
|
379
|
+
updatedIndexTokenCountMap[resultIndices[0]] = tokenCount;
|
|
380
|
+
} else if (resultIndices.length > 1) {
|
|
381
|
+
// If one message was split into multiple, distribute the token count
|
|
382
|
+
// This is a simplification - in reality, you might want a more sophisticated distribution
|
|
383
|
+
const countPerMessage = Math.floor(tokenCount / resultIndices.length);
|
|
384
|
+
resultIndices.forEach((resultIndex, idx) => {
|
|
385
|
+
if (idx === resultIndices.length - 1) {
|
|
386
|
+
// Give any remainder to the last message
|
|
387
|
+
updatedIndexTokenCountMap[resultIndex] = tokenCount - (countPerMessage * (resultIndices.length - 1));
|
|
388
|
+
} else {
|
|
389
|
+
updatedIndexTokenCountMap[resultIndex] = countPerMessage;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
messages,
|
|
399
|
+
indexTokenCountMap: indexTokenCountMap ? updatedIndexTokenCountMap : undefined
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Formats an array of messages for LangChain, making sure all content fields are strings
|
|
405
|
+
* @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.
|
|
406
|
+
* @returns {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} - The array of formatted LangChain messages, including ToolMessages for tool calls.
|
|
407
|
+
*/
|
|
408
|
+
export const formatContentStrings = (payload: Array<BaseMessage>): Array<BaseMessage> => {
|
|
409
|
+
// Create a copy of the payload to avoid modifying the original
|
|
410
|
+
const result = [...payload];
|
|
411
|
+
|
|
412
|
+
for (const message of result) {
|
|
413
|
+
if (typeof message.content === 'string') {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!Array.isArray(message.content)) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Reduce text types to a single string, ignore all other types
|
|
422
|
+
const content = message.content.reduce((acc, curr) => {
|
|
423
|
+
if (curr.type === ContentTypes.TEXT) {
|
|
424
|
+
return `${acc}${curr[ContentTypes.TEXT] || ''}\n`;
|
|
425
|
+
}
|
|
426
|
+
return acc;
|
|
427
|
+
}, '');
|
|
428
|
+
|
|
429
|
+
message.content = content.trim();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return result;
|
|
433
|
+
};
|