@effect/ai-openai 4.0.0-beta.50 → 4.0.0-beta.52
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/dist/OpenAiClient.d.ts +9 -9
- package/dist/OpenAiClient.d.ts.map +1 -1
- package/dist/OpenAiClient.js +27 -32
- package/dist/OpenAiClient.js.map +1 -1
- package/dist/OpenAiClientGenerated.d.ts +91 -0
- package/dist/OpenAiClientGenerated.d.ts.map +1 -0
- package/dist/OpenAiClientGenerated.js +84 -0
- package/dist/OpenAiClientGenerated.js.map +1 -0
- package/dist/OpenAiEmbeddingModel.d.ts.map +1 -1
- package/dist/OpenAiEmbeddingModel.js +3 -0
- package/dist/OpenAiEmbeddingModel.js.map +1 -1
- package/dist/OpenAiLanguageModel.d.ts +17 -35
- package/dist/OpenAiLanguageModel.d.ts.map +1 -1
- package/dist/OpenAiLanguageModel.js +54 -74
- package/dist/OpenAiLanguageModel.js.map +1 -1
- package/dist/OpenAiSchema.d.ts +1921 -0
- package/dist/OpenAiSchema.d.ts.map +1 -0
- package/dist/OpenAiSchema.js +533 -0
- package/dist/OpenAiSchema.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/OpenAiClient.ts +90 -59
- package/src/OpenAiClientGenerated.ts +202 -0
- package/src/OpenAiEmbeddingModel.ts +6 -3
- package/src/OpenAiLanguageModel.ts +145 -60
- package/src/OpenAiSchema.ts +873 -0
- package/src/index.ts +12 -0
package/dist/index.js
CHANGED
|
@@ -15,6 +15,10 @@ export * as Generated from "./Generated.js";
|
|
|
15
15
|
* @since 1.0.0
|
|
16
16
|
*/
|
|
17
17
|
export * as OpenAiClient from "./OpenAiClient.js";
|
|
18
|
+
/**
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
export * as OpenAiClientGenerated from "./OpenAiClientGenerated.js";
|
|
18
22
|
/**
|
|
19
23
|
* @since 1.0.0
|
|
20
24
|
*/
|
|
@@ -45,6 +49,12 @@ export * as OpenAiError from "./OpenAiError.js";
|
|
|
45
49
|
* @since 1.0.0
|
|
46
50
|
*/
|
|
47
51
|
export * as OpenAiLanguageModel from "./OpenAiLanguageModel.js";
|
|
52
|
+
/**
|
|
53
|
+
* Minimal local OpenAI schemas used by the handwritten Responses client path.
|
|
54
|
+
*
|
|
55
|
+
* @since 1.0.0
|
|
56
|
+
*/
|
|
57
|
+
export * as OpenAiSchema from "./OpenAiSchema.js";
|
|
48
58
|
/**
|
|
49
59
|
* OpenAI telemetry attributes for OpenTelemetry integration.
|
|
50
60
|
*
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Generated","OpenAiClient","OpenAiConfig","OpenAiEmbeddingModel","OpenAiError","OpenAiLanguageModel","OpenAiTelemetry","OpenAiTool"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAIA;AAEA;;;AAGA,OAAO,KAAKA,SAAS,MAAM,gBAAgB;AAE3C;;;;;;;;AAQA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;AAGA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;;;;;AAOA,OAAO,KAAKC,oBAAoB,MAAM,2BAA2B;AAEjE;;;;;;;;AAQA,OAAO,KAAKC,WAAW,MAAM,kBAAkB;AAE/C;;;;;;;;AAQA,OAAO,KAAKC,mBAAmB,MAAM,0BAA0B;AAE/D;;;;;;;;;AASA,OAAO,KAAKC,eAAe,MAAM,sBAAsB;AAEvD;;;;;;;;AAQA,OAAO,KAAKC,UAAU,MAAM,iBAAiB","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["Generated","OpenAiClient","OpenAiClientGenerated","OpenAiConfig","OpenAiEmbeddingModel","OpenAiError","OpenAiLanguageModel","OpenAiSchema","OpenAiTelemetry","OpenAiTool"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAIA;AAEA;;;AAGA,OAAO,KAAKA,SAAS,MAAM,gBAAgB;AAE3C;;;;;;;;AAQA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;AAGA,OAAO,KAAKC,qBAAqB,MAAM,4BAA4B;AAEnE;;;AAGA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;;;;;AAOA,OAAO,KAAKC,oBAAoB,MAAM,2BAA2B;AAEjE;;;;;;;;AAQA,OAAO,KAAKC,WAAW,MAAM,kBAAkB;AAE/C;;;;;;;;AAQA,OAAO,KAAKC,mBAAmB,MAAM,0BAA0B;AAE/D;;;;;AAKA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;;;;;;;AASA,OAAO,KAAKC,eAAe,MAAM,sBAAsB;AAEvD;;;;;;;;AAQA,OAAO,KAAKC,UAAU,MAAM,iBAAiB","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect/ai-openai",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "An OpenAI provider integration for Effect AI SDK",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"provenance": true
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"effect": "^4.0.0-beta.
|
|
46
|
+
"effect": "^4.0.0-beta.52"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"effect": "^4.0.0-beta.
|
|
49
|
+
"effect": "^4.0.0-beta.52"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"codegen": "effect-utils codegen",
|
package/src/OpenAiClient.ts
CHANGED
|
@@ -30,9 +30,9 @@ import * as HttpClient from "effect/unstable/http/HttpClient"
|
|
|
30
30
|
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
|
|
31
31
|
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
|
|
32
32
|
import * as Socket from "effect/unstable/socket/Socket"
|
|
33
|
-
import * as Generated from "./Generated.ts"
|
|
34
33
|
import * as Errors from "./internal/errors.ts"
|
|
35
34
|
import { OpenAiConfig } from "./OpenAiConfig.ts"
|
|
35
|
+
import * as OpenAiSchema from "./OpenAiSchema.ts"
|
|
36
36
|
|
|
37
37
|
// =============================================================================
|
|
38
38
|
// Service Interface
|
|
@@ -46,17 +46,17 @@ import { OpenAiConfig } from "./OpenAiConfig.ts"
|
|
|
46
46
|
*/
|
|
47
47
|
export interface Service {
|
|
48
48
|
/**
|
|
49
|
-
* The
|
|
49
|
+
* The transformed HTTP client used by this service.
|
|
50
50
|
*/
|
|
51
|
-
readonly client:
|
|
51
|
+
readonly client: HttpClient.HttpClient
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Create a response using the OpenAI responses endpoint.
|
|
55
55
|
*/
|
|
56
56
|
readonly createResponse: (
|
|
57
|
-
options: typeof
|
|
57
|
+
options: typeof OpenAiSchema.CreateResponse.Encoded
|
|
58
58
|
) => Effect.Effect<
|
|
59
|
-
readonly [body: typeof
|
|
59
|
+
readonly [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse],
|
|
60
60
|
AiError.AiError
|
|
61
61
|
>
|
|
62
62
|
|
|
@@ -64,11 +64,11 @@ export interface Service {
|
|
|
64
64
|
* Create a streaming response using the OpenAI responses endpoint.
|
|
65
65
|
*/
|
|
66
66
|
readonly createResponseStream: (
|
|
67
|
-
options: Omit<typeof
|
|
67
|
+
options: Omit<typeof OpenAiSchema.CreateResponse.Encoded, "stream">
|
|
68
68
|
) => Effect.Effect<
|
|
69
69
|
readonly [
|
|
70
70
|
response: HttpClientResponse.HttpClientResponse,
|
|
71
|
-
stream: Stream.Stream<typeof
|
|
71
|
+
stream: Stream.Stream<typeof OpenAiSchema.ResponseStreamEvent.Type, AiError.AiError>
|
|
72
72
|
],
|
|
73
73
|
AiError.AiError
|
|
74
74
|
>
|
|
@@ -77,8 +77,8 @@ export interface Service {
|
|
|
77
77
|
* Create embeddings using the OpenAI embeddings endpoint.
|
|
78
78
|
*/
|
|
79
79
|
readonly createEmbedding: (
|
|
80
|
-
options: typeof
|
|
81
|
-
) => Effect.Effect<typeof
|
|
80
|
+
options: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded
|
|
81
|
+
) => Effect.Effect<typeof OpenAiSchema.CreateEmbeddingResponse.Type, AiError.AiError>
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// =============================================================================
|
|
@@ -176,45 +176,57 @@ export const make = Effect.fnUntraced(
|
|
|
176
176
|
: identity,
|
|
177
177
|
HttpClientRequest.acceptJson
|
|
178
178
|
)),
|
|
179
|
+
HttpClient.filterStatusOk,
|
|
179
180
|
options.transformClient
|
|
180
181
|
? options.transformClient
|
|
181
182
|
: identity
|
|
182
183
|
)
|
|
183
184
|
|
|
184
|
-
const
|
|
185
|
+
const resolveHttpClient = Effect.map(
|
|
186
|
+
OpenAiConfig.getOrUndefined,
|
|
187
|
+
(config) =>
|
|
188
|
+
Predicate.isNotUndefined(config?.transformClient)
|
|
189
|
+
? config.transformClient(httpClient)
|
|
190
|
+
: httpClient
|
|
191
|
+
)
|
|
185
192
|
|
|
186
|
-
const
|
|
187
|
-
transformClient: Effect.fnUntraced(function*(client) {
|
|
188
|
-
const config = yield* OpenAiConfig.getOrUndefined
|
|
189
|
-
if (Predicate.isNotUndefined(config?.transformClient)) {
|
|
190
|
-
return config.transformClient(client)
|
|
191
|
-
}
|
|
192
|
-
return client
|
|
193
|
-
})
|
|
194
|
-
})
|
|
193
|
+
const decodeResponse = HttpClientResponse.schemaBodyJson(OpenAiSchema.Response)
|
|
195
194
|
|
|
196
195
|
const createResponse = (
|
|
197
|
-
payload: typeof
|
|
196
|
+
payload: typeof OpenAiSchema.CreateResponse.Encoded
|
|
198
197
|
): Effect.Effect<
|
|
199
|
-
[body: typeof
|
|
198
|
+
[body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse],
|
|
200
199
|
AiError.AiError
|
|
201
200
|
> =>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
Effect.flatMap(resolveHttpClient, (client) =>
|
|
202
|
+
client.execute(
|
|
203
|
+
HttpClientRequest.post("/responses", {
|
|
204
|
+
body: HttpBody.jsonUnsafe(payload)
|
|
205
|
+
})
|
|
206
|
+
).pipe(
|
|
207
|
+
Effect.flatMap((response) =>
|
|
208
|
+
decodeResponse(response).pipe(
|
|
209
|
+
Effect.map((body): [typeof OpenAiSchema.Response.Type, HttpClientResponse.HttpClientResponse] => [
|
|
210
|
+
body,
|
|
211
|
+
response
|
|
212
|
+
])
|
|
213
|
+
)
|
|
214
|
+
),
|
|
215
|
+
Effect.catchTags({
|
|
216
|
+
HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"),
|
|
217
|
+
SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse"))
|
|
218
|
+
})
|
|
219
|
+
))
|
|
208
220
|
|
|
209
221
|
const buildResponseStream = (
|
|
210
222
|
response: HttpClientResponse.HttpClientResponse
|
|
211
223
|
): [
|
|
212
224
|
HttpClientResponse.HttpClientResponse,
|
|
213
|
-
Stream.Stream<typeof
|
|
225
|
+
Stream.Stream<typeof OpenAiSchema.ResponseStreamEvent.Type, AiError.AiError>
|
|
214
226
|
] => {
|
|
215
227
|
const stream = response.stream.pipe(
|
|
216
228
|
Stream.decodeText(),
|
|
217
|
-
Stream.pipeThroughChannel(Sse.decodeDataSchema(
|
|
229
|
+
Stream.pipeThroughChannel(Sse.decodeDataSchema(OpenAiSchema.ResponseStreamEvent)),
|
|
218
230
|
Stream.takeUntil((event) =>
|
|
219
231
|
event.data.type === "response.completed" ||
|
|
220
232
|
event.data.type === "response.incomplete"
|
|
@@ -226,7 +238,7 @@ export const make = Effect.fnUntraced(
|
|
|
226
238
|
HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createResponseStream")),
|
|
227
239
|
SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createResponseStream"))
|
|
228
240
|
})
|
|
229
|
-
)
|
|
241
|
+
)
|
|
230
242
|
return [response, stream]
|
|
231
243
|
}
|
|
232
244
|
|
|
@@ -234,31 +246,40 @@ export const make = Effect.fnUntraced(
|
|
|
234
246
|
Effect.contextWith((services) => {
|
|
235
247
|
const socket = Context.getOrUndefined(services, OpenAiSocket)
|
|
236
248
|
if (socket) return socket.createResponseStream(payload)
|
|
237
|
-
return
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
return Effect.flatMap(resolveHttpClient, (client) =>
|
|
250
|
+
client.execute(
|
|
251
|
+
HttpClientRequest.post("/responses", {
|
|
252
|
+
body: HttpBody.jsonUnsafe({ ...payload, stream: true })
|
|
253
|
+
})
|
|
254
|
+
).pipe(
|
|
255
|
+
Effect.map(buildResponseStream),
|
|
256
|
+
Effect.catchTag(
|
|
257
|
+
"HttpClientError",
|
|
258
|
+
(error) => Errors.mapHttpClientError(error, "createResponseStream")
|
|
259
|
+
)
|
|
260
|
+
))
|
|
248
261
|
})
|
|
249
262
|
|
|
263
|
+
const decodeEmbedding = HttpClientResponse.schemaBodyJson(OpenAiSchema.CreateEmbeddingResponse)
|
|
264
|
+
|
|
250
265
|
const createEmbedding = (
|
|
251
|
-
payload: typeof
|
|
252
|
-
): Effect.Effect<typeof
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
266
|
+
payload: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded
|
|
267
|
+
): Effect.Effect<typeof OpenAiSchema.CreateEmbeddingResponse.Type, AiError.AiError> =>
|
|
268
|
+
Effect.flatMap(resolveHttpClient, (client) =>
|
|
269
|
+
client.execute(
|
|
270
|
+
HttpClientRequest.post("/embeddings", {
|
|
271
|
+
body: HttpBody.jsonUnsafe(payload)
|
|
272
|
+
})
|
|
273
|
+
).pipe(
|
|
274
|
+
Effect.flatMap(decodeEmbedding),
|
|
275
|
+
Effect.catchTags({
|
|
276
|
+
HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"),
|
|
277
|
+
SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding"))
|
|
278
|
+
})
|
|
279
|
+
))
|
|
259
280
|
|
|
260
281
|
return OpenAiClient.of({
|
|
261
|
-
client,
|
|
282
|
+
client: httpClient,
|
|
262
283
|
createResponse,
|
|
263
284
|
createResponseStream,
|
|
264
285
|
createEmbedding
|
|
@@ -349,7 +370,7 @@ export const layerConfig = (options?: {
|
|
|
349
370
|
* @since 1.0.0
|
|
350
371
|
* @category Events
|
|
351
372
|
*/
|
|
352
|
-
export type ResponseStreamEvent = typeof
|
|
373
|
+
export type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type
|
|
353
374
|
|
|
354
375
|
/**
|
|
355
376
|
* @since 1.0.0
|
|
@@ -360,7 +381,7 @@ export class OpenAiSocket extends Context.Service<OpenAiSocket, {
|
|
|
360
381
|
* Create a streaming response using the OpenAI responses endpoint.
|
|
361
382
|
*/
|
|
362
383
|
readonly createResponseStream: (
|
|
363
|
-
options: Omit<typeof
|
|
384
|
+
options: Omit<typeof OpenAiSchema.CreateResponse.Encoded, "stream">
|
|
364
385
|
) => Effect.Effect<
|
|
365
386
|
readonly [
|
|
366
387
|
response: HttpClientResponse.HttpClientResponse,
|
|
@@ -374,14 +395,22 @@ const makeSocket = Effect.gen(function*() {
|
|
|
374
395
|
const client = yield* OpenAiClient
|
|
375
396
|
const tracker = yield* ResponseIdTracker.make
|
|
376
397
|
const socketScope = yield* Effect.scope
|
|
377
|
-
const makeRequest = Effect.
|
|
398
|
+
const makeRequest = Effect.flatMap(
|
|
399
|
+
OpenAiConfig.getOrUndefined,
|
|
400
|
+
(config) => {
|
|
401
|
+
const httpClient = Predicate.isNotUndefined(config?.transformClient)
|
|
402
|
+
? config.transformClient(client.client)
|
|
403
|
+
: client.client
|
|
404
|
+
return Effect.orDie(httpClient.preprocess(HttpClientRequest.post("/responses")))
|
|
405
|
+
}
|
|
406
|
+
)
|
|
378
407
|
const makeWebSocket = yield* Socket.WebSocketConstructor
|
|
379
408
|
|
|
380
409
|
const decoder = new TextDecoder()
|
|
381
410
|
|
|
382
411
|
const queueRef: RcRef.RcRef<
|
|
383
412
|
{
|
|
384
|
-
readonly send: (message: typeof
|
|
413
|
+
readonly send: (message: typeof OpenAiSchema.CreateResponse.Encoded) => Effect.Effect<void, AiError.AiError>
|
|
385
414
|
readonly incoming: Queue.Dequeue<ResponseStreamEvent, AiError.AiError>
|
|
386
415
|
}
|
|
387
416
|
> = yield* RcRef.make({
|
|
@@ -403,7 +432,7 @@ const makeSocket = Effect.gen(function*() {
|
|
|
403
432
|
})
|
|
404
433
|
|
|
405
434
|
const incoming = yield* Queue.unbounded<ResponseStreamEvent, AiError.AiError>()
|
|
406
|
-
const send = (message: typeof
|
|
435
|
+
const send = (message: typeof OpenAiSchema.CreateResponse.Encoded) =>
|
|
407
436
|
write(JSON.stringify({
|
|
408
437
|
type: "response.create",
|
|
409
438
|
...message
|
|
@@ -432,15 +461,17 @@ const makeSocket = Effect.gen(function*() {
|
|
|
432
461
|
try {
|
|
433
462
|
const event = decodeEvent(text)
|
|
434
463
|
if (event.type === "error" && "status" in event) {
|
|
435
|
-
const
|
|
464
|
+
const status = Number(event.status)
|
|
465
|
+
const error = "error" in event ? event.error : event
|
|
466
|
+
const json = JSON.stringify(error)
|
|
436
467
|
return Effect.fail(
|
|
437
468
|
AiError.make({
|
|
438
469
|
module: "OpenAiClient",
|
|
439
470
|
method: "createResponseStream",
|
|
440
471
|
reason: AiError.reasonFromHttpStatus({
|
|
441
472
|
description: json,
|
|
442
|
-
status:
|
|
443
|
-
metadata:
|
|
473
|
+
status: isNaN(status) ? 500 : status,
|
|
474
|
+
metadata: error as any,
|
|
444
475
|
http: {
|
|
445
476
|
body: json,
|
|
446
477
|
request: {
|
|
@@ -542,7 +573,7 @@ const ErrorEvent = Schema.Struct({
|
|
|
542
573
|
})
|
|
543
574
|
})
|
|
544
575
|
|
|
545
|
-
const AllEvents = Schema.Union([ErrorEvent,
|
|
576
|
+
const AllEvents = Schema.Union([ErrorEvent, OpenAiSchema.ResponseStreamEvent])
|
|
546
577
|
const decodeEvent = Schema.decodeUnknownSync(Schema.fromJsonString(AllEvents))
|
|
547
578
|
|
|
548
579
|
/**
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as Array from "effect/Array"
|
|
5
|
+
import type * as Config from "effect/Config"
|
|
6
|
+
import * as Context from "effect/Context"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import { identity } from "effect/Function"
|
|
9
|
+
import * as Function from "effect/Function"
|
|
10
|
+
import * as Layer from "effect/Layer"
|
|
11
|
+
import * as Predicate from "effect/Predicate"
|
|
12
|
+
import * as Redacted from "effect/Redacted"
|
|
13
|
+
import * as Headers from "effect/unstable/http/Headers"
|
|
14
|
+
import * as HttpClient from "effect/unstable/http/HttpClient"
|
|
15
|
+
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
|
|
16
|
+
import * as Generated from "./Generated.ts"
|
|
17
|
+
import { OpenAiConfig } from "./OpenAiConfig.ts"
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Service Identifier
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Service identifier for the generated OpenAI client.
|
|
25
|
+
*
|
|
26
|
+
* @since 1.0.0
|
|
27
|
+
* @category service
|
|
28
|
+
*/
|
|
29
|
+
export class OpenAiClientGenerated extends Context.Service<OpenAiClientGenerated, Generated.OpenAiClient>()(
|
|
30
|
+
"@effect/ai-openai/OpenAiClientGenerated"
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Options
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for configuring the generated OpenAI client.
|
|
39
|
+
*
|
|
40
|
+
* @since 1.0.0
|
|
41
|
+
* @category models
|
|
42
|
+
*/
|
|
43
|
+
export type Options = {
|
|
44
|
+
/**
|
|
45
|
+
* The OpenAI API key.
|
|
46
|
+
*/
|
|
47
|
+
readonly apiKey?: Redacted.Redacted<string> | undefined
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The base URL for the OpenAI API.
|
|
51
|
+
*
|
|
52
|
+
* @default "https://api.openai.com/v1"
|
|
53
|
+
*/
|
|
54
|
+
readonly apiUrl?: string | undefined
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Optional organization ID for multi-org accounts.
|
|
58
|
+
*/
|
|
59
|
+
readonly organizationId?: Redacted.Redacted<string> | undefined
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Optional project ID for project-scoped requests.
|
|
63
|
+
*/
|
|
64
|
+
readonly projectId?: Redacted.Redacted<string> | undefined
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Optional transformer for the HTTP client.
|
|
68
|
+
*/
|
|
69
|
+
readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const RedactedOpenAiHeaders = {
|
|
73
|
+
OpenAiOrganization: "OpenAI-Organization",
|
|
74
|
+
OpenAiProject: "OpenAI-Project"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// Constructor
|
|
79
|
+
// =============================================================================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Creates a generated OpenAI client service with the given options.
|
|
83
|
+
*
|
|
84
|
+
* @since 1.0.0
|
|
85
|
+
* @category constructors
|
|
86
|
+
*/
|
|
87
|
+
export const make = Effect.fnUntraced(
|
|
88
|
+
function*(options: Options): Effect.fn.Return<Generated.OpenAiClient, never, HttpClient.HttpClient> {
|
|
89
|
+
const baseClient = yield* HttpClient.HttpClient
|
|
90
|
+
const apiUrl = options.apiUrl ?? "https://api.openai.com/v1"
|
|
91
|
+
|
|
92
|
+
const httpClient = baseClient.pipe(
|
|
93
|
+
HttpClient.mapRequest(Function.flow(
|
|
94
|
+
HttpClientRequest.prependUrl(apiUrl),
|
|
95
|
+
options.apiKey
|
|
96
|
+
? HttpClientRequest.bearerToken(Redacted.value(options.apiKey))
|
|
97
|
+
: identity,
|
|
98
|
+
options.organizationId
|
|
99
|
+
? HttpClientRequest.setHeader(
|
|
100
|
+
RedactedOpenAiHeaders.OpenAiOrganization,
|
|
101
|
+
Redacted.value(options.organizationId)
|
|
102
|
+
)
|
|
103
|
+
: identity,
|
|
104
|
+
options.projectId
|
|
105
|
+
? HttpClientRequest.setHeader(
|
|
106
|
+
RedactedOpenAiHeaders.OpenAiProject,
|
|
107
|
+
Redacted.value(options.projectId)
|
|
108
|
+
)
|
|
109
|
+
: identity,
|
|
110
|
+
HttpClientRequest.acceptJson
|
|
111
|
+
)),
|
|
112
|
+
options.transformClient
|
|
113
|
+
? options.transformClient
|
|
114
|
+
: identity
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return Generated.make(httpClient, {
|
|
118
|
+
transformClient: Effect.fnUntraced(function*(client) {
|
|
119
|
+
const config = yield* OpenAiConfig.getOrUndefined
|
|
120
|
+
if (Predicate.isNotUndefined(config?.transformClient)) {
|
|
121
|
+
return config.transformClient(client)
|
|
122
|
+
}
|
|
123
|
+
return client
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
},
|
|
127
|
+
Effect.updateService(
|
|
128
|
+
Headers.CurrentRedactedNames,
|
|
129
|
+
Array.appendAll(Object.values(RedactedOpenAiHeaders))
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// =============================================================================
|
|
134
|
+
// Layers
|
|
135
|
+
// =============================================================================
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Creates a layer for the generated OpenAI client with the given options.
|
|
139
|
+
*
|
|
140
|
+
* @since 1.0.0
|
|
141
|
+
* @category layers
|
|
142
|
+
*/
|
|
143
|
+
export const layer = (options: Options): Layer.Layer<OpenAiClientGenerated, never, HttpClient.HttpClient> =>
|
|
144
|
+
Layer.effect(OpenAiClientGenerated, make(options))
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Creates a layer for the generated OpenAI client, loading the requisite
|
|
148
|
+
* configuration via Effect's `Config` module.
|
|
149
|
+
*
|
|
150
|
+
* @since 1.0.0
|
|
151
|
+
* @category layers
|
|
152
|
+
*/
|
|
153
|
+
export const layerConfig = (options?: {
|
|
154
|
+
/**
|
|
155
|
+
* The config value to load for the API key.
|
|
156
|
+
*/
|
|
157
|
+
readonly apiKey?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The config value to load for the API URL.
|
|
161
|
+
*/
|
|
162
|
+
readonly apiUrl?: Config.Config<string> | undefined
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The config value to load for the organization ID.
|
|
166
|
+
*/
|
|
167
|
+
readonly organizationId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The config value to load for the project ID.
|
|
171
|
+
*/
|
|
172
|
+
readonly projectId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Optional transformer for the HTTP client.
|
|
176
|
+
*/
|
|
177
|
+
readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
|
|
178
|
+
}): Layer.Layer<OpenAiClientGenerated, Config.ConfigError, HttpClient.HttpClient> =>
|
|
179
|
+
Layer.effect(
|
|
180
|
+
OpenAiClientGenerated,
|
|
181
|
+
Effect.gen(function*() {
|
|
182
|
+
const apiKey = Predicate.isNotUndefined(options?.apiKey)
|
|
183
|
+
? yield* options.apiKey :
|
|
184
|
+
undefined
|
|
185
|
+
const apiUrl = Predicate.isNotUndefined(options?.apiUrl)
|
|
186
|
+
? yield* options.apiUrl :
|
|
187
|
+
undefined
|
|
188
|
+
const organizationId = Predicate.isNotUndefined(options?.organizationId)
|
|
189
|
+
? yield* options.organizationId
|
|
190
|
+
: undefined
|
|
191
|
+
const projectId = Predicate.isNotUndefined(options?.projectId)
|
|
192
|
+
? yield* options.projectId :
|
|
193
|
+
undefined
|
|
194
|
+
return yield* make({
|
|
195
|
+
apiKey,
|
|
196
|
+
apiUrl,
|
|
197
|
+
organizationId,
|
|
198
|
+
projectId,
|
|
199
|
+
transformClient: options?.transformClient
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
)
|
|
@@ -13,8 +13,8 @@ import type { Simplify } from "effect/Types"
|
|
|
13
13
|
import * as AiError from "effect/unstable/ai/AiError"
|
|
14
14
|
import * as EmbeddingModel from "effect/unstable/ai/EmbeddingModel"
|
|
15
15
|
import * as AiModel from "effect/unstable/ai/Model"
|
|
16
|
-
import type * as Generated from "./Generated.ts"
|
|
17
16
|
import { OpenAiClient } from "./OpenAiClient.ts"
|
|
17
|
+
import type * as OpenAiSchema from "./OpenAiSchema.ts"
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* @since 1.0.0
|
|
@@ -33,7 +33,7 @@ export class Config extends Context.Service<
|
|
|
33
33
|
Simplify<
|
|
34
34
|
& Partial<
|
|
35
35
|
Omit<
|
|
36
|
-
typeof
|
|
36
|
+
typeof OpenAiSchema.CreateEmbeddingRequest.Encoded,
|
|
37
37
|
"input"
|
|
38
38
|
>
|
|
39
39
|
>
|
|
@@ -155,7 +155,7 @@ export const withConfigOverride: {
|
|
|
155
155
|
|
|
156
156
|
const mapProviderResponse = (
|
|
157
157
|
inputLength: number,
|
|
158
|
-
response: typeof
|
|
158
|
+
response: typeof OpenAiSchema.CreateEmbeddingResponse.Type
|
|
159
159
|
): Effect.Effect<EmbeddingModel.ProviderResponse, AiError.AiError> => {
|
|
160
160
|
if (response.data.length !== inputLength) {
|
|
161
161
|
return Effect.fail(
|
|
@@ -173,6 +173,9 @@ const mapProviderResponse = (
|
|
|
173
173
|
if (seen.has(entry.index)) {
|
|
174
174
|
return Effect.fail(invalidOutput("Provider returned duplicate embedding index: " + entry.index))
|
|
175
175
|
}
|
|
176
|
+
if (!Array.isArray(entry.embedding)) {
|
|
177
|
+
return Effect.fail(invalidOutput("Provider returned non-vector embedding at index " + entry.index))
|
|
178
|
+
}
|
|
176
179
|
|
|
177
180
|
seen.add(entry.index)
|
|
178
181
|
results[entry.index] = [...entry.embedding]
|