From 2ecdb2406a179c30b02a3172284cae0e921a8b74 Mon Sep 17 00:00:00 2001 From: Dominic Nguyen Date: Tue, 29 Sep 2026 12:59:10 -0700 Subject: [PATCH] Read embedding settings from runtime config; warn on invalid ints --- ts/packages/aiclient/src/embeddingProvider.ts | 69 ++++++------------- ts/packages/aiclient/src/openai.ts | 9 +++ ts/packages/config/src/runtime/build.ts | 6 +- ts/packages/knowPro/src/conversation.ts | 7 +- 4 files changed, 38 insertions(+), 53 deletions(-) diff --git a/ts/packages/aiclient/src/embeddingProvider.ts b/ts/packages/aiclient/src/embeddingProvider.ts index ac974402d2..1606507e80 100644 --- a/ts/packages/aiclient/src/embeddingProvider.ts +++ b/ts/packages/aiclient/src/embeddingProvider.ts @@ -5,6 +5,7 @@ import { TextEmbeddingModel } from "./models.js"; import { createEmbeddingModel } from "./openai.js"; import { EnvVars } from "./apiTypes.js"; import { createLocalEmbeddingModel } from "./localEmbedding.js"; +import { getRuntimeConfig } from "./runtimeConfig.js"; import { createCopilotEmbeddingModel, DefaultCopilotEmbeddingModel, @@ -23,60 +24,40 @@ export type EmbeddingProvider = | "copilot" | "none"; -// Flattened form of the config `embedding:` section. -enum EmbeddingEnvVars { - PROVIDER = "TYPEAGENT_EMBEDDING_PROVIDER", - MODEL = "TYPEAGENT_EMBEDDING_MODEL", - CACHE_DIR = "TYPEAGENT_EMBEDDING_CACHE_DIR", - SIZE = "TYPEAGENT_EMBEDDING_SIZE", - MAX_BATCH_SIZE = "TYPEAGENT_EMBEDDING_MAX_BATCH_SIZE", -} - // Embedding sizes of well-known defaults, used when no size is configured. const LocalDefaultEmbeddingSize = 384; // Xenova/all-MiniLM-L6-v2 const HostedDefaultEmbeddingSize = 1536; // ada-002 / text-embedding-3-small -function readPositiveInt(name: string): number | undefined { - const raw = process.env[name]?.trim(); - if (!raw) return undefined; - const n = Number(raw); - return Number.isInteger(n) && n > 0 ? n : undefined; +// The `embedding:` section of the runtime config. +function getEmbeddingConfig() { + return getRuntimeConfig().embedding; } /** * The embedding vector size for the configured provider. An explicit - * `embedding.size` (`TYPEAGENT_EMBEDDING_SIZE`) always wins; otherwise the - * default model's size is used (set `size` when using a non-default model) (384 for the local MiniLM model, 1536 for - * hosted endpoints). Configuration only; never loads a model. + * `embedding.size` always wins; otherwise the default model's size is used: + * `LocalDefaultEmbeddingSize` for the local provider, + * `HostedDefaultEmbeddingSize` for hosted endpoints. Set `size` when using a + * non-default model. Configuration only; never loads a model. */ export function getEmbeddingSize(): number { - const configured = readPositiveInt(EmbeddingEnvVars.SIZE); + const configured = getEmbeddingConfig()?.size; if (configured !== undefined) return configured; return getEmbeddingProvider() === "local" ? LocalDefaultEmbeddingSize : HostedDefaultEmbeddingSize; } -function isEmbeddingProvider(value: string): value is EmbeddingProvider { - return ( - value === "local" || - value === "openai" || - value === "azure" || - value === "copilot" || - value === "none" - ); -} - /** * Determine the configured embedding provider using configuration only * (no network access, no model loading). An explicit - * `TYPEAGENT_EMBEDDING_PROVIDER` always wins; otherwise the provider is + * `embedding.provider` always wins; otherwise the provider is * inferred from the presence of hosted embedding endpoints, defaulting to * "none" when nothing is configured. */ export function getEmbeddingProvider(): EmbeddingProvider { - const explicit = process.env[EmbeddingEnvVars.PROVIDER]?.trim(); - if (explicit && isEmbeddingProvider(explicit)) { + const explicit = getEmbeddingConfig()?.provider; + if (explicit !== undefined) { return explicit; } if ( @@ -113,37 +94,31 @@ export function tryCreateEmbeddingModel( dimensions?: number, ): TextEmbeddingModel | undefined { const provider = getEmbeddingProvider(); + const config = getEmbeddingConfig(); switch (provider) { case "none": return undefined; case "local": return createLocalEmbeddingModel({ - model: process.env[EmbeddingEnvVars.MODEL]?.trim() || undefined, - cacheDir: - process.env[EmbeddingEnvVars.CACHE_DIR]?.trim() || - undefined, - maxBatchSize: readPositiveInt(EmbeddingEnvVars.MAX_BATCH_SIZE), + model: config?.model, + cacheDir: config?.cacheDir, + maxBatchSize: config?.maxBatchSize, }); case "copilot": return createCopilotEmbeddingModel( - process.env[EmbeddingEnvVars.MODEL]?.trim() || - DefaultCopilotEmbeddingModel, + config?.model ?? DefaultCopilotEmbeddingModel, undefined, undefined, { - dimensions: - dimensions ?? readPositiveInt(EmbeddingEnvVars.SIZE), - maxBatchSize: readPositiveInt( - EmbeddingEnvVars.MAX_BATCH_SIZE, - ), + dimensions: dimensions ?? config?.size, + maxBatchSize: config?.maxBatchSize, }, ); default: { - dimensions ??= readPositiveInt(EmbeddingEnvVars.SIZE); + dimensions ??= config?.size; const options = { - modelName: - process.env[EmbeddingEnvVars.MODEL]?.trim() || undefined, - maxBatchSize: readPositiveInt(EmbeddingEnvVars.MAX_BATCH_SIZE), + modelName: config?.model, + maxBatchSize: config?.maxBatchSize, }; return endpoint !== undefined ? createEmbeddingModel(endpoint, dimensions, options) diff --git a/ts/packages/aiclient/src/openai.ts b/ts/packages/aiclient/src/openai.ts index 2f025039a2..de7d550bf0 100644 --- a/ts/packages/aiclient/src/openai.ts +++ b/ts/packages/aiclient/src/openai.ts @@ -877,6 +877,15 @@ export function createEmbeddingModel( // https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-input const maxBatchSize = Math.min(options?.maxBatchSize ?? 2048, 2048); + // Trace config overrides of the pool's settings for troubleshooting. + if ( + options?.modelName !== undefined || + options?.maxBatchSize !== undefined + ) { + debugOpenAI( + `Embedding overrides for ${pool.modelKey}: model=${options.modelName ?? settings.modelName}, maxBatchSize=${maxBatchSize}`, + ); + } const defaultParams: any = settings.provider === "azure" ? {} diff --git a/ts/packages/config/src/runtime/build.ts b/ts/packages/config/src/runtime/build.ts index 3c76c6c6f3..777b29df59 100644 --- a/ts/packages/config/src/runtime/build.ts +++ b/ts/packages/config/src/runtime/build.ts @@ -1006,7 +1006,8 @@ function buildEmbedding( }; } -// Pop a positive integer; malformed values are left in extra untouched. +// Pop a positive integer; malformed values are left in extra untouched +// and reported, so a typo (e.g. `size: -1`) does not silently fall back. function popPositiveInt( flat: Map, key: string, @@ -1015,6 +1016,9 @@ function popPositiveInt( if (raw === undefined) return undefined; const n = Number(raw); if (Number.isInteger(n) && n > 0) return n; + console.warn( + `${key}: expected a positive integer, got "${raw}"; ignoring.`, + ); flat.set(key, raw); return undefined; } diff --git a/ts/packages/knowPro/src/conversation.ts b/ts/packages/knowPro/src/conversation.ts index d61d90fc20..1ef425ad51 100644 --- a/ts/packages/knowPro/src/conversation.ts +++ b/ts/packages/knowPro/src/conversation.ts @@ -32,11 +32,8 @@ export function createConversationSettings( // May be undefined when no embedding provider is configured (e.g. Copilot // self-host without a local embedder). Embedding-backed indexes then no-op // and search degrades to exact/alias/edit-distance matching. - if (embeddingModel === undefined) { - embeddingModel = tryCreateEmbeddingModel(); - embeddingSize ??= getEmbeddingSize(); - } - embeddingSize ??= 1536; + embeddingModel ??= tryCreateEmbeddingModel(); + embeddingSize ??= getEmbeddingSize(); if (embeddingModel === undefined && !embeddingUnavailableAnnounced) { embeddingUnavailableAnnounced = true; console.warn(