@librechat/agents 2.2.1 → 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/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/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/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/package.json +1 -1
- 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/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
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BaseMessage } from "@langchain/core/messages";
|
|
2
|
+
import { TrimMessagesFields } from "./transformers";
|
|
3
|
+
/**
|
|
4
|
+
* Factory function that creates a trimMessages function that maintains an indexTokenCountMap
|
|
5
|
+
* during a run. This allows for efficient token counting by avoiding recounting tokens
|
|
6
|
+
* for messages that have already been processed.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} options - Configuration options for the trimMessages function
|
|
9
|
+
* @param {TrimMessagesFields} options.trimOptions - Options for the trimMessages function
|
|
10
|
+
* @returns {Function} A function that trims messages while maintaining a token count map
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Create a trimmer function with specific options
|
|
15
|
+
* const trimmer = createTrimMessagesFunction({
|
|
16
|
+
* trimOptions: {
|
|
17
|
+
* maxTokens: 4000,
|
|
18
|
+
* tokenCounter: myTokenCounter,
|
|
19
|
+
* strategy: "last",
|
|
20
|
+
* includeSystem: true
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Use the trimmer with an array of messages
|
|
25
|
+
* // First call with initial messages
|
|
26
|
+
* const { messages: trimmedMessages1 } = await trimmer(initialMessages);
|
|
27
|
+
*
|
|
28
|
+
* // Later call with additional messages
|
|
29
|
+
* const { messages: trimmedMessages2 } = await trimmer([...initialMessages, ...newMessages]);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function createTrimMessagesFunction({ trimOptions }: {
|
|
33
|
+
trimOptions: TrimMessagesFields;
|
|
34
|
+
}): (messages: BaseMessage[]) => Promise<{
|
|
35
|
+
messages: BaseMessage[];
|
|
36
|
+
indexTokenCountMap: Record<number, number>;
|
|
37
|
+
}>;
|
package/package.json
CHANGED
|
@@ -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
|
+
};
|