Skip to content

Commit aa8f1c0

Browse files
feat(assistant): updated to the newest openai version (4.42.0)
1 parent 48697e4 commit aa8f1c0

21 files changed

+1344
-1338
lines changed

apps/api/src/app/chat/chat.config.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,21 @@ import 'dotenv/config';
55
export const assistantParams: AssistantCreateParams = {
66
name: '@boldare/openai-assistant',
77
instructions: `You are a chatbot assistant. Use the general knowledge to answer questions. Speak briefly and clearly.`,
8-
tools: [{ type: 'code_interpreter' }, { type: 'retrieval' }],
9-
model: 'gpt-4-1106-preview',
10-
metadata: {},
8+
tools: [{ type: 'code_interpreter' }, { type: 'file_search' }],
9+
model: 'gpt-4-turbo',
10+
temperature: 0.05,
1111
};
1212

1313
export const assistantConfig: AssistantConfigParams = {
1414
id: process.env['ASSISTANT_ID'] || '',
1515
params: assistantParams,
1616
filesDir: './apps/api/src/app/knowledge',
17-
files: ['33-things-to-ask-your-digital-product-development-partner.md'],
17+
toolResources: {
18+
fileSearch: {
19+
boldare: ['33-things-to-ask-your-digital-product-development-partner.md'],
20+
},
21+
codeInterpreter: {
22+
fileNames: [],
23+
},
24+
},
1825
};

apps/api/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async function bootstrap() {
99
const globalPrefix = 'api';
1010
const config = new DocumentBuilder()
1111
.setTitle('@boldare/openai-assistant')
12-
.setVersion('1.0.2')
12+
.setVersion('1.1.0')
1313
.build();
1414
const document = SwaggerModule.createDocument(app, config);
1515

apps/spa/src/app/modules/+chat/shared/chat.service.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import { ThreadService } from './thread.service';
1616
import { ChatFilesService } from './chat-files.service';
1717
import { environment } from '../../../../environments/environment';
1818
import { OpenAiFile, GetThreadResponseDto } from '@boldare/openai-assistant';
19-
import { Message } from 'openai/resources/beta/threads/messages';
20-
import { TextContentBlock } from 'openai/resources/beta/threads/messages/messages';
19+
import {
20+
Message,
21+
TextContentBlock,
22+
} from 'openai/resources/beta/threads/messages';
2123

2224
@Injectable({ providedIn: 'root' })
2325
export class ChatService {
@@ -134,7 +136,13 @@ export class ChatService {
134136
this.chatGatewayService.callStart({
135137
content,
136138
threadId: this.threadService.threadId$.value,
137-
file_ids: files.map(file => file.id) || [],
139+
attachments: files.map(
140+
file =>
141+
({
142+
file_id: file.id,
143+
tools: [{ type: 'code_interpreter' }],
144+
}) || [],
145+
),
138146
});
139147
}
140148

libs/openai-assistant/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "@boldare/openai-assistant",
33
"description": "NestJS library for building chatbot solutions based on the OpenAI Assistant API",
4-
"version": "1.0.3",
4+
"version": "1.1.0",
55
"private": false,
66
"dependencies": {
77
"tslib": "^2.3.0",
8-
"openai": "^4.26.1",
8+
"openai": "^4.42.0",
99
"@nestjs/common": "^10.0.2",
1010
"@nestjs/platform-express": "^10.0.2",
1111
"dotenv": "^16.3.1",
Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import OpenAI from 'openai';
12
import { ConfigService } from '../config';
23
import { AiService } from '../ai';
34
import { AssistantFilesService } from './assistant-files.service';
4-
import OpenAI from 'openai';
5+
import { AssistantToolResources } from './assistant.model';
56

67
jest.mock('fs', () => ({
78
createReadStream: jest.fn().mockReturnValue('file'),
@@ -11,11 +12,28 @@ describe('AssistantFilesService', () => {
1112
let aiService: AiService;
1213
let configService: ConfigService;
1314
let assistantFilesService: AssistantFilesService;
15+
const create = jest.fn().mockResolvedValue({ id: 'id' });
16+
const fileNames = ['file1', 'file2'];
17+
const toolResources: AssistantToolResources = {
18+
codeInterpreter: { fileNames },
19+
fileSearch: { boldare: fileNames },
20+
};
1421

1522
beforeEach(() => {
1623
aiService = new AiService();
1724
configService = new ConfigService();
1825
assistantFilesService = new AssistantFilesService(configService, aiService);
26+
aiService.provider = {
27+
files: { create },
28+
beta: {
29+
vectorStores: {
30+
create,
31+
fileBatches: {
32+
uploadAndPoll: jest.fn().mockResolvedValue({ id: 'id' }),
33+
},
34+
},
35+
},
36+
} as unknown as OpenAI;
1937
});
2038

2139
afterEach(() => {
@@ -27,34 +45,28 @@ describe('AssistantFilesService', () => {
2745
});
2846

2947
it('should create files', async () => {
30-
const fileNames = ['file1', 'file2'];
31-
const create = jest.fn().mockResolvedValue({ id: 'id' });
32-
aiService.provider = { files: { create } } as unknown as OpenAI;
3348
configService.get = jest.fn().mockReturnValue({ filesDir: 'dir' });
3449

35-
const result = await assistantFilesService.create(fileNames);
50+
const result = await assistantFilesService.create(toolResources);
3651

37-
expect(result).toEqual(['id', 'id']);
38-
expect(create).toHaveBeenCalledTimes(2);
52+
expect(result).toEqual({
53+
code_interpreter: { file_ids: ['id', 'id'] },
54+
file_search: { vector_store_ids: ['id'] },
55+
});
3956
expect(create).toHaveBeenCalledWith({
4057
file: 'file',
4158
purpose: 'assistants',
4259
});
4360
});
4461

4562
it('should create files without file directory', async () => {
46-
const fileNames = ['file1', 'file2'];
47-
const create = jest.fn().mockResolvedValue({ id: 'id' });
48-
aiService.provider = { files: { create } } as unknown as OpenAI;
4963
configService.get = jest.fn().mockReturnValue({});
5064

51-
const result = await assistantFilesService.create(fileNames);
65+
const result = await assistantFilesService.create(toolResources);
5266

53-
expect(result).toEqual(['id', 'id']);
54-
expect(create).toHaveBeenCalledTimes(2);
55-
expect(create).toHaveBeenCalledWith({
56-
file: 'file',
57-
purpose: 'assistants',
67+
expect(result).toEqual({
68+
code_interpreter: { file_ids: ['id', 'id'] },
69+
file_search: { vector_store_ids: ['id'] },
5870
});
5971
});
6072
});

libs/openai-assistant/src/lib/assistant/assistant-files.service.ts

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,103 @@ import { FileObject } from 'openai/resources';
33
import { createReadStream } from 'fs';
44
import { AiService } from '../ai';
55
import { ConfigService } from '../config';
6-
6+
import { AssistantCreateParams, VectorStore } from 'openai/resources/beta';
7+
import ToolResources = AssistantCreateParams.ToolResources;
8+
import { AssistantUpdateParams } from 'openai/src/resources/beta/assistants';
9+
import {
10+
AssistantCodeInterpreter,
11+
AssistantFileSearch,
12+
AssistantToolResources,
13+
} from './assistant.model';
714
@Injectable()
815
export class AssistantFilesService {
916
constructor(
1017
private readonly assistantConfig: ConfigService,
1118
private readonly aiService: AiService,
1219
) {}
1320

14-
async create(
15-
fileNames: string[],
21+
async getFiles(
22+
attachments: string[] = [],
1623
fileDir = this.assistantConfig.get().filesDir,
17-
): Promise<string[]> {
24+
): Promise<FileObject[]> {
1825
const files: FileObject[] = [];
1926

20-
for (const name of fileNames) {
21-
const file = await this.aiService.provider.files.create({
22-
file: createReadStream(`${fileDir || ''}/${name}`),
23-
purpose: 'assistants',
24-
});
27+
await Promise.all(
28+
attachments.map(async item => {
29+
const file = await this.aiService.provider.files.create({
30+
file: createReadStream(`${fileDir || ''}/${item}`),
31+
purpose: 'assistants',
32+
});
33+
34+
files.push(file);
35+
}),
36+
);
2537

26-
files.push(file);
38+
return files;
39+
}
40+
41+
async getCodeInterpreterResources(
42+
data: AssistantCodeInterpreter,
43+
fileDir = this.assistantConfig.get().filesDir,
44+
): Promise<ToolResources.CodeInterpreter> {
45+
const files = await this.getFiles(data?.fileNames, fileDir);
46+
47+
return { file_ids: files.map(({ id }) => id) };
48+
}
49+
50+
async getFileSearchResources(
51+
data: AssistantFileSearch,
52+
fileDir = this.assistantConfig.get().filesDir,
53+
): Promise<ToolResources.FileSearch> {
54+
if (!data) {
55+
return { vector_store_ids: [] };
2756
}
2857

29-
return files.map(({ id }) => id);
58+
const vectorStores: VectorStore[] = [];
59+
60+
await Promise.all(
61+
Object.entries(data).map(async ([name, values]) => {
62+
if (!values.length) {
63+
return;
64+
}
65+
66+
const files = values.map(item =>
67+
createReadStream(`${fileDir || ''}/${item}`),
68+
);
69+
70+
const vectorStore =
71+
await this.aiService.provider.beta.vectorStores.create({ name });
72+
73+
await this.aiService.provider.beta.vectorStores.fileBatches.uploadAndPoll(
74+
vectorStore.id,
75+
{ files },
76+
);
77+
78+
vectorStores.push(vectorStore);
79+
return vectorStore;
80+
}),
81+
);
82+
83+
return { vector_store_ids: vectorStores.map(({ id }) => id) };
84+
}
85+
86+
async create(
87+
toolResources: AssistantToolResources,
88+
fileDir = this.assistantConfig.get().filesDir,
89+
): Promise<AssistantUpdateParams.ToolResources> {
90+
const code_interpreter = toolResources.codeInterpreter
91+
? await this.getCodeInterpreterResources(
92+
toolResources.codeInterpreter,
93+
fileDir,
94+
)
95+
: undefined;
96+
const file_search = toolResources.fileSearch
97+
? await this.getFileSearchResources(toolResources.fileSearch, fileDir)
98+
: undefined;
99+
100+
return {
101+
...(code_interpreter ? { code_interpreter } : {}),
102+
...(file_search ? { file_search } : {}),
103+
};
30104
}
31105
}

libs/openai-assistant/src/lib/assistant/assistant-memory.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Injectable, Logger } from '@nestjs/common';
22
import { writeFile, readFile } from 'fs/promises';
33
import * as envfile from 'envfile';
4-
import * as process from 'process';
4+
import * as dotenv from 'dotenv';
55

66
@Injectable()
77
export class AssistantMemoryService {
@@ -20,6 +20,7 @@ export class AssistantMemoryService {
2020
process.env['ASSISTANT_ID'] = id;
2121

2222
await writeFile(sourcePath, envfile.stringify(newVariables));
23+
dotenv.config({ path: sourcePath });
2324
} catch (error) {
2425
this.logger.error(`Can't save variable: ${error}`);
2526
}

libs/openai-assistant/src/lib/assistant/assistant.controller.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AiService } from '../ai';
77
import { AgentService } from '../agent';
88
import { AssistantFilesService } from './assistant-files.service';
99
import { AssistantMemoryService } from './assistant-memory.service';
10+
import { AssistantToolResources } from './assistant.model';
1011

1112
describe('AssistantController', () => {
1213
let assistantController: AssistantController;
@@ -38,11 +39,14 @@ describe('AssistantController', () => {
3839
jest
3940
.spyOn(assistantService, 'updateFiles')
4041
.mockReturnValue(Promise.resolve({} as Assistant));
41-
const files = { files: [] };
42+
const toolResources: AssistantToolResources = {
43+
codeInterpreter: { fileNames: ['file1'] },
44+
fileSearch: { boldare: ['file1'] },
45+
};
4246

43-
await assistantController.updateAssistant(files);
47+
await assistantController.updateAssistant({ toolResources });
4448

45-
expect(assistantService.updateFiles).toHaveBeenCalledWith(files.files);
49+
expect(assistantService.updateFiles).toHaveBeenCalledWith(toolResources);
4650
});
4751

4852
afterEach(() => {
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { Body, Controller, Post } from '@nestjs/common';
22
import { Assistant } from 'openai/resources/beta';
33
import { AssistantService } from './assistant.service';
4-
import { AssistantFiles } from './assistant.model';
4+
import { AssistantUpdate } from './assistant.model';
55

66
@Controller('assistant')
77
export class AssistantController {
88
constructor(public readonly assistantService: AssistantService) {}
99

1010
@Post('')
11-
async updateAssistant(@Body() { files }: AssistantFiles): Promise<Assistant> {
12-
return this.assistantService.updateFiles(files);
11+
async updateAssistant(
12+
@Body() { toolResources }: AssistantUpdate,
13+
): Promise<Assistant> {
14+
return this.assistantService.updateFiles(toolResources);
1315
}
1416
}

libs/openai-assistant/src/lib/assistant/assistant.mock.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { AssistantConfigParams } from './assistant.model';
44
export const assistantParamsMock: AssistantCreateParams = {
55
name: '@boldare/tests',
66
instructions: `test instructions`,
7-
tools: [{ type: 'retrieval' }],
7+
tools: [{ type: 'file_search' }],
88
model: 'gpt-3.5-turbo',
99
metadata: {},
1010
};
@@ -13,5 +13,5 @@ export const assistantConfigMock: AssistantConfigParams = {
1313
id: 'test1234',
1414
params: assistantParamsMock,
1515
filesDir: './apps/api/src/app/knowledge',
16-
files: [],
16+
toolResources: null,
1717
};

0 commit comments

Comments
 (0)