-
Notifications
You must be signed in to change notification settings - Fork 0
/
openai.service.ts
138 lines (113 loc) · 3.6 KB
/
openai.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import { constructNetworkRequester } from "../../../lib/networkRequester";
import {
ChatCompletionCreateParams,
ChatCompletionMessageParam,
ChatModel,
ChatCompletion,
} from "openai/resources/index.mjs";
import { ServiceError } from "../../../shared/errors";
import { Ok, Result } from "../../../shared/result";
import { redis } from "../../../dependencies/redis";
import { openAIEmbedText } from "./openai.embeddings.service";
import { CreateFAISSVectorStore } from "../vector/faiss";
import logger from "../../../utils/logger";
import config from "../../../config";
import withCache from "../../../utils/withCache";
import { tokenize } from "../../../utils/tokenize";
type AIRequestPreferences = {
model?: ChatModel | "gpt-4o"; // <- "gpt-4o" is not updated the in openai sdk yet
skipCache?: boolean;
systemPrompt?: string;
};
class OpenAIServiceError extends ServiceError {
constructor(message: string, source?: string) {
super("OpenAI Service Error", message, source);
this.name = this.constructor.name;
}
}
const openAIChatCompletionsRequest = constructNetworkRequester(
"https://api.openai.com/v1/chat/completions",
{ Authorization: `Bearer ${config.openai.apiKey}` }
);
const vectorStore = CreateFAISSVectorStore();
const constructMessages = (
user: string,
system: string = config.constants.PROMPTS.DEFAULT_QUERY_PROMPT
): Array<ChatCompletionMessageParam> => {
return [
{
role: "system",
content: system,
},
{
role: "user",
content: user,
},
];
};
export const openAIRequest = async (
query: string,
{
model = config.openai.chatCompletions.model,
skipCache,
systemPrompt,
}: AIRequestPreferences
): Promise<Result<string, OpenAIServiceError>> => {
try {
const CACHE_KEY = `ai-response:${query}`;
if (!skipCache) {
const exactMatchResponse = await redis.get(CACHE_KEY);
if (exactMatchResponse) {
logger.debug(`[AI Service] Returning exact match cached response`);
return Ok(exactMatchResponse);
}
const queryEmbeddingsResult = await withCache(
openAIEmbedText(query),
`embeddings:${query}`
);
if (!queryEmbeddingsResult.ok) {
throw queryEmbeddingsResult.error;
}
const { value: embeddedQuery } = queryEmbeddingsResult;
const similarEmbeddings = vectorStore.getSimilarVector(embeddedQuery);
logger.debug(`[AI Service] Similar Embedding: ${similarEmbeddings}`);
if (similarEmbeddings) {
logger.debug(
`[AI Service] Similar query found: ${similarEmbeddings.label}`
);
const cachedResponse = await redis.get(
`ai-response:${similarEmbeddings.label}`
);
if (cachedResponse) {
logger.debug(`[AI Service] Returning cached response`);
return Ok(cachedResponse);
}
} else {
vectorStore.setVector(embeddedQuery, query);
}
}
query = tokenize(query)
.splice(0, config.openai.chatCompletions.rateLimitTPM - 100)
.join("");
const messages = constructMessages(query, systemPrompt);
const { data: response } = await openAIChatCompletionsRequest.post<
ChatCompletionCreateParams,
ChatCompletion
>(
"",
{
model,
messages,
},
{},
{},
{ timeout: 20000, retries: 2 }
);
const responseText = response.choices[0]?.message.content as string;
await redis.set(CACHE_KEY, responseText, "EX", 60 * 60 * 24);
return Ok(responseText);
} catch (error) {
logger.error("[AI Service] Error responding to query", error);
throw error;
}
};