| 1 | /** |
| 2 | * Cloudflare Clef implementation of DecisionModel over Workers AI REST. |
| 3 | * |
| 4 | * @stability unstable |
| 5 | * @since 4.0.0 |
| 6 | */ |
| 7 | import * as DecisionModel from "effect/ai/DecisionModel" |
| 8 | import * as AiModel from "effect/ai/Model" |
| 9 | import * as Effect from "effect/Effect" |
| 10 | import * as Layer from "effect/Layer" |
| 11 | import { CloudflareClient } from "./CloudflareClient.ts" |
| 12 | import type * as CloudflareSchema from "./CloudflareSchema.ts" |
| 13 | |
| 14 | /** |
| 15 | * Cloudflare Clef model identifiers. |
| 16 | * |
| 17 | * @stability unstable |
| 18 | * @category models |
| 19 | * @since 4.0.0 |
| 20 | */ |
| 21 | export type Model = typeof CloudflareSchema.DecisionsRequest.Encoded["model"] |
| 22 | |
| 23 | /** |
| 24 | * Creates a decision model with Cloudflare provider metadata. |
| 25 | * |
| 26 | * @stability unstable |
| 27 | * @category constructors |
| 28 | * @since 4.0.0 |
| 29 | */ |
| 30 | export const model = ( |
| 31 | model: Model |
| 32 | ): AiModel.Model<"cloudflare", DecisionModel.DecisionModel, CloudflareClient> => |
| 33 | AiModel.make("cloudflare", model, layer({ model })) |
| 34 | |
| 35 | /** |
| 36 | * Builds a decision service with tolerance for four-decimal probability rounding. |
| 37 | * |
| 38 | * @stability unstable |
| 39 | * @category constructors |
| 40 | * @since 4.0.0 |
| 41 | */ |
| 42 | export const make = Effect.fnUntraced( |
| 43 | function*( |
| 44 | options: { readonly model: Model } |
| 45 | ): Effect.fn.Return<DecisionModel.DecisionModel, never, CloudflareClient> { |
| 46 | const client = yield* CloudflareClient |
| 47 | return yield* DecisionModel.make({ |
| 48 | probabilityPrecision: 4, |
| 49 | decide: Effect.fnUntraced(function*({ state, decisions }) { |
| 50 | const questions: Record<string, typeof CloudflareSchema.Question.Encoded> = Object.create(null) |
| 51 | for (const [key, decision] of Object.entries(decisions)) { |
| 52 | switch (decision._tag) { |
| 53 | case "Classify": |
| 54 | questions[key] = { type: "choice", instructions: decision.instructions, criteria: decision.criteria } |
| 55 | break |
| 56 | case "Rate": |
| 57 | questions[key] = { type: "score", instructions: decision.instructions, criteria: decision.criteria } |
| 58 | break |
| 59 | case "Probability": |
| 60 | questions[key] = { type: "noul", instructions: decision.instructions, criteria: decision.criteria } |
| 61 | break |
| 62 | } |
| 63 | } |
| 64 | const response = yield* client.createDecisions({ model: options.model, state, questions }) |
| 65 | const answers: Record<string, DecisionModel.ProviderAnswer> = Object.create(null) |
| 66 | for (const [key, decision] of Object.entries(decisions)) { |
| 67 | const answer = response.answers[key] |
| 68 | switch (answer?.type) { |
| 69 | case "choice": |
| 70 | answers[key] = { |
| 71 | _tag: "Classify", |
| 72 | label: answer.choice, |
| 73 | probabilities: answer.probabilities, |
| 74 | confidence: answer.confidence |
| 75 | } |
| 76 | break |
| 77 | case "score": { |
| 78 | const levels = decision._tag === "Rate" ? decision.criteria : [] |
| 79 | const probabilities: Record<string, number> = Object.create(null) |
| 80 | for (let index = 0; index < levels.length; index++) { |
| 81 | const probability = answer.probabilities[String(index)] |
| 82 | if (probability !== undefined) probabilities[levels[index]] = probability |
| 83 | } |
| 84 | answers[key] = { _tag: "Rate", rating: answer.score, probabilities, confidence: answer.confidence } |
| 85 | break |
| 86 | } |
| 87 | case "noul": |
| 88 | answers[key] = { _tag: "Probability", probability: answer.noul } |
| 89 | break |
| 90 | } |
| 91 | } |
| 92 | return { |
| 93 | answers, |
| 94 | usage: { inputTokens: response.usage.input_tokens, outputTokens: response.usage.output_tokens } |
| 95 | } |
| 96 | }) |
| 97 | }) |
| 98 | } |
| 99 | ) |
| 100 | |
| 101 | /** |
| 102 | * Provides DecisionModel using an existing CloudflareClient. |
| 103 | * |
| 104 | * @stability unstable |
| 105 | * @category layers |
| 106 | * @since 4.0.0 |
| 107 | */ |
| 108 | export const layer = ( |
| 109 | options: { readonly model: Model } |
| 110 | ): Layer.Layer<DecisionModel.DecisionModel, never, CloudflareClient> => |
| 111 | Layer.effect(DecisionModel.DecisionModel, make(options)) |