mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 18:30:15 +01:00
♻️ 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:
parent
cf59f1ab45
commit
0bf708915b
4 changed files with 66 additions and 58 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -46467,7 +46467,7 @@
|
||||||
},
|
},
|
||||||
"packages/api": {
|
"packages/api": {
|
||||||
"name": "@librechat/api",
|
"name": "@librechat/api",
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.21.5",
|
"@babel/preset-env": "^7.21.5",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@librechat/api",
|
"name": "@librechat/api",
|
||||||
"version": "1.2.6",
|
"version": "1.2.7",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"description": "MCP services for LibreChat",
|
"description": "MCP services for LibreChat",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,8 @@ describe('formatContentStrings', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Non-human messages', () => {
|
describe('AI messages', () => {
|
||||||
it('should not modify AI message content', () => {
|
it('should convert AI message with all text blocks to string', () => {
|
||||||
const messages = [
|
const messages = [
|
||||||
new AIMessage({
|
new AIMessage({
|
||||||
content: [
|
content: [
|
||||||
|
|
@ -114,13 +114,32 @@ describe('formatContentStrings', () => {
|
||||||
const result = formatContentStrings(messages);
|
const result = formatContentStrings(messages);
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].content).toEqual([
|
expect(result[0].content).toBe('Hello\nWorld');
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Hello' },
|
expect(result[0].getType()).toBe('ai');
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'World' },
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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 = [
|
const messages = [
|
||||||
new SystemMessage({
|
new SystemMessage({
|
||||||
content: [
|
content: [
|
||||||
|
|
@ -133,15 +152,13 @@ describe('formatContentStrings', () => {
|
||||||
const result = formatContentStrings(messages);
|
const result = formatContentStrings(messages);
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].content).toEqual([
|
expect(result[0].content).toBe('System\nMessage');
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'System' },
|
expect(result[0].getType()).toBe('system');
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Message' },
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Mixed message types', () => {
|
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 = [
|
const messages = [
|
||||||
new HumanMessage({
|
new HumanMessage({
|
||||||
content: [
|
content: [
|
||||||
|
|
@ -166,18 +183,15 @@ describe('formatContentStrings', () => {
|
||||||
const result = formatContentStrings(messages);
|
const result = formatContentStrings(messages);
|
||||||
|
|
||||||
expect(result).toHaveLength(3);
|
expect(result).toHaveLength(3);
|
||||||
// Human message should be converted
|
// All messages should be converted
|
||||||
expect(result[0].content).toBe('Human\nMessage');
|
expect(result[0].content).toBe('Human\nMessage');
|
||||||
// AI message should remain unchanged
|
expect(result[0].getType()).toBe('human');
|
||||||
expect(result[1].content).toEqual([
|
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'AI' },
|
expect(result[1].content).toBe('AI\nResponse');
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Response' },
|
expect(result[1].getType()).toBe('ai');
|
||||||
]);
|
|
||||||
// System message should remain unchanged
|
expect(result[2].content).toBe('System\nPrompt');
|
||||||
expect(result[2].content).toEqual([
|
expect(result[2].getType()).toBe('system');
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'System' },
|
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Prompt' },
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -215,24 +229,6 @@ describe('formatContentStrings', () => {
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].content).toBe('Hello \n World');
|
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', () => {
|
describe('Real-world scenarios', () => {
|
||||||
|
|
@ -277,14 +273,11 @@ describe('formatContentStrings', () => {
|
||||||
|
|
||||||
// First human message (all text) should be converted
|
// First human message (all text) should be converted
|
||||||
expect(result[0].content).toBe('hi there');
|
expect(result[0].content).toBe('hi there');
|
||||||
|
expect(result[0].getType()).toBe('human');
|
||||||
|
|
||||||
// AI message should remain unchanged
|
// AI message (all text) should now also be converted
|
||||||
expect(result[1].content).toEqual([
|
expect(result[1].content).toBe('Hi Danny! How can I help you today?');
|
||||||
{
|
expect(result[1].getType()).toBe('ai');
|
||||||
type: 'text',
|
|
||||||
text: 'Hi Danny! How can I help you today?',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Third message (mixed content) should remain unchanged
|
// Third message (mixed content) should remain unchanged
|
||||||
expect(result[2].content).toEqual([
|
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 = [
|
const messages = [
|
||||||
new HumanMessage({
|
new HumanMessage({
|
||||||
content: [
|
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);
|
const result = formatContentStrings(messages);
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(2);
|
||||||
// Should not convert because not all blocks are text
|
// Should not convert because not all blocks are text
|
||||||
expect(result[0].content).toEqual([
|
expect(result[0].content).toEqual([
|
||||||
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Please use the calculator' },
|
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Please use the calculator' },
|
||||||
|
|
@ -326,6 +328,13 @@ describe('formatContentStrings', () => {
|
||||||
tool_call: { name: 'calculator', args: '{"a": 1, "b": 2}' },
|
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}' },
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { ContentTypes } from 'librechat-data-provider';
|
import { ContentTypes } from 'librechat-data-provider';
|
||||||
import { HumanMessage } from '@langchain/core/messages';
|
|
||||||
import type { BaseMessage } 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) {
|
for (const message of payload) {
|
||||||
const messageType = message.getType();
|
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 (!isValidMessage) {
|
||||||
if (!isHumanMessage) {
|
|
||||||
result.push(message);
|
result.push(message);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -50,8 +49,8 @@ export const formatContentStrings = (payload: Array<BaseMessage>): Array<BaseMes
|
||||||
return acc;
|
return acc;
|
||||||
}, '');
|
}, '');
|
||||||
|
|
||||||
const clonedMessage = new HumanMessage(content.trim());
|
message.content = content.trim();
|
||||||
result.push(clonedMessage);
|
result.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue