Skip to content

Commit cb115fc

Browse files
moving the OpenAI Assistant code logic to an abstraction
1 parent 529a9d7 commit cb115fc

File tree

5 files changed

+82
-420
lines changed

5 files changed

+82
-420
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {DirectConnection} from '../../../types/directConnection';
2+
import {Response as ResponseI} from '../../../types/response';
3+
import {OpenAIAssistantIOI} from './openAIAssistantIOI';
4+
import {DeepChat} from '../../../deepChat';
5+
6+
export class OpenAIAssistantIO extends OpenAIAssistantIOI {
7+
fetchHistory?: () => Promise<ResponseI[]>;
8+
private static readonly URL_SEGMENTS = {
9+
threadsPrefix: 'https://api.openai.com/v1/threads',
10+
threadsPosfix: '',
11+
newAssistantUrl: 'https://api.openai.com/v1/assistants',
12+
createMessagePostfix: '',
13+
listMessagesPostfix: 'order=desc',
14+
storeFiles: 'https://api.openai.com/v1/files',
15+
getFilesPrefix: 'https://api.openai.com/v1/files/',
16+
getFilesPostfix: '/content',
17+
};
18+
19+
constructor(deepChat: DeepChat) {
20+
const directConnectionCopy = JSON.parse(JSON.stringify(deepChat.directConnection)) as DirectConnection;
21+
const apiKey = directConnectionCopy.openAI;
22+
const config = directConnectionCopy.openAI?.assistant; // can be undefined as this is the default service
23+
if (typeof directConnectionCopy.openAI?.assistant === 'boolean' && directConnectionCopy.openAI?.assistant === true) {
24+
directConnectionCopy.openAI.assistant = config;
25+
}
26+
super(deepChat, config, OpenAIAssistantIO.URL_SEGMENTS, apiKey);
27+
this.connectSettings.headers ??= {};
28+
this.connectSettings.headers['OpenAI-Beta'] ??= 'assistants=v2'; // runs keep failing but keep trying
29+
if (this.shouldFetchHistory && this.sessionId) this.fetchHistory = this.fetchHistoryFunc.bind(this);
30+
}
31+
}

component/src/services/openAI/assistant/openAIAssistantIOI.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {AssistantFunctionHandler, OpenAI, OpenAIAssistant, OpenAINewAssistant} from '../../../types/openAI';
2-
import {OpenAIAssistantUtils, UploadedFile} from '../utils/openAIAssistantUtils';
2+
import {OpenAIAssistantUtils, UploadedFile} from './utils/openAIAssistantUtils';
33
import {MessageStream} from '../../../views/chat/messages/stream/messageStream';
44
import {FileMessageUtils} from '../../../views/chat/messages/fileMessageUtils';
55
import {OpenAIConverseBodyInternal} from '../../../types/openAIInternal';
@@ -37,16 +37,15 @@ type FileAttachments = {
3737
tools: {type: OpenAIAssistant['files_tool_type']}[];
3838
}[];
3939

