Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 22 additions & 47 deletions ts/packages/aiclient/src/embeddingProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -23,60 +24,40 @@ export type EmbeddingProvider =
| "copilot"
| "none";

// Flattened form of the config `embedding:` section.
enum EmbeddingEnvVars {
Comment thread
hillary-mutisya marked this conversation as resolved.
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 (
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions ts/packages/aiclient/src/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
? {}
Expand Down
6 changes: 5 additions & 1 deletion ts/packages/config/src/runtime/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>,
key: string,
Expand All @@ -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;
}
Expand Down
7 changes: 2 additions & 5 deletions ts/packages/knowPro/src/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading