feat(llm): move core to package
This commit is contained in:
@@ -1,215 +0,0 @@
|
||||
import { Effect, Stream } from "effect"
|
||||
import type { AnyPatch, Patch, PatchInput, PatchRegistry } from "./patch"
|
||||
import { context, emptyRegistry, plan, registry as makePatchRegistry, target as targetPatch } from "./patch"
|
||||
import type { TargetBuilder } from "./target"
|
||||
import type { Transport } from "./transport"
|
||||
import type {
|
||||
LLMError,
|
||||
LLMEvent,
|
||||
LLMRequest,
|
||||
ModelRef,
|
||||
PatchTrace,
|
||||
PreparedRequest,
|
||||
Protocol,
|
||||
TransportRequest,
|
||||
} from "./schema"
|
||||
import { LLMResponse, NoAdapterError, PreparedRequest as PreparedRequestSchema } from "./schema"
|
||||
|
||||
interface Compiled<Target> {
|
||||
readonly request: LLMRequest
|
||||
readonly target: Target
|
||||
readonly transport: TransportRequest
|
||||
readonly patchTrace: ReadonlyArray<PatchTrace>
|
||||
}
|
||||
|
||||
export interface TransportContext {
|
||||
readonly request: LLMRequest
|
||||
readonly patchTrace: ReadonlyArray<PatchTrace>
|
||||
}
|
||||
|
||||
export interface RaiseState {
|
||||
readonly request: LLMRequest
|
||||
readonly patchTrace: ReadonlyArray<PatchTrace>
|
||||
}
|
||||
|
||||
export interface Adapter<Draft, Target, Chunk> {
|
||||
readonly id: string
|
||||
readonly protocol: Protocol
|
||||
readonly builder: TargetBuilder<Draft, Target>
|
||||
readonly patches: ReadonlyArray<Patch<Draft>>
|
||||
readonly redact: (target: Target) => unknown
|
||||
readonly prepare: (request: LLMRequest) => Effect.Effect<Draft, LLMError>
|
||||
readonly toTransport: (target: Target, context: TransportContext) => Effect.Effect<TransportRequest, LLMError>
|
||||
readonly parse: (response: Response) => Stream.Stream<Chunk, LLMError>
|
||||
readonly raise: (chunk: Chunk, state: RaiseState) => Stream.Stream<LLMEvent, LLMError>
|
||||
}
|
||||
|
||||
export interface AdapterInput<Draft, Target, Chunk> {
|
||||
readonly id: string
|
||||
readonly protocol: Protocol
|
||||
readonly builder: TargetBuilder<Draft, Target>
|
||||
readonly patches?: ReadonlyArray<Patch<Draft>>
|
||||
readonly redact: (target: Target) => unknown
|
||||
readonly prepare: (request: LLMRequest) => Effect.Effect<Draft, LLMError>
|
||||
readonly toTransport: (target: Target, context: TransportContext) => Effect.Effect<TransportRequest, LLMError>
|
||||
readonly parse: (response: Response) => Stream.Stream<Chunk, LLMError>
|
||||
readonly raise: (chunk: Chunk, state: RaiseState) => Stream.Stream<LLMEvent, LLMError>
|
||||
}
|
||||
|
||||
export interface AdapterDefinition<Draft, Target, Chunk> extends Adapter<Draft, Target, Chunk> {
|
||||
readonly patch: (id: string, input: PatchInput<Draft>) => Patch<Draft>
|
||||
readonly withPatches: (patches: ReadonlyArray<Patch<Draft>>) => AdapterDefinition<Draft, Target, Chunk>
|
||||
}
|
||||
|
||||
export interface LLMClient {
|
||||
readonly prepare: (request: LLMRequest) => Effect.Effect<PreparedRequest, LLMError>
|
||||
readonly stream: (request: LLMRequest) => Stream.Stream<LLMEvent, LLMError>
|
||||
readonly generate: (request: LLMRequest) => Effect.Effect<LLMResponse, LLMError>
|
||||
}
|
||||
|
||||
export interface ClientOptions<Draft, Target, Chunk> {
|
||||
readonly adapter: Adapter<Draft, Target, Chunk>
|
||||
readonly transport: Transport
|
||||
readonly patches?: PatchRegistry | ReadonlyArray<AnyPatch>
|
||||
readonly small?: boolean
|
||||
readonly flags?: Record<string, string | number | boolean | undefined>
|
||||
}
|
||||
|
||||
const assertProtocol = (model: ModelRef, adapter: { readonly protocol: Protocol }) => {
|
||||
if (model.protocol === adapter.protocol) return Effect.void
|
||||
return Effect.fail(new NoAdapterError({ protocol: model.protocol, provider: model.provider, model: model.id }))
|
||||
}
|
||||
|
||||
const normalizeRegistry = (patches: PatchRegistry | ReadonlyArray<AnyPatch> | undefined): PatchRegistry => {
|
||||
if (!patches) return emptyRegistry
|
||||
if ("request" in patches) return patches
|
||||
return makePatchRegistry(patches)
|
||||
}
|
||||
|
||||
export function define<Draft, Target, Chunk>(input: AdapterInput<Draft, Target, Chunk>): AdapterDefinition<Draft, Target, Chunk> {
|
||||
const build = (patches: ReadonlyArray<Patch<Draft>>): AdapterDefinition<Draft, Target, Chunk> => ({
|
||||
id: input.id,
|
||||
protocol: input.protocol,
|
||||
builder: input.builder,
|
||||
patches,
|
||||
redact: input.redact,
|
||||
prepare: input.prepare,
|
||||
toTransport: input.toTransport,
|
||||
parse: input.parse,
|
||||
raise: input.raise,
|
||||
patch: (id, patchInput) => targetPatch(`${input.id}.${id}`, patchInput),
|
||||
withPatches: (next) => build([...patches, ...next]),
|
||||
})
|
||||
|
||||
return build(input.patches ?? [])
|
||||
}
|
||||
|
||||
export function makeClient<Draft, Target, Chunk>(options: ClientOptions<Draft, Target, Chunk>): LLMClient {
|
||||
const registry = normalizeRegistry(options.patches)
|
||||
|
||||
const compile = Effect.fn("LLMCore.compile")(function* (request: LLMRequest) {
|
||||
yield* assertProtocol(request.model, options.adapter)
|
||||
|
||||
const requestPlan = plan({
|
||||
phase: "request",
|
||||
context: context({ request, small: options.small, flags: options.flags }),
|
||||
patches: registry.request,
|
||||
})
|
||||
const requestAfterRequestPatches = requestPlan.apply(request)
|
||||
const promptPlan = plan({
|
||||
phase: "prompt",
|
||||
context: context({ request: requestAfterRequestPatches, small: options.small, flags: options.flags }),
|
||||
patches: registry.prompt,
|
||||
})
|
||||
const requestBeforeToolPatches = promptPlan.apply(requestAfterRequestPatches)
|
||||
const toolSchemaPlan = plan({
|
||||
phase: "tool-schema",
|
||||
context: context({ request: requestBeforeToolPatches, small: options.small, flags: options.flags }),
|
||||
patches: registry.toolSchema,
|
||||
})
|
||||
const patchedRequest =
|
||||
requestBeforeToolPatches.tools.length === 0
|
||||
? requestBeforeToolPatches
|
||||
: { ...requestBeforeToolPatches, tools: requestBeforeToolPatches.tools.map(toolSchemaPlan.apply) }
|
||||
const patchContext = context({ request: patchedRequest, small: options.small, flags: options.flags })
|
||||
const draft = yield* options.adapter.prepare(patchedRequest)
|
||||
const targetPlan = plan({
|
||||
phase: "target",
|
||||
context: patchContext,
|
||||
patches: [...options.adapter.patches, ...(registry.target as ReadonlyArray<Patch<Draft>>)],
|
||||
})
|
||||
const target = yield* options.adapter.builder.validate(targetPlan.apply(draft))
|
||||
const targetPatchTrace = [
|
||||
...requestPlan.trace,
|
||||
...promptPlan.trace,
|
||||
...(requestBeforeToolPatches.tools.length === 0 ? [] : toolSchemaPlan.trace),
|
||||
...targetPlan.trace,
|
||||
]
|
||||
const rawTransport = yield* options.adapter.toTransport(target, { request: patchedRequest, patchTrace: targetPatchTrace })
|
||||
const transportPlan = plan({
|
||||
phase: "transport",
|
||||
context: patchContext,
|
||||
patches: registry.transport,
|
||||
})
|
||||
const patchTrace = [...targetPatchTrace, ...transportPlan.trace]
|
||||
const transport = transportPlan.apply(rawTransport)
|
||||
|
||||
return { request: patchedRequest, target, transport, patchTrace }
|
||||
})
|
||||
|
||||
const prepare = Effect.fn("LLMCore.prepare")(function* (request: LLMRequest) {
|
||||
const compiled = yield* compile(request)
|
||||
|
||||
return new PreparedRequestSchema({
|
||||
id: compiled.request.id ?? "request",
|
||||
adapter: options.adapter.id,
|
||||
model: compiled.request.model,
|
||||
target: compiled.target,
|
||||
redactedTarget: options.adapter.redact(compiled.target),
|
||||
transport: compiled.transport,
|
||||
patchTrace: compiled.patchTrace,
|
||||
})
|
||||
})
|
||||
|
||||
const stream = (request: LLMRequest) =>
|
||||
Stream.unwrap(
|
||||
Effect.gen(function* () {
|
||||
const compiled = yield* compile(request)
|
||||
const response = yield* options.transport.fetch(compiled.transport)
|
||||
const streamPlan = plan({
|
||||
phase: "stream",
|
||||
context: context({ request: compiled.request, small: options.small, flags: options.flags }),
|
||||
patches: registry.stream,
|
||||
})
|
||||
const events = options.adapter.parse(response).pipe(
|
||||
Stream.flatMap((chunk) =>
|
||||
options.adapter.raise(chunk, {
|
||||
request: compiled.request,
|
||||
patchTrace: compiled.patchTrace,
|
||||
}),
|
||||
),
|
||||
)
|
||||
if (streamPlan.patches.length === 0) return events
|
||||
return events.pipe(Stream.map(streamPlan.apply))
|
||||
}),
|
||||
)
|
||||
|
||||
const generate = Effect.fn("LLMCore.generate")(function* (request: LLMRequest) {
|
||||
const events = Array.from(yield* stream(request).pipe(Stream.runCollect))
|
||||
const usage = events.reduce<LLMResponse["usage"]>(
|
||||
(last, event) => ("usage" in event && event.usage !== undefined ? event.usage : last),
|
||||
undefined,
|
||||
)
|
||||
return new LLMResponse({ events, usage })
|
||||
})
|
||||
|
||||
return { prepare, stream, generate }
|
||||
}
|
||||
|
||||
export const client = makeClient
|
||||
|
||||
export const Adapter = {
|
||||
define,
|
||||
}
|
||||
|
||||
export * as LLMCore from "./adapter"
|
||||
@@ -1,187 +0,0 @@
|
||||
import type { LLMEvent, LLMRequest, ModelRef, PatchPhase, Protocol, ToolDefinition, TransportRequest } from "./schema"
|
||||
import { PatchTrace } from "./schema"
|
||||
|
||||
export interface PatchContext {
|
||||
readonly request: LLMRequest
|
||||
readonly model: ModelRef
|
||||
readonly protocol: ModelRef["protocol"]
|
||||
readonly small: boolean
|
||||
readonly flags: Record<string, string | number | boolean | undefined>
|
||||
}
|
||||
|
||||
export interface Patch<A> {
|
||||
readonly id: string
|
||||
readonly phase: PatchPhase
|
||||
readonly reason: string
|
||||
readonly order?: number
|
||||
readonly when: (context: PatchContext) => boolean
|
||||
readonly apply: (value: A, context: PatchContext) => A
|
||||
}
|
||||
|
||||
export interface AnyPatch {
|
||||
readonly id: string
|
||||
readonly phase: PatchPhase
|
||||
readonly reason: string
|
||||
readonly order?: number
|
||||
readonly when: (context: PatchContext) => boolean
|
||||
readonly apply: (value: never, context: PatchContext) => unknown
|
||||
}
|
||||
|
||||
export interface PatchInput<A> {
|
||||
readonly reason: string
|
||||
readonly order?: number
|
||||
readonly when?: PatchPredicate | ((context: PatchContext) => boolean)
|
||||
readonly apply: (value: A, context: PatchContext) => A
|
||||
}
|
||||
|
||||
export interface PatchPredicate {
|
||||
(context: PatchContext): boolean
|
||||
readonly and: (...predicates: ReadonlyArray<PatchPredicate>) => PatchPredicate
|
||||
readonly or: (...predicates: ReadonlyArray<PatchPredicate>) => PatchPredicate
|
||||
readonly not: () => PatchPredicate
|
||||
}
|
||||
|
||||
export interface PatchPlan<A> {
|
||||
readonly phase: PatchPhase
|
||||
readonly patches: ReadonlyArray<Patch<A>>
|
||||
readonly trace: ReadonlyArray<PatchTrace>
|
||||
readonly apply: (value: A) => A
|
||||
}
|
||||
|
||||
export interface PatchRegistry {
|
||||
readonly request: ReadonlyArray<Patch<LLMRequest>>
|
||||
readonly prompt: ReadonlyArray<Patch<LLMRequest>>
|
||||
readonly toolSchema: ReadonlyArray<Patch<ToolDefinition>>
|
||||
readonly target: ReadonlyArray<Patch<unknown>>
|
||||
readonly transport: ReadonlyArray<Patch<TransportRequest>>
|
||||
readonly stream: ReadonlyArray<Patch<LLMEvent>>
|
||||
}
|
||||
|
||||
export const emptyRegistry: PatchRegistry = {
|
||||
request: [],
|
||||
prompt: [],
|
||||
toolSchema: [],
|
||||
target: [],
|
||||
transport: [],
|
||||
stream: [],
|
||||
}
|
||||
|
||||
export const predicate = (run: (context: PatchContext) => boolean): PatchPredicate => {
|
||||
const self = Object.assign(run, {
|
||||
and: (...predicates: ReadonlyArray<PatchPredicate>) =>
|
||||
predicate((context) => self(context) && predicates.every((item) => item(context))),
|
||||
or: (...predicates: ReadonlyArray<PatchPredicate>) =>
|
||||
predicate((context) => self(context) || predicates.some((item) => item(context))),
|
||||
not: () => predicate((context) => !self(context)),
|
||||
})
|
||||
return self
|
||||
}
|
||||
|
||||
export const Model = {
|
||||
provider: (provider: string) => predicate((context) => context.model.provider === provider),
|
||||
protocol: (protocol: Protocol) => predicate((context) => context.protocol === protocol),
|
||||
id: (id: string) => predicate((context) => context.model.id === id),
|
||||
idIncludes: (value: string) => predicate((context) => context.model.id.toLowerCase().includes(value.toLowerCase())),
|
||||
}
|
||||
|
||||
export const Request = {
|
||||
small: () => predicate((context) => context.small),
|
||||
flag: (name: string) => predicate((context) => context.flags[name] === true),
|
||||
}
|
||||
|
||||
export const make = <A>(id: string, phase: PatchPhase, input: PatchInput<A>): Patch<A> => ({
|
||||
id,
|
||||
phase,
|
||||
reason: input.reason,
|
||||
order: input.order,
|
||||
when: input.when ?? (() => true),
|
||||
apply: input.apply,
|
||||
})
|
||||
|
||||
export const request = (id: string, input: PatchInput<LLMRequest>) => make(`request.${id}`, "request", input)
|
||||
|
||||
export const prompt = (id: string, input: PatchInput<LLMRequest>) => make(`prompt.${id}`, "prompt", input)
|
||||
|
||||
export const toolSchema = (id: string, input: PatchInput<ToolDefinition>) => make(`schema.${id}`, "tool-schema", input)
|
||||
|
||||
export const target = <A>(id: string, input: PatchInput<A>) => make(`target.${id}`, "target", input)
|
||||
|
||||
export const transport = (id: string, input: PatchInput<TransportRequest>) => make(`transport.${id}`, "transport", input)
|
||||
|
||||
export const stream = (id: string, input: PatchInput<LLMEvent>) => make(`stream.${id}`, "stream", input)
|
||||
|
||||
export function registry(patches: ReadonlyArray<AnyPatch>): PatchRegistry {
|
||||
return {
|
||||
request: patches.filter((patch): patch is Patch<LLMRequest> => patch.phase === "request"),
|
||||
prompt: patches.filter((patch): patch is Patch<LLMRequest> => patch.phase === "prompt"),
|
||||
toolSchema: patches.filter((patch): patch is Patch<ToolDefinition> => patch.phase === "tool-schema"),
|
||||
target: patches.filter((patch) => patch.phase === "target") as unknown as ReadonlyArray<Patch<unknown>>,
|
||||
transport: patches.filter((patch): patch is Patch<TransportRequest> => patch.phase === "transport"),
|
||||
stream: patches.filter((patch): patch is Patch<LLMEvent> => patch.phase === "stream"),
|
||||
}
|
||||
}
|
||||
|
||||
export const Patch = {
|
||||
make,
|
||||
request,
|
||||
prompt,
|
||||
toolSchema,
|
||||
target,
|
||||
transport,
|
||||
stream,
|
||||
registry,
|
||||
}
|
||||
|
||||
export function context(input: {
|
||||
readonly request: LLMRequest
|
||||
readonly small?: boolean
|
||||
readonly flags?: Record<string, string | number | boolean | undefined>
|
||||
}): PatchContext {
|
||||
return {
|
||||
request: input.request,
|
||||
model: input.request.model,
|
||||
protocol: input.request.model.protocol,
|
||||
small: input.small ?? false,
|
||||
flags: input.flags ?? {},
|
||||
}
|
||||
}
|
||||
|
||||
export function plan<A>(input: {
|
||||
readonly phase: PatchPhase
|
||||
readonly context: PatchContext
|
||||
readonly patches: ReadonlyArray<Patch<A>>
|
||||
}): PatchPlan<A> {
|
||||
const patches = input.patches
|
||||
.filter((patch) => patch.phase === input.phase && patch.when(input.context))
|
||||
.toSorted((left, right) => (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id))
|
||||
|
||||
return {
|
||||
phase: input.phase,
|
||||
patches,
|
||||
trace: patches.map(
|
||||
(patch) =>
|
||||
new PatchTrace({
|
||||
id: patch.id,
|
||||
phase: patch.phase,
|
||||
reason: patch.reason,
|
||||
}),
|
||||
),
|
||||
apply: (value) => patches.reduce((next, patch) => patch.apply(next, input.context), value),
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeRegistries(registries: ReadonlyArray<PatchRegistry>): PatchRegistry {
|
||||
return registries.reduce(
|
||||
(merged, registry) => ({
|
||||
request: [...merged.request, ...registry.request],
|
||||
prompt: [...merged.prompt, ...registry.prompt],
|
||||
toolSchema: [...merged.toolSchema, ...registry.toolSchema],
|
||||
target: [...merged.target, ...registry.target],
|
||||
transport: [...merged.transport, ...registry.transport],
|
||||
stream: [...merged.stream, ...registry.stream],
|
||||
}),
|
||||
emptyRegistry,
|
||||
)
|
||||
}
|
||||
|
||||
export * as LLMCorePatch from "./patch"
|
||||
@@ -1,424 +0,0 @@
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const Protocol = Schema.Literals([
|
||||
"openai-chat",
|
||||
"openai-responses",
|
||||
"anthropic-messages",
|
||||
"gemini",
|
||||
"bedrock-converse",
|
||||
])
|
||||
export type Protocol = Schema.Schema.Type<typeof Protocol>
|
||||
|
||||
export const ReasoningEffort = Schema.Literals(["none", "minimal", "low", "medium", "high", "xhigh", "max"])
|
||||
export type ReasoningEffort = Schema.Schema.Type<typeof ReasoningEffort>
|
||||
|
||||
export const TargetSlot = Schema.Literals([
|
||||
"model",
|
||||
"system",
|
||||
"messages",
|
||||
"tools",
|
||||
"tool-choice",
|
||||
"generation",
|
||||
"reasoning",
|
||||
"cache",
|
||||
"response-format",
|
||||
"headers",
|
||||
"extensions",
|
||||
])
|
||||
export type TargetSlot = Schema.Schema.Type<typeof TargetSlot>
|
||||
|
||||
export const PatchPhase = Schema.Literals(["request", "prompt", "tool-schema", "target", "transport", "stream"])
|
||||
export type PatchPhase = Schema.Schema.Type<typeof PatchPhase>
|
||||
|
||||
export const MessageRole = Schema.Literals(["user", "assistant", "tool"])
|
||||
export type MessageRole = Schema.Schema.Type<typeof MessageRole>
|
||||
|
||||
export const FinishReason = Schema.Literals(["stop", "length", "tool-calls", "content-filter", "error", "unknown"])
|
||||
export type FinishReason = Schema.Schema.Type<typeof FinishReason>
|
||||
|
||||
export const JsonSchema = Schema.Record(Schema.String, Schema.Unknown)
|
||||
export type JsonSchema = Schema.Schema.Type<typeof JsonSchema>
|
||||
|
||||
export class ModelCapabilities extends Schema.Class<ModelCapabilities>("LLM.ModelCapabilities")({
|
||||
input: Schema.Struct({
|
||||
text: Schema.Boolean,
|
||||
image: Schema.Boolean,
|
||||
audio: Schema.Boolean,
|
||||
video: Schema.Boolean,
|
||||
pdf: Schema.Boolean,
|
||||
}),
|
||||
output: Schema.Struct({
|
||||
text: Schema.Boolean,
|
||||
reasoning: Schema.Boolean,
|
||||
}),
|
||||
tools: Schema.Struct({
|
||||
calls: Schema.Boolean,
|
||||
streamingInput: Schema.Boolean,
|
||||
providerExecuted: Schema.Boolean,
|
||||
}),
|
||||
cache: Schema.Struct({
|
||||
prompt: Schema.Boolean,
|
||||
messageBlocks: Schema.Boolean,
|
||||
contentBlocks: Schema.Boolean,
|
||||
}),
|
||||
reasoning: Schema.Struct({
|
||||
efforts: Schema.Array(ReasoningEffort),
|
||||
summaries: Schema.Boolean,
|
||||
encryptedContent: Schema.Boolean,
|
||||
}),
|
||||
}) {}
|
||||
|
||||
export class ModelLimits extends Schema.Class<ModelLimits>("LLM.ModelLimits")({
|
||||
context: Schema.optional(Schema.Number),
|
||||
output: Schema.optional(Schema.Number),
|
||||
}) {}
|
||||
|
||||
export class ModelRef extends Schema.Class<ModelRef>("LLM.ModelRef")({
|
||||
id: Schema.String,
|
||||
provider: Schema.String,
|
||||
protocol: Protocol,
|
||||
baseURL: Schema.optional(Schema.String),
|
||||
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
||||
capabilities: ModelCapabilities,
|
||||
limits: ModelLimits,
|
||||
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}) {}
|
||||
|
||||
export class CacheHint extends Schema.Class<CacheHint>("LLM.CacheHint")({
|
||||
type: Schema.Literals(["ephemeral", "persistent"]),
|
||||
ttlSeconds: Schema.optional(Schema.Number),
|
||||
}) {}
|
||||
|
||||
export const SystemPart = Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
text: Schema.String,
|
||||
cache: Schema.optional(CacheHint),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}).annotate({ identifier: "LLM.SystemPart" })
|
||||
export type SystemPart = Schema.Schema.Type<typeof SystemPart>
|
||||
|
||||
export const TextPart = Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
text: Schema.String,
|
||||
cache: Schema.optional(CacheHint),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}).annotate({ identifier: "LLM.Content.Text" })
|
||||
export type TextPart = Schema.Schema.Type<typeof TextPart>
|
||||
|
||||
export const MediaPart = Schema.Struct({
|
||||
type: Schema.Literal("media"),
|
||||
mediaType: Schema.String,
|
||||
data: Schema.Union([Schema.String, Schema.Uint8Array]),
|
||||
filename: Schema.optional(Schema.String),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}).annotate({ identifier: "LLM.Content.Media" })
|
||||
export type MediaPart = Schema.Schema.Type<typeof MediaPart>
|
||||
|
||||
export const ToolResultValue = Schema.Struct({
|
||||
type: Schema.Literals(["json", "text", "error"]),
|
||||
value: Schema.Unknown,
|
||||
}).annotate({ identifier: "LLM.ToolResult" })
|
||||
export type ToolResultValue = Schema.Schema.Type<typeof ToolResultValue>
|
||||
|
||||
export const ToolCallPart = Schema.Struct({
|
||||
type: Schema.Literal("tool-call"),
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
input: Schema.Unknown,
|
||||
providerExecuted: Schema.optional(Schema.Boolean),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}).annotate({ identifier: "LLM.Content.ToolCall" })
|
||||
export type ToolCallPart = Schema.Schema.Type<typeof ToolCallPart>
|
||||
|
||||
export const ToolResultPart = Schema.Struct({
|
||||
type: Schema.Literal("tool-result"),
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
result: ToolResultValue,
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}).annotate({ identifier: "LLM.Content.ToolResult" })
|
||||
export type ToolResultPart = Schema.Schema.Type<typeof ToolResultPart>
|
||||
|
||||
export const ReasoningPart = Schema.Struct({
|
||||
type: Schema.Literal("reasoning"),
|
||||
text: Schema.String,
|
||||
encrypted: Schema.optional(Schema.String),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}).annotate({ identifier: "LLM.Content.Reasoning" })
|
||||
export type ReasoningPart = Schema.Schema.Type<typeof ReasoningPart>
|
||||
|
||||
export const ContentPart = Schema.Union([TextPart, MediaPart, ToolCallPart, ToolResultPart, ReasoningPart]).pipe(
|
||||
Schema.toTaggedUnion("type"),
|
||||
)
|
||||
export type ContentPart = Schema.Schema.Type<typeof ContentPart>
|
||||
|
||||
export class Message extends Schema.Class<Message>("LLM.Message")({
|
||||
id: Schema.optional(Schema.String),
|
||||
role: MessageRole,
|
||||
content: Schema.Array(ContentPart),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}) {}
|
||||
|
||||
export class ToolDefinition extends Schema.Class<ToolDefinition>("LLM.ToolDefinition")({
|
||||
name: Schema.String,
|
||||
description: Schema.String,
|
||||
inputSchema: JsonSchema,
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}) {}
|
||||
|
||||
export class ToolChoice extends Schema.Class<ToolChoice>("LLM.ToolChoice")({
|
||||
type: Schema.Literals(["auto", "none", "required", "tool"]),
|
||||
name: Schema.optional(Schema.String),
|
||||
}) {}
|
||||
|
||||
export class GenerationOptions extends Schema.Class<GenerationOptions>("LLM.GenerationOptions")({
|
||||
maxTokens: Schema.optional(Schema.Number),
|
||||
temperature: Schema.optional(Schema.Number),
|
||||
topP: Schema.optional(Schema.Number),
|
||||
stop: Schema.optional(Schema.Array(Schema.String)),
|
||||
}) {}
|
||||
|
||||
export class ReasoningIntent extends Schema.Class<ReasoningIntent>("LLM.ReasoningIntent")({
|
||||
enabled: Schema.Boolean,
|
||||
effort: Schema.optional(ReasoningEffort),
|
||||
summary: Schema.optional(Schema.Boolean),
|
||||
encryptedContent: Schema.optional(Schema.Boolean),
|
||||
}) {}
|
||||
|
||||
export class CacheIntent extends Schema.Class<CacheIntent>("LLM.CacheIntent")({
|
||||
enabled: Schema.Boolean,
|
||||
key: Schema.optional(Schema.String),
|
||||
}) {}
|
||||
|
||||
export const ResponseFormat = Schema.Union([
|
||||
Schema.Struct({ type: Schema.Literal("text") }),
|
||||
Schema.Struct({ type: Schema.Literal("json"), schema: JsonSchema }),
|
||||
Schema.Struct({ type: Schema.Literal("tool"), tool: ToolDefinition }),
|
||||
])
|
||||
export type ResponseFormat = Schema.Schema.Type<typeof ResponseFormat>
|
||||
|
||||
export class LLMRequest extends Schema.Class<LLMRequest>("LLM.Request")({
|
||||
id: Schema.optional(Schema.String),
|
||||
model: ModelRef,
|
||||
system: Schema.Array(SystemPart),
|
||||
messages: Schema.Array(Message),
|
||||
tools: Schema.Array(ToolDefinition),
|
||||
toolChoice: Schema.optional(ToolChoice),
|
||||
generation: GenerationOptions,
|
||||
reasoning: Schema.optional(ReasoningIntent),
|
||||
cache: Schema.optional(CacheIntent),
|
||||
responseFormat: Schema.optional(ResponseFormat),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}) {}
|
||||
|
||||
export class Usage extends Schema.Class<Usage>("LLM.Usage")({
|
||||
inputTokens: Schema.optional(Schema.Number),
|
||||
outputTokens: Schema.optional(Schema.Number),
|
||||
reasoningTokens: Schema.optional(Schema.Number),
|
||||
cacheReadInputTokens: Schema.optional(Schema.Number),
|
||||
cacheWriteInputTokens: Schema.optional(Schema.Number),
|
||||
totalTokens: Schema.optional(Schema.Number),
|
||||
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}) {}
|
||||
|
||||
export const RequestStart = Schema.Struct({
|
||||
type: Schema.Literal("request-start"),
|
||||
id: Schema.String,
|
||||
model: ModelRef,
|
||||
}).annotate({ identifier: "LLM.Event.RequestStart" })
|
||||
export type RequestStart = Schema.Schema.Type<typeof RequestStart>
|
||||
|
||||
export const StepStart = Schema.Struct({
|
||||
type: Schema.Literal("step-start"),
|
||||
index: Schema.Number,
|
||||
}).annotate({ identifier: "LLM.Event.StepStart" })
|
||||
export type StepStart = Schema.Schema.Type<typeof StepStart>
|
||||
|
||||
export const TextStart = Schema.Struct({
|
||||
type: Schema.Literal("text-start"),
|
||||
id: Schema.String,
|
||||
}).annotate({ identifier: "LLM.Event.TextStart" })
|
||||
export type TextStart = Schema.Schema.Type<typeof TextStart>
|
||||
|
||||
export const TextDelta = Schema.Struct({
|
||||
type: Schema.Literal("text-delta"),
|
||||
id: Schema.optional(Schema.String),
|
||||
text: Schema.String,
|
||||
}).annotate({ identifier: "LLM.Event.TextDelta" })
|
||||
export type TextDelta = Schema.Schema.Type<typeof TextDelta>
|
||||
|
||||
export const TextEnd = Schema.Struct({
|
||||
type: Schema.Literal("text-end"),
|
||||
id: Schema.String,
|
||||
}).annotate({ identifier: "LLM.Event.TextEnd" })
|
||||
export type TextEnd = Schema.Schema.Type<typeof TextEnd>
|
||||
|
||||
export const ReasoningDelta = Schema.Struct({
|
||||
type: Schema.Literal("reasoning-delta"),
|
||||
id: Schema.optional(Schema.String),
|
||||
text: Schema.String,
|
||||
}).annotate({ identifier: "LLM.Event.ReasoningDelta" })
|
||||
export type ReasoningDelta = Schema.Schema.Type<typeof ReasoningDelta>
|
||||
|
||||
export const ToolInputDelta = Schema.Struct({
|
||||
type: Schema.Literal("tool-input-delta"),
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
text: Schema.String,
|
||||
}).annotate({ identifier: "LLM.Event.ToolInputDelta" })
|
||||
export type ToolInputDelta = Schema.Schema.Type<typeof ToolInputDelta>
|
||||
|
||||
export const ToolCall = Schema.Struct({
|
||||
type: Schema.Literal("tool-call"),
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
input: Schema.Unknown,
|
||||
}).annotate({ identifier: "LLM.Event.ToolCall" })
|
||||
export type ToolCall = Schema.Schema.Type<typeof ToolCall>
|
||||
|
||||
export const ToolResult = Schema.Struct({
|
||||
type: Schema.Literal("tool-result"),
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
result: ToolResultValue,
|
||||
}).annotate({ identifier: "LLM.Event.ToolResult" })
|
||||
export type ToolResult = Schema.Schema.Type<typeof ToolResult>
|
||||
|
||||
export const ToolError = Schema.Struct({
|
||||
type: Schema.Literal("tool-error"),
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
message: Schema.String,
|
||||
}).annotate({ identifier: "LLM.Event.ToolError" })
|
||||
export type ToolError = Schema.Schema.Type<typeof ToolError>
|
||||
|
||||
export const StepFinish = Schema.Struct({
|
||||
type: Schema.Literal("step-finish"),
|
||||
index: Schema.Number,
|
||||
reason: FinishReason,
|
||||
usage: Schema.optional(Usage),
|
||||
}).annotate({ identifier: "LLM.Event.StepFinish" })
|
||||
export type StepFinish = Schema.Schema.Type<typeof StepFinish>
|
||||
|
||||
export const RequestFinish = Schema.Struct({
|
||||
type: Schema.Literal("request-finish"),
|
||||
reason: FinishReason,
|
||||
usage: Schema.optional(Usage),
|
||||
}).annotate({ identifier: "LLM.Event.RequestFinish" })
|
||||
export type RequestFinish = Schema.Schema.Type<typeof RequestFinish>
|
||||
|
||||
export const ProviderErrorEvent = Schema.Struct({
|
||||
type: Schema.Literal("provider-error"),
|
||||
message: Schema.String,
|
||||
retryable: Schema.optional(Schema.Boolean),
|
||||
}).annotate({ identifier: "LLM.Event.ProviderError" })
|
||||
export type ProviderErrorEvent = Schema.Schema.Type<typeof ProviderErrorEvent>
|
||||
|
||||
export const LLMEvent = Schema.Union([
|
||||
RequestStart,
|
||||
StepStart,
|
||||
TextStart,
|
||||
TextDelta,
|
||||
TextEnd,
|
||||
ReasoningDelta,
|
||||
ToolInputDelta,
|
||||
ToolCall,
|
||||
ToolResult,
|
||||
ToolError,
|
||||
StepFinish,
|
||||
RequestFinish,
|
||||
ProviderErrorEvent,
|
||||
]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type LLMEvent = Schema.Schema.Type<typeof LLMEvent>
|
||||
|
||||
export class PatchTrace extends Schema.Class<PatchTrace>("LLM.PatchTrace")({
|
||||
id: Schema.String,
|
||||
phase: PatchPhase,
|
||||
reason: Schema.String,
|
||||
}) {}
|
||||
|
||||
export class TransportRequest extends Schema.Class<TransportRequest>("LLM.TransportRequest")({
|
||||
url: Schema.String,
|
||||
method: Schema.Literal("POST"),
|
||||
headers: Schema.Record(Schema.String, Schema.String),
|
||||
body: Schema.String,
|
||||
timeoutMs: Schema.optional(Schema.Number),
|
||||
}) {}
|
||||
|
||||
export class PreparedRequest extends Schema.Class<PreparedRequest>("LLM.PreparedRequest")({
|
||||
id: Schema.String,
|
||||
adapter: Schema.String,
|
||||
model: ModelRef,
|
||||
target: Schema.Unknown,
|
||||
redactedTarget: Schema.Unknown,
|
||||
transport: TransportRequest,
|
||||
patchTrace: Schema.Array(PatchTrace),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
}) {}
|
||||
|
||||
export class LLMResponse extends Schema.Class<LLMResponse>("LLM.Response")({
|
||||
events: Schema.Array(LLMEvent),
|
||||
usage: Schema.optional(Usage),
|
||||
}) {}
|
||||
|
||||
export class InvalidRequestError extends Schema.TaggedErrorClass<InvalidRequestError>()("LLM.InvalidRequestError", {
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
export class NoAdapterError extends Schema.TaggedErrorClass<NoAdapterError>()("LLM.NoAdapterError", {
|
||||
protocol: Protocol,
|
||||
provider: Schema.String,
|
||||
model: Schema.String,
|
||||
}) {
|
||||
override get message() {
|
||||
return `No LLM adapter for ${this.provider}/${this.model} using ${this.protocol}`
|
||||
}
|
||||
}
|
||||
|
||||
export class TargetMergeError extends Schema.TaggedErrorClass<TargetMergeError>()("LLM.TargetMergeError", {
|
||||
slot: TargetSlot,
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
export class TargetValidationError extends Schema.TaggedErrorClass<TargetValidationError>()(
|
||||
"LLM.TargetValidationError",
|
||||
{
|
||||
adapter: Schema.String,
|
||||
message: Schema.String,
|
||||
patchTrace: Schema.Array(PatchTrace),
|
||||
},
|
||||
) {}
|
||||
|
||||
export class ProviderRequestError extends Schema.TaggedErrorClass<ProviderRequestError>()("LLM.ProviderRequestError", {
|
||||
adapter: Schema.String,
|
||||
provider: Schema.String,
|
||||
model: Schema.String,
|
||||
status: Schema.optional(Schema.Number),
|
||||
message: Schema.String,
|
||||
body: Schema.optional(Schema.String),
|
||||
patchTrace: Schema.Array(PatchTrace),
|
||||
}) {}
|
||||
|
||||
export class ProviderChunkError extends Schema.TaggedErrorClass<ProviderChunkError>()("LLM.ProviderChunkError", {
|
||||
adapter: Schema.String,
|
||||
message: Schema.String,
|
||||
raw: Schema.optional(Schema.String),
|
||||
}) {}
|
||||
|
||||
export class TransportError extends Schema.TaggedErrorClass<TransportError>()("LLM.TransportError", {
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
export type LLMError =
|
||||
| InvalidRequestError
|
||||
| NoAdapterError
|
||||
| TargetMergeError
|
||||
| TargetValidationError
|
||||
| ProviderRequestError
|
||||
| ProviderChunkError
|
||||
| TransportError
|
||||
|
||||
export * as LLMCoreSchema from "./schema"
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Effect } from "effect"
|
||||
import type { LLMError } from "./schema"
|
||||
|
||||
export interface TargetBuilder<Draft, Target> {
|
||||
readonly empty: Draft
|
||||
readonly concat: (left: Draft, right: Draft) => Effect.Effect<Draft, LLMError>
|
||||
readonly validate: (draft: Draft) => Effect.Effect<Target, LLMError>
|
||||
}
|
||||
|
||||
export * as LLMCoreTarget from "./target"
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { Effect } from "effect"
|
||||
import type { LLMError, TransportRequest } from "./schema"
|
||||
|
||||
export interface Transport {
|
||||
readonly fetch: (request: TransportRequest) => Effect.Effect<Response, LLMError>
|
||||
}
|
||||
|
||||
export * as LLMCoreTransport from "./transport"
|
||||
Reference in New Issue
Block a user