58 lines
2.7 KiB
TypeScript
58 lines
2.7 KiB
TypeScript
/**
|
||
* Deterministic capability-aware model selection — the portable subset of
|
||
* OpenHuman's model routing (docs/analysis/openhuman-adoption-2026-06-28.md §3.B).
|
||
*
|
||
* Scope is deliberately NARROW (§3.2): a fixed allowlist of KNOWN-lightweight
|
||
* internal calls (compaction summary, classification, short extraction) is
|
||
* routed to the cheapest ready model — Haiku on the built-in Anthropic proxy by
|
||
* default. There is NO task-complexity classifier (that would need its own
|
||
* cost-bearing model and risks mis-routing real reasoning); call sites declare
|
||
* `class: 'lightweight'` a priori, so routing stays deterministic.
|
||
*
|
||
* The universal win is Haiku-on-proxy: ~10–12× cheaper than Sonnet and it
|
||
* materializes for a vanilla FREE user with no local model, protecting the
|
||
* Waggle-funded built-in proxy. A `privacyRequired` call is NEVER downgraded to
|
||
* a cloud budget model — it keeps the caller's model (or a provided on-device
|
||
* model), so "sensitive work stays on-device" can never silently leak to cloud.
|
||
*/
|
||
|
||
export type ModelClass = 'lightweight' | 'reasoning' | 'general';
|
||
|
||
export interface ModelClassOpts {
|
||
/** Declared workload class for this call (named `class` to match LlmCallInput). */
|
||
class?: ModelClass;
|
||
/** When true, never route to a cloud budget model; keep on-device. */
|
||
privacyRequired?: boolean;
|
||
/** Cheap model for lightweight internal calls (default applied by the caller). */
|
||
lightweightModel?: string;
|
||
/** A ready on-device model id (e.g. 'ollama/llama3.1'), when one is configured. */
|
||
localModel?: string;
|
||
}
|
||
|
||
/** The default cheap model for lightweight internal calls on the built-in proxy. */
|
||
export const LIGHTWEIGHT_MODEL = 'claude-haiku-4-5';
|
||
|
||
/**
|
||
* Resolve the effective model for a known internal call. Pure + synchronous.
|
||
*
|
||
* - privacyRequired → the on-device model; FAILS CLOSED (throws) when none is
|
||
* configured, rather than silently falling back to cloud.
|
||
* - class lightweight → the cheap model, when one is supplied.
|
||
* - otherwise → unchanged.
|
||
*
|
||
* Callers that may pass `privacyRequired` must handle the throw (e.g. return an
|
||
* error result without making a network call).
|
||
*/
|
||
export function resolveModelForClass(requestedModel: string, opts: ModelClassOpts = {}): string {
|
||
if (opts.privacyRequired) {
|
||
if (!opts.localModel) {
|
||
throw new Error('privacyRequired: no on-device model is configured — refusing cloud fallback');
|
||
}
|
||
return opts.localModel;
|
||
}
|
||
if (opts.class === 'lightweight' && opts.lightweightModel) return opts.lightweightModel;
|
||
// 'reasoning' and 'general' are passthrough today; 'reasoning' is reserved for a
|
||
// future frontier/extended-thinking upsell once telemetry justifies the cost.
|
||
return requestedModel;
|
||
}
|