mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 08:20:14 +01:00
983 lines
33 KiB
JavaScript
983 lines
33 KiB
JavaScript
const { retrieveAndProcessFile } = require('~/server/services/Files/process');
|
|
const { processMessages } = require('./manage');
|
|
|
|
jest.mock('~/server/services/Files/process', () => ({
|
|
retrieveAndProcessFile: jest.fn(),
|
|
}));
|
|
|
|
describe('processMessages', () => {
|
|
let openai, client;
|
|
|
|
beforeEach(() => {
|
|
openai = {};
|
|
client = {
|
|
processedFileIds: new Set(),
|
|
};
|
|
jest.clearAllMocks();
|
|
retrieveAndProcessFile.mockReset();
|
|
});
|
|
|
|
test('handles normal case with single source', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'This is a test ^1^ and another^1^',
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 15,
|
|
end_index: 18,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 30,
|
|
end_index: 33,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockResolvedValue({ filename: 'test.txt' });
|
|
|
|
const result = await processMessages({ openai, client, messages });
|
|
|
|
expect(result.text).toBe('This is a test ^1^ and another^1^\n\n^1.^ test.txt');
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles multiple different sources', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'This is a test ^1^ and another^2^',
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 15,
|
|
end_index: 18,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 30,
|
|
end_index: 33,
|
|
file_citation: { file_id: 'file2' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile
|
|
.mockResolvedValueOnce({ filename: 'test1.txt' })
|
|
.mockResolvedValueOnce({ filename: 'test2.txt' });
|
|
|
|
const result = await processMessages({ openai, client, messages });
|
|
|
|
expect(result.text).toBe('This is a test ^1^ and another^2^\n\n^1.^ test1.txt\n^2.^ test2.txt');
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles file retrieval failure', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'This is a test ^1^',
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 15,
|
|
end_index: 18,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockRejectedValue(new Error('File not found'));
|
|
|
|
const result = await processMessages({ openai, client, messages });
|
|
|
|
expect(result.text).toBe('This is a test ^1^');
|
|
expect(result.edited).toBe(false);
|
|
});
|
|
|
|
test('handles citations without file ids', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'This is a test ^1^',
|
|
annotations: [{ type: 'file_citation', start_index: 15, end_index: 18 }],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
const result = await processMessages({ openai, client, messages });
|
|
|
|
expect(result.text).toBe('This is a test ^1^');
|
|
expect(result.edited).toBe(false);
|
|
});
|
|
|
|
test('handles mixed valid and invalid citations', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'This is a test ^1^ and ^2^ and ^3^',
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 15,
|
|
end_index: 18,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
{ type: 'file_citation', start_index: 23, end_index: 26 },
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 31,
|
|
end_index: 34,
|
|
file_citation: { file_id: 'file3' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile
|
|
.mockResolvedValueOnce({ filename: 'test1.txt' })
|
|
.mockResolvedValueOnce({ filename: 'test3.txt' });
|
|
|
|
const result = await processMessages({ openai, client, messages });
|
|
|
|
expect(result.text).toBe(
|
|
'This is a test ^1^ and ^2^ and ^2^\n\n^1.^ test1.txt\n^2.^ test3.txt',
|
|
);
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles adjacent identical citations', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'This is a test ^1^^1^ and ^1^ ^1^',
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 15,
|
|
end_index: 18,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 18,
|
|
end_index: 21,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 26,
|
|
end_index: 29,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
start_index: 30,
|
|
end_index: 33,
|
|
file_citation: { file_id: 'file1' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockResolvedValue({ filename: 'test.txt' });
|
|
|
|
const result = await processMessages({ openai, client, messages });
|
|
|
|
expect(result.text).toBe('This is a test ^1^ and ^1^\n\n^1.^ test.txt');
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
test('handles real data with multiple adjacent citations', async () => {
|
|
const messages = [
|
|
{
|
|
id: 'msg_XXXXXXXXXXXXXXXXXXXX',
|
|
object: 'thread.message',
|
|
created_at: 1722980324,
|
|
assistant_id: 'asst_XXXXXXXXXXXXXXXXXXXX',
|
|
thread_id: 'thread_XXXXXXXXXXXXXXXXXXXX',
|
|
run_id: 'run_XXXXXXXXXXXXXXXXXXXX',
|
|
status: 'completed',
|
|
incomplete_details: null,
|
|
incomplete_at: null,
|
|
completed_at: 1722980331,
|
|
role: 'assistant',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value:
|
|
"The text you have uploaded is from the book \"Harry Potter and the Philosopher's Stone\" by J.K. Rowling. It follows the story of a young boy named Harry Potter who discovers that he is a wizard on his eleventh birthday. Here are some key points of the narrative:\n\n1. **Discovery and Invitation to Hogwarts**: Harry learns that he is a wizard and receives an invitation to attend Hogwarts School of Witchcraft and Wizardry【11:2†source】【11:4†source】.\n\n2. **Shopping for Supplies**: Hagrid takes Harry to Diagon Alley to buy his school supplies, including his wand from Ollivander's【11:9†source】【11:14†source】.\n\n3. **Introduction to Hogwarts**: Harry is introduced to Hogwarts, the magical school where he will learn about magic and discover more about his own background【11:12†source】【11:18†source】.\n\n4. **Meeting Friends and Enemies**: At Hogwarts, Harry makes friends like Ron Weasley and Hermione Granger, and enemies like Draco Malfoy【11:16†source】.\n\n5. **Uncovering the Mystery**: Harry, along with Ron and Hermione, uncovers the mystery of the Philosopher's Stone and its connection to the dark wizard Voldemort【11:1†source】【11:10†source】【11:7†source】.\n\nThese points highlight Harry's initial experiences in the magical world and set the stage for his adventures at Hogwarts.",
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:2†source】',
|
|
start_index: 420,
|
|
end_index: 433,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:4†source】',
|
|
start_index: 433,
|
|
end_index: 446,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:9†source】',
|
|
start_index: 578,
|
|
end_index: 591,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:14†source】',
|
|
start_index: 591,
|
|
end_index: 605,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:12†source】',
|
|
start_index: 767,
|
|
end_index: 781,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:18†source】',
|
|
start_index: 781,
|
|
end_index: 795,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:16†source】',
|
|
start_index: 935,
|
|
end_index: 949,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:1†source】',
|
|
start_index: 1114,
|
|
end_index: 1127,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:10†source】',
|
|
start_index: 1127,
|
|
end_index: 1141,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:7†source】',
|
|
start_index: 1141,
|
|
end_index: 1154,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
attachments: [],
|
|
metadata: {},
|
|
files: [
|
|
{
|
|
object: 'file',
|
|
id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
purpose: 'assistants',
|
|
filename: 'hp1.txt',
|
|
bytes: 439742,
|
|
created_at: 1722962139,
|
|
status: 'processed',
|
|
status_details: null,
|
|
type: 'text/plain',
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
filepath:
|
|
'https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-XXXXXXXXXXXXXXXXXXXX/hp1.txt',
|
|
usage: 1,
|
|
user: 'XXXXXXXXXXXXXXXXXXXX',
|
|
context: 'assistants',
|
|
source: 'openai',
|
|
model: 'gpt-4o',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockResolvedValue({ filename: 'hp1.txt' });
|
|
|
|
const result = await processMessages({
|
|
openai: {},
|
|
client: { processedFileIds: new Set() },
|
|
messages,
|
|
});
|
|
|
|
const expectedText = `The text you have uploaded is from the book "Harry Potter and the Philosopher's Stone" by J.K. Rowling. It follows the story of a young boy named Harry Potter who discovers that he is a wizard on his eleventh birthday. Here are some key points of the narrative:
|
|
|
|
1. **Discovery and Invitation to Hogwarts**: Harry learns that he is a wizard and receives an invitation to attend Hogwarts School of Witchcraft and Wizardry^1^.
|
|
|
|
2. **Shopping for Supplies**: Hagrid takes Harry to Diagon Alley to buy his school supplies, including his wand from Ollivander's^1^.
|
|
|
|
3. **Introduction to Hogwarts**: Harry is introduced to Hogwarts, the magical school where he will learn about magic and discover more about his own background^1^.
|
|
|
|
4. **Meeting Friends and Enemies**: At Hogwarts, Harry makes friends like Ron Weasley and Hermione Granger, and enemies like Draco Malfoy^1^.
|
|
|
|
5. **Uncovering the Mystery**: Harry, along with Ron and Hermione, uncovers the mystery of the Philosopher's Stone and its connection to the dark wizard Voldemort^1^.
|
|
|
|
These points highlight Harry's initial experiences in the magical world and set the stage for his adventures at Hogwarts.
|
|
|
|
^1.^ hp1.txt`;
|
|
|
|
expect(result.text).toBe(expectedText);
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles real data with multiple adjacent citations with multiple sources', async () => {
|
|
const messages = [
|
|
{
|
|
id: 'msg_XXXXXXXXXXXXXXXXXXXX',
|
|
object: 'thread.message',
|
|
created_at: 1722980324,
|
|
assistant_id: 'asst_XXXXXXXXXXXXXXXXXXXX',
|
|
thread_id: 'thread_XXXXXXXXXXXXXXXXXXXX',
|
|
run_id: 'run_XXXXXXXXXXXXXXXXXXXX',
|
|
status: 'completed',
|
|
incomplete_details: null,
|
|
incomplete_at: null,
|
|
completed_at: 1722980331,
|
|
role: 'assistant',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value:
|
|
"The text you have uploaded is from the book \"Harry Potter and the Philosopher's Stone\" by J.K. Rowling. It follows the story of a young boy named Harry Potter who discovers that he is a wizard on his eleventh birthday. Here are some key points of the narrative:\n\n1. **Discovery and Invitation to Hogwarts**: Harry learns that he is a wizard and receives an invitation to attend Hogwarts School of Witchcraft and Wizardry【11:2†source】【11:4†source】.\n\n2. **Shopping for Supplies**: Hagrid takes Harry to Diagon Alley to buy his school supplies, including his wand from Ollivander's【11:9†source】【11:14†source】.\n\n3. **Introduction to Hogwarts**: Harry is introduced to Hogwarts, the magical school where he will learn about magic and discover more about his own background【11:12†source】【11:18†source】.\n\n4. **Meeting Friends and Enemies**: At Hogwarts, Harry makes friends like Ron Weasley and Hermione Granger, and enemies like Draco Malfoy【11:16†source】.\n\n5. **Uncovering the Mystery**: Harry, along with Ron and Hermione, uncovers the mystery of the Philosopher's Stone and its connection to the dark wizard Voldemort【11:1†source】【11:10†source】【11:7†source】.\n\nThese points highlight Harry's initial experiences in the magical world and set the stage for his adventures at Hogwarts.",
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:2†source】',
|
|
start_index: 420,
|
|
end_index: 433,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:4†source】',
|
|
start_index: 433,
|
|
end_index: 446,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:9†source】',
|
|
start_index: 578,
|
|
end_index: 591,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:14†source】',
|
|
start_index: 591,
|
|
end_index: 605,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:12†source】',
|
|
start_index: 767,
|
|
end_index: 781,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:18†source】',
|
|
start_index: 781,
|
|
end_index: 795,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:16†source】',
|
|
start_index: 935,
|
|
end_index: 949,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:1†source】',
|
|
start_index: 1114,
|
|
end_index: 1127,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:10†source】',
|
|
start_index: 1127,
|
|
end_index: 1141,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:7†source】',
|
|
start_index: 1141,
|
|
end_index: 1154,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
attachments: [],
|
|
metadata: {},
|
|
files: [
|
|
{
|
|
object: 'file',
|
|
id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
purpose: 'assistants',
|
|
filename: 'hp1.txt',
|
|
bytes: 439742,
|
|
created_at: 1722962139,
|
|
status: 'processed',
|
|
status_details: null,
|
|
type: 'text/plain',
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
filepath:
|
|
'https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-XXXXXXXXXXXXXXXXXXXX/hp1.txt',
|
|
usage: 1,
|
|
user: 'XXXXXXXXXXXXXXXXXXXX',
|
|
context: 'assistants',
|
|
source: 'openai',
|
|
model: 'gpt-4o',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockResolvedValue({ filename: 'hp1.txt' });
|
|
|
|
const result = await processMessages({
|
|
openai: {},
|
|
client: { processedFileIds: new Set() },
|
|
messages,
|
|
});
|
|
|
|
const expectedText = `The text you have uploaded is from the book "Harry Potter and the Philosopher's Stone" by J.K. Rowling. It follows the story of a young boy named Harry Potter who discovers that he is a wizard on his eleventh birthday. Here are some key points of the narrative:
|
|
|
|
1. **Discovery and Invitation to Hogwarts**: Harry learns that he is a wizard and receives an invitation to attend Hogwarts School of Witchcraft and Wizardry^1^.
|
|
|
|
2. **Shopping for Supplies**: Hagrid takes Harry to Diagon Alley to buy his school supplies, including his wand from Ollivander's^1^.
|
|
|
|
3. **Introduction to Hogwarts**: Harry is introduced to Hogwarts, the magical school where he will learn about magic and discover more about his own background^1^.
|
|
|
|
4. **Meeting Friends and Enemies**: At Hogwarts, Harry makes friends like Ron Weasley and Hermione Granger, and enemies like Draco Malfoy^1^.
|
|
|
|
5. **Uncovering the Mystery**: Harry, along with Ron and Hermione, uncovers the mystery of the Philosopher's Stone and its connection to the dark wizard Voldemort^1^.
|
|
|
|
These points highlight Harry's initial experiences in the magical world and set the stage for his adventures at Hogwarts.
|
|
|
|
^1.^ hp1.txt`;
|
|
|
|
expect(result.text).toBe(expectedText);
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles edge case with pre-existing citation-like text', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value:
|
|
"This is a test ^1^ with pre-existing citation-like text. Here's a real citation【11:2†source】.",
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
text: '【11:2†source】',
|
|
start_index: 79,
|
|
end_index: 92,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockResolvedValue({ filename: 'test.txt' });
|
|
|
|
const result = await processMessages({
|
|
openai: {},
|
|
client: { processedFileIds: new Set() },
|
|
messages,
|
|
});
|
|
|
|
const expectedText =
|
|
"This is a test ^1^ with pre-existing citation-like text. Here's a real citation^1^.\n\n^1.^ test.txt";
|
|
|
|
expect(result.text).toBe(expectedText);
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles FILE_PATH annotation type', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'Here is a file path: [file_path]',
|
|
annotations: [
|
|
{
|
|
type: 'file_path',
|
|
text: '[file_path]',
|
|
start_index: 21,
|
|
end_index: 32,
|
|
file_path: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockResolvedValue({
|
|
filename: 'test.txt',
|
|
filepath: '/path/to/test.txt',
|
|
});
|
|
|
|
const result = await processMessages({
|
|
openai: {},
|
|
client: { processedFileIds: new Set() },
|
|
messages,
|
|
});
|
|
|
|
const expectedText = 'Here is a file path: /path/to/test.txt';
|
|
|
|
expect(result.text).toBe(expectedText);
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles FILE_CITATION annotation type', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'Here is a citation: [citation]',
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
text: '[citation]',
|
|
start_index: 20,
|
|
end_index: 30,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockResolvedValue({ filename: 'test.txt' });
|
|
|
|
const result = await processMessages({
|
|
openai: {},
|
|
client: { processedFileIds: new Set() },
|
|
messages,
|
|
});
|
|
|
|
const expectedText = 'Here is a citation: ^1^\n\n^1.^ test.txt';
|
|
|
|
expect(result.text).toBe(expectedText);
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles multiple annotation types in a single message', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value:
|
|
'File path: [file_path]. Citation: [citation1]. Another citation: [citation2].',
|
|
annotations: [
|
|
{
|
|
type: 'file_path',
|
|
text: '[file_path]',
|
|
start_index: 11,
|
|
end_index: 22,
|
|
file_path: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXX1',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '[citation1]',
|
|
start_index: 34,
|
|
end_index: 45,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXX2',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_citation',
|
|
text: '[citation2]',
|
|
start_index: 65,
|
|
end_index: 76,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXX3',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockResolvedValueOnce({
|
|
filename: 'file1.txt',
|
|
filepath: '/path/to/file1.txt',
|
|
});
|
|
retrieveAndProcessFile.mockResolvedValueOnce({ filename: 'file2.txt' });
|
|
retrieveAndProcessFile.mockResolvedValueOnce({ filename: 'file3.txt' });
|
|
|
|
const result = await processMessages({
|
|
openai: {},
|
|
client: { processedFileIds: new Set() },
|
|
messages,
|
|
});
|
|
|
|
const expectedText =
|
|
'File path: /path/to/file1.txt. Citation: ^1^. Another citation: ^2^.\n\n^1.^ file2.txt\n^2.^ file3.txt';
|
|
|
|
expect(result.text).toBe(expectedText);
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
|
|
test('handles annotation processing failure', async () => {
|
|
const messages = [
|
|
{
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value: 'This citation will fail: [citation]',
|
|
annotations: [
|
|
{
|
|
type: 'file_citation',
|
|
text: '[citation]',
|
|
start_index: 25,
|
|
end_index: 35,
|
|
file_citation: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
created_at: 1,
|
|
},
|
|
];
|
|
|
|
retrieveAndProcessFile.mockRejectedValue(new Error('File not found'));
|
|
|
|
const result = await processMessages({
|
|
openai: {},
|
|
client: { processedFileIds: new Set() },
|
|
messages,
|
|
});
|
|
|
|
const expectedText = 'This citation will fail: [citation]';
|
|
|
|
expect(result.text).toBe(expectedText);
|
|
expect(result.edited).toBe(false);
|
|
});
|
|
|
|
test('handles multiple FILE_PATH annotations with sandbox links', async () => {
|
|
const messages = [
|
|
{
|
|
id: 'msg_XXXXXXXXXXXXXXXXXXXX',
|
|
object: 'thread.message',
|
|
created_at: 1722983745,
|
|
assistant_id: 'asst_XXXXXXXXXXXXXXXXXXXX',
|
|
thread_id: 'thread_XXXXXXXXXXXXXXXXXXXX',
|
|
run_id: 'run_XXXXXXXXXXXXXXXXXXXX',
|
|
status: 'completed',
|
|
incomplete_details: null,
|
|
incomplete_at: null,
|
|
completed_at: 1722983747,
|
|
role: 'assistant',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: {
|
|
value:
|
|
'I have generated three dummy CSV files for you. You can download them using the links below:\n\n1. [Download Dummy Data 1](sandbox:/mnt/data/dummy_data1.csv)\n2. [Download Dummy Data 2](sandbox:/mnt/data/dummy_data2.csv)\n3. [Download Dummy Data 3](sandbox:/mnt/data/dummy_data3.csv)',
|
|
annotations: [
|
|
{
|
|
type: 'file_path',
|
|
text: 'sandbox:/mnt/data/dummy_data1.csv',
|
|
start_index: 121,
|
|
end_index: 154,
|
|
file_path: {
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_path',
|
|
text: 'sandbox:/mnt/data/dummy_data2.csv',
|
|
start_index: 183,
|
|
end_index: 216,
|
|
file_path: {
|
|
file_id: 'file-YYYYYYYYYYYYYYYYYYYY',
|
|
},
|
|
},
|
|
{
|
|
type: 'file_path',
|
|
text: 'sandbox:/mnt/data/dummy_data3.csv',
|
|
start_index: 245,
|
|
end_index: 278,
|
|
file_path: {
|
|
file_id: 'file-ZZZZZZZZZZZZZZZZZZZZ',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
attachments: [
|
|
{
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
tools: [
|
|
{
|
|
type: 'code_interpreter',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
file_id: 'file-YYYYYYYYYYYYYYYYYYYY',
|
|
tools: [
|
|
{
|
|
type: 'code_interpreter',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
file_id: 'file-ZZZZZZZZZZZZZZZZZZZZ',
|
|
tools: [
|
|
{
|
|
type: 'code_interpreter',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
metadata: {},
|
|
files: [
|
|
{
|
|
object: 'file',
|
|
id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
purpose: 'assistants_output',
|
|
filename: 'dummy_data1.csv',
|
|
bytes: 1925,
|
|
created_at: 1722983746,
|
|
status: 'processed',
|
|
status_details: null,
|
|
type: 'text/csv',
|
|
file_id: 'file-XXXXXXXXXXXXXXXXXXXX',
|
|
filepath:
|
|
'https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-XXXXXXXXXXXXXXXXXXXX/dummy_data1.csv',
|
|
usage: 1,
|
|
user: 'XXXXXXXXXXXXXXXXXXXX',
|
|
context: 'assistants_output',
|
|
source: 'openai',
|
|
model: 'gpt-4o-mini',
|
|
},
|
|
{
|
|
object: 'file',
|
|
id: 'file-YYYYYYYYYYYYYYYYYYYY',
|
|
purpose: 'assistants_output',
|
|
filename: 'dummy_data2.csv',
|
|
bytes: 4221,
|
|
created_at: 1722983746,
|
|
status: 'processed',
|
|
status_details: null,
|
|
type: 'text/csv',
|
|
file_id: 'file-YYYYYYYYYYYYYYYYYYYY',
|
|
filepath:
|
|
'https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-YYYYYYYYYYYYYYYYYYYY/dummy_data2.csv',
|
|
usage: 1,
|
|
user: 'XXXXXXXXXXXXXXXXXXXX',
|
|
context: 'assistants_output',
|
|
source: 'openai',
|
|
model: 'gpt-4o-mini',
|
|
},
|
|
{
|
|
object: 'file',
|
|
id: 'file-ZZZZZZZZZZZZZZZZZZZZ',
|
|
purpose: 'assistants_output',
|
|
filename: 'dummy_data3.csv',
|
|
bytes: 3534,
|
|
created_at: 1722983747,
|
|
status: 'processed',
|
|
status_details: null,
|
|
type: 'text/csv',
|
|
file_id: 'file-ZZZZZZZZZZZZZZZZZZZZ',
|
|
filepath:
|
|
'https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-ZZZZZZZZZZZZZZZZZZZZ/dummy_data3.csv',
|
|
usage: 1,
|
|
user: 'XXXXXXXXXXXXXXXXXXXX',
|
|
context: 'assistants_output',
|
|
source: 'openai',
|
|
model: 'gpt-4o-mini',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const mockClient = {
|
|
processedFileIds: new Set(),
|
|
};
|
|
|
|
// Mock the retrieveAndProcessFile function for each file
|
|
retrieveAndProcessFile.mockImplementation(({ file_id }) => {
|
|
const fileMap = {
|
|
'file-XXXXXXXXXXXXXXXXXXXX': {
|
|
filename: 'dummy_data1.csv',
|
|
filepath:
|
|
'https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-XXXXXXXXXXXXXXXXXXXX/dummy_data1.csv',
|
|
},
|
|
'file-YYYYYYYYYYYYYYYYYYYY': {
|
|
filename: 'dummy_data2.csv',
|
|
filepath:
|
|
'https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-YYYYYYYYYYYYYYYYYYYY/dummy_data2.csv',
|
|
},
|
|
'file-ZZZZZZZZZZZZZZZZZZZZ': {
|
|
filename: 'dummy_data3.csv',
|
|
filepath:
|
|
'https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-ZZZZZZZZZZZZZZZZZZZZ/dummy_data3.csv',
|
|
},
|
|
};
|
|
|
|
return Promise.resolve(fileMap[file_id]);
|
|
});
|
|
|
|
const result = await processMessages({ openai: {}, client: mockClient, messages });
|
|
|
|
const expectedText =
|
|
'I have generated three dummy CSV files for you. You can download them using the links below:\n\n1. [Download Dummy Data 1](https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-XXXXXXXXXXXXXXXXXXXX/dummy_data1.csv)\n2. [Download Dummy Data 2](https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-YYYYYYYYYYYYYYYYYYYY/dummy_data2.csv)\n3. [Download Dummy Data 3](https://api.openai.com/v1/files/XXXXXXXXXXXXXXXXXXXX/file-ZZZZZZZZZZZZZZZZZZZZ/dummy_data3.csv)';
|
|
|
|
expect(result.text).toBe(expectedText);
|
|
expect(result.edited).toBe(true);
|
|
});
|
|
});
|