fix: harden model catalog projections
This commit is contained in:
@@ -268,6 +268,7 @@ export function buildApp(config: AppConfig, deps?: BuildAppDeps): FastifyInstanc
|
||||
);
|
||||
|
||||
const listModels = deps?.listModels ?? createPiModelLister(config, {
|
||||
modelCatalog: runtimeModelCatalog,
|
||||
warn: (detail) => app.log.warn(
|
||||
{ component: "pi-model-list", detail },
|
||||
"Pi enabled-model configuration warning",
|
||||
|
||||
@@ -3,16 +3,17 @@ import type { CatalogDatabase } from "../repository.js";
|
||||
|
||||
const canonicalModelPattern = "^[a-z][a-z0-9._-]{0,63}/[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$";
|
||||
const legacyModelPattern = "^[a-z][a-z0-9._-]{0,63}$";
|
||||
const historicalOrCanonicalModelPattern = `(${legacyModelPattern})|(${canonicalModelPattern})`;
|
||||
|
||||
export async function up(db: Kysely<CatalogDatabase>): Promise<void> {
|
||||
await sql.raw(`alter table description_generation_runs
|
||||
drop constraint description_generation_runs_model_id_check,
|
||||
add constraint description_generation_runs_model_id_check
|
||||
check (model_id ~ '${canonicalModelPattern}')`).execute(db);
|
||||
check (model_id ~ '${historicalOrCanonicalModelPattern}')`).execute(db);
|
||||
await sql.raw(`alter table sensitive_data_suggestion_runs
|
||||
drop constraint sensitive_data_suggestion_runs_model_id_check,
|
||||
add constraint sensitive_data_suggestion_runs_model_id_check
|
||||
check (model_id ~ '${canonicalModelPattern}')`).execute(db);
|
||||
check (model_id ~ '${historicalOrCanonicalModelPattern}')`).execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<CatalogDatabase>): Promise<void> {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
readConfiguredPiAgentFile,
|
||||
validateDeclarativePiConfig,
|
||||
} from "./managed-config.js";
|
||||
import type { RuntimeModelCatalog } from "../models/runtime-model-catalog.js";
|
||||
|
||||
export interface PiModel {
|
||||
provider: string;
|
||||
@@ -30,6 +31,7 @@ interface Opts {
|
||||
nowMs?: () => number;
|
||||
loadEnabledModels?: () => PiEnabledModelsResult;
|
||||
readModelsStore?: () => string | undefined;
|
||||
modelCatalog?: RuntimeModelCatalog;
|
||||
warn?: (message: string) => void;
|
||||
}
|
||||
|
||||
@@ -82,9 +84,23 @@ export function createPiModelLister(cfg: AppConfig, opts: Opts = {}): ListModels
|
||||
const byCompositeId = new Map(
|
||||
available.map((model) => [`${model.provider}/${model.id}`, model]),
|
||||
);
|
||||
const catalogByPiId = new Map(
|
||||
(opts.modelCatalog?.sessionModels() ?? []).map((model) => [
|
||||
`${model.provider}/${model.upstreamModel}`,
|
||||
model,
|
||||
]),
|
||||
);
|
||||
const models = enabled.ids.flatMap((id) => {
|
||||
const model = byCompositeId.get(id);
|
||||
return model ? [model] : [];
|
||||
if (!model) return [];
|
||||
const catalogModel = catalogByPiId.get(id);
|
||||
return [catalogModel ? {
|
||||
...model,
|
||||
provider: catalogModel.provider,
|
||||
id: catalogModel.model,
|
||||
name: catalogModel.label,
|
||||
reasoning: catalogModel.session?.reasoning ?? model.reasoning,
|
||||
} : model];
|
||||
});
|
||||
if (models.length === 0) opts.warn?.("No Pi-enabled models are currently available");
|
||||
cache = { at: now(), models };
|
||||
|
||||
@@ -260,8 +260,11 @@ export class PiProcessManager {
|
||||
const thinking = o.thinking ?? this.cfg.defaults.thinking;
|
||||
|
||||
if (provider && model) {
|
||||
const upstreamModel = this.modelCatalog.sessionModels()
|
||||
.find((entry) => entry.provider === provider && entry.model === model)
|
||||
?.upstreamModel ?? model;
|
||||
const response = await rt.rpc.request(
|
||||
{ type: "set_model", provider, modelId: model } as object & { type: string },
|
||||
{ type: "set_model", provider, modelId: upstreamModel } as object & { type: string },
|
||||
);
|
||||
rt.bridge.setContextWindow(response?.data?.contextWindow);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ export function createPiProviderSmoke(
|
||||
|| catalog.defaultSession !== null;
|
||||
const catalogModel = catalog.sessionModels()
|
||||
.find((entry) => entry.provider === canonicalProvider && entry.model === model);
|
||||
const upstreamModel = catalogModel?.upstreamModel ?? model;
|
||||
const credentialName = catalogModel?.authentication.mode === "secret_env"
|
||||
? catalogModel.authentication.apiKeyEnv
|
||||
: undefined;
|
||||
@@ -130,7 +131,7 @@ export function createPiProviderSmoke(
|
||||
const capabilityGuard = failOnUnexpectedCapabilities(rpc);
|
||||
const turn = async (): Promise<void> => {
|
||||
requireSuccessfulResponse(await rpc.request({
|
||||
type: "set_model", provider: canonicalProvider, modelId: model,
|
||||
type: "set_model", provider: canonicalProvider, modelId: upstreamModel,
|
||||
} as object & { type: string }));
|
||||
requireSuccessfulResponse(await rpc.request({
|
||||
type: "set_thinking_level", level: reasoning,
|
||||
|
||||
@@ -140,7 +140,7 @@ function buildEmbeddingConfig(rendered: Record<string, unknown>): CanonicalEmbed
|
||||
}
|
||||
const model = requireString(embeddings, "model");
|
||||
return {
|
||||
id: `ollama/${model}`,
|
||||
id: optionalString(embeddings, "id") ?? `ollama/${model}`,
|
||||
model,
|
||||
dimensions: requireNumber(embeddings, "dimensions"),
|
||||
};
|
||||
|
||||
@@ -274,6 +274,8 @@ export function renderRuntimeConfig(
|
||||
},
|
||||
embedding: {
|
||||
provider: "ollama_internal",
|
||||
id: semanticRuntime.internalEmbeddingId
|
||||
?? `ollama/${semanticRuntime.internalEmbeddingModel}`,
|
||||
model: semanticRuntime.internalEmbeddingModel,
|
||||
dimensions: semanticRuntime.internalEmbeddingDimensions,
|
||||
},
|
||||
@@ -287,6 +289,8 @@ export function renderRuntimeConfig(
|
||||
embeddings: {
|
||||
provider: "ollama_internal",
|
||||
base_url: semanticRuntime.internalEmbeddingUrl,
|
||||
id: semanticRuntime.internalEmbeddingId
|
||||
?? `ollama/${semanticRuntime.internalEmbeddingModel}`,
|
||||
model: semanticRuntime.internalEmbeddingModel,
|
||||
dimensions: semanticRuntime.internalEmbeddingDimensions,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user