Download server/src/lib/served-model.ts from automindy/fokemal: direct link, hf CLI and curl.
- Browser
- Download file 4.22 kB
-
https://huggingface.co/spaces/automindy/fokemal/resolve/main/server/src/lib/served-model.ts
- Command line
-
hf download hf://spaces/automindy/fokemal/server/src/lib/served-model.ts
-
curl -L -o served-model.ts https://huggingface.co/spaces/automindy/fokemal/resolve/main/server/src/lib/served-model.ts
4.22 kB
| // Served-model drift guard (#534 follow-up). | |
| // | |
| // The gateway deliberately overwrites the upstream `model` field with the | |
| // routed model id before the caller sees it β the public contract exposes the | |
| // model that was ROUTED, because provider metadata is unreliable (Reka returns | |
| // the literal placeholder "default" for concrete models, #568). That overwrite | |
| // also destroys the only evidence when a provider silently serves a DIFFERENT | |
| // model than requested (verified live: kilo-auto/small serving Gemma; | |
| // kilo-auto/free already drifted once). This module receives the raw upstream | |
| // value β captured at the proxy boundary BEFORE the overwrite β compares it | |
| // against the routed id ignoring cosmetic spelling differences, and reports | |
| // genuine drift two ways: | |
| // - a structured `[ServedModel]` warn log, once per (platform, requested, | |
| // served) tuple per process, so a busy drifted route cannot spam the log; | |
| // - the raw served id for persistence in requests.served_model (NULL when | |
| // it matches, which keeps the column empty in the healthy case). | |
| // The response contract is untouched: callers keep seeing the routed model. | |
| // Cosmetic differences a provider may legitimately introduce when echoing the | |
| // model id back. Anything that survives normalization on BOTH sides and still | |
| // differs is treated as drift. | |
| // 1. case-insensitive; | |
| // 2. one trailing pricing/routing tier tag (":free"-style β OpenRouter and | |
| // friends: :free, :beta, :extended, :nitro, :standard); | |
| // 3. provider/org namespacing β only the last '/'-segment identifies the | |
| // model ("meta-llama/llama-3.3-70b" echoes "llama-3.3-70b"); | |
| // 4. separator spelling β runs of whitespace/underscores/hyphens are one | |
| // separator (mirrors services/model-groups.ts normalizeGroupKey, which | |
| // applies the same rule to display names). | |
| export function normalizeModelIdForDrift(id: string): string { | |
| let s = (id ?? '').trim().toLowerCase(); | |
| s = s.replace(/:(free|beta|extended|nitro|standard)$/, ''); | |
| const lastSlash = s.lastIndexOf('/'); | |
| if (lastSlash >= 0) s = s.slice(lastSlash + 1); | |
| return s.replace(/[\s_-]+/g, '-').trim(); | |
| } | |
| // Upstream values that name no model at all β comparing them proves nothing. | |
| // "default" is Reka's documented placeholder; empty/unknown guard against | |
| // sloppy OpenAI-compatible shims. | |
| const PLACEHOLDER_MODEL_VALUES = new Set(['', 'default', 'unknown']); | |
| // Once-per-tuple warn cache. Bounded: a hostile/broken upstream that invents a | |
| // fresh id per response must not grow this set without limit β past the cap we | |
| // keep persisting drift but stop adding (and therefore logging) new tuples. | |
| const MAX_OBSERVED_TUPLES = 1000; | |
| const observedTuples = new Set<string>(); | |
| /** Test hook: clear the once-per-tuple warn cache. */ | |
| export function resetServedModelObservations(): void { | |
| observedTuples.clear(); | |
| } | |
| /** | |
| * Compare the raw upstream-reported model against the routed id. | |
| * Returns the raw served id when it GENUINELY differs (persist it), or null | |
| * when it matches, differs only cosmetically, or names no model at all | |
| * (store NULL β keeps the requests table small). Logs a structured warn once | |
| * per (platform, requested, served) tuple per process. | |
| */ | |
| export function observeServedModel(opts: { | |
| platform: string; | |
| requestedModel: string; | |
| servedModel: string | null | undefined; | |
| }): string | null { | |
| const { platform, requestedModel, servedModel } = opts; | |
| if (typeof servedModel !== 'string') return null; | |
| const served = servedModel.trim(); | |
| if (PLACEHOLDER_MODEL_VALUES.has(served.toLowerCase())) return null; | |
| if (normalizeModelIdForDrift(requestedModel) === normalizeModelIdForDrift(served)) return null; | |
| const tuple = `${platform} |