Skip to content

Commit f68baab

Browse files
authored
Merge branch 'dev' into goal-driver
2 parents 50f4761 + c272a63 commit f68baab

180 files changed

Lines changed: 3644 additions & 1793 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎bun.lock‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎nix/hashes.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"nodeModules": {
3-
"x86_64-linux": "sha256-3c3nMY6ebAxqRugN01rdvACinbe1vIVDCpVDFR/N94M=",
4-
"aarch64-linux": "sha256-CzS3Jn73+0j2qZOpWBfOYaMiPEX6E6QIjNLRe/qbytM=",
5-
"aarch64-darwin": "sha256-Tg8BTFoXv5/N+gdf+VYlxP60fTUE3G76547Wk2Tz06k="
3+
"x86_64-linux": "sha256-nCmoU2+BcjcKmNAHrSi5nwgxEDulsyVksNaKSSQjGRo=",
4+
"aarch64-linux": "sha256-rEYPrXMHEkJR2H0rvntiWN0taXMvQJMbk+uDgFMi7Vc=",
5+
"aarch64-darwin": "sha256-Tai++WEfaIpN8IzeVu5lDNr02knr8NqeDpbSRWYV5ro="
66
}
77
}

‎packages/ai/README.md‎

Lines changed: 79 additions & 41 deletions
Large diffs are not rendered by default.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Effect, Schema } from "effect"
2+
import {
3+
EvaluationModel,
4+
type EvaluationRequestFor,
5+
type EvaluationResponse,
6+
type EvaluationRounding,
7+
} from "./evaluation.js"
8+
import { SystemOne } from "./system-one.js"
9+
import { Endpoint } from "../route/endpoint.js"
10+
import { MediaProtocol } from "../route/media-protocol.js"
11+
import { MediaRoute } from "../route/media.js"
12+
import type { ProviderID } from "../schema/index.js"
13+
14+
// Workers AI models answer with the System One response; third-party models such as TypeSafe Jev wrap it in a
15+
// completed run record.
16+
const Envelope = Schema.Struct({
17+
result: Schema.Union([
18+
SystemOne.Response,
19+
Schema.Struct({ state: Schema.Literal("Completed"), result: SystemOne.Response }),
20+
]),
21+
})
22+
23+
export interface ModelInput extends MediaRoute.ModelInput {
24+
/** Also the provider metadata key. */
25+
readonly provider: ProviderID
26+
/** The Run API base, `.../client/v4/accounts/{account}/ai/run`. */
27+
readonly baseURL: string
28+
readonly rounding?: EvaluationRounding
29+
}
30+
31+
/** Posts `{ model, input }` to Cloudflare's `/ai/run` with the System One request body as `input`. */
32+
export const model = (input: ModelInput) => {
33+
const route = MediaProtocol.identity({
34+
id: "cloudflare-run-evaluation",
35+
name: "Cloudflare Run",
36+
provider: input.provider,
37+
})
38+
const decodeEnvelope = route.decodeJson(Envelope)
39+
const composed = MediaRoute.inline({
40+
protocol: MediaProtocol.inline<EvaluationRequestFor, EvaluationResponse>(route, {
41+
body: {
42+
from: (request) =>
43+
SystemOne.encodeInput(request).pipe(
44+
Effect.map((body) => MediaProtocol.json({ model: request.model.id, input: body })),
45+
),
46+
},
47+
response: {
48+
decode: (response) =>
49+
decodeEnvelope(response).pipe(
50+
Effect.map((output) =>
51+
SystemOne.toResponse(
52+
"answers" in output.value.result ? output.value.result : output.value.result.result,
53+
input.provider,
54+
input.rounding,
55+
),
56+
),
57+
),
58+
},
59+
}),
60+
endpoint: Endpoint.path<MediaProtocol.Body, EvaluationRequestFor>("", { baseURL: input.baseURL }),
61+
auth: input.auth,
62+
headers: input.headers,
63+
})
64+
return EvaluationModel.make({
65+
id: input.id,
66+
provider: input.provider,
67+
http: input.http,
68+
route: { id: composed.id, evaluate: composed.generate },
69+
})
70+
}
71+
72+
export const CloudflareRunEvaluation = { model } as const
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { Effect, Schema } from "effect"
2+
import type { HttpClientResponse } from "effect/http"
3+
import {
4+
EvaluationModel,
5+
EvaluationResponse,
6+
EvaluationRounding,
7+
type EvaluationAnswer,
8+
type EvaluationInput,
9+
type EvaluationRequestFor,
10+
} from "./evaluation.js"
11+
import { OpenResponses } from "../protocols/open-responses.js"
12+
import { Endpoint } from "../route/endpoint.js"
13+
import { MediaProtocol } from "../route/media-protocol.js"
14+
import { MediaRoute } from "../route/media.js"
15+
import { ModelID, mergeJsonRecords, type OpenString } from "../schema/index.js"
16+
import { encodeJson } from "../utils/json.js"
17+
18+
const route = MediaProtocol.identity({ id: "openai-decisions", name: "OpenAI Decisions", provider: "openai" })
19+
export const DEFAULT_BASE_URL = "https://api.openai.com/v1"
20+
export const PATH = "/decisions"
21+
22+
/** Native body fields, sent as given. */
23+
export type OpenAIDecisionOptions = {
24+
readonly prompt_cache_key?: string
25+
readonly prompt_cache_options?: Readonly<{ mode?: OpenString<"implicit" | "explicit">; ttl?: OpenString<"30m"> }>
26+
readonly safety_identifier?: string
27+
} & Record<string, unknown>
28+
29+
type Request = EvaluationRequestFor<OpenAIDecisionOptions>
30+
31+
const Probability = Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 }))
32+
const Answer = Schema.Union([
33+
Schema.Struct({ type: Schema.Literal("predicate"), name: Schema.String, probability: Probability }),
34+
Schema.Struct({
35+
type: Schema.Literal("choice"),
36+
name: Schema.String,
37+
choice: Schema.String,
38+
probabilities: Schema.Array(Schema.Struct({ value: Schema.String, probability: Probability })),
39+
confidence: Schema.optional(Probability),
40+
}),
41+
Schema.Struct({
42+
type: Schema.Literal("score"),
43+
name: Schema.String,
44+
score: Schema.Number,
45+
probabilities: Schema.Array(Schema.Struct({ value: Schema.Int, probability: Probability })),
46+
confidence: Schema.optional(Probability),
47+
}),
48+
Schema.Struct({ type: Schema.Literal("refusal"), name: Schema.String }),
49+
])
50+
const Response = Schema.Struct({
51+
model: Schema.String,
52+
answers: Schema.Array(Answer),
53+
usage: Schema.optional(OpenResponses.OpenResponsesUsage),
54+
})
55+
56+
const fromRequest = Effect.fn("OpenAIDecisions.fromRequest")(function* (request: Request) {
57+
const criteria = Object.entries(request.questions).flatMap(([name, question]) =>
58+
question.type === "boolean" && question.criteria !== undefined ? [name] : [],
59+
)
60+
if (criteria.length > 0)
61+
return yield* route.unsupported(
62+
"evaluation.criteria",
63+
`${route.name} boolean questions take instructions only; remove criteria from ${criteria.join(", ")}`,
64+
)
65+
return MediaProtocol.json({
66+
...mergeJsonRecords(request.options, request.http?.body),
67+
model: request.model.id,
68+
input: typeof request.state === "string" ? request.state : [{ role: "user", content: json(request.state) }],
69+
questions: Object.entries(request.questions).map(([name, question]) => {
70+
const instructions = typeof question.instructions === "string" ? question.instructions : json(question.instructions)
71+
if (question.type === "boolean") return { type: "predicate", name, instructions }
72+
if (question.type === "choice")
73+
return {
74+
type: "choice",
75+
name,
76+
instructions,
77+
choices: Object.entries(question.criteria).map(([value, description]) => ({
78+
value,
79+
...(description === null ? {} : { description: text(description) }),
80+
})),
81+
}
82+
return {
83+
type: "score",
84+
name,
85+
instructions,
86+
levels: question.criteria.map((level, index) =>
87+
typeof level === "string"
88+
? { label: level }
89+
: { label: String(index), ...(level === null ? {} : { description: text(level) }) },
90+
),
91+
}
92+
}),
93+
})
94+
})
95+
96+
const json = (data: EvaluationInput) => [{ type: "input_json", data }]
97+
const text = (value: EvaluationInput) => (typeof value === "string" ? value : encodeJson(value))
98+
99+
const decodeDocument = route.decodeJson(Response)
100+
101+
const decodeResponse = Effect.fn("OpenAIDecisions.decodeResponse")(function* (
102+
response: HttpClientResponse.HttpClientResponse,
103+
) {
104+
const output = yield* decodeDocument(response)
105+
const refused = output.value.answers.flatMap((answer) => (answer.type === "refusal" ? [answer.name] : []))
106+
if (refused.length > 0) return yield* output.contentPolicy(`${route.name} refused to answer ${refused.join(", ")}`)
107+
return new EvaluationResponse({
108+
model: ModelID.make(output.value.model),
109+
answers: Object.fromEntries(
110+
output.value.answers.flatMap((answer): Array<[string, EvaluationAnswer]> => {
111+
if (answer.type === "refusal") return []
112+
if (answer.type === "predicate") return [[answer.name, { type: "boolean", probability: answer.probability }]]
113+
const probabilities = Object.fromEntries(answer.probabilities.map((item) => [item.value, item.probability]))
114+
const confidence = answer.confidence === undefined ? {} : { confidence: answer.confidence }
115+
if (answer.type === "choice")
116+
return [[answer.name, { type: "choice", choice: answer.choice, probabilities, ...confidence }]]
117+
return [[answer.name, { type: "score", score: answer.score, probabilities, ...confidence }]]
118+
}),
119+
),
120+
usage: OpenResponses.mapUsage(output.value.usage, "openai"),
121+
rounding: new EvaluationRounding({ probabilityDecimals: 2, scoreDecimals: 2 }),
122+
})
123+
})
124+
125+
export const protocol = MediaProtocol.inline<Request, EvaluationResponse>(route, {
126+
body: { from: fromRequest },
127+
response: { decode: decodeResponse },
128+
})
129+
130+
export const model = (input: MediaRoute.ModelInput) => {
131+
const composed = MediaRoute.inline({
132+
protocol,
133+
endpoint: Endpoint.path<MediaProtocol.Body, Request>(PATH, { baseURL: input.baseURL ?? DEFAULT_BASE_URL }),
134+
auth: input.auth,
135+
headers: input.headers,
136+
})
137+
return EvaluationModel.make<OpenAIDecisionOptions>({
138+
id: input.id,
139+
provider: protocol.provider,
140+
http: input.http,
141+
route: { id: composed.id, evaluate: composed.generate },
142+
})
143+
}
144+
145+
export const OpenAIDecisions = {
146+
protocol,
147+
model,
148+
} as const

0 commit comments

Comments
 (0)