@effect/ai-openai 0.11.0 → 0.11.2
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.
- package/OpenAiEmbeddings/package.json +6 -0
- package/OpenAiTelemetry/package.json +6 -0
- package/dist/cjs/Generated.js +1910 -316
- package/dist/cjs/Generated.js.map +1 -1
- package/dist/cjs/OpenAiClient.js +88 -60
- package/dist/cjs/OpenAiClient.js.map +1 -1
- package/dist/cjs/OpenAiCompletions.js +82 -6
- package/dist/cjs/OpenAiCompletions.js.map +1 -1
- package/dist/cjs/OpenAiEmbeddings.js +95 -0
- package/dist/cjs/OpenAiEmbeddings.js.map +1 -0
- package/dist/cjs/OpenAiTelemetry.js +39 -0
- package/dist/cjs/OpenAiTelemetry.js.map +1 -0
- package/dist/cjs/index.js +5 -1
- package/dist/dts/Generated.d.ts +2789 -517
- package/dist/dts/Generated.d.ts.map +1 -1
- package/dist/dts/OpenAiClient.d.ts +15 -1
- package/dist/dts/OpenAiClient.d.ts.map +1 -1
- package/dist/dts/OpenAiCompletions.d.ts +8 -2
- package/dist/dts/OpenAiCompletions.d.ts.map +1 -1
- package/dist/dts/OpenAiConfig.d.ts +12 -1
- package/dist/dts/OpenAiConfig.d.ts.map +1 -1
- package/dist/dts/OpenAiEmbeddings.d.ts +51 -0
- package/dist/dts/OpenAiEmbeddings.d.ts.map +1 -0
- package/dist/dts/OpenAiTelemetry.d.ts +107 -0
- package/dist/dts/OpenAiTelemetry.d.ts.map +1 -0
- package/dist/dts/index.d.ts +8 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Generated.js +1612 -311
- package/dist/esm/Generated.js.map +1 -1
- package/dist/esm/OpenAiClient.js +88 -60
- package/dist/esm/OpenAiClient.js.map +1 -1
- package/dist/esm/OpenAiCompletions.js +82 -6
- package/dist/esm/OpenAiCompletions.js.map +1 -1
- package/dist/esm/OpenAiEmbeddings.js +83 -0
- package/dist/esm/OpenAiEmbeddings.js.map +1 -0
- package/dist/esm/OpenAiTelemetry.js +30 -0
- package/dist/esm/OpenAiTelemetry.js.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +22 -6
- package/src/Generated.ts +1892 -398
- package/src/OpenAiClient.ts +118 -67
- package/src/OpenAiCompletions.ts +108 -14
- package/src/OpenAiEmbeddings.ts +149 -0
- package/src/OpenAiTelemetry.ts +159 -0
- package/src/index.ts +10 -0
package/src/OpenAiClient.ts
CHANGED
|
@@ -90,62 +90,86 @@ export const make = (options: {
|
|
|
90
90
|
Stream.map((event) => JSON.parse(event.data) as A)
|
|
91
91
|
)
|
|
92
92
|
const stream = (request: StreamCompletionRequest) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
_tag: "
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
{
|
|
121
|
-
_tag: "ToolCall",
|
|
122
|
-
...toolCall,
|
|
123
|
-
...toolCall.function,
|
|
124
|
-
role: choice.delta.role!
|
|
125
|
-
} as any
|
|
126
|
-
parts[toolCall.index] = toolPart
|
|
93
|
+
Stream.suspend(() => {
|
|
94
|
+
const finishReasons: Array<string> = []
|
|
95
|
+
return streamRequest<RawCompletionChunk>(HttpClientRequest.post("/chat/completions", {
|
|
96
|
+
body: HttpBody.unsafeJson({
|
|
97
|
+
...request,
|
|
98
|
+
stream: true,
|
|
99
|
+
stream_options: { include_usage: true }
|
|
100
|
+
})
|
|
101
|
+
})).pipe(
|
|
102
|
+
Stream.mapAccum(new Map<number, ContentPart | Array<ToolCallPart>>(), (acc, chunk) => {
|
|
103
|
+
const parts: Array<StreamChunkPart> = []
|
|
104
|
+
if (chunk.usage !== null) {
|
|
105
|
+
parts.push({
|
|
106
|
+
_tag: "Usage",
|
|
107
|
+
id: chunk.id,
|
|
108
|
+
model: chunk.model,
|
|
109
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
110
|
+
outputTokens: chunk.usage.completion_tokens,
|
|
111
|
+
finishReasons,
|
|
112
|
+
systemFingerprint: chunk.system_fingerprint,
|
|
113
|
+
serviceTier: chunk.service_tier
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
for (let i = 0; i < chunk.choices.length; i++) {
|
|
117
|
+
const choice = chunk.choices[i]
|
|
118
|
+
if (choice.finish_reason !== null) {
|
|
119
|
+
finishReasons.push(choice.finish_reason)
|
|
127
120
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
121
|
+
if ("content" in choice.delta && typeof choice.delta.content === "string") {
|
|
122
|
+
let part = acc.get(choice.index) as ContentPart | undefined
|
|
123
|
+
part = {
|
|
124
|
+
_tag: "Content",
|
|
125
|
+
content: choice.delta.content
|
|
126
|
+
}
|
|
127
|
+
acc.set(choice.index, part)
|
|
128
|
+
parts.push(part)
|
|
129
|
+
} else if ("tool_calls" in choice.delta && Array.isArray(choice.delta.tool_calls)) {
|
|
130
|
+
const parts = (acc.get(choice.index) ?? []) as Array<ToolCallPart>
|
|
131
|
+
for (const toolCall of choice.delta.tool_calls) {
|
|
132
|
+
const part = parts[toolCall.index]
|
|
133
|
+
const toolPart = part?._tag === "ToolCall" ?
|
|
134
|
+
{
|
|
135
|
+
...part,
|
|
136
|
+
arguments: part.arguments + toolCall.function.arguments
|
|
137
|
+
} :
|
|
138
|
+
{
|
|
139
|
+
_tag: "ToolCall",
|
|
140
|
+
...toolCall,
|
|
141
|
+
...toolCall.function,
|
|
142
|
+
role: choice.delta.role!
|
|
143
|
+
} as any
|
|
144
|
+
parts[toolCall.index] = toolPart
|
|
145
|
+
}
|
|
146
|
+
acc.set(choice.index, parts)
|
|
147
|
+
} else if (choice.finish_reason === "tool_calls") {
|
|
148
|
+
const toolParts = acc.get(choice.index) as Array<ToolCallPart>
|
|
149
|
+
for (const part of toolParts) {
|
|
150
|
+
try {
|
|
151
|
+
const args = JSON.parse(part.arguments as string)
|
|
152
|
+
parts.push({
|
|
153
|
+
_tag: "ToolCall",
|
|
154
|
+
id: part.id,
|
|
155
|
+
name: part.name,
|
|
156
|
+
arguments: args
|
|
157
|
+
})
|
|
158
|
+
// eslint-disable-next-line no-empty
|
|
159
|
+
} catch {}
|
|
160
|
+
}
|
|
142
161
|
}
|
|
143
162
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
return [
|
|
164
|
+
acc,
|
|
165
|
+
parts.length === 0
|
|
166
|
+
? Option.none()
|
|
167
|
+
: Option.some(new StreamChunk({ parts }))
|
|
168
|
+
]
|
|
169
|
+
}),
|
|
170
|
+
Stream.filterMap(identity)
|
|
171
|
+
)
|
|
172
|
+
})
|
|
149
173
|
return OpenAiClient.of({ client, streamRequest, stream })
|
|
150
174
|
})
|
|
151
175
|
|
|
@@ -202,6 +226,13 @@ interface RawCompletionChunk {
|
|
|
202
226
|
}
|
|
203
227
|
>
|
|
204
228
|
readonly system_fingerprint: string
|
|
229
|
+
readonly service_tier: string
|
|
230
|
+
readonly usage: RawUsage | null
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface RawUsage {
|
|
234
|
+
readonly prompt_tokens: number
|
|
235
|
+
readonly completion_tokens: number
|
|
205
236
|
}
|
|
206
237
|
|
|
207
238
|
type RawDelta = {
|
|
@@ -254,19 +285,24 @@ export class StreamChunk extends Data.Class<{
|
|
|
254
285
|
})
|
|
255
286
|
}
|
|
256
287
|
const part = this.parts[0]
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
288
|
+
switch (part._tag) {
|
|
289
|
+
case "Content":
|
|
290
|
+
return AiResponse.AiResponse.fromText({
|
|
291
|
+
role: AiRole.model,
|
|
292
|
+
content: part.content
|
|
293
|
+
})
|
|
294
|
+
case "ToolCall":
|
|
295
|
+
return new AiResponse.AiResponse({
|
|
296
|
+
role: AiRole.model,
|
|
297
|
+
parts: Chunk.of(AiResponse.ToolCallPart.fromUnknown({
|
|
298
|
+
id: part.id,
|
|
299
|
+
name: part.name,
|
|
300
|
+
params: part.arguments
|
|
301
|
+
}))
|
|
302
|
+
})
|
|
303
|
+
case "Usage":
|
|
304
|
+
return AiResponse.AiResponse.empty
|
|
305
|
+
}
|
|
270
306
|
}
|
|
271
307
|
}
|
|
272
308
|
|
|
@@ -274,7 +310,7 @@ export class StreamChunk extends Data.Class<{
|
|
|
274
310
|
* @since 1.0.0
|
|
275
311
|
* @category models
|
|
276
312
|
*/
|
|
277
|
-
export type StreamChunkPart = ContentPart | ToolCallPart
|
|
313
|
+
export type StreamChunkPart = ContentPart | ToolCallPart | UsagePart
|
|
278
314
|
|
|
279
315
|
/**
|
|
280
316
|
* @since 1.0.0
|
|
@@ -296,3 +332,18 @@ export interface ToolCallPart {
|
|
|
296
332
|
readonly name: string
|
|
297
333
|
readonly arguments: unknown
|
|
298
334
|
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @since 1.0.0
|
|
338
|
+
* @category models
|
|
339
|
+
*/
|
|
340
|
+
export interface UsagePart {
|
|
341
|
+
readonly _tag: "Usage"
|
|
342
|
+
readonly id: string
|
|
343
|
+
readonly model: string
|
|
344
|
+
readonly inputTokens: number
|
|
345
|
+
readonly outputTokens: number
|
|
346
|
+
readonly finishReasons: ReadonlyArray<string>
|
|
347
|
+
readonly systemFingerprint: string
|
|
348
|
+
readonly serviceTier: string | null
|
|
349
|
+
}
|
package/src/OpenAiCompletions.ts
CHANGED
|
@@ -11,14 +11,24 @@ import * as Arr from "effect/Array"
|
|
|
11
11
|
import * as Effect from "effect/Effect"
|
|
12
12
|
import * as Layer from "effect/Layer"
|
|
13
13
|
import type * as Option from "effect/Option"
|
|
14
|
+
import * as Predicate from "effect/Predicate"
|
|
14
15
|
import * as Stream from "effect/Stream"
|
|
16
|
+
import type { Span } from "effect/Tracer"
|
|
15
17
|
import type * as Generated from "./Generated.js"
|
|
18
|
+
import type { StreamChunk } from "./OpenAiClient.js"
|
|
16
19
|
import { OpenAiClient } from "./OpenAiClient.js"
|
|
17
20
|
import { OpenAiConfig } from "./OpenAiConfig.js"
|
|
21
|
+
import { addGenAIAnnotations } from "./OpenAiTelemetry.js"
|
|
18
22
|
import * as OpenAiTokenizer from "./OpenAiTokenizer.js"
|
|
19
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @since 1.0.0
|
|
26
|
+
* @category models
|
|
27
|
+
*/
|
|
28
|
+
export type Model = typeof Generated.CreateChatCompletionRequestModel.Encoded
|
|
29
|
+
|
|
20
30
|
const make = (options: {
|
|
21
|
-
readonly model: string
|
|
31
|
+
readonly model: (string & {}) | Model
|
|
22
32
|
}) =>
|
|
23
33
|
Effect.gen(function*() {
|
|
24
34
|
const client = yield* OpenAiClient
|
|
@@ -66,9 +76,20 @@ const make = (options: {
|
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
return yield* Completions.make({
|
|
69
|
-
create(options) {
|
|
79
|
+
create({ span, ...options }) {
|
|
70
80
|
return makeRequest(options).pipe(
|
|
81
|
+
Effect.tap((request) => annotateRequest(span, request)),
|
|
71
82
|
Effect.flatMap(client.client.createChatCompletion),
|
|
83
|
+
Effect.tap((response) => annotateChatResponse(span, response)),
|
|
84
|
+
Effect.flatMap((response) =>
|
|
85
|
+
makeResponse(
|
|
86
|
+
response,
|
|
87
|
+
"create",
|
|
88
|
+
options.tools.length === 1 && options.tools[0].structured
|
|
89
|
+
? options.tools[0]
|
|
90
|
+
: undefined
|
|
91
|
+
)
|
|
92
|
+
),
|
|
72
93
|
Effect.catchAll((cause) =>
|
|
73
94
|
Effect.fail(
|
|
74
95
|
new AiError({
|
|
@@ -78,20 +99,19 @@ const make = (options: {
|
|
|
78
99
|
cause
|
|
79
100
|
})
|
|
80
101
|
)
|
|
81
|
-
),
|
|
82
|
-
Effect.flatMap((response) =>
|
|
83
|
-
makeResponse(
|
|
84
|
-
response,
|
|
85
|
-
"create",
|
|
86
|
-
options.tools.length === 1 && options.tools[0].structured ? options.tools[0] : undefined
|
|
87
|
-
)
|
|
88
102
|
)
|
|
89
103
|
)
|
|
90
104
|
},
|
|
91
|
-
stream(options) {
|
|
105
|
+
stream({ span, ...options }) {
|
|
92
106
|
return makeRequest(options).pipe(
|
|
107
|
+
Effect.tap((request) => annotateRequest(span, request)),
|
|
93
108
|
Effect.map(client.stream),
|
|
94
109
|
Stream.unwrap,
|
|
110
|
+
Stream.tap((response) => {
|
|
111
|
+
annotateStreamResponse(span, response)
|
|
112
|
+
return Effect.void
|
|
113
|
+
}),
|
|
114
|
+
Stream.map((response) => response.asAiResponse),
|
|
95
115
|
Stream.catchAll((cause) =>
|
|
96
116
|
Effect.fail(
|
|
97
117
|
new AiError({
|
|
@@ -101,8 +121,7 @@ const make = (options: {
|
|
|
101
121
|
cause
|
|
102
122
|
})
|
|
103
123
|
)
|
|
104
|
-
)
|
|
105
|
-
Stream.map((response) => response.asAiResponse)
|
|
124
|
+
)
|
|
106
125
|
)
|
|
107
126
|
}
|
|
108
127
|
})
|
|
@@ -113,7 +132,7 @@ const make = (options: {
|
|
|
113
132
|
* @category layers
|
|
114
133
|
*/
|
|
115
134
|
export const layerCompletions = (options: {
|
|
116
|
-
readonly model: string
|
|
135
|
+
readonly model: (string & {}) | Model
|
|
117
136
|
}): Layer.Layer<Completions.Completions, never, OpenAiClient> => Layer.effect(Completions.Completions, make(options))
|
|
118
137
|
|
|
119
138
|
/**
|
|
@@ -121,7 +140,7 @@ export const layerCompletions = (options: {
|
|
|
121
140
|
* @category layers
|
|
122
141
|
*/
|
|
123
142
|
export const layer = (options: {
|
|
124
|
-
readonly model: string
|
|
143
|
+
readonly model: (string & {}) | Model
|
|
125
144
|
}): Layer.Layer<Completions.Completions | Tokenizer.Tokenizer, never, OpenAiClient> =>
|
|
126
145
|
Layer.merge(layerCompletions(options), OpenAiTokenizer.layer(options))
|
|
127
146
|
|
|
@@ -291,3 +310,78 @@ const makeSystemMessage = (content: string): typeof Generated.ChatCompletionRequ
|
|
|
291
310
|
}
|
|
292
311
|
|
|
293
312
|
const safeName = (name: string) => name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/, "_")
|
|
313
|
+
|
|
314
|
+
const annotateRequest = (
|
|
315
|
+
span: Span,
|
|
316
|
+
request: typeof Generated.CreateChatCompletionRequest.Encoded
|
|
317
|
+
): void => {
|
|
318
|
+
addGenAIAnnotations(span, {
|
|
319
|
+
system: "openai",
|
|
320
|
+
operation: { name: "chat" },
|
|
321
|
+
request: {
|
|
322
|
+
model: request.model,
|
|
323
|
+
temperature: request.temperature,
|
|
324
|
+
topP: request.top_p,
|
|
325
|
+
maxTokens: request.max_tokens,
|
|
326
|
+
stopSequences: Arr.ensure(request.stop).filter(Predicate.isNotNullable),
|
|
327
|
+
frequencyPenalty: request.frequency_penalty,
|
|
328
|
+
presencePenalty: request.presence_penalty,
|
|
329
|
+
seed: request.seed
|
|
330
|
+
},
|
|
331
|
+
openai: {
|
|
332
|
+
request: {
|
|
333
|
+
responseFormat: request.response_format?.type,
|
|
334
|
+
serviceTier: request.service_tier
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const annotateChatResponse = (
|
|
341
|
+
span: Span,
|
|
342
|
+
response: Generated.CreateChatCompletionResponse
|
|
343
|
+
): void => {
|
|
344
|
+
addGenAIAnnotations(span, {
|
|
345
|
+
response: {
|
|
346
|
+
id: response.id,
|
|
347
|
+
model: response.model,
|
|
348
|
+
finishReasons: response.choices.map((choice) => choice.finish_reason)
|
|
349
|
+
},
|
|
350
|
+
usage: {
|
|
351
|
+
inputTokens: response.usage?.prompt_tokens,
|
|
352
|
+
outputTokens: response.usage?.completion_tokens
|
|
353
|
+
},
|
|
354
|
+
openai: {
|
|
355
|
+
response: {
|
|
356
|
+
systemFingerprint: response.system_fingerprint,
|
|
357
|
+
serviceTier: response.service_tier
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const annotateStreamResponse = (
|
|
364
|
+
span: Span,
|
|
365
|
+
response: StreamChunk
|
|
366
|
+
) => {
|
|
367
|
+
const usage = response.parts.find((part) => part._tag === "Usage")
|
|
368
|
+
if (Predicate.isNotNullable(usage)) {
|
|
369
|
+
addGenAIAnnotations(span, {
|
|
370
|
+
response: {
|
|
371
|
+
id: usage.id,
|
|
372
|
+
model: usage.model,
|
|
373
|
+
finishReasons: usage.finishReasons
|
|
374
|
+
},
|
|
375
|
+
usage: {
|
|
376
|
+
inputTokens: usage.inputTokens,
|
|
377
|
+
outputTokens: usage.outputTokens
|
|
378
|
+
},
|
|
379
|
+
openai: {
|
|
380
|
+
response: {
|
|
381
|
+
systemFingerprint: usage.systemFingerprint,
|
|
382
|
+
serviceTier: usage.serviceTier
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import { AiError } from "@effect/ai/AiError"
|
|
5
|
+
import * as Embeddings from "@effect/ai/Embeddings"
|
|
6
|
+
import * as Context from "effect/Context"
|
|
7
|
+
import type * as Duration from "effect/Duration"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as Layer from "effect/Layer"
|
|
10
|
+
import type { Simplify } from "effect/Types"
|
|
11
|
+
import type * as Generated from "./Generated.js"
|
|
12
|
+
import { OpenAiClient } from "./OpenAiClient.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @since 1.0.0
|
|
16
|
+
* @category models
|
|
17
|
+
*/
|
|
18
|
+
export type Model = typeof Generated.CreateEmbeddingRequestModel.Encoded
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @since 1.0.0
|
|
22
|
+
* @category tags
|
|
23
|
+
*/
|
|
24
|
+
export class OpenAiEmbeddingsConfig extends Context.Tag("@effect/ai-openai/OpenAiEmbeddings/Config")<
|
|
25
|
+
OpenAiEmbeddingsConfig,
|
|
26
|
+
Simplify<
|
|
27
|
+
Partial<
|
|
28
|
+
Omit<
|
|
29
|
+
typeof Generated.CreateEmbeddingRequest.Encoded,
|
|
30
|
+
"input"
|
|
31
|
+
>
|
|
32
|
+
>
|
|
33
|
+
>
|
|
34
|
+
>() {
|
|
35
|
+
/**
|
|
36
|
+
* @since 1.0.0
|
|
37
|
+
*/
|
|
38
|
+
static readonly getOrUndefined: Effect.Effect<typeof OpenAiEmbeddingsConfig.Service | undefined> = Effect.map(
|
|
39
|
+
Effect.context<never>(),
|
|
40
|
+
(context) => context.unsafeMap.get(OpenAiEmbeddingsConfig.key)
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const makeRequest = (
|
|
45
|
+
client: OpenAiClient.Service,
|
|
46
|
+
input: ReadonlyArray<string>,
|
|
47
|
+
parentConfig: typeof OpenAiEmbeddingsConfig.Service | undefined,
|
|
48
|
+
options: {
|
|
49
|
+
readonly model: string
|
|
50
|
+
readonly maxBatchSize?: number
|
|
51
|
+
readonly cache?: {
|
|
52
|
+
readonly capacity: number
|
|
53
|
+
readonly timeToLive: Duration.DurationInput
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
) =>
|
|
57
|
+
Effect.context<never>().pipe(
|
|
58
|
+
Effect.flatMap((context) => {
|
|
59
|
+
const localConfig = context.unsafeMap.get(OpenAiEmbeddingsConfig.key)
|
|
60
|
+
return client.client.createEmbedding({
|
|
61
|
+
input,
|
|
62
|
+
model: options.model,
|
|
63
|
+
...parentConfig,
|
|
64
|
+
...localConfig
|
|
65
|
+
})
|
|
66
|
+
}),
|
|
67
|
+
Effect.map((response) =>
|
|
68
|
+
response.data.map(({ embedding, index }) => ({
|
|
69
|
+
embeddings: embedding as Array<number>,
|
|
70
|
+
index
|
|
71
|
+
}))
|
|
72
|
+
),
|
|
73
|
+
Effect.mapError((cause) => {
|
|
74
|
+
const common = {
|
|
75
|
+
module: "OpenAiEmbeddings",
|
|
76
|
+
method: "embed",
|
|
77
|
+
cause
|
|
78
|
+
}
|
|
79
|
+
if (cause._tag === "ParseError") {
|
|
80
|
+
return new AiError({
|
|
81
|
+
description: "Malformed input detected in request",
|
|
82
|
+
...common
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
return new AiError({
|
|
86
|
+
description: "An error occurred with the OpenAI API",
|
|
87
|
+
...common
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
const make = Effect.fnUntraced(function*(options: {
|
|
93
|
+
readonly model: (string & {}) | Model
|
|
94
|
+
readonly maxBatchSize?: number
|
|
95
|
+
readonly cache?: {
|
|
96
|
+
readonly capacity: number
|
|
97
|
+
readonly timeToLive: Duration.DurationInput
|
|
98
|
+
}
|
|
99
|
+
}) {
|
|
100
|
+
const client = yield* OpenAiClient
|
|
101
|
+
const parentConfig = yield* OpenAiEmbeddingsConfig.getOrUndefined
|
|
102
|
+
return yield* Embeddings.make({
|
|
103
|
+
cache: options.cache,
|
|
104
|
+
maxBatchSize: options.maxBatchSize ?? 2048,
|
|
105
|
+
embedMany(input) {
|
|
106
|
+
return makeRequest(client, input, parentConfig, options)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const makeDataLoader = Effect.fnUntraced(function*(options: {
|
|
112
|
+
readonly model: (string & {}) | Model
|
|
113
|
+
readonly window: Duration.DurationInput
|
|
114
|
+
readonly maxBatchSize?: number
|
|
115
|
+
}) {
|
|
116
|
+
const client = yield* OpenAiClient
|
|
117
|
+
const parentConfig = yield* OpenAiEmbeddingsConfig.getOrUndefined
|
|
118
|
+
return yield* Embeddings.makeDataLoader({
|
|
119
|
+
window: options.window,
|
|
120
|
+
maxBatchSize: options.maxBatchSize ?? 2048,
|
|
121
|
+
embedMany(input) {
|
|
122
|
+
return makeRequest(client, input, parentConfig, options)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @since 1.0.0
|
|
129
|
+
* @category layers
|
|
130
|
+
*/
|
|
131
|
+
export const layer = (options: {
|
|
132
|
+
readonly model: (string & {}) | Model
|
|
133
|
+
readonly maxBatchSize?: number
|
|
134
|
+
readonly cache?: {
|
|
135
|
+
readonly capacity: number
|
|
136
|
+
readonly timeToLive: Duration.DurationInput
|
|
137
|
+
}
|
|
138
|
+
}): Layer.Layer<Embeddings.Embeddings, never, OpenAiClient> => Layer.effect(Embeddings.Embeddings, make(options))
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @since 1.0.0
|
|
142
|
+
* @category layers
|
|
143
|
+
*/
|
|
144
|
+
export const layerDataLoader = (options: {
|
|
145
|
+
readonly model: (string & {}) | Model
|
|
146
|
+
readonly window: Duration.DurationInput
|
|
147
|
+
readonly maxBatchSize?: number
|
|
148
|
+
}): Layer.Layer<Embeddings.Embeddings, never, OpenAiClient> =>
|
|
149
|
+
Layer.scoped(Embeddings.Embeddings, makeDataLoader(options))
|