♻️ refactor: formatContentStrings to support AI and System messages (#8528)

* ♻️ refactor: `formatContentStrings` to support AI and System messages

* 📦 chore: bump @librechat/api version to 1.2.7
This commit is contained in:
Danny Avila 2025-07-17 19:19:37 -04:00 committed by GitHub
parent cf59f1ab45
commit 0bf708915b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 58 deletions

2
package-lock.json generated
View file

@ -46467,7 +46467,7 @@
},
"packages/api": {
"name": "@librechat/api",
"version": "1.2.6",
"version": "1.2.7",
"license": "ISC",
"devDependencies": {
"@babel/preset-env": "^7.21.5",

View file

@ -1,6 +1,6 @@
{
"name": "@librechat/api",
"version": "1.2.6",
"version": "1.2.7",
"type": "commonjs",
"description": "MCP services for LibreChat",
"main": "dist/index.js",

View file

@ -100,8 +100,8 @@ describe('formatContentStrings', () => {
});
});
describe('Non-human messages', () => {
it('should not modify AI message content', () => {
describe('AI messages', () => {
it('should convert AI message with all text blocks to string', () => {
const messages = [
new AIMessage({
content: [
@ -114,13 +114,32 @@ describe('formatContentStrings', () => {
const result = formatContentStrings(messages);
expect(result).toHaveLength(1);
expect(result[0].content).toEqual([
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Hello' },
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'World' },
]);
expect(result[0].content).toBe('Hello\nWorld');
expect(result[0].getType()).toBe('ai');
});
it('should not modify System message content', () => {
it('should not convert AI message with mixed content types', () => {
const messages = [
new AIMessage({
content: [
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Here is an image' },
{ type: ContentTypes.TOOL_CALL, tool_call: { name: 'generate_image' } },
],
}),
];
const result = formatContentStrings(messages);
expect(result).toHaveLength(1);
expect(result[0].content).toEqual([
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Here is an image' },
{ type: ContentTypes.TOOL_CALL, tool_call: { name: 'generate_image' } },
]);
});
});
describe('System messages', () => {
it('should convert System message with all text blocks to string', () => {
const messages = [
new SystemMessage({
content: [
@ -133,15 +152,13 @@ describe('formatContentStrings', () => {
const result = formatContentStrings(messages);
expect(result).toHaveLength(1);
expect(result[0].content).toEqual([
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'System' },
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Message' },
]);
expect(result[0].content).toBe('System\nMessage');
expect(result[0].getType()).toBe('system');
});
});
describe('Mixed message types', () => {
it('should only process human messages in mixed array', () => {
it('should process all valid message types in mixed array', () => {
const messages = [
new HumanMessage({
content: [
@ -166,18 +183,15 @@ describe('formatContentStrings', () => {
const result = formatContentStrings(messages);
expect(result).toHaveLength(3);
// Human message should be converted
// All messages should be converted
expect(result[0].content).toBe('Human\nMessage');
// AI message should remain unchanged
expect(result[1].content).toEqual([
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'AI' },
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Response' },
]);
// System message should remain unchanged
expect(result[2].content).toEqual([
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'System' },
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Prompt' },
]);
expect(result[0].getType()).toBe('human');
expect(result[1].content).toBe('AI\nResponse');
expect(result[1].getType()).toBe('ai');
expect(result[2].content).toBe('System\nPrompt');
expect(result[2].getType()).toBe('system');
});
});
@ -215,24 +229,6 @@ describe('formatContentStrings', () => {
expect(result).toHaveLength(1);
expect(result[0].content).toBe('Hello \n World');
});
it('should not modify the original messages array', () => {
const messages = [
new HumanMessage({
content: [
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Hello' },
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'World' },
],
}),
];
const originalContent = [
...(messages[0].content as Array<{ type: string; [key: string]: unknown }>),
];
formatContentStrings(messages);
expect(messages[0].content).toEqual(originalContent);
});
});
describe('Real-world scenarios', () => {
@ -277,14 +273,11 @@ describe('formatContentStrings', () => {
// First human message (all text) should be converted
expect(result[0].content).toBe('hi there');
expect(result[0].getType()).toBe('human');
// AI message should remain unchanged
expect(result[1].content).toEqual([
{
type: 'text',
text: 'Hi Danny! How can I help you today?',
},
]);
// AI message (all text) should now also be converted
expect(result[1].content).toBe('Hi Danny! How can I help you today?');
expect(result[1].getType()).toBe('ai');
// Third message (mixed content) should remain unchanged
expect(result[2].content).toEqual([
@ -302,7 +295,7 @@ describe('formatContentStrings', () => {
]);
});
it('should handle human messages with tool calls', () => {
it('should handle messages with tool calls', () => {
const messages = [
new HumanMessage({
content: [
@ -313,11 +306,20 @@ describe('formatContentStrings', () => {
},
],
}),
new AIMessage({
content: [
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'I will calculate that for you' },
{
type: ContentTypes.TOOL_CALL,
tool_call: { name: 'calculator', args: '{"a": 1, "b": 2}' },
},
],
}),
];
const result = formatContentStrings(messages);
expect(result).toHaveLength(1);
expect(result).toHaveLength(2);
// Should not convert because not all blocks are text
expect(result[0].content).toEqual([
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Please use the calculator' },
@ -326,6 +328,13 @@ describe('formatContentStrings', () => {
tool_call: { name: 'calculator', args: '{"a": 1, "b": 2}' },
},
]);
expect(result[1].content).toEqual([
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'I will calculate that for you' },
{
type: ContentTypes.TOOL_CALL,
tool_call: { name: 'calculator', args: '{"a": 1, "b": 2}' },
},
]);
});
});
});

View file

@ -1,5 +1,4 @@
import { ContentTypes } from 'librechat-data-provider';
import { HumanMessage } from '@langchain/core/messages';
import type { BaseMessage } from '@langchain/core/messages';
/**
@ -13,10 +12,10 @@ export const formatContentStrings = (payload: Array<BaseMessage>): Array<BaseMes
for (const message of payload) {
const messageType = message.getType();
const isHumanMessage = messageType === 'human';
const isValidMessage =
messageType === 'human' || messageType === 'ai' || messageType === 'system';
// Skip non-human messages - add them as-is
if (!isHumanMessage) {
if (!isValidMessage) {
result.push(message);
continue;
}
@ -50,8 +49,8 @@ export const formatContentStrings = (payload: Array<BaseMessage>): Array<BaseMes
return acc;
}, '');
const clonedMessage = new HumanMessage(content.trim());
result.push(clonedMessage);
message.content = content.trim();
result.push(message);
}
return result;