@effect/ai-openai 4.0.0-beta.8 → 4.0.0-beta.81
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/Generated.d.ts +66675 -37672
- package/dist/Generated.d.ts.map +1 -1
- package/dist/Generated.js +1 -1
- package/dist/Generated.js.map +1 -1
- package/dist/OpenAiClient.d.ts +170 -29
- package/dist/OpenAiClient.d.ts.map +1 -1
- package/dist/OpenAiClient.js +321 -46
- 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/OpenAiConfig.d.ts +88 -10
- package/dist/OpenAiConfig.d.ts.map +1 -1
- package/dist/OpenAiConfig.js +42 -7
- package/dist/OpenAiConfig.js.map +1 -1
- package/dist/OpenAiEmbeddingModel.d.ts +188 -0
- package/dist/OpenAiEmbeddingModel.d.ts.map +1 -0
- package/dist/OpenAiEmbeddingModel.js +194 -0
- package/dist/OpenAiEmbeddingModel.js.map +1 -0
- package/dist/OpenAiError.d.ts +168 -35
- package/dist/OpenAiError.d.ts.map +1 -1
- package/dist/OpenAiError.js +1 -1
- package/dist/OpenAiLanguageModel.d.ts +357 -63
- package/dist/OpenAiLanguageModel.d.ts.map +1 -1
- package/dist/OpenAiLanguageModel.js +390 -168
- package/dist/OpenAiLanguageModel.js.map +1 -1
- package/dist/OpenAiSchema.d.ts +2325 -0
- package/dist/OpenAiSchema.d.ts.map +1 -0
- package/dist/OpenAiSchema.js +811 -0
- package/dist/OpenAiSchema.js.map +1 -0
- package/dist/OpenAiTelemetry.d.ts +63 -22
- package/dist/OpenAiTelemetry.d.ts.map +1 -1
- package/dist/OpenAiTelemetry.js +20 -10
- package/dist/OpenAiTelemetry.js.map +1 -1
- package/dist/OpenAiTool.d.ts +148 -62
- package/dist/OpenAiTool.d.ts.map +1 -1
- package/dist/OpenAiTool.js +125 -39
- package/dist/OpenAiTool.js.map +1 -1
- package/dist/index.d.ts +19 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -33
- package/dist/index.js.map +1 -1
- package/dist/internal/errors.js +4 -4
- package/dist/internal/errors.js.map +1 -1
- package/package.json +3 -3
- package/src/Generated.ts +9717 -4887
- package/src/OpenAiClient.ts +499 -98
- package/src/OpenAiClientGenerated.ts +202 -0
- package/src/OpenAiConfig.ts +89 -11
- package/src/OpenAiEmbeddingModel.ts +332 -0
- package/src/OpenAiError.ts +170 -35
- package/src/OpenAiLanguageModel.ts +776 -169
- package/src/OpenAiSchema.ts +1286 -0
- package/src/OpenAiTelemetry.ts +69 -28
- package/src/OpenAiTool.ts +126 -40
- package/src/index.ts +22 -33
- package/src/internal/errors.ts +6 -4
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 4.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 4.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 4.0.0
|
|
41
|
+
* @category options
|
|
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 4.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 4.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 4.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
|
+
)
|
package/src/OpenAiConfig.ts
CHANGED
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `OpenAiConfig` module lets a workflow temporarily customize the HTTP
|
|
3
|
+
* client used by `@effect/ai-openai` request helpers. OpenAI client, language
|
|
4
|
+
* model, and embedding code read this scoped transform when they execute
|
|
5
|
+
* provider calls.
|
|
6
|
+
*
|
|
7
|
+
* @since 4.0.0
|
|
3
8
|
*/
|
|
9
|
+
import * as Context from "effect/Context"
|
|
4
10
|
import * as Effect from "effect/Effect"
|
|
5
11
|
import { dual } from "effect/Function"
|
|
6
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
7
12
|
import type { HttpClient } from "effect/unstable/http/HttpClient"
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
|
-
*
|
|
15
|
+
* Context service for scoped OpenAI configuration used by provider operations.
|
|
16
|
+
*
|
|
17
|
+
* **When to use**
|
|
18
|
+
*
|
|
19
|
+
* Use to provide scoped OpenAI client configuration, such as an HTTP client
|
|
20
|
+
* transform, to OpenAI provider operations without passing it through each call.
|
|
21
|
+
*
|
|
22
|
+
* @see {@link withClientTransform} for scoping an HTTP client transformation
|
|
23
|
+
*
|
|
11
24
|
* @category services
|
|
25
|
+
* @since 4.0.0
|
|
12
26
|
*/
|
|
13
|
-
export class OpenAiConfig extends
|
|
27
|
+
export class OpenAiConfig extends Context.Service<
|
|
14
28
|
OpenAiConfig,
|
|
15
29
|
OpenAiConfig.Service
|
|
16
30
|
>()("@effect/ai-openai/OpenAiConfig") {
|
|
17
31
|
/**
|
|
18
|
-
*
|
|
32
|
+
* Gets the configured OpenAI service from the current context when present.
|
|
33
|
+
*
|
|
34
|
+
* @since 4.0.0
|
|
19
35
|
*/
|
|
20
36
|
static readonly getOrUndefined: Effect.Effect<typeof OpenAiConfig.Service | undefined> = Effect.map(
|
|
21
|
-
Effect.
|
|
37
|
+
Effect.context<never>(),
|
|
22
38
|
(context) => context.mapUnsafe.get(OpenAiConfig.key)
|
|
23
39
|
)
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
/**
|
|
27
|
-
*
|
|
43
|
+
* Types used by the `OpenAiConfig` context service.
|
|
44
|
+
*
|
|
45
|
+
* @since 4.0.0
|
|
28
46
|
*/
|
|
29
47
|
export declare namespace OpenAiConfig {
|
|
30
48
|
/**
|
|
31
|
-
*
|
|
49
|
+
* Configuration values read by OpenAI provider operations when executing
|
|
50
|
+
* requests.
|
|
51
|
+
*
|
|
32
52
|
* @category models
|
|
53
|
+
* @since 4.0.0
|
|
33
54
|
*/
|
|
34
55
|
export interface Service {
|
|
35
56
|
readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined
|
|
@@ -37,18 +58,75 @@ export declare namespace OpenAiConfig {
|
|
|
37
58
|
}
|
|
38
59
|
|
|
39
60
|
/**
|
|
40
|
-
*
|
|
61
|
+
* Provides a scoped transform for the OpenAI HTTP client used by provider
|
|
62
|
+
* operations.
|
|
63
|
+
*
|
|
64
|
+
* **When to use**
|
|
65
|
+
*
|
|
66
|
+
* Use when you need temporary OpenAI HTTP client customization for a single
|
|
67
|
+
* effect or workflow without rebuilding the client layer.
|
|
68
|
+
*
|
|
69
|
+
* **Details**
|
|
70
|
+
*
|
|
71
|
+
* Supports both data-first and data-last forms. The transform is stored in the
|
|
72
|
+
* scoped `OpenAiConfig` service and read by OpenAI provider operations while
|
|
73
|
+
* running the supplied effect.
|
|
74
|
+
*
|
|
75
|
+
* **Gotchas**
|
|
76
|
+
*
|
|
77
|
+
* If a transform is already present in the scoped config, this helper replaces
|
|
78
|
+
* it. Compose transforms manually when both should apply.
|
|
79
|
+
*
|
|
41
80
|
* @category configuration
|
|
81
|
+
* @since 4.0.0
|
|
42
82
|
*/
|
|
43
83
|
export const withClientTransform: {
|
|
44
84
|
/**
|
|
45
|
-
*
|
|
85
|
+
* Provides a scoped transform for the OpenAI HTTP client used by provider
|
|
86
|
+
* operations.
|
|
87
|
+
*
|
|
88
|
+
* **When to use**
|
|
89
|
+
*
|
|
90
|
+
* Use when you need temporary OpenAI HTTP client customization for a single
|
|
91
|
+
* effect or workflow without rebuilding the client layer.
|
|
92
|
+
*
|
|
93
|
+
* **Details**
|
|
94
|
+
*
|
|
95
|
+
* Supports both data-first and data-last forms. The transform is stored in the
|
|
96
|
+
* scoped `OpenAiConfig` service and read by OpenAI provider operations while
|
|
97
|
+
* running the supplied effect.
|
|
98
|
+
*
|
|
99
|
+
* **Gotchas**
|
|
100
|
+
*
|
|
101
|
+
* If a transform is already present in the scoped config, this helper replaces
|
|
102
|
+
* it. Compose transforms manually when both should apply.
|
|
103
|
+
*
|
|
46
104
|
* @category configuration
|
|
105
|
+
* @since 4.0.0
|
|
47
106
|
*/
|
|
48
107
|
(transform: (client: HttpClient) => HttpClient): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
|
|
49
108
|
/**
|
|
50
|
-
*
|
|
109
|
+
* Provides a scoped transform for the OpenAI HTTP client used by provider
|
|
110
|
+
* operations.
|
|
111
|
+
*
|
|
112
|
+
* **When to use**
|
|
113
|
+
*
|
|
114
|
+
* Use when you need temporary OpenAI HTTP client customization for a single
|
|
115
|
+
* effect or workflow without rebuilding the client layer.
|
|
116
|
+
*
|
|
117
|
+
* **Details**
|
|
118
|
+
*
|
|
119
|
+
* Supports both data-first and data-last forms. The transform is stored in the
|
|
120
|
+
* scoped `OpenAiConfig` service and read by OpenAI provider operations while
|
|
121
|
+
* running the supplied effect.
|
|
122
|
+
*
|
|
123
|
+
* **Gotchas**
|
|
124
|
+
*
|
|
125
|
+
* If a transform is already present in the scoped config, this helper replaces
|
|
126
|
+
* it. Compose transforms manually when both should apply.
|
|
127
|
+
*
|
|
51
128
|
* @category configuration
|
|
129
|
+
* @since 4.0.0
|
|
52
130
|
*/
|
|
53
131
|
<A, E, R>(
|
|
54
132
|
self: Effect.Effect<A, E, R>,
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `OpenAiEmbeddingModel` module provides the OpenAI implementation of
|
|
3
|
+
* Effect AI's `EmbeddingModel` service. It sends embedding requests through
|
|
4
|
+
* `OpenAiClient`, exposes constructors for layers and `AiModel` values,
|
|
5
|
+
* supports scoped request configuration overrides, and checks that OpenAI
|
|
6
|
+
* returns one numeric vector for each requested input.
|
|
7
|
+
*
|
|
8
|
+
* @since 4.0.0
|
|
9
|
+
*/
|
|
10
|
+
import * as Context from "effect/Context"
|
|
11
|
+
import * as Effect from "effect/Effect"
|
|
12
|
+
import { dual } from "effect/Function"
|
|
13
|
+
import * as Layer from "effect/Layer"
|
|
14
|
+
import type { Simplify } from "effect/Types"
|
|
15
|
+
import * as AiError from "effect/unstable/ai/AiError"
|
|
16
|
+
import * as EmbeddingModel from "effect/unstable/ai/EmbeddingModel"
|
|
17
|
+
import * as AiModel from "effect/unstable/ai/Model"
|
|
18
|
+
import { OpenAiClient } from "./OpenAiClient.ts"
|
|
19
|
+
import type * as OpenAiSchema from "./OpenAiSchema.ts"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Model identifiers supported by OpenAI's embeddings API.
|
|
23
|
+
*
|
|
24
|
+
* @category models
|
|
25
|
+
* @since 4.0.0
|
|
26
|
+
*/
|
|
27
|
+
export type Model = "text-embedding-ada-002" | "text-embedding-3-small" | "text-embedding-3-large"
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Context service for OpenAI embedding model configuration.
|
|
31
|
+
*
|
|
32
|
+
* **When to use**
|
|
33
|
+
*
|
|
34
|
+
* Use when you need scoped OpenAI request defaults or overrides for embedding
|
|
35
|
+
* requests from Effect context.
|
|
36
|
+
*
|
|
37
|
+
* **Details**
|
|
38
|
+
*
|
|
39
|
+
* The service stores the OpenAI create-embedding request payload without
|
|
40
|
+
* `input`, carrying options such as `model`, `dimensions`, `encoding_format`,
|
|
41
|
+
* and `user`.
|
|
42
|
+
*
|
|
43
|
+
* @see {@link withConfigOverride} for scoping embedding request overrides
|
|
44
|
+
*
|
|
45
|
+
* @category services
|
|
46
|
+
* @since 4.0.0
|
|
47
|
+
*/
|
|
48
|
+
export class Config extends Context.Service<
|
|
49
|
+
Config,
|
|
50
|
+
Simplify<
|
|
51
|
+
& Partial<
|
|
52
|
+
Omit<
|
|
53
|
+
typeof OpenAiSchema.CreateEmbeddingRequest.Encoded,
|
|
54
|
+
"input"
|
|
55
|
+
>
|
|
56
|
+
>
|
|
57
|
+
& {
|
|
58
|
+
readonly [x: string]: unknown
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
>()("@effect/ai-openai/OpenAiEmbeddingModel/Config") {}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates an `AiModel` for an OpenAI embedding model with its configured vector dimensions.
|
|
65
|
+
*
|
|
66
|
+
* **When to use**
|
|
67
|
+
*
|
|
68
|
+
* Use to provide an OpenAI `EmbeddingModel` and its `Dimensions` service to an
|
|
69
|
+
* Effect program.
|
|
70
|
+
*
|
|
71
|
+
* @see {@link layer} for providing only the embedding model service
|
|
72
|
+
* @see {@link withConfigOverride} for scoped request configuration overrides
|
|
73
|
+
*
|
|
74
|
+
* @category constructors
|
|
75
|
+
* @since 4.0.0
|
|
76
|
+
*/
|
|
77
|
+
export const model = (
|
|
78
|
+
model: (string & {}) | Model,
|
|
79
|
+
options: {
|
|
80
|
+
readonly dimensions: number
|
|
81
|
+
readonly config?: Omit<typeof Config.Service, "model" | "dimensions">
|
|
82
|
+
}
|
|
83
|
+
): AiModel.Model<"openai", EmbeddingModel.EmbeddingModel | EmbeddingModel.Dimensions, OpenAiClient> =>
|
|
84
|
+
AiModel.make(
|
|
85
|
+
"openai",
|
|
86
|
+
model,
|
|
87
|
+
Layer.merge(
|
|
88
|
+
layer({
|
|
89
|
+
model,
|
|
90
|
+
config: {
|
|
91
|
+
...options.config,
|
|
92
|
+
dimensions: options.dimensions
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
Layer.succeed(EmbeddingModel.Dimensions, options.dimensions)
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Creates an OpenAI embedding model service.
|
|
101
|
+
*
|
|
102
|
+
* **When to use**
|
|
103
|
+
*
|
|
104
|
+
* Use to construct the `EmbeddingModel.Service` effectfully when
|
|
105
|
+
* `OpenAiClient` is already available in the environment.
|
|
106
|
+
*
|
|
107
|
+
* **Details**
|
|
108
|
+
*
|
|
109
|
+
* The `model` option is sent with each embedding request. Constructor `config`
|
|
110
|
+
* supplies create-embedding request fields other than `model` and `input`, and
|
|
111
|
+
* scoped overrides from `withConfigOverride` are merged last for each request.
|
|
112
|
+
*
|
|
113
|
+
* **Gotchas**
|
|
114
|
+
*
|
|
115
|
+
* The service expects numeric embedding vectors. It fails with
|
|
116
|
+
* `InvalidOutputError` when the provider returns base64 embeddings,
|
|
117
|
+
* out-of-range indexes, duplicate indexes, or an unexpected number of
|
|
118
|
+
* embeddings.
|
|
119
|
+
*
|
|
120
|
+
* @see {@link layer} for providing the embedding model service as a layer
|
|
121
|
+
* @see {@link model} for creating an `AiModel` that also provides dimensions
|
|
122
|
+
* @see {@link withConfigOverride} for scoped request configuration overrides
|
|
123
|
+
*
|
|
124
|
+
* @category constructors
|
|
125
|
+
* @since 4.0.0
|
|
126
|
+
*/
|
|
127
|
+
export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: {
|
|
128
|
+
readonly model: (string & {}) | Model
|
|
129
|
+
readonly config?: Omit<typeof Config.Service, "model"> | undefined
|
|
130
|
+
}): Effect.fn.Return<EmbeddingModel.Service, never, OpenAiClient> {
|
|
131
|
+
const client = yield* OpenAiClient
|
|
132
|
+
|
|
133
|
+
const makeConfig = Effect.gen(function*() {
|
|
134
|
+
const services = yield* Effect.context<never>()
|
|
135
|
+
return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) }
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
return yield* EmbeddingModel.make({
|
|
139
|
+
embedMany: Effect.fnUntraced(function*({ inputs }) {
|
|
140
|
+
const config = yield* makeConfig
|
|
141
|
+
const response = yield* client.createEmbedding({ ...config, input: inputs })
|
|
142
|
+
return yield* mapProviderResponse(inputs.length, response)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Creates a layer for the OpenAI embedding model.
|
|
149
|
+
*
|
|
150
|
+
* **When to use**
|
|
151
|
+
*
|
|
152
|
+
* Use when composing application layers and you want OpenAI to satisfy
|
|
153
|
+
* `EmbeddingModel.EmbeddingModel` while supplying `OpenAiClient` from another
|
|
154
|
+
* layer.
|
|
155
|
+
*
|
|
156
|
+
* **Gotchas**
|
|
157
|
+
*
|
|
158
|
+
* Use the default floating-point embedding format. The service expects numeric
|
|
159
|
+
* vectors and fails with `InvalidOutputError` if OpenAI returns base64
|
|
160
|
+
* embeddings.
|
|
161
|
+
*
|
|
162
|
+
* @see {@link make} for constructing the embedding model service effectfully
|
|
163
|
+
* @see {@link model} for creating an `AiModel` that also provides embedding dimensions
|
|
164
|
+
*
|
|
165
|
+
* @category layers
|
|
166
|
+
* @since 4.0.0
|
|
167
|
+
*/
|
|
168
|
+
export const layer = (options: {
|
|
169
|
+
readonly model: (string & {}) | Model
|
|
170
|
+
readonly config?: Omit<typeof Config.Service, "model"> | undefined
|
|
171
|
+
}): Layer.Layer<EmbeddingModel.EmbeddingModel, never, OpenAiClient> =>
|
|
172
|
+
Layer.effect(EmbeddingModel.EmbeddingModel, make(options))
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Provides config overrides for OpenAI embedding model operations.
|
|
176
|
+
*
|
|
177
|
+
* **When to use**
|
|
178
|
+
*
|
|
179
|
+
* Use when you need scoped OpenAI embedding request defaults for a single
|
|
180
|
+
* effect or workflow without rebuilding the embedding model service.
|
|
181
|
+
*
|
|
182
|
+
* **Details**
|
|
183
|
+
*
|
|
184
|
+
* Supports both data-first and data-last forms. Existing scoped config is read
|
|
185
|
+
* first, then the provided overrides are applied so override fields take
|
|
186
|
+
* precedence.
|
|
187
|
+
*
|
|
188
|
+
* @see {@link Config} for the scoped embedding request configuration service
|
|
189
|
+
*
|
|
190
|
+
* @category configuration
|
|
191
|
+
* @since 4.0.0
|
|
192
|
+
*/
|
|
193
|
+
export const withConfigOverride: {
|
|
194
|
+
/**
|
|
195
|
+
* Provides config overrides for OpenAI embedding model operations.
|
|
196
|
+
*
|
|
197
|
+
* **When to use**
|
|
198
|
+
*
|
|
199
|
+
* Use when you need scoped OpenAI embedding request defaults for a single
|
|
200
|
+
* effect or workflow without rebuilding the embedding model service.
|
|
201
|
+
*
|
|
202
|
+
* **Details**
|
|
203
|
+
*
|
|
204
|
+
* Supports both data-first and data-last forms. Existing scoped config is read
|
|
205
|
+
* first, then the provided overrides are applied so override fields take
|
|
206
|
+
* precedence.
|
|
207
|
+
*
|
|
208
|
+
* @see {@link Config} for the scoped embedding request configuration service
|
|
209
|
+
*
|
|
210
|
+
* @category configuration
|
|
211
|
+
* @since 4.0.0
|
|
212
|
+
*/
|
|
213
|
+
(overrides: typeof Config.Service): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>
|
|
214
|
+
/**
|
|
215
|
+
* Provides config overrides for OpenAI embedding model operations.
|
|
216
|
+
*
|
|
217
|
+
* **When to use**
|
|
218
|
+
*
|
|
219
|
+
* Use when you need scoped OpenAI embedding request defaults for a single
|
|
220
|
+
* effect or workflow without rebuilding the embedding model service.
|
|
221
|
+
*
|
|
222
|
+
* **Details**
|
|
223
|
+
*
|
|
224
|
+
* Supports both data-first and data-last forms. Existing scoped config is read
|
|
225
|
+
* first, then the provided overrides are applied so override fields take
|
|
226
|
+
* precedence.
|
|
227
|
+
*
|
|
228
|
+
* @see {@link Config} for the scoped embedding request configuration service
|
|
229
|
+
*
|
|
230
|
+
* @category configuration
|
|
231
|
+
* @since 4.0.0
|
|
232
|
+
*/
|
|
233
|
+
<A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service): Effect.Effect<A, E, Exclude<R, Config>>
|
|
234
|
+
} = dual<
|
|
235
|
+
/**
|
|
236
|
+
* Provides config overrides for OpenAI embedding model operations.
|
|
237
|
+
*
|
|
238
|
+
* **When to use**
|
|
239
|
+
*
|
|
240
|
+
* Use when you need scoped OpenAI embedding request defaults for a single
|
|
241
|
+
* effect or workflow without rebuilding the embedding model service.
|
|
242
|
+
*
|
|
243
|
+
* **Details**
|
|
244
|
+
*
|
|
245
|
+
* Supports both data-first and data-last forms. Existing scoped config is read
|
|
246
|
+
* first, then the provided overrides are applied so override fields take
|
|
247
|
+
* precedence.
|
|
248
|
+
*
|
|
249
|
+
* @see {@link Config} for the scoped embedding request configuration service
|
|
250
|
+
*
|
|
251
|
+
* @category configuration
|
|
252
|
+
* @since 4.0.0
|
|
253
|
+
*/
|
|
254
|
+
(overrides: typeof Config.Service) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>,
|
|
255
|
+
/**
|
|
256
|
+
* Provides config overrides for OpenAI embedding model operations.
|
|
257
|
+
*
|
|
258
|
+
* **When to use**
|
|
259
|
+
*
|
|
260
|
+
* Use when you need scoped OpenAI embedding request defaults for a single
|
|
261
|
+
* effect or workflow without rebuilding the embedding model service.
|
|
262
|
+
*
|
|
263
|
+
* **Details**
|
|
264
|
+
*
|
|
265
|
+
* Supports both data-first and data-last forms. Existing scoped config is read
|
|
266
|
+
* first, then the provided overrides are applied so override fields take
|
|
267
|
+
* precedence.
|
|
268
|
+
*
|
|
269
|
+
* @see {@link Config} for the scoped embedding request configuration service
|
|
270
|
+
*
|
|
271
|
+
* @category configuration
|
|
272
|
+
* @since 4.0.0
|
|
273
|
+
*/
|
|
274
|
+
<A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service) => Effect.Effect<A, E, Exclude<R, Config>>
|
|
275
|
+
>(2, (self, overrides) =>
|
|
276
|
+
Effect.flatMap(
|
|
277
|
+
Effect.serviceOption(Config),
|
|
278
|
+
(config) =>
|
|
279
|
+
Effect.provideService(self, Config, {
|
|
280
|
+
...(config._tag === "Some" ? config.value : {}),
|
|
281
|
+
...overrides
|
|
282
|
+
})
|
|
283
|
+
))
|
|
284
|
+
|
|
285
|
+
const mapProviderResponse = (
|
|
286
|
+
inputLength: number,
|
|
287
|
+
response: typeof OpenAiSchema.CreateEmbeddingResponse.Type
|
|
288
|
+
): Effect.Effect<EmbeddingModel.ProviderResponse, AiError.AiError> => {
|
|
289
|
+
if (response.data.length !== inputLength) {
|
|
290
|
+
return Effect.fail(
|
|
291
|
+
invalidOutput("Provider returned " + response.data.length + " embeddings but expected " + inputLength)
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const results = new Array<Array<number>>(inputLength)
|
|
296
|
+
const seen = new Set<number>()
|
|
297
|
+
|
|
298
|
+
for (const entry of response.data) {
|
|
299
|
+
if (!Number.isInteger(entry.index) || entry.index < 0 || entry.index >= inputLength) {
|
|
300
|
+
return Effect.fail(invalidOutput("Provider returned invalid embedding index: " + entry.index))
|
|
301
|
+
}
|
|
302
|
+
if (seen.has(entry.index)) {
|
|
303
|
+
return Effect.fail(invalidOutput("Provider returned duplicate embedding index: " + entry.index))
|
|
304
|
+
}
|
|
305
|
+
if (!Array.isArray(entry.embedding)) {
|
|
306
|
+
return Effect.fail(invalidOutput("Provider returned non-vector embedding at index " + entry.index))
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
seen.add(entry.index)
|
|
310
|
+
results[entry.index] = [...entry.embedding]
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (seen.size !== inputLength) {
|
|
314
|
+
return Effect.fail(
|
|
315
|
+
invalidOutput("Provider returned embeddings for " + seen.size + " inputs but expected " + inputLength)
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return Effect.succeed({
|
|
320
|
+
results,
|
|
321
|
+
usage: {
|
|
322
|
+
inputTokens: response.usage?.prompt_tokens
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const invalidOutput = (description: string): AiError.AiError =>
|
|
328
|
+
AiError.make({
|
|
329
|
+
module: "OpenAiEmbeddingModel",
|
|
330
|
+
method: "embedMany",
|
|
331
|
+
reason: new AiError.InvalidOutputError({ description })
|
|
332
|
+
})
|