@effect/ai-openai 0.17.0 → 0.18.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.
Files changed (70) hide show
  1. package/OpenAiEmbeddingModel/package.json +6 -0
  2. package/OpenAiLanguageModel/package.json +6 -0
  3. package/dist/cjs/OpenAiClient.js +111 -109
  4. package/dist/cjs/OpenAiClient.js.map +1 -1
  5. package/dist/cjs/OpenAiConfig.js +2 -2
  6. package/dist/cjs/OpenAiConfig.js.map +1 -1
  7. package/dist/cjs/{OpenAiEmbeddings.js → OpenAiEmbeddingModel.js} +22 -51
  8. package/dist/cjs/OpenAiEmbeddingModel.js.map +1 -0
  9. package/dist/cjs/OpenAiLanguageModel.js +490 -0
  10. package/dist/cjs/OpenAiLanguageModel.js.map +1 -0
  11. package/dist/cjs/OpenAiTelemetry.js +1 -1
  12. package/dist/cjs/OpenAiTelemetry.js.map +1 -1
  13. package/dist/cjs/OpenAiTokenizer.js +11 -13
  14. package/dist/cjs/OpenAiTokenizer.js.map +1 -1
  15. package/dist/cjs/index.js +5 -5
  16. package/dist/cjs/internal/utilities.js +25 -0
  17. package/dist/cjs/internal/utilities.js.map +1 -0
  18. package/dist/dts/OpenAiClient.d.ts +32 -75
  19. package/dist/dts/OpenAiClient.d.ts.map +1 -1
  20. package/dist/dts/OpenAiConfig.d.ts +5 -6
  21. package/dist/dts/OpenAiConfig.d.ts.map +1 -1
  22. package/dist/dts/{OpenAiEmbeddings.d.ts → OpenAiEmbeddingModel.d.ts} +16 -37
  23. package/dist/dts/OpenAiEmbeddingModel.d.ts.map +1 -0
  24. package/dist/dts/OpenAiLanguageModel.d.ts +108 -0
  25. package/dist/dts/OpenAiLanguageModel.d.ts.map +1 -0
  26. package/dist/dts/OpenAiTelemetry.d.ts +59 -64
  27. package/dist/dts/OpenAiTelemetry.d.ts.map +1 -1
  28. package/dist/dts/OpenAiTokenizer.d.ts +2 -2
  29. package/dist/dts/OpenAiTokenizer.d.ts.map +1 -1
  30. package/dist/dts/index.d.ts +3 -3
  31. package/dist/dts/index.d.ts.map +1 -1
  32. package/dist/dts/internal/utilities.d.ts +2 -0
  33. package/dist/dts/internal/utilities.d.ts.map +1 -0
  34. package/dist/esm/OpenAiClient.js +110 -106
  35. package/dist/esm/OpenAiClient.js.map +1 -1
  36. package/dist/esm/OpenAiConfig.js +2 -2
  37. package/dist/esm/OpenAiConfig.js.map +1 -1
  38. package/dist/esm/{OpenAiEmbeddings.js → OpenAiEmbeddingModel.js} +21 -48
  39. package/dist/esm/OpenAiEmbeddingModel.js.map +1 -0
  40. package/dist/esm/OpenAiLanguageModel.js +478 -0
  41. package/dist/esm/OpenAiLanguageModel.js.map +1 -0
  42. package/dist/esm/OpenAiTelemetry.js +1 -1
  43. package/dist/esm/OpenAiTelemetry.js.map +1 -1
  44. package/dist/esm/OpenAiTokenizer.js +11 -13
  45. package/dist/esm/OpenAiTokenizer.js.map +1 -1
  46. package/dist/esm/index.js +3 -3
  47. package/dist/esm/index.js.map +1 -1
  48. package/dist/esm/internal/utilities.js +16 -0
  49. package/dist/esm/internal/utilities.js.map +1 -0
  50. package/package.json +19 -19
  51. package/src/OpenAiClient.ts +184 -191
  52. package/src/OpenAiConfig.ts +7 -8
  53. package/src/{OpenAiEmbeddings.ts → OpenAiEmbeddingModel.ts} +39 -85
  54. package/src/OpenAiLanguageModel.ts +601 -0
  55. package/src/OpenAiTelemetry.ts +66 -71
  56. package/src/OpenAiTokenizer.ts +35 -26
  57. package/src/index.ts +3 -3
  58. package/src/internal/utilities.ts +19 -0
  59. package/OpenAiCompletions/package.json +0 -6
  60. package/OpenAiEmbeddings/package.json +0 -6
  61. package/dist/cjs/OpenAiCompletions.js +0 -358
  62. package/dist/cjs/OpenAiCompletions.js.map +0 -1
  63. package/dist/cjs/OpenAiEmbeddings.js.map +0 -1
  64. package/dist/dts/OpenAiCompletions.d.ts +0 -75
  65. package/dist/dts/OpenAiCompletions.d.ts.map +0 -1
  66. package/dist/dts/OpenAiEmbeddings.d.ts.map +0 -1
  67. package/dist/esm/OpenAiCompletions.js +0 -345
  68. package/dist/esm/OpenAiCompletions.js.map +0 -1
  69. package/dist/esm/OpenAiEmbeddings.js.map +0 -1
  70. package/src/OpenAiCompletions.ts +0 -500