40-
type URLSegments = {
41-
prefix: string;
42-
posfix: string;
40+
export type URLSegments = {
41+
threadsPrefix: string;
42+
threadsPosfix: string;
4343
newAssistantUrl: string;
44-
createMessageUrlPostfix: string;
45-
listMessagesUrlPostfix: string;
46-
storeFilesUrl: string;
47-
getFilesUrl: string;
48-
getFilesPrefixUrl: string;
49-
getFilesPostfixUrl: string;
44+
createMessagePostfix: string;
45+
listMessagesPostfix: string;
46+
storeFiles: string;
47+
getFilesPrefix: string;
48+
getFilesPostfix: string;
5049
};
5150

5251
export class OpenAIAssistantIOI extends DirectServiceIO {
@@ -179,12 +178,12 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
179178
this.messages = messages;
180179
if (this.sessionId) {
181180
// https://platform.openai.com/docs/api-reference/messages/createMessage
182-
this.url = `${this.urlSegments.prefix}/${this.sessionId}/messages${this.urlSegments.createMessageUrlPostfix}`;
181+
this.url = `${this.urlSegments.threadsPrefix}/${this.sessionId}/messages${this.urlSegments.createMessagePostfix}`;
183182
const body = this.processMessage(pMessages, uploadedFiles);
184183
HTTPRequest.request(this, body, messages);
185184
} else {
186185
// https://platform.openai.com/docs/api-reference/runs/createThreadAndRun
187-
this.url = `${this.urlSegments.prefix}/runs${this.urlSegments.prefix}`;
186+
this.url = `${this.urlSegments.threadsPrefix}/runs${this.urlSegments.threadsPosfix}`;
188187
const body = this.createNewThreadMessages(this.rawBody, pMessages, uploadedFiles);
189188
if (this.isSSEStream) {
190189
this.createStreamRun(body);
@@ -200,7 +199,9 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
200199
this.rawBody.assistant_id ??= this.config.assistant_id || (await this.createNewAssistant());
201200
// here instead of constructor as messages may be loaded later
202201
if (!this.searchedForThreadId) this.searchPreviousMessagesForThreadId(messages.messages);
203-
const uploadedFiles = files ? await OpenAIAssistantUtils.storeFiles(this, messages, files) : undefined;
202+
const uploadedFiles = files
203+
? await OpenAIAssistantUtils.storeFiles(this, messages, files, this.urlSegments.storeFiles)
204+
: undefined;
204205
this.connectSettings.method = 'POST';
205206
this.callService(messages, pMessages, uploadedFiles);
206207
}
@@ -238,7 +239,7 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
238239
}
239240
await this.assignThreadAndRun(result);
240241
// https://platform.openai.com/docs/api-reference/runs/getRun
241-
const url = `${this.urlSegments.prefix}/${this.sessionId}/runs/${this.run_id}${this.urlSegments.posfix}`;
242+
const url = `${this.urlSegments.threadsPrefix}/${this.sessionId}/runs/${this.run_id}${this.urlSegments.threadsPosfix}`;
242243
const requestInit = {method: 'GET', headers: this.connectSettings?.headers};
243244
HTTPRequest.executePollRequest(this, url, requestInit, this.messages as Messages); // poll for run status
244245
return {makingAnotherRequest: true};
@@ -247,7 +248,7 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
247248
private async assignThreadAndRun(result: OpenAIAssistantInitReqResult) {
248249
if (this.sessionId) {
249250
// https://platform.openai.com/docs/api-reference/runs/createRun
250-
this.url = `${this.urlSegments.prefix}/${this.sessionId}/runs${this.urlSegments.posfix}`;
251+
this.url = `${this.urlSegments.threadsPrefix}/${this.sessionId}/runs${this.urlSegments.threadsPosfix}`;
251252
const runObj = await OpenAIUtils.directFetch(this, JSON.parse(JSON.stringify(this.rawBody)), 'POST');
252253
this.run_id = runObj.id;
253254
} else {
@@ -263,12 +264,12 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
263264

264265
private async getThreadMessages(thread_id: string, isHistory = false) {
265266
// https://platform.openai.com/docs/api-reference/messages/listMessages
266-
this.url = `${this.urlSegments.prefix}/${thread_id}/messages?${this.urlSegments.listMessagesUrlPostfix}`;
267+
this.url = `${this.urlSegments.threadsPrefix}/${thread_id}/messages?${this.urlSegments.listMessagesPostfix}`;
267268
let threadMessages = (await OpenAIUtils.directFetch(this, {}, 'GET')) as OpenAIAssistantMessagesResult;
268269
if (!isHistory && this.deepChat.responseInterceptor) {
269270
threadMessages = (await this.deepChat.responseInterceptor?.(threadMessages)) as OpenAIAssistantMessagesResult;
270271
}
271-
return OpenAIAssistantUtils.processAPIMessages(this, threadMessages, isHistory);
272+
return OpenAIAssistantUtils.processAPIMessages(this, threadMessages, isHistory, this.urlSegments);
272273
}
273274

274275
async extractPollResultData(result: OpenAIRunResult): PollResult {
@@ -312,8 +313,8 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
312313
return {tool_call_id: toolCalls[index].id, output: resp};
313314
});
314315
// https://platform.openai.com/docs/api-reference/runs/submitToolOutputs
315-
const prefix = `${this.urlSegments.prefix}/${this.sessionId}`;
316-
const postfix = `/runs/${this.run_id}/submit_tool_outputs${this.urlSegments.posfix}`;
316+
const prefix = `${this.urlSegments.threadsPrefix}/${this.sessionId}`;
317+
const postfix = `/runs/${this.run_id}/submit_tool_outputs${this.urlSegments.threadsPosfix}`;
317318
this.url = `${prefix}${postfix}`;
318319
if (this.isSSEStream) {
319320
await this.createStreamRun({tool_outputs});
@@ -334,7 +335,7 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
334335
}
335336
if (this.isSSEStream && this.sessionId) {
336337
// https://platform.openai.com/docs/api-reference/runs/createRun
337-
this.url = `${this.urlSegments.prefix}/${this.sessionId}/runs${this.urlSegments.posfix}`;
338+
this.url = `${this.urlSegments.threadsPrefix}/${this.sessionId}/runs${this.urlSegments.threadsPosfix}`;
338339
const newBody = JSON.parse(JSON.stringify(this.rawBody));
339340
this.createStreamRun(newBody);
340341
}
@@ -349,7 +350,7 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
349350
if (textContent?.text?.annotations && textContent.text.annotations.length > 0) {
350351
const textFileFirst = result.content.find((content) => !!content.text) || result.content[0];
351352
const downloadCb = OpenAIAssistantUtils.getFilesAndText.bind(this,
352-
this, {role: 'assistant', content: result.content}, textFileFirst);
353+
this, {role: 'assistant', content: result.content}, this.urlSegments, textFileFirst);
353354
this.messageStream?.endStreamAfterFileDownloaded(this.messages, downloadCb);
354355
return {text: ''};
355356
}
@@ -359,7 +360,7 @@ export class OpenAIAssistantIOI extends DirectServiceIO {
359360
// if file is included and there is no annotation/link in text, process during the stream
360361
const textContent = result.delta.content.find((content) => content.text);
361362
if (textContent?.text?.annotations && textContent.text.annotations.length === 0) {
362-
const messages = await OpenAIAssistantUtils.processStreamMessages(this, result.delta.content);
363+
const messages = await OpenAIAssistantUtils.processStreamMessages(this, result.delta.content, this.urlSegments);
363364
return {text: messages[0].text, files: messages[1].files};
364365
}
365366
}

component/src/services/openAI/utils/openAIAssistantUtils.ts renamed to component/src/services/openAI/assistant/utils/openAIAssistantUtils.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import {OpenAIAssistantData, OpenAIAssistantContent, OpenAIAssistantMessagesResult} from '../../../types/openAIResult';
2-
import {MessageFileType, MessageFile} from '../../../types/messageFile';
3-
import {Messages} from '../../../views/chat/messages/messages';
4-
import {RequestUtils} from '../../../utils/HTTP/requestUtils';
5-
import {DirectServiceIO} from '../../utils/directServiceIO';
6-
import {OpenAIUtils} from './openAIUtils';
7-
import {ServiceIO} from '../../serviceIO';
1+
import {OpenAIAssistantData, OpenAIAssistantContent, OpenAIAssistantMessagesResult} from '../../../../types/openAIResult';
2+
import {MessageFileType, MessageFile} from '../../../../types/messageFile';
3+
import {Messages} from '../../../../views/chat/messages/messages';
4+
import {RequestUtils} from '../../../../utils/HTTP/requestUtils';
5+
import {DirectServiceIO} from '../../../utils/directServiceIO';
6+
import {OpenAIUtils} from '../../utils/openAIUtils';
7+
import {URLSegments} from '../openAIAssistantIOI';
8+
import {ServiceIO} from '../../../serviceIO';
89

910
type FileDetails = {fileId: string; path?: string; name?: string}[];
1011

@@ -18,10 +19,10 @@ export class OpenAIAssistantUtils {
1819
'Response must contain an array of strings for each individual function/tool_call, ' +
1920
'see https://deepchat.dev/docs/directConnection/OpenAI/#assistant-functions.';
2021

21-
public static async storeFiles(serviceIO: ServiceIO, messages: Messages, files: File[]) {
22+
public static async storeFiles(serviceIO: ServiceIO, messages: Messages, files: File[], storeFilesUrl: string) {
2223
const headers = serviceIO.connectSettings.headers;
2324
if (!headers) return;
24-
serviceIO.url = 'https://api.openai.com/v1/files'; // stores files
25+
serviceIO.url = storeFilesUrl; // stores files
2526
const previousContentType = headers[RequestUtils.CONTENT_TYPE];
2627
delete headers[RequestUtils.CONTENT_TYPE];
2728
const requests = files.map(async (file) => {
@@ -54,10 +55,10 @@ export class OpenAIAssistantUtils {
5455
return 'any';
5556
}
5657

57-
private static async getFiles(serviceIO: ServiceIO, fileDetails: FileDetails) {
58+
private static async getFiles(serviceIO: ServiceIO, fileDetails: FileDetails, urlPrefix: string, urlPosfix: string) {
5859
const fileRequests = fileDetails.map(({fileId}) => {
5960
// https://platform.openai.com/docs/api-reference/files/retrieve-contents
60-
serviceIO.url = `https://api.openai.com/v1/files/${fileId}/content`;
61+
serviceIO.url = `${urlPrefix}${fileId}${urlPosfix}`;
6162
return new Promise<Blob>((resolve) => {
6263
resolve(OpenAIUtils.directFetch(serviceIO, undefined, 'GET', false));
6364
});
@@ -86,10 +87,11 @@ export class OpenAIAssistantUtils {
8687

8788
// prettier-ignore
8889
private static async getFilesAndNewText(io: ServiceIO, fileDetails: FileDetails,
89-
role?: string, content?: OpenAIAssistantContent) {
90+
urls: URLSegments, role?: string, content?: OpenAIAssistantContent) {
9091
let files: MessageFile[] | undefined;
92+
const {getFilesPrefix, getFilesPostfix} = urls;
9193
if (fileDetails.length > 0) {
92-
files = await OpenAIAssistantUtils.getFiles(io, fileDetails);
94+
files = await OpenAIAssistantUtils.getFiles(io, fileDetails, getFilesPrefix, getFilesPostfix);
9395
if (content?.text?.value) {
9496
files.forEach((file, index) => {
9597
if (!file.src) return;
@@ -132,10 +134,12 @@ export class OpenAIAssistantUtils {
132134
return fileDetails;
133135
}
134136

135-
public static async getFilesAndText(io: ServiceIO, message: OpenAIAssistantData, content?: OpenAIAssistantContent) {
137+
// prettier-ignore
138+
public static async getFilesAndText(io: ServiceIO, message: OpenAIAssistantData,
139+
urls: URLSegments, content?: OpenAIAssistantContent) {
136140
const fileDetails = OpenAIAssistantUtils.getFileDetails(message, content);
137141
// gets files and replaces hyperlinks with base64 file encodings
138-
return await OpenAIAssistantUtils.getFilesAndNewText(io, fileDetails, message.role, content);
142+
return await OpenAIAssistantUtils.getFilesAndNewText(io, fileDetails, urls, message.role, content);
139143
}
140144

141145
private static parseResult(result: OpenAIAssistantMessagesResult, isHistory: boolean) {
@@ -157,7 +161,7 @@ export class OpenAIAssistantUtils {
157161

158162
// test this using this prompt and it should give 2 text mesages and a file:
159163
// "give example data for a csv and create a suitable bar chart"
160-
private static parseMessages(io: DirectServiceIO, messages: OpenAIAssistantData[]) {
164+
private static parseMessages(io: DirectServiceIO, messages: OpenAIAssistantData[], urls: URLSegments) {
161165
const parsedContent: Promise<{text?: string; files?: MessageFile[]}>[] = [];
162166
messages.forEach(async (data) => {
163167
data.content
@@ -168,18 +172,20 @@ export class OpenAIAssistantUtils {
168172
return 0;
169173
})
170174
.forEach(async (content) => {
171-
parsedContent.push(OpenAIAssistantUtils.getFilesAndText(io, data, content));
175+
parsedContent.push(OpenAIAssistantUtils.getFilesAndText(io, data, urls, content));
172176
});
173177
});
174178
return Promise.all(parsedContent);
175179
}
176180

177-
public static async processStreamMessages(io: DirectServiceIO, content: OpenAIAssistantContent[]) {
178-
return OpenAIAssistantUtils.parseMessages(io, [{content, role: 'assistant'}]);
181+
public static async processStreamMessages(io: DirectServiceIO, content: OpenAIAssistantContent[], urls: URLSegments) {
182+
return OpenAIAssistantUtils.parseMessages(io, [{content, role: 'assistant'}], urls);
179183
}
180184

181-
public static async processAPIMessages(io: DirectServiceIO, result: OpenAIAssistantMessagesResult, isHistory: boolean) {
185+
// prettier-ignore
186+
public static async processAPIMessages(
187+
io: DirectServiceIO, result: OpenAIAssistantMessagesResult, isHistory: boolean, urls: URLSegments) {
182188
const messages = OpenAIAssistantUtils.parseResult(result, isHistory);
183-
return OpenAIAssistantUtils.parseMessages(io, messages);
189+
return OpenAIAssistantUtils.parseMessages(io, messages, urls);
184190
}
185191
}

0 commit comments

Comments
 (0)