/** * module: llm —— 对外类型(编码规范 §10.1)。 * * 两条缝之一(§7.3):`ModelGateway` 支持 record | replay | live 三模式 —— * **任何模型调用不经 ModelGateway,回放审就对它失明**(§12.5#10)。 */ import type { LlmInstanceView, LlmProvider, TaskId } from '@contracts'; import type { ModelBinding, BindingViolation } from './internal/domain/model-binding.ts'; export type { LlmInstanceView, LlmProvider, ModelBinding, BindingViolation }; /** 一条缝的三个开关位 */ export const GATEWAY_MODES = ['live', 'record', 'replay'] as const; export type GatewayMode = (typeof GATEWAY_MODES)[number]; export class ModelNotBoundError extends Error { constructor( readonly modelName: string, readonly violations: readonly BindingViolation[], ) { super(`模型未绑定:${modelName}(${violations.map((v) => `${v.field}:${v.message}`).join('; ')})`); this.name = 'ModelNotBoundError'; } } export class ModelDisabledError extends Error { constructor(readonly modelName: string) { super(`模型实例已停用:${modelName}`); this.name = 'ModelDisabledError'; } } export class ReplayMissError extends Error { constructor(readonly key: string) { super(`回放缺录:${key} —— 回放模式下不得回退为实调(否则回放审失去意义)`); this.name = 'ReplayMissError'; } } /** 模型调用的语义入参 —— 录制的就是它,不含任何凭证(ADR-009 #5) */ export type ModelCall = { /** 活动声明的自定义模型名(逻辑名) */ readonly model: string; readonly system?: string; readonly prompt: string; readonly temperature?: number; readonly maxOutputTokens?: number; /** 出参 schema(JSON Schema);给定即要求结构化出参并校验 */ readonly outputSchema?: Record; /** 录制/回放的定位:任务 × 步序号(§7.3) */ readonly taskId?: TaskId; readonly stepNo?: number; /** 归因用:调用点标签(flow 步名 / loop 回合 / 系统用途) */ readonly label?: string; }; export type ModelUsage = { readonly inputTokens: number; readonly outputTokens: number; readonly latencyMs: number; }; export type ModelResult = { readonly text: string; /** outputSchema 给定时的解析结果 */ readonly object?: unknown; readonly usage: ModelUsage; /** * 推理模型的思考正文 —— **过程,不是结论**。 * 出参只认 `text` / `object`;这一段单独留着,供任务记忆归档与断点复盘, * 不参与 schema 校验、不进业务字段。 */ readonly reasoning?: string; /** 行为可归因:模型与版本记入留痕(OS 4.1) */ readonly binding: { readonly name: string; readonly provider: LlmProvider; readonly model: string }; readonly mode: GatewayMode; }; /** 解好的模型端点(四元 + 明文密钥)—— app 层装配专用,见 ModelGateway.resolve */ export type ResolvedEndpoint = { readonly provider: LlmProvider; readonly baseUrl: string; readonly model: string; readonly apiKey: string; }; /** * 模型网关 —— 活动基座六件里的「LLM 调用」。 * 活动只见自定义模型名;解析、凭证、录制回放全在此。 */ export type ModelGateway = { readonly mode: GatewayMode; /** * 解绑定:名 → 四元(含当场解出的密钥)—— **只给 app 层装配用**: * Mastra Agent 那类需要真模型对象做 tool-calling 往返的场合, * `generate` 的 text-in/text-out 抽象承载不了。 * 密钥与 endpoint 仍不入 domain、不入包、不入留痕明文(OS 7.3 凭证隔离)。 */ resolve(modelName: string): Promise; generate(call: ModelCall): Promise; /** 换模式的网关 —— 回放审用 replay 模式重跑引擎(§7.3) */ withMode(mode: GatewayMode): ModelGateway; }; export type UpsertLlmInstance = { readonly id?: string | undefined; readonly name: string; readonly provider: LlmProvider; /** 留空 = 取该 provider 的默认端点 */ readonly baseUrl: string; readonly model: string; /** 只写不读:留空表示保持原值 */ readonly apiKey?: string | undefined; readonly systemUse: boolean; readonly enabled: boolean; }; export type LlmService = { list(): Promise; upsert(input: UpsertLlmInstance): Promise; remove(id: string): Promise; /** 连通性探针 —— 治理台「测试连接」 */ probe(id: string): Promise<{ ok: boolean; latencyMs?: number; error?: string }>; /** * 草稿探针 —— **只测不存**:拿表单上正填的四元去试,一个字节都不落库。 * 密钥现填就用现填的,留空则取该实例已存的密文(改端点不必重打钥匙)。 */ probeDraft(draft: { id?: string | undefined; name?: string | undefined; provider: LlmProvider; baseUrl: string; model: string; apiKey?: string | undefined; }): Promise<{ ok: boolean; latencyMs?: number; error?: string }>; /** 安装静态审:核对活动声明的模型名已绑定(AM 11.3) */ assertNamesBound(names: readonly string[]): Promise<{ ok: boolean; missing: string[] }>; /** 系统用途模型 —— 叙述性小结 / 汇报生成 / 蒸馏钩子 */ systemUseBinding(): Promise; };