Skip to content
Open
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
67 changes: 56 additions & 11 deletions src/LLMProviders/embeddingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import { CohereEmbeddings } from "@langchain/cohere";
import { Embeddings } from "@langchain/core/embeddings";
import { GoogleGenerativeAIEmbeddings } from "@langchain/google-genai";
import { OllamaEmbeddings } from "@langchain/ollama";
import { AzureOpenAIEmbeddings, OpenAIEmbeddings } from "@langchain/openai";
import { OpenAIEmbeddings } from "@langchain/openai";
import { Notice } from "obsidian";
import { BrevilabsClient } from "./brevilabsClient";
import { CustomJinaEmbeddings } from "./CustomJinaEmbeddings";
import { CustomOpenAIEmbeddings } from "./CustomOpenAIEmbeddings";
import { normalizeAzureUrl } from "./chatModelManager";

type EmbeddingConstructorType = new (config: any) => Embeddings;

Expand All @@ -23,7 +24,7 @@ const EMBEDDING_PROVIDER_CONSTRUCTORS = {
[EmbeddingModelProviders.OPENAI]: OpenAIEmbeddings,
[EmbeddingModelProviders.COHEREAI]: CohereEmbeddings,
[EmbeddingModelProviders.GOOGLE]: GoogleGenerativeAIEmbeddings,
[EmbeddingModelProviders.AZURE_OPENAI]: AzureOpenAIEmbeddings,
[EmbeddingModelProviders.AZURE_OPENAI]: OpenAIEmbeddings,
[EmbeddingModelProviders.OLLAMA]: OllamaEmbeddings,
[EmbeddingModelProviders.LM_STUDIO]: CustomOpenAIEmbeddings,
[EmbeddingModelProviders.OPENAI_FORMAT]: OpenAIEmbeddings,
Expand Down Expand Up @@ -248,16 +249,60 @@ export default class EmbeddingManager {
modelName: modelName,
apiKey: await getDecryptedKey(settings.googleApiKey),
},
[EmbeddingModelProviders.AZURE_OPENAI]: {
modelName,
azureOpenAIApiKey: await getDecryptedKey(customModel.apiKey || settings.azureOpenAIApiKey),
azureOpenAIApiInstanceName:
customModel.azureOpenAIApiInstanceName || settings.azureOpenAIApiInstanceName,
azureOpenAIApiDeploymentName:
[EmbeddingModelProviders.AZURE_OPENAI]: await (async () => {
const decryptedApiKey = await getDecryptedKey(
customModel.apiKey || settings.azureOpenAIApiKey
);

const hasCustomBaseUrl = Boolean(customModel.baseUrl?.trim());
const apiVersion = customModel.azureOpenAIApiVersion || settings.azureOpenAIApiVersion;

if (hasCustomBaseUrl) {
const azureUrl = normalizeAzureUrl(customModel.baseUrl!.trim());

return {
modelName,
apiKey: decryptedApiKey,
timeout: 10000,
batchSize: getSettings().embeddingBatchSize,
configuration: {
baseURL: azureUrl.baseUrl || customModel.baseUrl!.trim(),
defaultQuery: {
"api-version": azureUrl.apiVersion || apiVersion,
},
fetch: customModel.enableCors ? safeFetch : undefined,
},
} as ExtendedConfig<
ConstructorParameters<
EmbeddingProviderConstructorMap[typeof EmbeddingModelProviders.AZURE_OPENAI]
>[0]
>;
}

const instanceName =
customModel.azureOpenAIApiInstanceName || settings.azureOpenAIApiInstanceName;
const deploymentName =
customModel.azureOpenAIApiEmbeddingDeploymentName ||
settings.azureOpenAIApiEmbeddingDeploymentName,
azureOpenAIApiVersion: customModel.azureOpenAIApiVersion || settings.azureOpenAIApiVersion,
},
settings.azureOpenAIApiEmbeddingDeploymentName;

return {
modelName,
apiKey: decryptedApiKey,
timeout: 10000,
batchSize: getSettings().embeddingBatchSize,
configuration: {
baseURL: `https://${instanceName}.openai.azure.com/openai/deployments/${deploymentName}`,
defaultQuery: {
"api-version": apiVersion,
},
fetch: customModel.enableCors ? safeFetch : undefined,
},
} as ExtendedConfig<
ConstructorParameters<
EmbeddingProviderConstructorMap[typeof EmbeddingModelProviders.AZURE_OPENAI]
>[0]
>;
})(),
[EmbeddingModelProviders.OLLAMA]: {
baseUrl: customModel.baseUrl || "http://localhost:11434",
model: modelName,
Expand Down
31 changes: 14 additions & 17 deletions src/settings/v2/components/ModelAddDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
if (!model.name) isValid = false;

// Validate Azure OpenAI specific fields.
// Embedding models always require the legacy fields because EmbeddingManager
// does not consume baseUrl and still reads azureOpenAIApiInstanceName,
// azureOpenAIApiEmbeddingDeploymentName, and azureOpenAIApiVersion directly.
// Chat models may skip legacy fields when a full base URL is supplied instead.
// When a Base URL is provided, both chat and embedding models can
// skip the legacy Azure fields and rely on the normalized endpoint.
// When Base URL is empty, fall back to legacy instance/deployment/version
// fields for backward compatibility.
const isAzure = model.provider === ChatModelProviders.AZURE_OPENAI;
const azureRequiresLegacyFields = isAzure && (isEmbeddingModel || !model.baseUrl?.trim());
const azureRequiresLegacyFields = isAzure && !model.baseUrl?.trim();
if (azureRequiresLegacyFields) {
newErrors.instanceName = !model.azureOpenAIApiInstanceName;
newErrors.apiVersion = !model.azureOpenAIApiVersion;
Expand Down Expand Up @@ -340,10 +340,10 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
</FormField>
);
case ChatModelProviders.AZURE_OPENAI:
// Chat models with a base URL use the new flow and skip legacy fields.
// Embedding models always require legacy fields since EmbeddingManager
// reads them directly and does not consume baseUrl.
if (model.baseUrl?.trim() && !isEmbeddingModel) return null;
// Chat and embedding models with a Base URL use the new flow
// and skip legacy fields. When Base URL is empty, fall back to
// legacy instance/deployment/version inputs.
if (model.baseUrl?.trim()) return null;
return (
<>
<FormField
Expand Down Expand Up @@ -500,14 +500,11 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
return providerInfo.host;
}

const instanceName = model.azureOpenAIApiInstanceName || "[instance]";
const deploymentName = isEmbeddingModel
? model.azureOpenAIApiEmbeddingDeploymentName || "[deployment]"
: model.azureOpenAIApiDeploymentName || "[deployment]";
const apiVersion = model.azureOpenAIApiVersion || "[api-version]";
const endpoint = isEmbeddingModel ? "embeddings" : "chat/completions";

return `https://${instanceName}.openai.azure.com/openai/deployments/${deploymentName}/${endpoint}?api-version=${apiVersion}`;
// For Azure, encourage users to paste either the full endpoint URL
// (including /embeddings or /chat/completions and api-version) or
// the deployment base URL. Legacy instance/deployment/version fields
// are still available below when Base URL is empty.
return "https://[proxy]/deployments/[model]/embeddings?api-version=[version]";
};

const capabilityOptions = Object.entries(MODEL_CAPABILITIES).map(([id, description]) => ({
Expand Down
Loading