@celljs/ai-openai 3.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/lib/common/api/api-protocol.d.ts +47 -0
  4. package/lib/common/api/api-protocol.d.ts.map +1 -0
  5. package/lib/common/api/api-protocol.js +6 -0
  6. package/lib/common/api/api-protocol.js.map +1 -0
  7. package/lib/common/api/chat-request.d.ts +198 -0
  8. package/lib/common/api/chat-request.d.ts.map +1 -0
  9. package/lib/common/api/chat-request.js +145 -0
  10. package/lib/common/api/chat-request.js.map +1 -0
  11. package/lib/common/api/chat-response.d.ts +193 -0
  12. package/lib/common/api/chat-response.d.ts.map +1 -0
  13. package/lib/common/api/chat-response.js +124 -0
  14. package/lib/common/api/chat-response.js.map +1 -0
  15. package/lib/common/api/embeddings-request.d.ts +59 -0
  16. package/lib/common/api/embeddings-request.d.ts.map +1 -0
  17. package/lib/common/api/embeddings-request.js +49 -0
  18. package/lib/common/api/embeddings-request.js.map +1 -0
  19. package/lib/common/api/embeddings-response.d.ts +27 -0
  20. package/lib/common/api/embeddings-response.d.ts.map +1 -0
  21. package/lib/common/api/embeddings-response.js +30 -0
  22. package/lib/common/api/embeddings-response.js.map +1 -0
  23. package/lib/common/api/index.d.ts +10 -0
  24. package/lib/common/api/index.d.ts.map +1 -0
  25. package/lib/common/api/index.js +13 -0
  26. package/lib/common/api/index.js.map +1 -0
  27. package/lib/common/api/message.d.ts +216 -0
  28. package/lib/common/api/message.d.ts.map +1 -0
  29. package/lib/common/api/message.js +179 -0
  30. package/lib/common/api/message.js.map +1 -0
  31. package/lib/common/api/openai-api.d.ts +18 -0
  32. package/lib/common/api/openai-api.d.ts.map +1 -0
  33. package/lib/common/api/openai-api.js +99 -0
  34. package/lib/common/api/openai-api.js.map +1 -0
  35. package/lib/common/api/openai-api.spec.d.ts +2 -0
  36. package/lib/common/api/openai-api.spec.d.ts.map +1 -0
  37. package/lib/common/api/openai-api.spec.js +162 -0
  38. package/lib/common/api/openai-api.spec.js.map +1 -0
  39. package/lib/common/api/openai-model.d.ts +117 -0
  40. package/lib/common/api/openai-model.d.ts.map +1 -0
  41. package/lib/common/api/openai-model.js +121 -0
  42. package/lib/common/api/openai-model.js.map +1 -0
  43. package/lib/common/api/response-format.d.ts +60 -0
  44. package/lib/common/api/response-format.d.ts.map +1 -0
  45. package/lib/common/api/response-format.js +79 -0
  46. package/lib/common/api/response-format.js.map +1 -0
  47. package/lib/common/api/usage.d.ts +79 -0
  48. package/lib/common/api/usage.d.ts.map +1 -0
  49. package/lib/common/api/usage.js +95 -0
  50. package/lib/common/api/usage.js.map +1 -0
  51. package/lib/common/index.d.ts +4 -0
  52. package/lib/common/index.d.ts.map +1 -0
  53. package/lib/common/index.js +7 -0
  54. package/lib/common/index.js.map +1 -0
  55. package/lib/common/model/index.d.ts +5 -0
  56. package/lib/common/model/index.d.ts.map +1 -0
  57. package/lib/common/model/index.js +8 -0
  58. package/lib/common/model/index.js.map +1 -0
  59. package/lib/common/model/openai-chat-model.d.ts +37 -0
  60. package/lib/common/model/openai-chat-model.d.ts.map +1 -0
  61. package/lib/common/model/openai-chat-model.js +357 -0
  62. package/lib/common/model/openai-chat-model.js.map +1 -0
  63. package/lib/common/model/openai-chat-model.spec.d.ts +2 -0
  64. package/lib/common/model/openai-chat-model.spec.d.ts.map +1 -0
  65. package/lib/common/model/openai-chat-model.spec.js +82 -0
  66. package/lib/common/model/openai-chat-model.spec.js.map +1 -0
  67. package/lib/common/model/openai-chat-options.d.ts +159 -0
  68. package/lib/common/model/openai-chat-options.d.ts.map +1 -0
  69. package/lib/common/model/openai-chat-options.js +231 -0
  70. package/lib/common/model/openai-chat-options.js.map +1 -0
  71. package/lib/common/model/openai-embedding-model.d.ts +15 -0
  72. package/lib/common/model/openai-embedding-model.d.ts.map +1 -0
  73. package/lib/common/model/openai-embedding-model.js +64 -0
  74. package/lib/common/model/openai-embedding-model.js.map +1 -0
  75. package/lib/common/model/openai-embedding-model.spec.d.ts +2 -0
  76. package/lib/common/model/openai-embedding-model.spec.d.ts.map +1 -0
  77. package/lib/common/model/openai-embedding-model.spec.js +100 -0
  78. package/lib/common/model/openai-embedding-model.spec.js.map +1 -0
  79. package/lib/common/model/openai-embedding-options.d.ts +35 -0
  80. package/lib/common/model/openai-embedding-options.d.ts.map +1 -0
  81. package/lib/common/model/openai-embedding-options.js +56 -0
  82. package/lib/common/model/openai-embedding-options.js.map +1 -0
  83. package/lib/common/module.d.ts +4 -0
  84. package/lib/common/module.d.ts.map +1 -0
  85. package/lib/common/module.js +6 -0
  86. package/lib/common/module.js.map +1 -0
  87. package/lib/common/support/index.d.ts +3 -0
  88. package/lib/common/support/index.d.ts.map +1 -0
  89. package/lib/common/support/index.js +6 -0
  90. package/lib/common/support/index.js.map +1 -0
  91. package/lib/common/support/openai-api-response-headers.d.ts +34 -0
  92. package/lib/common/support/openai-api-response-headers.d.ts.map +1 -0
  93. package/lib/common/support/openai-api-response-headers.js +45 -0
  94. package/lib/common/support/openai-api-response-headers.js.map +1 -0
  95. package/lib/common/support/openai-response-header-extractor.d.ts +12 -0
  96. package/lib/common/support/openai-response-header-extractor.d.ts.map +1 -0
  97. package/lib/common/support/openai-response-header-extractor.js +131 -0
  98. package/lib/common/support/openai-response-header-extractor.js.map +1 -0
  99. package/lib/common/test/mock-openai-api.d.ts +13 -0
  100. package/lib/common/test/mock-openai-api.d.ts.map +1 -0
  101. package/lib/common/test/mock-openai-api.js +57 -0
  102. package/lib/common/test/mock-openai-api.js.map +1 -0
  103. package/lib/common/test/test-container.d.ts +3 -0
  104. package/lib/common/test/test-container.d.ts.map +1 -0
  105. package/lib/common/test/test-container.js +16 -0
  106. package/lib/common/test/test-container.js.map +1 -0
  107. package/lib/package.spec.d.ts +1 -0
  108. package/lib/package.spec.d.ts.map +1 -0
  109. package/lib/package.spec.js +4 -0
  110. package/lib/package.spec.js.map +1 -0
  111. package/package.json +46 -0
  112. package/src/common/api/api-protocol.ts +51 -0
  113. package/src/common/api/chat-request.ts +258 -0
  114. package/src/common/api/chat-response.ts +218 -0
  115. package/src/common/api/embeddings-request.ts +92 -0
  116. package/src/common/api/embeddings-response.ts +44 -0
  117. package/src/common/api/index.ts +9 -0
  118. package/src/common/api/message.ts +273 -0
  119. package/src/common/api/openai-api.spec.ts +175 -0
  120. package/src/common/api/openai-api.ts +111 -0
  121. package/src/common/api/openai-model.ts +137 -0
  122. package/src/common/api/response-format.ts +109 -0
  123. package/src/common/api/usage.ts +135 -0
  124. package/src/common/index.ts +3 -0
  125. package/src/common/model/index.ts +4 -0
  126. package/src/common/model/openai-chat-model.spec.ts +85 -0
  127. package/src/common/model/openai-chat-model.ts +399 -0
  128. package/src/common/model/openai-chat-options.ts +326 -0
  129. package/src/common/model/openai-embedding-model.spec.ts +112 -0
  130. package/src/common/model/openai-embedding-model.ts +83 -0
  131. package/src/common/model/openai-embedding-options.ts +72 -0
  132. package/src/common/module.ts +4 -0
  133. package/src/common/support/index.ts +2 -0
  134. package/src/common/support/openai-api-response-headers.ts +46 -0
  135. package/src/common/support/openai-response-header-extractor.ts +141 -0
  136. package/src/common/test/mock-openai-api.ts +70 -0
  137. package/src/common/test/test-container.ts +13 -0
  138. package/src/package.spec.ts +4 -0
@@ -0,0 +1,326 @@
1
+ import { FunctionCallback, FunctionCallingOptions } from '@celljs/ai-core';
2
+ import { Expose, Exclude } from 'class-transformer';
3
+ import { OpenAIModel } from '../api/openai-model';
4
+ import { Constant } from '@celljs/core';
5
+ import { AudioParameters, FunctionTool, ResponseFormat, StreamOptions } from '../api';
6
+
7
+ /**
8
+ * Options for the OpenAI Chat API.
9
+ */
10
+ @Constant(OpenAIChatOptions, new OpenAIChatOptions())
11
+ export class OpenAIChatOptions implements FunctionCallingOptions {
12
+ static readonly DEFAULT_MODEL = OpenAIModel.GPT_3_5_TURBO;
13
+
14
+ /**
15
+ * ID of the model to use.
16
+ */
17
+ model?: string;
18
+
19
+ /**
20
+ * What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output
21
+ * more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend
22
+ * altering this or top_p but not both.
23
+ */
24
+ temperature?: number;
25
+
26
+ /**
27
+ * The maximum number of tokens to generate in the chat completion. The total length of input
28
+ * tokens and generated tokens is limited by the model's context length.
29
+ */
30
+ @Expose({ name: 'max_tokens' })
31
+ maxTokens?: number;
32
+
33
+ /**
34
+ * An alternative to sampling with temperature, called nucleus sampling, where the model considers the
35
+ * results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10%
36
+ * probability mass are considered. We generally recommend altering this or temperature but not both.
37
+ */
38
+ @Expose({ name: 'top_p' })
39
+ topP?: number;
40
+
41
+ /**
42
+ * Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they
43
+ * appear in the text so far, increasing the model's likelihood to talk about new topics.
44
+ */
45
+ @Expose({ name: 'presence_penalty' })
46
+ presencePenalty?: number;
47
+
48
+ /**
49
+ * Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing
50
+ * frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.
51
+ */
52
+ @Expose({ name: 'frequency_penalty' })
53
+ frequencyPenalty?: number;
54
+
55
+ /**
56
+ * How many chat completion choices to generate for each input message. Note that you will be charged based
57
+ * on the number of generated tokens across all of the choices. Keep n as 1 to minimize costs.
58
+ */
59
+ n?: number;
60
+
61
+ /**
62
+ * Output types that you would like the model to generate for this request.
63
+ * Most models are capable of generating text, which is the default.
64
+ */
65
+ @Expose({ name: 'modalities' })
66
+ outputModalities?: string[];
67
+
68
+ /**
69
+ * Audio parameters for the audio generation. Required when audio output is requested with
70
+ * modalities: ["audio"]
71
+ */
72
+ @Expose({ name: 'audio' })
73
+ outputAudio?: AudioParameters;
74
+
75
+ /**
76
+ * Modify the likelihood of specified tokens appearing in the completion. Accepts a JSON object
77
+ * that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100.
78
+ */
79
+ @Expose({ name: 'logit_bias' })
80
+ logitBias?: Record<string, number>;
81
+
82
+ /**
83
+ * Whether to return log probabilities of the output tokens or not. If true, returns the log probabilities
84
+ * of each output token returned in the 'content' of 'message'.
85
+ */
86
+ logprobs?: boolean;
87
+
88
+ /**
89
+ * An integer between 0 and 5 specifying the number of most likely tokens to return at each token position,
90
+ * each with an associated log probability. 'logprobs' must be set to 'true' if this parameter is used.
91
+ */
92
+ @Expose({ name: 'top_logprobs' })
93
+ topLogprobs?: number;
94
+
95
+ /**
96
+ * An object specifying the format that the model must output. Setting to { "type":
97
+ * "json_object" } enables JSON mode, which guarantees the message the model generates is valid JSON.
98
+ */
99
+ @Expose({ name: 'response_format' })
100
+ responseFormat?: ResponseFormat;
101
+
102
+ /**
103
+ * Options for streaming response. Included in the API only if streaming-mode completion is requested.
104
+ */
105
+ @Expose({ name: 'stream_options' })
106
+ streamOptions?: StreamOptions;
107
+
108
+ /**
109
+ * This feature is in Beta. If specified, our system will make a best effort to sample
110
+ * deterministically, such that repeated requests with the same seed and parameters should return the same result.
111
+ */
112
+ seed?: number;
113
+
114
+ /**
115
+ * Up to 4 sequences where the API will stop generating further tokens.
116
+ */
117
+ stop?: string[];
118
+
119
+ /**
120
+ * A list of tools the model may call. Currently, only functions are supported as a tool. Use this to
121
+ * provide a list of functions the model may generate JSON inputs for.
122
+ */
123
+ tools?: FunctionTool[];
124
+
125
+ /**
126
+ * Controls which (if any) function is called by the model.
127
+ */
128
+ @Expose({ name: 'tool_choice' })
129
+ toolChoice?: any;
130
+
131
+ /**
132
+ * A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
133
+ */
134
+ user?: string;
135
+
136
+ /**
137
+ * Whether to enable parallel function calling during tool use.
138
+ */
139
+ @Expose({ name: 'parallel_tool_calls' })
140
+ parallelToolCalls?: boolean;
141
+
142
+ /**
143
+ * Tool Function Callbacks to register with the ChatModel.
144
+ */
145
+ @Exclude()
146
+ functionCallbacks: FunctionCallback[] = [];
147
+
148
+ /**
149
+ * List of functions, identified by their names, to configure for function calling in
150
+ * the chat completion requests.
151
+ */
152
+ @Exclude()
153
+ functions: Set<string> = new Set<string>();
154
+
155
+ /**
156
+ * If true, the AI will not handle the function calls internally, but will proxy them to the client.
157
+ */
158
+ @Exclude()
159
+ proxyToolCalls?: boolean;
160
+
161
+ /**
162
+ * Optional HTTP headers to be added to the chat completion request.
163
+ */
164
+ @Exclude()
165
+ httpHeaders: Record<string, string> = {};
166
+
167
+ /**
168
+ * Tool context data.
169
+ */
170
+ @Exclude()
171
+ toolContext?: Record<string, any>;
172
+
173
+ copy(): OpenAIChatOptions {
174
+ return OpenAIChatOptions.options(this);
175
+ }
176
+
177
+ static options(fromOptions: OpenAIChatOptions): OpenAIChatOptions {
178
+ const options = new OpenAIChatOptions();
179
+ Object.assign(options, fromOptions);
180
+ options.functionCallbacks = [...(fromOptions.functionCallbacks || [])];
181
+ options.functions = new Set(fromOptions.functions);
182
+ options.httpHeaders = { ...fromOptions.httpHeaders };
183
+ return options;
184
+ }
185
+
186
+ static builder(): OpenAIOptionsBuilder {
187
+ return new OpenAIOptionsBuilder();
188
+ }
189
+ }
190
+
191
+ export class OpenAIOptionsBuilder {
192
+ private options = new OpenAIChatOptions();
193
+
194
+ withModel(model: string): OpenAIOptionsBuilder {
195
+ this.options.model = model;
196
+ return this;
197
+ }
198
+
199
+ withTemperature(temperature: number): OpenAIOptionsBuilder {
200
+ this.options.temperature = temperature;
201
+ return this;
202
+ }
203
+
204
+ withMaxTokens(maxTokens: number): OpenAIOptionsBuilder {
205
+ this.options.maxTokens = maxTokens;
206
+ return this;
207
+ }
208
+
209
+ withTopP(topP: number): OpenAIOptionsBuilder {
210
+ this.options.topP = topP;
211
+ return this;
212
+ }
213
+
214
+ withPresencePenalty(presencePenalty: number): OpenAIOptionsBuilder {
215
+ this.options.presencePenalty = presencePenalty;
216
+ return this;
217
+ }
218
+
219
+ withFrequencyPenalty(frequencyPenalty: number): OpenAIOptionsBuilder {
220
+ this.options.frequencyPenalty = frequencyPenalty;
221
+ return this;
222
+ }
223
+
224
+ withN(n: number): OpenAIOptionsBuilder {
225
+ this.options.n = n;
226
+ return this;
227
+ }
228
+
229
+ withOutputModalities(modalities: string[]): OpenAIOptionsBuilder {
230
+ this.options.outputModalities = modalities;
231
+ return this;
232
+ }
233
+
234
+ withOutputAudio(audio: AudioParameters): OpenAIOptionsBuilder {
235
+ this.options.outputAudio = audio;
236
+ return this;
237
+ }
238
+
239
+ withLogitBias(bias: Record<string, number>): OpenAIOptionsBuilder {
240
+ this.options.logitBias = bias;
241
+ return this;
242
+ }
243
+
244
+ withLogprobs(logprobs: boolean): OpenAIOptionsBuilder {
245
+ this.options.logprobs = logprobs;
246
+ return this;
247
+ }
248
+
249
+ withTopLogprobs(topLogprobs: number): OpenAIOptionsBuilder {
250
+ this.options.topLogprobs = topLogprobs;
251
+ return this;
252
+ }
253
+
254
+ withResponseFormat(format: ResponseFormat): OpenAIOptionsBuilder {
255
+ this.options.responseFormat = format;
256
+ return this;
257
+ }
258
+
259
+ withStreamOptions(options: StreamOptions): OpenAIOptionsBuilder {
260
+ this.options.streamOptions = options;
261
+ return this;
262
+ }
263
+
264
+ withSeed(seed: number): OpenAIOptionsBuilder {
265
+ this.options.seed = seed;
266
+ return this;
267
+ }
268
+
269
+ withStop(stop: string[]): OpenAIOptionsBuilder {
270
+ this.options.stop = stop;
271
+ return this;
272
+ }
273
+
274
+ withTools(tools: FunctionTool[]): OpenAIOptionsBuilder {
275
+ this.options.tools = tools;
276
+ return this;
277
+ }
278
+
279
+ withToolChoice(choice: any): OpenAIOptionsBuilder {
280
+ this.options.toolChoice = choice;
281
+ return this;
282
+ }
283
+
284
+ withUser(user: string): OpenAIOptionsBuilder {
285
+ this.options.user = user;
286
+ return this;
287
+ }
288
+
289
+ withParallelToolCalls(parallel: boolean): OpenAIOptionsBuilder {
290
+ this.options.parallelToolCalls = parallel;
291
+ return this;
292
+ }
293
+
294
+ withFunctionCallbacks(callbacks: FunctionCallback[]): OpenAIOptionsBuilder {
295
+ this.options.functionCallbacks = callbacks;
296
+ return this;
297
+ }
298
+
299
+ withFunctions(functions: Set<string>): OpenAIOptionsBuilder {
300
+ this.options.functions = functions;
301
+ return this;
302
+ }
303
+
304
+ withProxyToolCalls(proxy: boolean): OpenAIOptionsBuilder {
305
+ this.options.proxyToolCalls = proxy;
306
+ return this;
307
+ }
308
+
309
+ withHttpHeaders(headers: Record<string, string>): OpenAIOptionsBuilder {
310
+ this.options.httpHeaders = headers;
311
+ return this;
312
+ }
313
+
314
+ withToolContext(context: Record<string, any>): OpenAIOptionsBuilder {
315
+ if (!this.options.toolContext) {
316
+ this.options.toolContext = context;
317
+ } else {
318
+ Object.assign(this.options.toolContext, context);
319
+ }
320
+ return this;
321
+ }
322
+
323
+ build(): OpenAIChatOptions {
324
+ return OpenAIChatOptions.options(this.options);
325
+ }
326
+ }
@@ -0,0 +1,112 @@
1
+ import { expect } from 'chai';
2
+ import '../index';
3
+ import { OpenAIEmbeddingModel } from './openai-embedding-model';
4
+ import { EmbeddingOptions, EmbeddingRequest } from '@celljs/ai-core';
5
+ import { createContainer } from '../test/test-container';
6
+ import { OpenAIAPI } from '../api/api-protocol';
7
+ import { MockOpenAIAPI } from '../test/mock-openai-api';
8
+ import { OpenAIEmbeddingOptions } from './openai-embedding-options';
9
+
10
+ const container = createContainer();
11
+
12
+ describe('OpenAIEmbeddingModel', () => {
13
+ let openAIEmbeddingModel: OpenAIEmbeddingModel;
14
+ let defaultOptions: OpenAIEmbeddingOptions;
15
+
16
+ before(() => {
17
+ container.rebind(OpenAIAPI).to(MockOpenAIAPI).inSingletonScope();
18
+ openAIEmbeddingModel = container.get(OpenAIEmbeddingModel);
19
+ defaultOptions = container.get(OpenAIEmbeddingOptions);
20
+ });
21
+
22
+ describe('call', () => {
23
+ it('should return embedding response for given text', async () => {
24
+ const request: EmbeddingRequest = {
25
+ instructions: ['Hello world', 'Another text sample'],
26
+ inputs: ['Hello world', 'Another text sample'],
27
+ options: {
28
+ model: 'text-embedding-ada-002'
29
+ }
30
+ };
31
+
32
+ const response = await openAIEmbeddingModel.call(request);
33
+
34
+ expect(response).to.have.property('embeddings');
35
+ expect(response.embeddings).to.be.an('array');
36
+ expect(response.embeddings.length).to.equal(1);
37
+ expect(response.embeddings[0].embedding).to.be.an('array');
38
+ expect(response.metadata).to.have.property('model');
39
+ expect(response.metadata).to.have.property('usage');
40
+ });
41
+
42
+ it('should throw error when no text is provided', async () => {
43
+ const request: EmbeddingRequest = {
44
+ instructions: [],
45
+ inputs: [],
46
+ options: {
47
+ model: 'text-embedding-ada-002'
48
+ }
49
+ };
50
+
51
+ try {
52
+ await openAIEmbeddingModel.call(request);
53
+ expect.fail('Should throw error for empty instructions');
54
+ } catch (error) {
55
+ expect(error.message).to.include('At least one text is required');
56
+ }
57
+ });
58
+
59
+ it('should throw error when model is not set', async () => {
60
+ const request: EmbeddingRequest = {
61
+ instructions: ['Test text'],
62
+ inputs: ['Test text'],
63
+ options: {
64
+ model: ''
65
+ }
66
+ };
67
+
68
+ try {
69
+ await openAIEmbeddingModel.call(request);
70
+ expect.fail('Should throw error for missing model');
71
+ } catch (error) {
72
+ expect(error.message).to.include('Model is not set');
73
+ }
74
+ });
75
+
76
+ it('should use default model when not specified in request', async () => {
77
+ // Temporarily set default model for this test
78
+ const originalModel = defaultOptions.model;
79
+ defaultOptions.model = 'text-embedding-ada-002';
80
+
81
+ const request: EmbeddingRequest = {
82
+ instructions: ['Test text'],
83
+ inputs: ['Test text'],
84
+ options: {}
85
+ };
86
+
87
+ const response = await openAIEmbeddingModel.call(request);
88
+ expect(response.metadata.model).to.equal('text-embedding-ada-002');
89
+
90
+ // Restore original default model
91
+ defaultOptions.model = originalModel;
92
+ });
93
+
94
+ it('should pass embedding options correctly', async () => {
95
+ const request: EmbeddingRequest = {
96
+ instructions: ['Test text'],
97
+ inputs: ['Test text'],
98
+ options: {
99
+ model: 'text-embedding-ada-002',
100
+ dimensions: 512,
101
+ encodingFormat: 'float',
102
+ user: 'test-user'
103
+ } as EmbeddingOptions
104
+ };
105
+
106
+ const response = await openAIEmbeddingModel.call(request);
107
+ expect(response).to.have.property('embeddings');
108
+ expect(response.embeddings[0]).to.have.property('embedding');
109
+ expect(response.result).to.equal(response.embeddings[0]);
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,83 @@
1
+ import {
2
+ Embedding,
3
+ EmbeddingModel,
4
+ EmbeddingOptions,
5
+ EmbeddingRequest,
6
+ EmbeddingResponse,
7
+ EmbeddingResponseMetadata,
8
+ EmbeddingResultMetadata
9
+ } from '@celljs/ai-core';
10
+ import {
11
+ EmbeddingRequest as OpenAIEmbeddingsRequest,
12
+ OpenAIAPI,
13
+ } from '../api';
14
+ import { Assert, Autowired, Component, IllegalArgumentError, Logger } from '@celljs/core';
15
+ import { OpenAIEmbeddingOptions } from './openai-embedding-options';
16
+
17
+ @Component(EmbeddingModel)
18
+ export class OpenAIEmbeddingModel implements EmbeddingModel {
19
+
20
+ @Autowired(OpenAIEmbeddingOptions)
21
+ protected readonly defaultOptions: OpenAIEmbeddingOptions;
22
+
23
+ /**
24
+ * Low-level access to the OpenAI API.
25
+ */
26
+ @Autowired(OpenAIAPI)
27
+ protected readonly chatApi: OpenAIAPI;
28
+
29
+ @Autowired(Logger)
30
+ protected readonly logger: Logger;
31
+
32
+ public async call(request: EmbeddingRequest): Promise<EmbeddingResponse> {
33
+ Assert.notEmpty(request.instructions, 'At least one text is required!');
34
+ const openAIRequet = this.createOpenAIEmbeddingsRequest(request.instructions, request.options);
35
+ const openAIResponse = (await this.chatApi.embed(openAIRequet)).body;
36
+ Assert.notNull(openAIResponse, 'No embedding data available.');
37
+
38
+ const embeddings: Embedding[] = openAIResponse.data.map(embedding => ({
39
+ embedding: embedding.embedding,
40
+ index: embedding.index,
41
+ metadata: EmbeddingResultMetadata.EMPTY,
42
+ output: embedding.embedding
43
+ }));
44
+
45
+ const embeddingResponseMetadata = <EmbeddingResponseMetadata>{
46
+ model: openAIResponse.model,
47
+ usage: openAIResponse.usage,
48
+ extra: {}
49
+ };
50
+ Assert.notEmpty(embeddings, 'No embedding data available.');
51
+ return {
52
+ embeddings,
53
+ result: embeddings[0],
54
+ results: embeddings,
55
+ metadata: embeddingResponseMetadata
56
+ };
57
+
58
+ }
59
+
60
+ protected createOpenAIEmbeddingsRequest(inputContent: string[], options?: EmbeddingOptions): OpenAIEmbeddingsRequest<string[]> {
61
+ let runtimeOptions: OpenAIEmbeddingOptions = OpenAIEmbeddingOptions.builder().build();
62
+ if (options) {
63
+ runtimeOptions = Object.assign(runtimeOptions, options);
64
+ }
65
+
66
+ runtimeOptions = Object.assign(this.defaultOptions, runtimeOptions);
67
+
68
+ // Override the model.
69
+ if (!runtimeOptions.model) {
70
+ throw new IllegalArgumentError('Model is not set!');
71
+ }
72
+ const model = runtimeOptions.model;
73
+
74
+ return new OpenAIEmbeddingsRequest<string[]>(
75
+ inputContent,
76
+ model,
77
+ runtimeOptions.encodingFormat,
78
+ runtimeOptions.dimensions,
79
+ runtimeOptions.user
80
+ );
81
+ }
82
+
83
+ }
@@ -0,0 +1,72 @@
1
+ import { EmbeddingOptions } from '@celljs/ai-core';
2
+ import { Expose } from 'class-transformer';
3
+ import { Constant } from '@celljs/core';
4
+
5
+ /**
6
+ * OpenAI Embedding Options.
7
+ */
8
+ @Constant(OpenAIEmbeddingOptions, new OpenAIEmbeddingOptions())
9
+ export class OpenAIEmbeddingOptions implements EmbeddingOptions {
10
+ /**
11
+ * ID of the model to use.
12
+ */
13
+ model?: string;
14
+
15
+ /**
16
+ * The format to return the embeddings in. Can be either float or base64.
17
+ */
18
+ @Expose({ name: 'encoding_format' })
19
+ encodingFormat?: string;
20
+
21
+ /**
22
+ * The number of dimensions the resulting output embeddings should have.
23
+ * Only supported in text-embedding-3 and later models.
24
+ */
25
+ dimensions?: number;
26
+
27
+ /**
28
+ * A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
29
+ */
30
+ user?: string;
31
+
32
+ copy(): OpenAIEmbeddingOptions {
33
+ return OpenAIEmbeddingOptions.options(this);
34
+ }
35
+
36
+ static options(fromOptions: OpenAIEmbeddingOptions): OpenAIEmbeddingOptions {
37
+ const options = new OpenAIEmbeddingOptions();
38
+ return Object.assign(options, fromOptions);
39
+ }
40
+
41
+ static builder(): OpenAIEmbeddingOptionsBuilder {
42
+ return new OpenAIEmbeddingOptionsBuilder();
43
+ }
44
+ }
45
+
46
+ export class OpenAIEmbeddingOptionsBuilder {
47
+ private options = new OpenAIEmbeddingOptions();
48
+
49
+ withModel(model: string): OpenAIEmbeddingOptionsBuilder {
50
+ this.options.model = model;
51
+ return this;
52
+ }
53
+
54
+ withEncodingFormat(encodingFormat: string): OpenAIEmbeddingOptionsBuilder {
55
+ this.options.encodingFormat = encodingFormat;
56
+ return this;
57
+ }
58
+
59
+ withDimensions(dimensions: number): OpenAIEmbeddingOptionsBuilder {
60
+ this.options.dimensions = dimensions;
61
+ return this;
62
+ }
63
+
64
+ withUser(user: string): OpenAIEmbeddingOptionsBuilder {
65
+ this.options.user = user;
66
+ return this;
67
+ }
68
+
69
+ build(): OpenAIEmbeddingOptions {
70
+ return OpenAIEmbeddingOptions.options(this.options);
71
+ }
72
+ }
@@ -0,0 +1,4 @@
1
+ import { autoBind } from '@celljs/core';
2
+ import './index';
3
+
4
+ export default autoBind();
@@ -0,0 +1,2 @@
1
+ export * from './openai-api-response-headers';
2
+ export * from './openai-response-header-extractor';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Enumeration of {@literal OpenAI} API response headers.
3
+ */
4
+ export enum OpenAIApiResponseHeaders {
5
+ /**
6
+ * Total number of requests allowed within timeframe.
7
+ */
8
+ REQUESTS_LIMIT_HEADER = 'x-ratelimit-limit-requests',
9
+
10
+ /**
11
+ * Remaining number of requests available in timeframe.
12
+ */
13
+ REQUESTS_REMAINING_HEADER = 'x-ratelimit-remaining-requests',
14
+
15
+ /**
16
+ * Duration of time until the number of requests reset.
17
+ */
18
+ REQUESTS_RESET_HEADER = 'x-ratelimit-reset-requests',
19
+
20
+ /**
21
+ * Total number of tokens allowed within timeframe.
22
+ */
23
+ TOKENS_RESET_HEADER = 'x-ratelimit-reset-tokens',
24
+
25
+ /**
26
+ * Remaining number of tokens available in timeframe.
27
+ */
28
+ TOKENS_LIMIT_HEADER = 'x-ratelimit-limit-tokens',
29
+
30
+ /**
31
+ * Duration of time until the number of tokens reset.
32
+ */
33
+ TOKENS_REMAINING_HEADER = 'x-ratelimit-remaining-tokens'
34
+ }
35
+
36
+ /**
37
+ * Metadata description for OpenAI API response headers.
38
+ */
39
+ export const OpenAIApiResponseHeadersDescription: Record<OpenAIApiResponseHeaders, string> = {
40
+ [OpenAIApiResponseHeaders.REQUESTS_LIMIT_HEADER]: 'Total number of requests allowed within timeframe.',
41
+ [OpenAIApiResponseHeaders.REQUESTS_REMAINING_HEADER]: 'Remaining number of requests available in timeframe.',
42
+ [OpenAIApiResponseHeaders.REQUESTS_RESET_HEADER]: 'Duration of time until the number of requests reset.',
43
+ [OpenAIApiResponseHeaders.TOKENS_RESET_HEADER]: 'Total number of tokens allowed within timeframe.',
44
+ [OpenAIApiResponseHeaders.TOKENS_LIMIT_HEADER]: 'Remaining number of tokens available in timeframe.',
45
+ [OpenAIApiResponseHeaders.TOKENS_REMAINING_HEADER]: 'Duration of time until the number of tokens reset.'
46
+ };