@effect/ai-openai 0.29.0 → 0.30.0
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/OpenAiTool/package.json +6 -0
- package/dist/cjs/Generated.js +5845 -4262
- package/dist/cjs/Generated.js.map +1 -1
- package/dist/cjs/OpenAiClient.js +1493 -129
- package/dist/cjs/OpenAiClient.js.map +1 -1
- package/dist/cjs/OpenAiEmbeddingModel.js +61 -50
- package/dist/cjs/OpenAiEmbeddingModel.js.map +1 -1
- package/dist/cjs/OpenAiLanguageModel.js +950 -326
- package/dist/cjs/OpenAiLanguageModel.js.map +1 -1
- package/dist/cjs/OpenAiTelemetry.js +4 -4
- package/dist/cjs/OpenAiTelemetry.js.map +1 -1
- package/dist/cjs/OpenAiTokenizer.js +46 -14
- package/dist/cjs/OpenAiTokenizer.js.map +1 -1
- package/dist/cjs/OpenAiTool.js +93 -0
- package/dist/cjs/OpenAiTool.js.map +1 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/utilities.js +11 -3
- package/dist/cjs/internal/utilities.js.map +1 -1
- package/dist/dts/Generated.d.ts +19663 -11762
- package/dist/dts/Generated.d.ts.map +1 -1
- package/dist/dts/OpenAiClient.d.ts +3022 -14
- package/dist/dts/OpenAiClient.d.ts.map +1 -1
- package/dist/dts/OpenAiEmbeddingModel.d.ts +8 -8
- package/dist/dts/OpenAiEmbeddingModel.d.ts.map +1 -1
- package/dist/dts/OpenAiLanguageModel.d.ts +123 -43
- package/dist/dts/OpenAiLanguageModel.d.ts.map +1 -1
- package/dist/dts/OpenAiTelemetry.d.ts +4 -4
- package/dist/dts/OpenAiTelemetry.d.ts.map +1 -1
- package/dist/dts/OpenAiTokenizer.d.ts +1 -1
- package/dist/dts/OpenAiTokenizer.d.ts.map +1 -1
- package/dist/dts/OpenAiTool.d.ts +176 -0
- package/dist/dts/OpenAiTool.d.ts.map +1 -0
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Generated.js +5846 -12846
- package/dist/esm/Generated.js.map +1 -1
- package/dist/esm/OpenAiClient.js +1440 -128
- package/dist/esm/OpenAiClient.js.map +1 -1
- package/dist/esm/OpenAiEmbeddingModel.js +60 -49
- package/dist/esm/OpenAiEmbeddingModel.js.map +1 -1
- package/dist/esm/OpenAiLanguageModel.js +949 -325
- package/dist/esm/OpenAiLanguageModel.js.map +1 -1
- package/dist/esm/OpenAiTelemetry.js +4 -4
- package/dist/esm/OpenAiTelemetry.js.map +1 -1
- package/dist/esm/OpenAiTokenizer.js +46 -14
- package/dist/esm/OpenAiTokenizer.js.map +1 -1
- package/dist/esm/OpenAiTool.js +84 -0
- package/dist/esm/OpenAiTool.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/utilities.js +10 -2
- package/dist/esm/internal/utilities.js.map +1 -1
- package/package.json +12 -4
- package/src/Generated.ts +9692 -5599
- package/src/OpenAiClient.ts +1761 -224
- package/src/OpenAiEmbeddingModel.ts +70 -62
- package/src/OpenAiLanguageModel.ts +1134 -369
- package/src/OpenAiTelemetry.ts +9 -9
- package/src/OpenAiTokenizer.ts +38 -39
- package/src/OpenAiTool.ts +110 -0
- package/src/index.ts +5 -0
- package/src/internal/utilities.ts +16 -4
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import * as
|
|
7
|
-
import * as AiModel from "@effect/ai/
|
|
8
|
-
import * as
|
|
4
|
+
import * as AiError from "@effect/ai/AiError"
|
|
5
|
+
import * as IdGenerator from "@effect/ai/IdGenerator"
|
|
6
|
+
import * as LanguageModel from "@effect/ai/LanguageModel"
|
|
7
|
+
import * as AiModel from "@effect/ai/Model"
|
|
8
|
+
import type * as Prompt from "@effect/ai/Prompt"
|
|
9
|
+
import type * as Response from "@effect/ai/Response"
|
|
9
10
|
import type * as Tokenizer from "@effect/ai/Tokenizer"
|
|
10
|
-
import * as
|
|
11
|
+
import * as Tool from "@effect/ai/Tool"
|
|
11
12
|
import * as Context from "effect/Context"
|
|
13
|
+
import * as DateTime from "effect/DateTime"
|
|
12
14
|
import * as Effect from "effect/Effect"
|
|
13
15
|
import * as Encoding from "effect/Encoding"
|
|
14
16
|
import { dual } from "effect/Function"
|
|
15
17
|
import * as Layer from "effect/Layer"
|
|
16
|
-
import * as Option from "effect/Option"
|
|
17
18
|
import * as Predicate from "effect/Predicate"
|
|
18
19
|
import * as Stream from "effect/Stream"
|
|
19
20
|
import type { Span } from "effect/Tracer"
|
|
20
|
-
import type { Simplify } from "effect/Types"
|
|
21
|
+
import type { DeepMutable, Mutable, Simplify } from "effect/Types"
|
|
21
22
|
import type * as Generated from "./Generated.js"
|
|
22
|
-
import { resolveFinishReason } from "./internal/utilities.js"
|
|
23
23
|
import * as InternalUtilities from "./internal/utilities.js"
|
|
24
|
+
import type { ResponseStreamEvent } from "./OpenAiClient.js"
|
|
24
25
|
import { OpenAiClient } from "./OpenAiClient.js"
|
|
25
26
|
import { addGenAIAnnotations } from "./OpenAiTelemetry.js"
|
|
26
27
|
import * as OpenAiTokenizer from "./OpenAiTokenizer.js"
|
|
27
|
-
|
|
28
|
-
const constDisableValidation = { disableValidation: true } as const
|
|
28
|
+
import * as OpenAiTool from "./OpenAiTool.js"
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* @since 1.0.0
|
|
32
32
|
* @category Models
|
|
33
33
|
*/
|
|
34
|
-
export type Model = typeof Generated.
|
|
34
|
+
export type Model = typeof Generated.ChatModel.Encoded | typeof Generated.ModelIdsResponsesEnum.Encoded
|
|
35
35
|
|
|
36
36
|
// =============================================================================
|
|
37
37
|
// Configuration
|
|
@@ -66,18 +66,85 @@ export declare namespace Config {
|
|
|
66
66
|
Simplify<
|
|
67
67
|
Partial<
|
|
68
68
|
Omit<
|
|
69
|
-
typeof Generated.
|
|
70
|
-
"
|
|
69
|
+
typeof Generated.CreateResponse.Encoded,
|
|
70
|
+
"input" | "tools" | "tool_choice" | "stream" | "text"
|
|
71
71
|
>
|
|
72
72
|
>
|
|
73
73
|
>
|
|
74
|
-
{
|
|
74
|
+
{
|
|
75
|
+
/**
|
|
76
|
+
* File ID prefixes used to identify file IDs in Responses API.
|
|
77
|
+
* When undefined, all file data is treated as base64 content.
|
|
78
|
+
*
|
|
79
|
+
* Examples:
|
|
80
|
+
* - OpenAI: ['file-'] for IDs like 'file-abc123'
|
|
81
|
+
* - Azure OpenAI: ['assistant-'] for IDs like 'assistant-abc123'
|
|
82
|
+
*/
|
|
83
|
+
readonly fileIdPrefixes?: ReadonlyArray<string>
|
|
84
|
+
/**
|
|
85
|
+
* Configuration options for a text response from the model.
|
|
86
|
+
*/
|
|
87
|
+
readonly text?: {
|
|
88
|
+
/**
|
|
89
|
+
* Constrains the verbosity of the model's response. Lower values will
|
|
90
|
+
* result in more concise responses, while higher values will result in
|
|
91
|
+
* more verbose responses.
|
|
92
|
+
*
|
|
93
|
+
* Defaults to `"medium"`.
|
|
94
|
+
*/
|
|
95
|
+
readonly verbosity?: "low" | "medium" | "high"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
// =============================================================================
|
|
78
|
-
//
|
|
101
|
+
// OpenAI Provider Options / Metadata
|
|
79
102
|
// =============================================================================
|
|
80
103
|
|
|
104
|
+
declare module "@effect/ai/Prompt" {
|
|
105
|
+
export interface FilePartOptions extends ProviderOptions {
|
|
106
|
+
readonly openai?: {
|
|
107
|
+
/**
|
|
108
|
+
* The detail level of the image to be sent to the model. One of `high`, `low`, or `auto`. Defaults to `auto`.
|
|
109
|
+
*/
|
|
110
|
+
readonly imageDetail?: typeof Generated.InputImageContentDetail.Encoded | undefined
|
|
111
|
+
} | undefined
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ReasoningPartOptions extends ProviderOptions {
|
|
115
|
+
readonly openai?: {
|
|
116
|
+
/**
|
|
117
|
+
* The ID of the item to reference.
|
|
118
|
+
*/
|
|
119
|
+
readonly itemId?: string | undefined
|
|
120
|
+
/**
|
|
121
|
+
* The encrypted content of the reasoning item - populated when a response
|
|
122
|
+
* is generated with `reasoning.encrypted_content` in the `include`
|
|
123
|
+
* parameter.
|
|
124
|
+
*/
|
|
125
|
+
readonly encryptedContent?: string | undefined
|
|
126
|
+
} | undefined
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface ToolCallPartOptions extends ProviderOptions {
|
|
130
|
+
readonly openai?: {
|
|
131
|
+
/**
|
|
132
|
+
* The ID of the item to reference.
|
|
133
|
+
*/
|
|
134
|
+
readonly itemId?: string | undefined
|
|
135
|
+
} | undefined
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface TextPartOptions extends ProviderOptions {
|
|
139
|
+
readonly openai?: {
|
|
140
|
+
/**
|
|
141
|
+
* The ID of the item to reference.
|
|
142
|
+
*/
|
|
143
|
+
readonly itemId?: string | undefined
|
|
144
|
+
} | undefined
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
81
148
|
/**
|
|
82
149
|
* @since 1.0.0
|
|
83
150
|
* @category Context
|
|
@@ -96,63 +163,90 @@ export declare namespace ProviderMetadata {
|
|
|
96
163
|
* @category Provider Metadata
|
|
97
164
|
*/
|
|
98
165
|
export interface Service {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
166
|
+
"finish": {
|
|
167
|
+
readonly serviceTier?: "default" | "auto" | "flex" | "scale" | "priority" | undefined
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
"reasoning": {
|
|
171
|
+
readonly itemId?: string | undefined
|
|
172
|
+
readonly encryptedContent?: string | undefined
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
"reasoning-start": {
|
|
176
|
+
readonly itemId?: string | undefined
|
|
177
|
+
readonly encryptedContent?: string | undefined
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
"reasoning-delta": {
|
|
181
|
+
readonly itemId?: string | undefined
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
"reasoning-end": {
|
|
185
|
+
readonly itemId?: string | undefined
|
|
186
|
+
readonly encryptedContent?: string | undefined
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
"source": {
|
|
190
|
+
readonly type: "file_citation"
|
|
191
|
+
/**
|
|
192
|
+
* The index of the file in the list of files.
|
|
193
|
+
*/
|
|
194
|
+
readonly index: number
|
|
195
|
+
} | {
|
|
196
|
+
readonly type: "url_citation"
|
|
197
|
+
/**
|
|
198
|
+
* The index of the first character of the URL citation in the message.
|
|
199
|
+
*/
|
|
200
|
+
readonly startIndex: number
|
|
201
|
+
/**
|
|
202
|
+
* The index of the last character of the URL citation in the message.
|
|
203
|
+
*/
|
|
204
|
+
readonly endIndex: number
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
"text": {
|
|
208
|
+
readonly itemId?: string | undefined
|
|
209
|
+
/**
|
|
210
|
+
* If the model emits a refusal content part, the refusal explanation
|
|
211
|
+
* from the model will be contained in the metadata of an empty text
|
|
212
|
+
* part.
|
|
213
|
+
*/
|
|
214
|
+
readonly refusal?: string | undefined
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
"text-start": {
|
|
218
|
+
readonly itemId?: string | undefined
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
"tool-call": {
|
|
222
|
+
readonly itemId?: string | undefined
|
|
223
|
+
}
|
|
131
224
|
}
|
|
132
225
|
}
|
|
133
226
|
|
|
134
227
|
// =============================================================================
|
|
135
|
-
//
|
|
228
|
+
// OpenAI Language Model
|
|
136
229
|
// =============================================================================
|
|
137
230
|
|
|
138
231
|
/**
|
|
139
232
|
* @since 1.0.0
|
|
140
|
-
* @category
|
|
233
|
+
* @category Ai Models
|
|
141
234
|
*/
|
|
142
235
|
export const model = (
|
|
143
236
|
model: (string & {}) | Model,
|
|
144
237
|
config?: Omit<Config.Service, "model">
|
|
145
|
-
): AiModel.
|
|
238
|
+
): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenAiClient> =>
|
|
239
|
+
AiModel.make("openai", layer({ model, config }))
|
|
146
240
|
|
|
147
241
|
/**
|
|
148
242
|
* @since 1.0.0
|
|
149
|
-
* @category
|
|
243
|
+
* @category Ai Models
|
|
150
244
|
*/
|
|
151
245
|
export const modelWithTokenizer = (
|
|
152
246
|
model: (string & {}) | Model,
|
|
153
247
|
config?: Omit<Config.Service, "model">
|
|
154
|
-
): AiModel.
|
|
155
|
-
AiModel.make(layerWithTokenizer({ model, config }))
|
|
248
|
+
): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> =>
|
|
249
|
+
AiModel.make("openai", layerWithTokenizer({ model, config }))
|
|
156
250
|
|
|
157
251
|
/**
|
|
158
252
|
* @since 1.0.0
|
|
@@ -164,92 +258,56 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
164
258
|
}) {
|
|
165
259
|
const client = yield* OpenAiClient
|
|
166
260
|
|
|
167
|
-
const makeRequest
|
|
168
|
-
|
|
261
|
+
const makeRequest: (providerOptions: LanguageModel.ProviderOptions) => Effect.Effect<
|
|
262
|
+
typeof Generated.CreateResponse.Encoded,
|
|
263
|
+
AiError.AiError
|
|
264
|
+
> = Effect.fnUntraced(
|
|
265
|
+
function*(providerOptions) {
|
|
169
266
|
const context = yield* Effect.context<never>()
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
267
|
+
const config = { model: options.model, ...options.config, ...context.unsafeMap.get(Config.key) }
|
|
268
|
+
const messages = yield* prepareMessages(providerOptions, config)
|
|
269
|
+
const { toolChoice, tools } = yield* prepareTools(providerOptions)
|
|
270
|
+
const include = prepareInclude(providerOptions, config)
|
|
271
|
+
const responseFormat = prepareResponseFormat(providerOptions)
|
|
272
|
+
const verbosity = config.text?.verbosity
|
|
273
|
+
const request: typeof Generated.CreateResponse.Encoded = {
|
|
274
|
+
...config,
|
|
275
|
+
input: messages,
|
|
276
|
+
include,
|
|
277
|
+
text: { format: responseFormat, verbosity },
|
|
278
|
+
tools,
|
|
279
|
+
tool_choice: toolChoice
|
|
178
280
|
}
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
model: options.model,
|
|
182
|
-
...options.config,
|
|
183
|
-
...context.unsafeMap.get(Config.key),
|
|
184
|
-
messages,
|
|
185
|
-
response_format: useStructured ?
|
|
186
|
-
{
|
|
187
|
-
type: "json_schema",
|
|
188
|
-
json_schema: {
|
|
189
|
-
strict: true,
|
|
190
|
-
name: tools[0].name,
|
|
191
|
-
description: tools[0].description,
|
|
192
|
-
schema: tools[0].parameters as any
|
|
193
|
-
}
|
|
194
|
-
} :
|
|
195
|
-
undefined,
|
|
196
|
-
tools: !useStructured && tools.length > 0 ?
|
|
197
|
-
tools.map((tool) => ({
|
|
198
|
-
type: "function",
|
|
199
|
-
function: {
|
|
200
|
-
name: tool.name,
|
|
201
|
-
description: tool.description,
|
|
202
|
-
parameters: tool.parameters as any,
|
|
203
|
-
strict: true
|
|
204
|
-
}
|
|
205
|
-
})) :
|
|
206
|
-
undefined,
|
|
207
|
-
tool_choice
|
|
208
|
-
} satisfies typeof Generated.CreateChatCompletionRequest.Encoded
|
|
281
|
+
return request
|
|
209
282
|
}
|
|
210
283
|
)
|
|
211
284
|
|
|
212
|
-
return yield*
|
|
285
|
+
return yield* LanguageModel.make({
|
|
213
286
|
generateText: Effect.fnUntraced(
|
|
214
287
|
function*(options) {
|
|
215
|
-
const
|
|
216
|
-
? options.tools[0]
|
|
217
|
-
: undefined
|
|
218
|
-
const request = yield* makeRequest("generateText", options)
|
|
288
|
+
const request = yield* makeRequest(options)
|
|
219
289
|
annotateRequest(options.span, request)
|
|
220
|
-
const rawResponse = yield* client.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
},
|
|
225
|
-
Effect.catchAll((cause) =>
|
|
226
|
-
AiError.is(cause) ? cause : new AiError({
|
|
227
|
-
module: "OpenAiLanguageModel",
|
|
228
|
-
method: "generateText",
|
|
229
|
-
description: "An error occurred",
|
|
230
|
-
cause
|
|
231
|
-
})
|
|
232
|
-
)
|
|
290
|
+
const rawResponse = yield* client.createResponse(request)
|
|
291
|
+
annotateResponse(options.span, rawResponse)
|
|
292
|
+
return yield* makeResponse(rawResponse, options)
|
|
293
|
+
}
|
|
233
294
|
),
|
|
234
|
-
streamText(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
description: "An error occurred",
|
|
248
|
-
cause
|
|
295
|
+
streamText: Effect.fnUntraced(
|
|
296
|
+
function*(options) {
|
|
297
|
+
const request = yield* makeRequest(options)
|
|
298
|
+
annotateRequest(options.span, request)
|
|
299
|
+
return client.createResponseStream(request)
|
|
300
|
+
},
|
|
301
|
+
(effect, options) =>
|
|
302
|
+
effect.pipe(
|
|
303
|
+
Effect.flatMap((stream) => makeStreamResponse(stream, options)),
|
|
304
|
+
Stream.unwrap,
|
|
305
|
+
Stream.map((response) => {
|
|
306
|
+
annotateStreamResponse(options.span, response)
|
|
307
|
+
return response
|
|
249
308
|
})
|
|
250
309
|
)
|
|
251
|
-
|
|
252
|
-
}
|
|
310
|
+
)
|
|
253
311
|
})
|
|
254
312
|
})
|
|
255
313
|
|
|
@@ -260,8 +318,8 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
260
318
|
export const layer = (options: {
|
|
261
319
|
readonly model: (string & {}) | Model
|
|
262
320
|
readonly config?: Omit<Config.Service, "model">
|
|
263
|
-
}): Layer.Layer<
|
|
264
|
-
Layer.effect(
|
|
321
|
+
}): Layer.Layer<LanguageModel.LanguageModel, never, OpenAiClient> =>
|
|
322
|
+
Layer.effect(LanguageModel.LanguageModel, make({ model: options.model, config: options.config }))
|
|
265
323
|
|
|
266
324
|
/**
|
|
267
325
|
* @since 1.0.0
|
|
@@ -270,7 +328,7 @@ export const layer = (options: {
|
|
|
270
328
|
export const layerWithTokenizer = (options: {
|
|
271
329
|
readonly model: (string & {}) | Model
|
|
272
330
|
readonly config?: Omit<Config.Service, "model">
|
|
273
|
-
}): Layer.Layer<
|
|
331
|
+
}): Layer.Layer<LanguageModel.LanguageModel | Tokenizer.Tokenizer, never, OpenAiClient> =>
|
|
274
332
|
Layer.merge(layer(options), OpenAiTokenizer.layer(options))
|
|
275
333
|
|
|
276
334
|
/**
|
|
@@ -305,247 +363,805 @@ export const withConfigOverride: {
|
|
|
305
363
|
(config) => Effect.provideService(self, Config, { ...config, ...overrides })
|
|
306
364
|
))
|
|
307
365
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
366
|
+
// =============================================================================
|
|
367
|
+
// Prompt Conversion
|
|
368
|
+
// =============================================================================
|
|
369
|
+
|
|
370
|
+
const getSystemMessageMode = (model: string): "system" | "developer" =>
|
|
371
|
+
model.startsWith("o") ||
|
|
372
|
+
model.startsWith("gpt-5") ||
|
|
373
|
+
model.startsWith("codex-") ||
|
|
374
|
+
model.startsWith("computer-use")
|
|
375
|
+
? "developer"
|
|
376
|
+
: "system"
|
|
377
|
+
|
|
378
|
+
const prepareMessages: (
|
|
379
|
+
options: LanguageModel.ProviderOptions,
|
|
380
|
+
config: Config.Service
|
|
381
|
+
) => Effect.Effect<
|
|
382
|
+
ReadonlyArray<typeof Generated.InputItem.Encoded>,
|
|
383
|
+
AiError.AiError
|
|
384
|
+
> = Effect.fnUntraced(function*(options, config) {
|
|
385
|
+
const messages: Array<typeof Generated.InputItem.Encoded> = []
|
|
386
|
+
|
|
387
|
+
for (const message of options.prompt.content) {
|
|
388
|
+
switch (message.role) {
|
|
389
|
+
case "system": {
|
|
390
|
+
messages.push({
|
|
391
|
+
role: getSystemMessageMode(config.model!),
|
|
392
|
+
content: message.content
|
|
393
|
+
})
|
|
394
|
+
break
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
case "user": {
|
|
398
|
+
const content: Array<typeof Generated.InputContent.Encoded> = []
|
|
399
|
+
|
|
400
|
+
for (let index = 0; index < message.content.length; index++) {
|
|
401
|
+
const part = message.content[index]
|
|
402
|
+
|
|
403
|
+
switch (part.type) {
|
|
404
|
+
case "text": {
|
|
405
|
+
content.push({ type: "input_text", text: part.text })
|
|
328
406
|
break
|
|
329
407
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
408
|
+
|
|
409
|
+
case "file": {
|
|
410
|
+
if (part.mediaType.startsWith("image/")) {
|
|
411
|
+
const detail = getImageDetail(part)
|
|
412
|
+
const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType
|
|
413
|
+
|
|
414
|
+
if (typeof part.data === "string" && isFileId(part.data, config)) {
|
|
415
|
+
content.push({ type: "input_image", file_id: part.data, detail })
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (part.data instanceof URL) {
|
|
419
|
+
content.push({ type: "input_image", image_url: part.data.toString(), detail })
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (part.data instanceof Uint8Array) {
|
|
423
|
+
const base64 = Encoding.encodeBase64(part.data)
|
|
424
|
+
const imageUrl = `data:${mediaType};base64,${base64}`
|
|
425
|
+
content.push({ type: "input_image", image_url: imageUrl, detail })
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (part.mediaType === "application/pdf") {
|
|
430
|
+
if (typeof part.data === "string" && isFileId(part.data, config)) {
|
|
431
|
+
content.push({ type: "input_file", file_id: part.data })
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (part.data instanceof URL) {
|
|
435
|
+
content.push({ type: "input_file", file_url: part.data.toString() })
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (part.data instanceof Uint8Array) {
|
|
439
|
+
const base64 = Encoding.encodeBase64(part.data)
|
|
440
|
+
const fileName = part.fileName ?? `part-${index}.pdf`
|
|
441
|
+
const fileData = `data:application/pdf;base64,${base64}`
|
|
442
|
+
content.push({ type: "input_file", filename: fileName, file_data: fileData })
|
|
337
443
|
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return yield* new AiError.MalformedInput({
|
|
447
|
+
module: "OpenAiLanguageModel",
|
|
448
|
+
method: "prepareMessages",
|
|
449
|
+
description: `Detected unsupported media type for file: '${part.mediaType}'`
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
messages.push({ role: "user", content })
|
|
456
|
+
|
|
457
|
+
break
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
case "assistant": {
|
|
461
|
+
const reasoningMessages: Record<string, DeepMutable<typeof Generated.ReasoningItem.Encoded>> = {}
|
|
462
|
+
|
|
463
|
+
for (const part of message.content) {
|
|
464
|
+
switch (part.type) {
|
|
465
|
+
case "text": {
|
|
466
|
+
messages.push({
|
|
467
|
+
role: "assistant",
|
|
468
|
+
content: [{ type: "output_text", text: part.text }],
|
|
469
|
+
id: getItemId(part)
|
|
338
470
|
})
|
|
339
471
|
break
|
|
340
472
|
}
|
|
473
|
+
|
|
474
|
+
case "reasoning": {
|
|
475
|
+
const options = part.options.openai
|
|
476
|
+
|
|
477
|
+
if (Predicate.isNotUndefined(options?.itemId)) {
|
|
478
|
+
const reasoningMessage = reasoningMessages[options.itemId]
|
|
479
|
+
const summaryParts: Mutable<typeof Generated.ReasoningItem.fields.summary.Encoded> = []
|
|
480
|
+
|
|
481
|
+
if (part.text.length > 0) {
|
|
482
|
+
summaryParts.push({ type: "summary_text", text: part.text })
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (Predicate.isUndefined(reasoningMessage)) {
|
|
486
|
+
reasoningMessages[options.itemId] = {
|
|
487
|
+
id: options.itemId,
|
|
488
|
+
type: "reasoning",
|
|
489
|
+
summary: summaryParts,
|
|
490
|
+
encrypted_content: options.encryptedContent
|
|
491
|
+
}
|
|
492
|
+
messages.push(reasoningMessages[options.itemId])
|
|
493
|
+
} else {
|
|
494
|
+
for (const summaryPart of summaryParts) {
|
|
495
|
+
reasoningMessage.summary.push(summaryPart)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
break
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
case "tool-call": {
|
|
504
|
+
if (!part.providerExecuted) {
|
|
505
|
+
messages.push({
|
|
506
|
+
id: getItemId(part),
|
|
507
|
+
type: "function_call",
|
|
508
|
+
call_id: part.id,
|
|
509
|
+
name: part.name,
|
|
510
|
+
arguments: JSON.stringify(part.params)
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
break
|
|
515
|
+
}
|
|
341
516
|
}
|
|
342
517
|
}
|
|
343
|
-
messages.push({
|
|
344
|
-
role: "assistant",
|
|
345
|
-
content: text,
|
|
346
|
-
tool_calls: toolCalls.length > 0 ? toolCalls : undefined
|
|
347
|
-
})
|
|
348
518
|
|
|
349
519
|
break
|
|
350
520
|
}
|
|
351
|
-
|
|
352
|
-
|
|
521
|
+
|
|
522
|
+
case "tool": {
|
|
523
|
+
for (const part of message.content) {
|
|
353
524
|
messages.push({
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
525
|
+
type: "function_call_output",
|
|
526
|
+
call_id: part.id,
|
|
527
|
+
result: JSON.stringify(part.result)
|
|
357
528
|
})
|
|
358
529
|
}
|
|
530
|
+
|
|
359
531
|
break
|
|
360
532
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return messages
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
// =============================================================================
|
|
540
|
+
// Response Conversion
|
|
541
|
+
// =============================================================================
|
|
542
|
+
|
|
543
|
+
const makeResponse: (
|
|
544
|
+
response: Generated.Response,
|
|
545
|
+
options: LanguageModel.ProviderOptions
|
|
546
|
+
) => Effect.Effect<
|
|
547
|
+
Array<Response.PartEncoded>,
|
|
548
|
+
never,
|
|
549
|
+
IdGenerator.IdGenerator
|
|
550
|
+
> = Effect.fnUntraced(
|
|
551
|
+
function*(response, options) {
|
|
552
|
+
const idGenerator = yield* IdGenerator.IdGenerator
|
|
553
|
+
|
|
554
|
+
const webSearchTool = options.tools.find((tool) =>
|
|
555
|
+
Tool.isProviderDefined(tool) &&
|
|
556
|
+
(tool.id === "openai.web_search" ||
|
|
557
|
+
tool.id === "openai.web_search_preview")
|
|
558
|
+
) as Tool.AnyProviderDefined | undefined
|
|
559
|
+
|
|
560
|
+
let hasToolCalls = false
|
|
561
|
+
const parts: Array<Response.PartEncoded> = []
|
|
562
|
+
|
|
563
|
+
const createdAt = new Date(response.created_at * 1000)
|
|
564
|
+
parts.push({
|
|
565
|
+
type: "response-metadata",
|
|
566
|
+
id: response.id,
|
|
567
|
+
modelId: response.model,
|
|
568
|
+
timestamp: DateTime.formatIso(DateTime.unsafeFromDate(createdAt))
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
for (const part of response.output) {
|
|
572
|
+
switch (part.type) {
|
|
573
|
+
case "message": {
|
|
574
|
+
for (const contentPart of part.content) {
|
|
575
|
+
switch (contentPart.type) {
|
|
576
|
+
case "output_text": {
|
|
577
|
+
parts.push({
|
|
578
|
+
type: "text",
|
|
579
|
+
text: contentPart.text,
|
|
580
|
+
metadata: { [ProviderMetadata.key]: { itemId: part.id } }
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
for (const annotation of contentPart.annotations) {
|
|
584
|
+
if (annotation.type === "file_citation") {
|
|
585
|
+
const metadata = {
|
|
586
|
+
type: annotation.type,
|
|
587
|
+
index: annotation.index
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
parts.push({
|
|
591
|
+
type: "source",
|
|
592
|
+
sourceType: "document",
|
|
593
|
+
id: yield* idGenerator.generateId(),
|
|
594
|
+
mediaType: "text/plain",
|
|
595
|
+
title: annotation.filename ?? "Untitled Document",
|
|
596
|
+
metadata: { [ProviderMetadata.key]: metadata }
|
|
597
|
+
})
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (annotation.type === "url_citation") {
|
|
601
|
+
const metadata = {
|
|
602
|
+
type: annotation.type,
|
|
603
|
+
startIndex: annotation.start_index,
|
|
604
|
+
endIndex: annotation.end_index
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
parts.push({
|
|
608
|
+
type: "source",
|
|
609
|
+
sourceType: "url",
|
|
610
|
+
id: yield* idGenerator.generateId(),
|
|
611
|
+
url: annotation.url,
|
|
612
|
+
title: annotation.title,
|
|
613
|
+
metadata: { [ProviderMetadata.key]: metadata }
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
break
|
|
619
|
+
}
|
|
620
|
+
case "refusal": {
|
|
621
|
+
parts.push({
|
|
622
|
+
type: "text",
|
|
623
|
+
text: "",
|
|
624
|
+
metadata: { [ProviderMetadata.key]: { refusal: contentPart.refusal } }
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
break
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
365
632
|
break
|
|
366
633
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
634
|
+
|
|
635
|
+
case "function_call": {
|
|
636
|
+
hasToolCalls = true
|
|
637
|
+
|
|
638
|
+
parts.push({
|
|
639
|
+
type: "tool-call",
|
|
640
|
+
id: part.call_id,
|
|
641
|
+
name: part.name,
|
|
642
|
+
params: JSON.parse(part.arguments),
|
|
643
|
+
metadata: { [ProviderMetadata.key]: { itemId: part.id } }
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
break
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
case "code_interpreter_call": {
|
|
650
|
+
parts.push({
|
|
651
|
+
type: "tool-call",
|
|
652
|
+
id: part.id,
|
|
653
|
+
name: "OpenAiCodeInterpreter",
|
|
654
|
+
params: { code: part.code, container_id: part.container_id },
|
|
655
|
+
providerName: "code_interpreter",
|
|
656
|
+
providerExecuted: true
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
parts.push({
|
|
660
|
+
type: "tool-result",
|
|
661
|
+
id: part.id,
|
|
662
|
+
name: "OpenAiCodeInterpreter",
|
|
663
|
+
result: { outputs: part.outputs },
|
|
664
|
+
providerName: "code_interpreter",
|
|
665
|
+
providerExecuted: true
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
break
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
case "file_search_call": {
|
|
672
|
+
parts.push({
|
|
673
|
+
type: "tool-call",
|
|
674
|
+
id: part.id,
|
|
675
|
+
name: "OpenAiFileSearch",
|
|
676
|
+
params: {},
|
|
677
|
+
providerName: "file_search",
|
|
678
|
+
providerExecuted: true
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
parts.push({
|
|
682
|
+
type: "tool-result",
|
|
683
|
+
id: part.id,
|
|
684
|
+
name: "OpenAiFileSearch",
|
|
685
|
+
result: {
|
|
686
|
+
status: part.status,
|
|
687
|
+
queries: part.queries,
|
|
688
|
+
...(part.results && { results: part.results })
|
|
689
|
+
},
|
|
690
|
+
providerName: "file_search",
|
|
691
|
+
providerExecuted: true
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
break
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
case "web_search_call": {
|
|
698
|
+
parts.push({
|
|
699
|
+
type: "tool-call",
|
|
700
|
+
id: part.id,
|
|
701
|
+
name: webSearchTool?.name ?? "OpenAiWebSearch",
|
|
702
|
+
params: { action: part.action },
|
|
703
|
+
providerName: webSearchTool?.providerName ?? "web_search",
|
|
704
|
+
providerExecuted: true
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
parts.push({
|
|
708
|
+
type: "tool-result",
|
|
709
|
+
id: part.id,
|
|
710
|
+
name: webSearchTool?.name ?? "OpenAiWebSearch",
|
|
711
|
+
result: { status: part.status },
|
|
712
|
+
providerName: webSearchTool?.providerName ?? "web_search",
|
|
713
|
+
providerExecuted: true
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
break
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// TODO(Max): support computer use
|
|
720
|
+
// case "computer_call": {
|
|
721
|
+
// parts.push({
|
|
722
|
+
// type: "tool-call",
|
|
723
|
+
// id: part.id,
|
|
724
|
+
// name: "OpenAiComputerUse",
|
|
725
|
+
// params: { action: part.action },
|
|
726
|
+
// providerName: webSearchTool?.providerName ?? "web_search",
|
|
727
|
+
// providerExecuted: true
|
|
728
|
+
// })
|
|
729
|
+
//
|
|
730
|
+
// parts.push({
|
|
731
|
+
// type: "tool-result",
|
|
732
|
+
// id: part.id,
|
|
733
|
+
// name: webSearchTool?.name ?? "OpenAiWebSearch",
|
|
734
|
+
// result: { status: part.status },
|
|
735
|
+
// providerName: webSearchTool?.providerName ?? "web_search",
|
|
736
|
+
// providerExecuted: true
|
|
737
|
+
// })
|
|
738
|
+
// break
|
|
739
|
+
// }
|
|
740
|
+
|
|
741
|
+
case "reasoning": {
|
|
742
|
+
// If there are no summary parts, we have to add an empty one to
|
|
743
|
+
// propagate the part identifier
|
|
744
|
+
if (part.summary.length === 0) {
|
|
745
|
+
parts.push({
|
|
746
|
+
type: "reasoning",
|
|
747
|
+
text: "",
|
|
748
|
+
metadata: { [ProviderMetadata.key]: { itemId: part.id } }
|
|
749
|
+
})
|
|
750
|
+
} else {
|
|
751
|
+
for (const summary of part.summary) {
|
|
752
|
+
const metadata = {
|
|
753
|
+
itemId: part.id,
|
|
754
|
+
encryptedContent: part.encrypted_content ?? undefined
|
|
755
|
+
}
|
|
756
|
+
parts.push({
|
|
757
|
+
type: "reasoning",
|
|
758
|
+
text: summary.text,
|
|
759
|
+
metadata: { [ProviderMetadata.key]: metadata }
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
break
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const finishReason = InternalUtilities.resolveFinishReason(
|
|
770
|
+
response.incomplete_details?.reason,
|
|
771
|
+
hasToolCalls
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
const metadata = {
|
|
775
|
+
serviceTier: response.service_tier
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
parts.push({
|
|
779
|
+
type: "finish",
|
|
780
|
+
reason: finishReason,
|
|
781
|
+
usage: {
|
|
782
|
+
inputTokens: response.usage?.input_tokens,
|
|
783
|
+
outputTokens: response.usage?.output_tokens,
|
|
784
|
+
totalTokens: (response.usage?.input_tokens ?? 0) + (response.usage?.output_tokens ?? 0),
|
|
785
|
+
reasoningTokens: response.usage?.output_tokens_details?.reasoning_tokens,
|
|
786
|
+
cachedInputTokens: response.usage?.input_tokens_details?.cached_tokens
|
|
787
|
+
},
|
|
788
|
+
metadata: { [ProviderMetadata.key]: metadata }
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
return parts
|
|
792
|
+
}
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
const makeStreamResponse: (
|
|
796
|
+
stream: Stream.Stream<ResponseStreamEvent, AiError.AiError>,
|
|
797
|
+
options: LanguageModel.ProviderOptions
|
|
798
|
+
) => Effect.Effect<
|
|
799
|
+
Stream.Stream<Response.StreamPartEncoded, AiError.AiError>,
|
|
800
|
+
never,
|
|
801
|
+
IdGenerator.IdGenerator
|
|
802
|
+
> = Effect.fnUntraced(
|
|
803
|
+
function*(stream, options) {
|
|
804
|
+
const idGenerator = yield* IdGenerator.IdGenerator
|
|
805
|
+
|
|
806
|
+
let hasToolCalls = false
|
|
807
|
+
|
|
808
|
+
const activeReasoning: Record<string, {
|
|
809
|
+
readonly summaryParts: Array<number>
|
|
810
|
+
readonly encryptedContent: string | undefined
|
|
811
|
+
}> = {}
|
|
812
|
+
|
|
813
|
+
const activeToolCalls: Record<number, {
|
|
814
|
+
readonly id: string
|
|
815
|
+
readonly name: string
|
|
816
|
+
}> = {}
|
|
817
|
+
|
|
818
|
+
const webSearchTool = options.tools.find((tool) =>
|
|
819
|
+
Tool.isProviderDefined(tool) &&
|
|
820
|
+
(tool.id === "openai.web_search" ||
|
|
821
|
+
tool.id === "openai.web_search_preview")
|
|
822
|
+
) as Tool.AnyProviderDefined | undefined
|
|
823
|
+
|
|
824
|
+
return stream.pipe(
|
|
825
|
+
Stream.mapEffect(Effect.fnUntraced(function*(event) {
|
|
826
|
+
const parts: Array<Response.StreamPartEncoded> = []
|
|
827
|
+
|
|
828
|
+
switch (event.type) {
|
|
829
|
+
case "response.created": {
|
|
830
|
+
const createdAt = new Date(event.response.created_at * 1000)
|
|
831
|
+
parts.push({
|
|
832
|
+
type: "response-metadata",
|
|
833
|
+
id: event.response.id,
|
|
834
|
+
modelId: event.response.model,
|
|
835
|
+
timestamp: DateTime.formatIso(DateTime.unsafeFromDate(createdAt))
|
|
836
|
+
})
|
|
837
|
+
break
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
case "error": {
|
|
841
|
+
parts.push({ type: "error", error: event })
|
|
842
|
+
break
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
case "response.completed":
|
|
846
|
+
case "response.incomplete":
|
|
847
|
+
case "response.failed": {
|
|
848
|
+
parts.push({
|
|
849
|
+
type: "finish",
|
|
850
|
+
reason: InternalUtilities.resolveFinishReason(
|
|
851
|
+
event.response.incomplete_details?.reason,
|
|
852
|
+
hasToolCalls
|
|
853
|
+
),
|
|
854
|
+
usage: {
|
|
855
|
+
inputTokens: event.response.usage?.input_tokens,
|
|
856
|
+
outputTokens: event.response.usage?.output_tokens,
|
|
857
|
+
totalTokens: (event.response.usage?.input_tokens ?? 0) + (event.response.usage?.output_tokens ?? 0),
|
|
858
|
+
reasoningTokens: event.response.usage?.output_tokens_details?.reasoning_tokens,
|
|
859
|
+
cachedInputTokens: event.response.usage?.input_tokens_details?.cached_tokens
|
|
860
|
+
},
|
|
861
|
+
metadata: { [ProviderMetadata.key]: { serviceTier: event.response.service_tier } }
|
|
862
|
+
})
|
|
863
|
+
break
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
case "response.output_item.added": {
|
|
867
|
+
switch (event.item.type) {
|
|
868
|
+
case "computer_call": {
|
|
869
|
+
// TODO(Max): support computer use
|
|
870
|
+
break
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
case "file_search_call": {
|
|
874
|
+
activeToolCalls[event.output_index] = {
|
|
875
|
+
id: event.item.id,
|
|
876
|
+
name: "OpenAiFileSearch"
|
|
381
877
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
878
|
+
parts.push({
|
|
879
|
+
type: "tool-params-start",
|
|
880
|
+
id: event.item.id,
|
|
881
|
+
name: "OpenAiFileSearch",
|
|
882
|
+
providerName: "file_search",
|
|
883
|
+
providerExecuted: true
|
|
884
|
+
})
|
|
885
|
+
break
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
case "function_call": {
|
|
889
|
+
activeToolCalls[event.output_index] = {
|
|
890
|
+
id: event.item.call_id,
|
|
891
|
+
name: event.item.name
|
|
389
892
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
893
|
+
parts.push({
|
|
894
|
+
type: "tool-params-start",
|
|
895
|
+
id: event.item.call_id,
|
|
896
|
+
name: event.item.name
|
|
897
|
+
})
|
|
898
|
+
break
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
case "message": {
|
|
902
|
+
parts.push({
|
|
903
|
+
type: "text-start",
|
|
904
|
+
id: event.item.id,
|
|
905
|
+
metadata: { [ProviderMetadata.key]: { itemId: event.item.id } }
|
|
906
|
+
})
|
|
907
|
+
break
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
case "reasoning": {
|
|
911
|
+
activeReasoning[event.item.id] = {
|
|
912
|
+
summaryParts: [0],
|
|
913
|
+
encryptedContent: event.item.encrypted_content
|
|
914
|
+
}
|
|
915
|
+
parts.push({
|
|
916
|
+
type: "reasoning-start",
|
|
917
|
+
id: `${event.item.id}:0`,
|
|
918
|
+
metadata: {
|
|
919
|
+
[ProviderMetadata.key]: {
|
|
920
|
+
itemId: event.item.id,
|
|
921
|
+
encryptedContent: event.item.encrypted_content
|
|
396
922
|
}
|
|
397
|
-
}
|
|
398
|
-
|
|
923
|
+
}
|
|
924
|
+
})
|
|
925
|
+
break
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
case "web_search_call": {
|
|
929
|
+
activeToolCalls[event.output_index] = {
|
|
930
|
+
id: event.item.id,
|
|
931
|
+
name: webSearchTool?.name ?? "OpenAiWebSearch"
|
|
399
932
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
933
|
+
parts.push({
|
|
934
|
+
type: "tool-params-start",
|
|
935
|
+
id: event.item.id,
|
|
936
|
+
name: webSearchTool?.name ?? "OpenAiWebSearch",
|
|
937
|
+
providerName: webSearchTool?.providerName ?? "web_search",
|
|
938
|
+
providerExecuted: true
|
|
939
|
+
})
|
|
940
|
+
break
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
break
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
case "response.output_item.done": {
|
|
948
|
+
switch (event.item.type) {
|
|
949
|
+
case "code_interpreter_call": {
|
|
950
|
+
parts.push({
|
|
951
|
+
type: "tool-call",
|
|
952
|
+
id: event.item.id,
|
|
953
|
+
name: "OpenAiCodeInterpreter",
|
|
954
|
+
params: { code: event.item.code, container_id: event.item.container_id },
|
|
955
|
+
providerName: "code_interpreter",
|
|
956
|
+
providerExecuted: true
|
|
957
|
+
})
|
|
958
|
+
parts.push({
|
|
959
|
+
type: "tool-result",
|
|
960
|
+
id: event.item.id,
|
|
961
|
+
name: "OpenAiCodeInterpreter",
|
|
962
|
+
result: { outputs: event.item.outputs },
|
|
963
|
+
providerName: "code_interpreter",
|
|
964
|
+
providerExecuted: true
|
|
965
|
+
})
|
|
966
|
+
break
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// TODO(Max): support computer use
|
|
970
|
+
case "computer_call": {
|
|
971
|
+
break
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
case "file_search_call": {
|
|
975
|
+
delete activeToolCalls[event.output_index]
|
|
976
|
+
parts.push({
|
|
977
|
+
type: "tool-params-end",
|
|
978
|
+
id: event.item.id
|
|
979
|
+
})
|
|
980
|
+
parts.push({
|
|
981
|
+
type: "tool-call",
|
|
982
|
+
id: event.item.id,
|
|
983
|
+
name: "OpenAiFileSearch",
|
|
984
|
+
params: {},
|
|
985
|
+
providerName: "file_search",
|
|
986
|
+
providerExecuted: true
|
|
987
|
+
})
|
|
988
|
+
parts.push({
|
|
989
|
+
type: "tool-result",
|
|
990
|
+
id: event.item.id,
|
|
991
|
+
name: "OpenAiFileSearch",
|
|
992
|
+
result: {
|
|
993
|
+
status: event.item.status,
|
|
994
|
+
queries: event.item.queries,
|
|
995
|
+
...(event.item.results && { results: event.item.results })
|
|
996
|
+
},
|
|
997
|
+
providerName: "file_search",
|
|
998
|
+
providerExecuted: true
|
|
999
|
+
})
|
|
1000
|
+
break
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
case "function_call": {
|
|
1004
|
+
hasToolCalls = true
|
|
1005
|
+
delete activeToolCalls[event.output_index]
|
|
1006
|
+
parts.push({
|
|
1007
|
+
type: "tool-params-end",
|
|
1008
|
+
id: event.item.call_id
|
|
1009
|
+
})
|
|
1010
|
+
parts.push({
|
|
1011
|
+
type: "tool-call",
|
|
1012
|
+
id: event.item.call_id,
|
|
1013
|
+
name: event.item.name,
|
|
1014
|
+
params: JSON.parse(event.item.arguments),
|
|
1015
|
+
metadata: { [ProviderMetadata.key]: { itemId: event.item.id } }
|
|
1016
|
+
})
|
|
1017
|
+
break
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
case "message": {
|
|
1021
|
+
parts.push({
|
|
1022
|
+
type: "text-end",
|
|
1023
|
+
id: event.item.id
|
|
1024
|
+
})
|
|
1025
|
+
break
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
case "reasoning": {
|
|
1029
|
+
const reasoningPart = activeReasoning[event.item.id]
|
|
1030
|
+
for (const summaryIndex of reasoningPart.summaryParts) {
|
|
1031
|
+
parts.push({
|
|
1032
|
+
type: "reasoning-end",
|
|
1033
|
+
id: `${event.item.id}:${summaryIndex}`,
|
|
1034
|
+
metadata: {
|
|
1035
|
+
[ProviderMetadata.key]: {
|
|
1036
|
+
itemId: event.item.id,
|
|
1037
|
+
encryptedContent: event.item.encrypted_content
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
405
1040
|
})
|
|
406
1041
|
}
|
|
1042
|
+
delete activeReasoning[event.item.id]
|
|
1043
|
+
break
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
case "web_search_call": {
|
|
1047
|
+
delete activeToolCalls[event.output_index]
|
|
1048
|
+
parts.push({
|
|
1049
|
+
type: "tool-params-end",
|
|
1050
|
+
id: event.item.id
|
|
1051
|
+
})
|
|
1052
|
+
parts.push({
|
|
1053
|
+
type: "tool-call",
|
|
1054
|
+
id: event.item.id,
|
|
1055
|
+
name: "OpenAiWebSearch",
|
|
1056
|
+
params: { action: event.item.action },
|
|
1057
|
+
providerName: "web_search",
|
|
1058
|
+
providerExecuted: true
|
|
1059
|
+
})
|
|
1060
|
+
parts.push({
|
|
1061
|
+
type: "tool-result",
|
|
1062
|
+
id: event.item.id,
|
|
1063
|
+
name: "OpenAiWebSearch",
|
|
1064
|
+
result: { status: event.item.status },
|
|
1065
|
+
providerName: "web_search",
|
|
1066
|
+
providerExecuted: true
|
|
1067
|
+
})
|
|
1068
|
+
break
|
|
407
1069
|
}
|
|
408
|
-
break
|
|
409
1070
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
1071
|
+
|
|
1072
|
+
break
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
case "response.output_text.delta": {
|
|
1076
|
+
parts.push({
|
|
1077
|
+
type: "text-delta",
|
|
1078
|
+
id: event.item_id,
|
|
1079
|
+
delta: event.delta
|
|
1080
|
+
})
|
|
1081
|
+
break
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
case "response.output_text.annotation.added": {
|
|
1085
|
+
if (event.annotation.type === "file_citation") {
|
|
1086
|
+
parts.push({
|
|
1087
|
+
type: "source",
|
|
1088
|
+
sourceType: "document",
|
|
1089
|
+
id: yield* idGenerator.generateId(),
|
|
1090
|
+
mediaType: "text/plain",
|
|
1091
|
+
title: event.annotation.filename ?? "Untitled Document",
|
|
1092
|
+
fileName: event.annotation.filename ?? event.annotation.file_id
|
|
415
1093
|
})
|
|
416
1094
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
1095
|
+
if (event.annotation.type === "url_citation") {
|
|
1096
|
+
parts.push({
|
|
1097
|
+
type: "source",
|
|
1098
|
+
sourceType: "url",
|
|
1099
|
+
id: yield* idGenerator.generateId(),
|
|
1100
|
+
url: event.annotation.url,
|
|
1101
|
+
title: event.annotation.title
|
|
1102
|
+
})
|
|
420
1103
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
1104
|
+
break
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
case "response.function_call_arguments.delta": {
|
|
1108
|
+
const toolCallPart = activeToolCalls[event.output_index]
|
|
1109
|
+
if (Predicate.isNotUndefined(toolCallPart)) {
|
|
1110
|
+
parts.push({
|
|
1111
|
+
type: "tool-params-delta",
|
|
1112
|
+
id: toolCallPart.id,
|
|
1113
|
+
delta: event.delta
|
|
1114
|
+
})
|
|
427
1115
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
1116
|
+
break
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
case "response.reasoning_summary_part.added": {
|
|
1120
|
+
// The first reasoning start is pushed in the `response.output_item.added` block
|
|
1121
|
+
if (event.summary_index > 0) {
|
|
1122
|
+
const reasoningPart = activeReasoning[event.item_id]
|
|
1123
|
+
if (Predicate.isNotUndefined(reasoningPart)) {
|
|
1124
|
+
reasoningPart.summaryParts.push(event.summary_index)
|
|
1125
|
+
}
|
|
1126
|
+
parts.push({
|
|
1127
|
+
type: "reasoning-start",
|
|
1128
|
+
id: `${event.item_id}:${event.summary_index}`,
|
|
1129
|
+
metadata: {
|
|
1130
|
+
[ProviderMetadata.key]: {
|
|
1131
|
+
itemId: event.item_id,
|
|
1132
|
+
encryptedContent: reasoningPart?.encryptedContent
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
})
|
|
432
1136
|
}
|
|
1137
|
+
break
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
case "response.reasoning_summary_text.delta": {
|
|
1141
|
+
parts.push({
|
|
1142
|
+
type: "reasoning-delta",
|
|
1143
|
+
id: `${event.item_id}:${event.summary_index}`,
|
|
1144
|
+
delta: event.delta,
|
|
1145
|
+
metadata: { [ProviderMetadata.key]: { itemId: event.item_id } }
|
|
1146
|
+
})
|
|
1147
|
+
break
|
|
433
1148
|
}
|
|
434
1149
|
}
|
|
435
|
-
if (Arr.isNonEmptyArray(content)) {
|
|
436
|
-
messages.push({
|
|
437
|
-
role: "user",
|
|
438
|
-
name: message.userName,
|
|
439
|
-
content
|
|
440
|
-
})
|
|
441
|
-
}
|
|
442
|
-
break
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
if (Arr.isNonEmptyReadonlyArray(messages)) {
|
|
447
|
-
return messages
|
|
448
|
-
}
|
|
449
|
-
return yield* new AiError({
|
|
450
|
-
module: "OpenAiLanguageModel",
|
|
451
|
-
method,
|
|
452
|
-
description: "Prompt contained no messages"
|
|
453
|
-
})
|
|
454
|
-
})
|
|
455
1150
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
structuredTool?: AiLanguageModel.AiLanguageModelOptions["tools"][number]
|
|
460
|
-
) {
|
|
461
|
-
const choice = response.choices[0]
|
|
462
|
-
if (Predicate.isUndefined(choice)) {
|
|
463
|
-
return yield* new AiError({
|
|
464
|
-
module: "OpenAiLanguageModel",
|
|
465
|
-
method,
|
|
466
|
-
description: "Could not get response"
|
|
467
|
-
})
|
|
468
|
-
}
|
|
469
|
-
const parts: Array<AiResponse.Part> = []
|
|
470
|
-
parts.push(
|
|
471
|
-
new AiResponse.MetadataPart({
|
|
472
|
-
id: response.id,
|
|
473
|
-
model: response.model,
|
|
474
|
-
// OpenAi returns the `created` time in seconds
|
|
475
|
-
timestamp: new Date(response.created * 1000)
|
|
476
|
-
}, constDisableValidation)
|
|
477
|
-
)
|
|
478
|
-
const finishReason = resolveFinishReason(choice.finish_reason)
|
|
479
|
-
const inputTokens = response.usage?.prompt_tokens ?? 0
|
|
480
|
-
const outputTokens = response.usage?.completion_tokens ?? 0
|
|
481
|
-
const totalTokens = inputTokens + outputTokens
|
|
482
|
-
const metadata: Record<string, unknown> = {}
|
|
483
|
-
if (Predicate.isNotUndefined(response.service_tier)) {
|
|
484
|
-
metadata.serviceTier = response.service_tier
|
|
485
|
-
}
|
|
486
|
-
if (Predicate.isNotUndefined(response.system_fingerprint)) {
|
|
487
|
-
metadata.systemFingerprint = response.system_fingerprint
|
|
488
|
-
}
|
|
489
|
-
if (Predicate.isNotUndefined(response.usage?.completion_tokens_details?.accepted_prediction_tokens)) {
|
|
490
|
-
metadata.acceptedPredictionTokens = response.usage?.completion_tokens_details?.accepted_prediction_tokens ?? 0
|
|
491
|
-
}
|
|
492
|
-
if (Predicate.isNotUndefined(response.usage?.completion_tokens_details?.rejected_prediction_tokens)) {
|
|
493
|
-
metadata.rejectedPredictionTokens = response.usage?.completion_tokens_details?.rejected_prediction_tokens ?? 0
|
|
494
|
-
}
|
|
495
|
-
if (Predicate.isNotUndefined(response.usage?.prompt_tokens_details?.audio_tokens)) {
|
|
496
|
-
metadata.inputAudioTokens = response.usage?.prompt_tokens_details?.audio_tokens ?? 0
|
|
497
|
-
}
|
|
498
|
-
if (Predicate.isNotUndefined(response.usage?.completion_tokens_details?.audio_tokens)) {
|
|
499
|
-
metadata.outputAudioTokens = response.usage?.completion_tokens_details?.audio_tokens ?? 0
|
|
500
|
-
}
|
|
501
|
-
parts.push(
|
|
502
|
-
new AiResponse.FinishPart({
|
|
503
|
-
reason: finishReason,
|
|
504
|
-
usage: new AiResponse.Usage({
|
|
505
|
-
inputTokens,
|
|
506
|
-
outputTokens,
|
|
507
|
-
totalTokens,
|
|
508
|
-
reasoningTokens: response.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
509
|
-
cacheReadInputTokens: response.usage?.prompt_tokens_details?.cached_tokens ?? 0,
|
|
510
|
-
cacheWriteInputTokens: 0
|
|
511
|
-
}, constDisableValidation),
|
|
512
|
-
providerMetadata: { [InternalUtilities.ProviderMetadataKey]: metadata }
|
|
513
|
-
}, constDisableValidation)
|
|
514
|
-
)
|
|
515
|
-
if (Predicate.isNotNullable(choice.message.content)) {
|
|
516
|
-
parts.push(
|
|
517
|
-
new AiResponse.TextPart({
|
|
518
|
-
text: choice.message.content
|
|
519
|
-
}, constDisableValidation)
|
|
1151
|
+
return parts
|
|
1152
|
+
})),
|
|
1153
|
+
Stream.flattenIterables
|
|
520
1154
|
)
|
|
521
1155
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
params: choice.message.content!
|
|
528
|
-
}])
|
|
529
|
-
}
|
|
530
|
-
if (
|
|
531
|
-
Predicate.isNotUndefined(choice.message.tool_calls) &&
|
|
532
|
-
choice.message.tool_calls.length > 0
|
|
533
|
-
) {
|
|
534
|
-
return yield* AiResponse.withToolCallsJson(
|
|
535
|
-
output,
|
|
536
|
-
choice.message.tool_calls.map((tool) => ({
|
|
537
|
-
id: tool.id,
|
|
538
|
-
name: tool.function.name,
|
|
539
|
-
params: tool.function.arguments
|
|
540
|
-
}))
|
|
541
|
-
)
|
|
542
|
-
}
|
|
543
|
-
return output
|
|
544
|
-
})
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
// =============================================================================
|
|
1159
|
+
// Telemetry
|
|
1160
|
+
// =============================================================================
|
|
545
1161
|
|
|
546
1162
|
const annotateRequest = (
|
|
547
1163
|
span: Span,
|
|
548
|
-
request: typeof Generated.
|
|
1164
|
+
request: typeof Generated.CreateResponse.Encoded
|
|
549
1165
|
): void => {
|
|
550
1166
|
addGenAIAnnotations(span, {
|
|
551
1167
|
system: "openai",
|
|
@@ -554,66 +1170,215 @@ const annotateRequest = (
|
|
|
554
1170
|
model: request.model,
|
|
555
1171
|
temperature: request.temperature,
|
|
556
1172
|
topP: request.top_p,
|
|
557
|
-
maxTokens: request.
|
|
558
|
-
stopSequences: Arr.ensure(request.stop).filter(Predicate.isNotNullable),
|
|
559
|
-
frequencyPenalty: request.frequency_penalty,
|
|
560
|
-
presencePenalty: request.presence_penalty,
|
|
561
|
-
seed: request.seed
|
|
1173
|
+
maxTokens: request.max_output_tokens
|
|
562
1174
|
},
|
|
563
1175
|
openai: {
|
|
564
1176
|
request: {
|
|
565
|
-
responseFormat: request.
|
|
1177
|
+
responseFormat: request.text?.format?.type,
|
|
566
1178
|
serviceTier: request.service_tier
|
|
567
1179
|
}
|
|
568
1180
|
}
|
|
569
1181
|
})
|
|
570
1182
|
}
|
|
571
1183
|
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
response: typeof Generated.CreateChatCompletionResponse.Type
|
|
575
|
-
): void => {
|
|
1184
|
+
const annotateResponse = (span: Span, response: Generated.Response): void => {
|
|
1185
|
+
const finishReason = response.incomplete_details?.reason
|
|
576
1186
|
addGenAIAnnotations(span, {
|
|
577
1187
|
response: {
|
|
578
1188
|
id: response.id,
|
|
579
1189
|
model: response.model,
|
|
580
|
-
finishReasons:
|
|
1190
|
+
finishReasons: Predicate.isNotUndefined(finishReason) ? [finishReason] : undefined
|
|
581
1191
|
},
|
|
582
1192
|
usage: {
|
|
583
|
-
inputTokens: response.usage?.
|
|
584
|
-
outputTokens: response.usage?.
|
|
1193
|
+
inputTokens: response.usage?.input_tokens,
|
|
1194
|
+
outputTokens: response.usage?.output_tokens
|
|
585
1195
|
},
|
|
586
1196
|
openai: {
|
|
587
1197
|
response: {
|
|
588
|
-
systemFingerprint: response.system_fingerprint,
|
|
589
1198
|
serviceTier: response.service_tier
|
|
590
1199
|
}
|
|
591
1200
|
}
|
|
592
1201
|
})
|
|
593
1202
|
}
|
|
594
1203
|
|
|
595
|
-
const annotateStreamResponse = (
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
) => {
|
|
599
|
-
const metadataPart = response.parts.find((part) => part._tag === "MetadataPart")
|
|
600
|
-
const finishPart = response.parts.find((part) => part._tag === "FinishPart")
|
|
601
|
-
const providerMetadata = finishPart?.providerMetadata[ProviderMetadata.key]
|
|
602
|
-
addGenAIAnnotations(span, {
|
|
603
|
-
response: {
|
|
604
|
-
id: metadataPart?.id,
|
|
605
|
-
model: metadataPart?.model,
|
|
606
|
-
finishReasons: finishPart?.reason ? [finishPart.reason] : undefined
|
|
607
|
-
},
|
|
608
|
-
usage: {
|
|
609
|
-
inputTokens: finishPart?.usage.inputTokens,
|
|
610
|
-
outputTokens: finishPart?.usage.outputTokens
|
|
611
|
-
},
|
|
612
|
-
openai: {
|
|
1204
|
+
const annotateStreamResponse = (span: Span, part: Response.StreamPartEncoded) => {
|
|
1205
|
+
if (part.type === "response-metadata") {
|
|
1206
|
+
addGenAIAnnotations(span, {
|
|
613
1207
|
response: {
|
|
614
|
-
|
|
615
|
-
|
|
1208
|
+
id: part.id,
|
|
1209
|
+
model: part.modelId
|
|
1210
|
+
}
|
|
1211
|
+
})
|
|
1212
|
+
}
|
|
1213
|
+
if (part.type === "finish") {
|
|
1214
|
+
const serviceTier = part.metadata?.[ProviderMetadata.key]?.serviceTier as string | undefined
|
|
1215
|
+
addGenAIAnnotations(span, {
|
|
1216
|
+
response: {
|
|
1217
|
+
finishReasons: [part.reason]
|
|
1218
|
+
},
|
|
1219
|
+
usage: {
|
|
1220
|
+
inputTokens: part.usage.inputTokens,
|
|
1221
|
+
outputTokens: part.usage.outputTokens
|
|
1222
|
+
},
|
|
1223
|
+
openai: {
|
|
1224
|
+
response: { serviceTier }
|
|
1225
|
+
}
|
|
1226
|
+
})
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// =============================================================================
|
|
1231
|
+
// Tool Calling
|
|
1232
|
+
// =============================================================================
|
|
1233
|
+
|
|
1234
|
+
type OpenAiToolChoice = typeof Generated.CreateResponse.fields.tool_choice.from.Encoded
|
|
1235
|
+
|
|
1236
|
+
const prepareTools: (options: LanguageModel.ProviderOptions) => Effect.Effect<{
|
|
1237
|
+
readonly tools: ReadonlyArray<typeof Generated.Tool.Encoded> | undefined
|
|
1238
|
+
readonly toolChoice: OpenAiToolChoice | undefined
|
|
1239
|
+
}, AiError.AiError> = Effect.fnUntraced(function*(options) {
|
|
1240
|
+
// Return immediately if no tools are in the toolkit
|
|
1241
|
+
if (options.tools.length === 0) {
|
|
1242
|
+
return { tools: undefined, toolChoice: undefined }
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
const tools: Array<typeof Generated.Tool.Encoded> = []
|
|
1246
|
+
let toolChoice: OpenAiToolChoice | undefined = undefined
|
|
1247
|
+
|
|
1248
|
+
// Filter the incoming tools down to the set of allowed tools as indicated by
|
|
1249
|
+
// the tool choice. This must be done here given that there is no tool name
|
|
1250
|
+
// in OpenAI's provider-defined tools, so there would be no way to perform
|
|
1251
|
+
// this filter otherwise
|
|
1252
|
+
let allowedTools = options.tools
|
|
1253
|
+
if (typeof options.toolChoice === "object" && "oneOf" in options.toolChoice) {
|
|
1254
|
+
const allowedToolNames = new Set(options.toolChoice.oneOf)
|
|
1255
|
+
allowedTools = options.tools.filter((tool) => allowedToolNames.has(tool.name))
|
|
1256
|
+
toolChoice = options.toolChoice.mode === "required" ? "required" : "auto"
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// Convert the tools in the toolkit to the provider-defined format
|
|
1260
|
+
for (const tool of allowedTools) {
|
|
1261
|
+
if (Tool.isUserDefined(tool)) {
|
|
1262
|
+
tools.push({
|
|
1263
|
+
type: "function",
|
|
1264
|
+
name: tool.name,
|
|
1265
|
+
description: Tool.getDescription(tool as any),
|
|
1266
|
+
parameters: Tool.getJsonSchema(tool as any) as any,
|
|
1267
|
+
strict: true
|
|
1268
|
+
})
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
if (Tool.isProviderDefined(tool)) {
|
|
1272
|
+
switch (tool.id) {
|
|
1273
|
+
case "openai.code_interpreter": {
|
|
1274
|
+
tools.push({
|
|
1275
|
+
...tool.args,
|
|
1276
|
+
type: "code_interpreter"
|
|
1277
|
+
})
|
|
1278
|
+
break
|
|
1279
|
+
}
|
|
1280
|
+
case "openai.file_search": {
|
|
1281
|
+
tools.push({
|
|
1282
|
+
...tool.args,
|
|
1283
|
+
type: "file_search"
|
|
1284
|
+
})
|
|
1285
|
+
break
|
|
1286
|
+
}
|
|
1287
|
+
case "openai.web_search": {
|
|
1288
|
+
tools.push({
|
|
1289
|
+
...tool.args,
|
|
1290
|
+
type: "web_search"
|
|
1291
|
+
})
|
|
1292
|
+
break
|
|
1293
|
+
}
|
|
1294
|
+
case "openai.web_search_preview": {
|
|
1295
|
+
tools.push({
|
|
1296
|
+
...tool.args,
|
|
1297
|
+
type: "web_search_preview"
|
|
1298
|
+
})
|
|
1299
|
+
break
|
|
1300
|
+
}
|
|
1301
|
+
default: {
|
|
1302
|
+
return yield* new AiError.MalformedInput({
|
|
1303
|
+
module: "AnthropicLanguageModel",
|
|
1304
|
+
method: "prepareTools",
|
|
1305
|
+
description: `Received request to call unknown provider-defined tool '${tool.name}'`
|
|
1306
|
+
})
|
|
1307
|
+
}
|
|
616
1308
|
}
|
|
617
1309
|
}
|
|
618
|
-
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
if (options.toolChoice === "auto" || options.toolChoice === "none" || options.toolChoice === "required") {
|
|
1313
|
+
toolChoice = options.toolChoice
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
if (typeof options.toolChoice === "object" && "tool" in options.toolChoice) {
|
|
1317
|
+
toolChoice = Predicate.isUndefined(OpenAiTool.getProviderDefinedToolName(options.toolChoice.tool))
|
|
1318
|
+
? { type: "function", name: options.toolChoice.tool }
|
|
1319
|
+
: { type: options.toolChoice.tool }
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
return { tools, toolChoice }
|
|
1323
|
+
})
|
|
1324
|
+
|
|
1325
|
+
// =============================================================================
|
|
1326
|
+
// Utilities
|
|
1327
|
+
// =============================================================================
|
|
1328
|
+
|
|
1329
|
+
const isFileId = (data: string, config: Config.Service): boolean =>
|
|
1330
|
+
Predicate.isNotUndefined(config.fileIdPrefixes) && config.fileIdPrefixes.some((prefix) => data.startsWith(prefix))
|
|
1331
|
+
|
|
1332
|
+
const getItemId = (
|
|
1333
|
+
part:
|
|
1334
|
+
| Prompt.TextPart
|
|
1335
|
+
| Prompt.ToolCallPart
|
|
1336
|
+
): string | undefined => part.options.openai?.itemId
|
|
1337
|
+
|
|
1338
|
+
const getImageDetail = (part: Prompt.FilePart): typeof Generated.InputImageContentDetail.Encoded =>
|
|
1339
|
+
part.options.openai?.imageDetail ?? "auto"
|
|
1340
|
+
|
|
1341
|
+
const prepareInclude = (
|
|
1342
|
+
options: LanguageModel.ProviderOptions,
|
|
1343
|
+
config: Config.Service
|
|
1344
|
+
): ReadonlyArray<typeof Generated.Includable.Encoded> => {
|
|
1345
|
+
const include: Set<typeof Generated.Includable.Encoded> = new Set(config.include ?? [])
|
|
1346
|
+
|
|
1347
|
+
const codeInterpreterTool = options.tools.find((tool) =>
|
|
1348
|
+
Tool.isProviderDefined(tool) &&
|
|
1349
|
+
tool.id === "openai.code_interpreter"
|
|
1350
|
+
) as Tool.AnyProviderDefined | undefined
|
|
1351
|
+
|
|
1352
|
+
if (Predicate.isNotUndefined(codeInterpreterTool)) {
|
|
1353
|
+
include.add("code_interpreter_call.outputs")
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const webSearchTool = options.tools.find((tool) =>
|
|
1357
|
+
Tool.isProviderDefined(tool) &&
|
|
1358
|
+
(tool.id === "openai.web_search" ||
|
|
1359
|
+
tool.id === "openai.web_search_preview")
|
|
1360
|
+
) as Tool.AnyProviderDefined | undefined
|
|
1361
|
+
|
|
1362
|
+
if (Predicate.isNotUndefined(webSearchTool)) {
|
|
1363
|
+
include.add("web_search_call.action.sources")
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
return Array.from(include)
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const prepareResponseFormat = (
|
|
1370
|
+
options: LanguageModel.ProviderOptions
|
|
1371
|
+
): typeof Generated.TextResponseFormatConfiguration.Encoded => {
|
|
1372
|
+
if (options.responseFormat.type === "json") {
|
|
1373
|
+
const name = options.responseFormat.objectName
|
|
1374
|
+
const schema = options.responseFormat.schema
|
|
1375
|
+
return {
|
|
1376
|
+
type: "json_schema",
|
|
1377
|
+
name,
|
|
1378
|
+
description: Tool.getDescriptionFromSchemaAst(schema.ast) ?? "Response with a JSON object",
|
|
1379
|
+
schema: Tool.getJsonSchemaFromSchemaAst(schema.ast) as any,
|
|
1380
|
+
strict: true
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return { type: "text" }
|
|
619
1384
|
}
|