@@ -1,500 +0,0 @@
1
- /**
2
- * @since 1.0.0
3
- */
4
- import { AiError } from "@effect/ai/AiError"
5
- import type * as AiInput from "@effect/ai/AiInput"
6
- import * as AiModel from "@effect/ai/AiModel"
7
- import * as AiResponse from "@effect/ai/AiResponse"
8
- import * as AiRole from "@effect/ai/AiRole"
9
- import * as Completions from "@effect/ai/Completions"
10
- import * as Tokenizer from "@effect/ai/Tokenizer"
11
- import * as Arr from "effect/Array"
12
- import * as Context from "effect/Context"
13
- import * as Effect from "effect/Effect"
14
- import { dual } from "effect/Function"
15
- import * as Layer from "effect/Layer"
16
- import type * as Option from "effect/Option"
17
- import * as Predicate from "effect/Predicate"
18
- import * as Stream from "effect/Stream"
19
- import type { Span } from "effect/Tracer"
20
- import type { Simplify } from "effect/Types"
21
- import type * as Generated from "./Generated.js"
22
- import type { StreamChunk } from "./OpenAiClient.js"
23
- import { OpenAiClient } from "./OpenAiClient.js"
24
- import { addGenAIAnnotations } from "./OpenAiTelemetry.js"
25
- import * as OpenAiTokenizer from "./OpenAiTokenizer.js"
26
-
27
- /**
28
- * @since 1.0.0
29
- * @category models
30
- */
31
- export type Model = typeof Generated.ModelIdsSharedEnum.Encoded
32
-
33
- // =============================================================================
34
- // Configuration
35
- // =============================================================================
36
-
37
- /**
38
- * @since 1.0.0
39
- * @category tags
40
- */
41
- export class Config extends Context.Tag("@effect/ai-openai/OpenAiCompletions/Config")<
42
- Config,
43
- Config.Service
44
- >() {
45
- /**
46
- * @since 1.0.0
47
- */
48
- static readonly getOrUndefined: Effect.Effect<Config.Service | undefined> = Effect.map(
49
- Effect.context<never>(),
50
- (context) => context.unsafeMap.get(Config.key)
51
- )
52
- }
53
-
54
- /**
55
- * @since 1.0.0
56
- */
57
- export declare namespace Config {
58
- /**
59
- * @since 1.0.0
60
- * @category configuration
61
- */
62
- export interface Service extends
63
- Simplify<
64
- Partial<
65
- Omit<
66
- typeof Generated.CreateChatCompletionRequest.Encoded,
67
- "messages" | "tools" | "tool_choice" | "stream" | "stream_options" | "functions"
68
- >
69
- >
70
- >
71
- {}
72
- }
73
-
74
- // =============================================================================
75
- // OpenAi Completions
76
- // =============================================================================
77
-
78
- const modelCacheKey = Symbol.for("@effect/ai-openai/OpenAiCompletions/AiModel")
79
-
80
- /**
81
- * @since 1.0.0
82
- * @category ai models
83
- */
84
- export const model = (
85
- model: (string & {}) | Model,
86
- config?: Omit<Config.Service, "model">
87
- ): AiModel.AiModel<Completions.Completions | Tokenizer.Tokenizer, OpenAiClient> =>
88
- AiModel.make({
89
- model,
90
- cacheKey: modelCacheKey,
91
- requires: OpenAiClient,
92
- provides: Effect.map(
93
- make({ model, config }),
94
- (completions) => Context.make(Completions.Completions, completions)
95
- ) as Effect.Effect<Context.Context<Completions.Completions | Tokenizer.Tokenizer>>,
96
- updateContext: (context) => {
97
- const innerConfig = context.unsafeMap.get(Config.key) as Config.Service | undefined
98
- return Context.mergeAll(
99
- context,
100
- Context.make(Config, { model, ...config, ...innerConfig }),
101
- Context.make(Tokenizer.Tokenizer, OpenAiTokenizer.make({ model: innerConfig?.model ?? model }))
102
- )
103
- }
104
- })
105
-
106
- const make = Effect.fnUntraced(function*(options: {
107
- readonly model: (string & {}) | Model
108
- readonly config?: Omit<Config.Service, "model">
109
- }) {
110
- const client = yield* OpenAiClient
111
-
112
- const makeRequest = ({ input, required, system, tools }: Completions.CompletionOptions) => {
113
- const useStructured = tools.length === 1 && tools[0].structured
114
- return Effect.map(
115
- Effect.context<never>(),
116
- (context): typeof Generated.CreateChatCompletionRequest.Encoded => ({
117
- model: options.model,
118
- ...options.config,
119
- ...context.unsafeMap.get(Config.key),
120
- messages: makeMessages(input, system),
121
- response_format: useStructured ?
122
- {
123
- type: "json_schema",
124
- json_schema: {
125
- strict: true,
126
- name: tools[0].name,
127
- description: tools[0].description,
128
- schema: tools[0].parameters as any
129
- }
130
- } :
131
- undefined,
132
- tools: !useStructured && tools.length > 0 ?
133
- tools.map((tool) => ({
134
- type: "function",
135
- function: {
136
- name: tool.name,
137
- description: tool.description,
138
- parameters: tool.parameters as any,
139
- strict: true
140
- }
141
- })) :
142
- undefined,
143
- tool_choice: !useStructured && tools.length > 0 ?
144
- typeof required === "boolean" ? (required ? "required" : "auto") : {
145
- type: "function",
146
- function: { name: required }
147
- } :
148
- undefined
149
- })
150
- )
151
- }
152
-
153
- return yield* Completions.make({
154
- create({ span, ...options }) {
155
- return makeRequest(options).pipe(
156
- Effect.tap((request) => annotateRequest(span, request)),
157
- Effect.flatMap(client.client.createChatCompletion),
158
- Effect.tap((response) => annotateChatResponse(span, response)),
159
- Effect.flatMap((response) =>
160
- makeResponse(
161
- response,
162
- "create",
163
- options.tools.length === 1 && options.tools[0].structured
164
- ? options.tools[0]
165
- : undefined
166
- )
167
- ),
168
- Effect.catchAll((cause) =>
169
- Effect.fail(
170
- new AiError({
171
- module: "OpenAiCompletions",
172
- method: "create",
173
- description: "An error occurred",
174
- cause
175
- })
176
- )
177
- )
178
- )
179
- },
180
- stream({ span, ...options }) {
181
- return makeRequest(options).pipe(
182
- Effect.tap((request) => annotateRequest(span, request)),
183
- Effect.map(client.stream),
184
- Stream.unwrap,
185
- Stream.tap((response) => {
186
- annotateStreamResponse(span, response)
187
- return Effect.void
188
- }),
189
- Stream.map((response) => response.asAiResponse),
190
- Stream.catchAll((cause) =>
191
- Effect.fail(
192
- new AiError({
193
- module: "OpenAiCompletions",
194
- method: "stream",
195
- description: "An error occurred",
196
- cause
197
- })
198
- )
199
- )
200
- )
201
- }
202
- })
203
- })
204
-
205
- /**
206
- * @since 1.0.0
207
- * @category layers
208
- */
209
- export const layerCompletions = (options: {
210
- readonly model: (string & {}) | Model
211
- readonly config?: Omit<Config.Service, "model">
212
- }): Layer.Layer<Completions.Completions, never, OpenAiClient> =>
213
- Layer.effect(
214
- Completions.Completions,
215
- make({ model: options.model, config: options.config })
216
- )
217
-
218
- /**
219
- * @since 1.0.0
220
- * @category layers
221
- */
222
- export const layer = (options: {
223
- readonly model: (string & {}) | Model
224
- readonly config?: Omit<Config.Service, "model">
225
- }): Layer.Layer<Completions.Completions | Tokenizer.Tokenizer, never, OpenAiClient> =>
226
- Layer.merge(layerCompletions(options), OpenAiTokenizer.layer(options))
227
-
228
- /**
229
- * @since 1.0.0
230
- * @category configuration
231
- */
232
- export const withConfigOverride: {
233
- /**
234
- * @since 1.0.0
235
- * @category configuration
236
- */
237
- (overrides: Config.Service): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
238
- /**
239
- * @since 1.0.0
240
- * @category configuration
241
- */
242
- <A, E, R>(self: Effect.Effect<A, E, R>, overrides: Config.Service): Effect.Effect<A, E, R>
243
- } = dual<
244
- /**
245
- * @since 1.0.0
246
- * @category configuration
247
- */
248
- (overrides: Config.Service) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>,
249
- /**
250
- * @since 1.0.0
251
- * @category configuration
252
- */
253
- <A, E, R>(self: Effect.Effect<A, E, R>, overrides: Config.Service) => Effect.Effect<A, E, R>
254
- >(2, (self, overrides) =>
255
- Effect.flatMap(
256
- Config.getOrUndefined,
257
- (config) => Effect.provideService(self, Config, { ...config, ...overrides })
258
- ))
259
-
260
- const makeMessages = (
261
- input: AiInput.AiInput,
262
- system: Option.Option<string>
263
- ): Arr.NonEmptyReadonlyArray<typeof Generated.ChatCompletionRequestMessage.Encoded> => {
264
- const messages: Array<typeof Generated.ChatCompletionRequestMessage.Encoded> = system._tag === "Some" ?
265
- [makeSystemMessage(system.value)] :
266
- []
267
- for (const message of input) {
268
- // eslint-disable-next-line no-restricted-syntax
269
- messages.push(...convertMessage(message))
270
- }
271
- return messages as any
272
- }
273
-
274
- const formatRole = (role: AiRole.AiRole) => {
275
- switch (role._tag) {
276
- case "UserWithName":
277
- return {
278
- role: "user",
279
- name: safeName(role.name)
280
- } as const
281
- case "User":
282
- return {
283
- role: "user"
284
- } as const
285
- case "Model":
286
- return {
287
- role: "assistant"
288
- } as const
289
- }
290
- }
291
-
292
- const convertMessage = (
293
- message: AiInput.Message
294
- ): Array<typeof Generated.ChatCompletionRequestMessage.Encoded> => {
295
- const messages: Array<typeof Generated.ChatCompletionRequestMessage.Encoded> = []
296
- let parts: Array<typeof Generated.ChatCompletionRequestUserMessageContentPart.Encoded> = []
297
- let toolCalls: Array<typeof Generated.ChatCompletionMessageToolCall.Encoded> = []
298
- function flushContent() {
299
- if (parts.length === 0) return
300
- messages.push({
301
- ...formatRole(message.role),
302
- content: parts as any
303
- })
304
- parts = []
305
- }
306
- function flushToolCalls() {
307
- if (toolCalls.length === 0) return
308
- messages.push({
309
- role: "assistant",
310
- content: null,
311
- tool_calls: toolCalls
312
- })
313
- toolCalls = []
314
- }
315
- for (const part of message.parts) {
316
- if (part._tag === "ToolCall") {
317
- flushContent()
318
- toolCalls.push({
319
- id: part.id,
320
- type: "function",
321
- function: {
322
- name: part.name,
323
- arguments: JSON.stringify(part.params)
324
- }
325
- })
326
- } else if (part._tag === "ToolCallResolved") {
327
- flushContent()
328
- flushToolCalls()
329
- messages.push({
330
- role: "tool",
331
- tool_call_id: part.toolCallId,
332
- content: JSON.stringify(part.value)
333
- })
334
- } else {
335
- flushToolCalls()
336
- parts.push(makeContentPart(part))
337
- }
338
- }
339
- flushContent()
340
- flushToolCalls()
341
- return messages
342
- }
343
-
344
- const makeContentPart = (
345
- part: AiInput.TextPart | AiInput.ImagePart | AiInput.ImageUrlPart
346
- ): typeof Generated.ChatCompletionRequestUserMessageContentPart.Encoded => {
347
- switch (part._tag) {
348
- case "Image":
349
- return {
350
- type: "image_url",
351
- image_url: {
352
- url: part.asDataUri,
353
- detail: part.quality
354
- }
355
- }
356
- case "ImageUrl":
357
- return {
358
- type: "image_url",
359
- image_url: {
360
- url: part.url,
361
- detail: part.quality
362
- }
363
- }
364
- case "Text":
365
- return {
366
- type: "text",
367
- text: part.content
368
- }
369
- }
370
- }
371
-
372
- const makeResponse = (
373
- response: typeof Generated.CreateChatCompletionResponse.Type,
374
- method: string,
375
- structuredTool?: {
376
- readonly name: string
377
- readonly description: string
378
- }
379
- ) =>
380
- Arr.head(response.choices).pipe(
381
- Effect.mapError(() =>
382
- new AiError({
383
- module: "OpenAiCompletions",
384
- method,
385
- description: "Could not get response"
386
- })
387
- ),
388
- Effect.flatMap((choice) => {
389
- if (structuredTool) {
390
- return AiResponse.AiResponse.empty.withToolCallsJson([
391
- {
392
- id: response.id,
393
- name: structuredTool.name,
394
- params: choice.message.content!
395
- }
396
- ])
397
- }
398
-
399
- const res = typeof choice.message.content === "string" ?
400
- AiResponse.AiResponse.fromText({
401
- role: AiRole.model,
402
- content: choice.message.content!
403
- }) :
404
- AiResponse.AiResponse.empty
405
-
406
- if (choice.message.tool_calls && choice.message.tool_calls.length > 0) {
407
- return res.withToolCallsJson(choice.message.tool_calls.map((toolCall) => ({
408
- id: toolCall.id,
409
- name: toolCall.function.name,
410
- params: toolCall.function.arguments
411
- })))
412
- }
413
-
414
- return Effect.succeed(res)
415
- })
416
- )
417
-
418
- const makeSystemMessage = (content: string): typeof Generated.ChatCompletionRequestSystemMessage.Encoded => {
419
- return {
420
- role: "system",
421
- content
422
- }
423
- }
424
-
425
- const safeName = (name: string) => name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/, "_")
426
-
427
- const annotateRequest = (
428
- span: Span,
429
- request: typeof Generated.CreateChatCompletionRequest.Encoded
430
- ): void => {
431
- addGenAIAnnotations(span, {
432
- system: "openai",
433
- operation: { name: "chat" },
434
- request: {
435
- model: request.model,
436
- temperature: request.temperature,
437
- topP: request.top_p,
438
- maxTokens: request.max_tokens,
439
- stopSequences: Arr.ensure(request.stop).filter(Predicate.isNotNullable),
440
- frequencyPenalty: request.frequency_penalty,
441
- presencePenalty: request.presence_penalty,
442
- seed: request.seed
443
- },
444
- openai: {
445
- request: {
446
- responseFormat: request.response_format?.type,
447
- serviceTier: request.service_tier
448
- }
449
- }
450
- })
451
- }
452
-
453
- const annotateChatResponse = (
454
- span: Span,
455
- response: typeof Generated.CreateChatCompletionResponse.Type
456
- ): void => {
457
- addGenAIAnnotations(span, {
458
- response: {
459
- id: response.id,
460
- model: response.model,
461
- finishReasons: response.choices.map((choice) => choice.finish_reason)
462
- },
463
- usage: {
464
- inputTokens: response.usage?.prompt_tokens,
465
- outputTokens: response.usage?.completion_tokens
466
- },
467
- openai: {
468
- response: {
469
- systemFingerprint: response.system_fingerprint,
470
- serviceTier: response.service_tier
471
- }
472
- }
473
- })
474
- }
475
-
476
- const annotateStreamResponse = (
477
- span: Span,
478
- response: StreamChunk
479
- ) => {
480
- const usage = response.parts.find((part) => part._tag === "Usage")
481
- if (Predicate.isNotNullable(usage)) {
482
- addGenAIAnnotations(span, {
483
- response: {
484
- id: usage.id,
485
- model: usage.model,
486
- finishReasons: usage.finishReasons
487
- },
488
- usage: {
489
- inputTokens: usage.inputTokens,
490
- outputTokens: usage.outputTokens
491
- },
492
- openai: {
493
- response: {
494
- systemFingerprint: usage.systemFingerprint,
495
- serviceTier: usage.serviceTier
496
- }
497
- }
498
- })
499
- }
500
- }