How to Configure Vane's LLM Providers and Models: A Complete Guide
Vane configures LLM providers through a three-layer architecture where you declare configuration fields in TypeScript, persist instances via REST API or environment variables to config.json, and load models through the ModelRegistry class.
Vane is an open-source AI search engine that abstracts every LLM service as a configurable provider. Whether you need to connect OpenAI, Ollama, or Gemini, understanding how to configure Vane's LLM providers and models is essential for customizing your search experience.
Understanding Vane's Provider Architecture
Vane treats every LLM service as a provider supplying two model types:
- Chat models – used for generating text (e.g.,
gpt-4,gemini-1.5-pro). - Embedding models – used for vector-embedding generation (e.g.,
text-embedding-3-small).
The configuration flow consists of three layers:
| Layer | Responsibility | Core files |
|---|---|---|
| UI / Environment | Declares which fields a provider needs (API key, base URL, etc.) and maps them to environment variables. | src/lib/config/types.ts, src/lib/config/index.ts, src/lib/models/providers/index.ts |
| Runtime Registry | Reads the persisted config.json, builds concrete provider instances, and exposes the list of active providers + their model catalogs. | src/lib/models/registry.ts, src/lib/models/base/provider.ts, src/lib/config/serverRegistry.ts |
| API / Consumer | Public HTTP endpoints let the front-end (or scripts) add, update, or delete providers; the app then loads the requested chat or embedding model. | src/app/api/providers/route.ts, src/app/api/providers/[id]/models/route.ts |
Declaring Provider Configuration Fields
Each provider ships a static getProviderConfigFields() method that returns an array of UIConfigField definitions. For OpenAI, this lives in src/lib/models/providers/openai/index.ts:
const providerConfigFields: UIConfigField[] = [
{
type: 'password',
name: 'API Key',
key: 'apiKey',
description: 'Your OpenAI API key',
required: true,
env: 'OPENAI_API_KEY', // ← linked to an env var
scope: 'server',
},
{
type: 'string',
name: 'Base URL',
key: 'baseURL',
description: 'The base URL for the OpenAI API',
required: true,
default: 'https://api.openai.com/v1',
env: 'OPENAI_BASE_URL',
scope: 'server',
},
];
When Vane boots, ConfigManager in src/lib/config/index.ts reads all providers via getModelProvidersUIConfigSection() and automatically builds UI sections for the Settings page. The UI fields are stored in ConfigManager.uiConfigSections.modelProviders, later used when populating the Add Provider dialog.
Persisting Provider Instances
Vane stores providers in a JSON file (data/config.json, default path built in ConfigManager.configPath). The model provider schema lives in src/lib/config/types.ts:
type ConfigModelProvider = {
id: string;
name: string;
type: string; // e.g. "openai", "ollama", …
chatModels: Model[];
embeddingModels: Model[];
config: { [key: string]: any };
hash: string;
};
When environment variables satisfy every required field, ConfigManager.initializeFromEnv() auto-creates a provider entry (hash-deduped) and writes it to the file.
Manual addition is done via the HTTP POST /api/providers endpoint in src/app/api/providers/route.ts. The request body must contain:
{
"type": "openai",
"name": "My OpenAI",
"config": {
"apiKey": "sk-xxxxxxxxxxxx",
"baseURL": "https://api.openai.com/v1"
}
}
The route calls ModelRegistry.addProvider(), which:
- Persists the provider via
configManager.addModelProvider(). - Instantiates the concrete provider (
createProviderInstancefromsrc/lib/models/base/provider.ts). - Queries the provider for its model list and returns the enriched provider object.
Building the Active Provider Registry
ModelRegistry in src/lib/models/registry.ts is the runtime façade:
export default class ModelRegistry {
activeProviders = []; // populated in the constructor
constructor() {
this.initializeActiveProviders();
}
// … (methods for getActiveProviders, loadChatModel, loadEmbeddingModel)
}
During construction it reads configured providers (getConfiguredModelProviders() from src/lib/config/serverRegistry.ts) and creates an instance for each:
const provider = providers[p.type]; // providers map from src/lib/models/providers/index.ts
this.activeProviders.push({
...p,
provider: createProviderInstance(provider, p.id, p.name, p.config),
});
The registry exposes:
getActiveProviders()– returns an array of providers with their chatModels and embeddingModels (used by the UI to display available models).loadChatModel(providerId, modelName)– returns a concreteBaseLLM(e.g.,OpenAILLM).loadEmbeddingModel(providerId, modelName)– returns a concreteBaseEmbedding.
Loading Specific Chat and Embedding Models
Chat model (LLM)
import ModelRegistry from '@/lib/models/registry';
async function getChatModel() {
const registry = new ModelRegistry();
// Suppose we want "gpt-4" from the OpenAI provider we added earlier
const llm = await registry.loadChatModel('provider-uuid-here', 'gpt-4');
// `llm` is an instance of OpenAILLM (src/lib/models/providers/openai/openaiLLM.ts)
const response = await llm.generateText({
messages: [{ role: 'user', content: 'Explain quantum entanglement' }],
});
console.log(response.content);
}
OpenAILLM implements generateText, streamText, generateObject, etc., forwarding the call to the official OpenAI SDK.
Embedding model
async function getEmbeddingModel() {
const registry = new ModelRegistry();
const embedder = await registry.loadEmbeddingModel(
'provider-uuid-here',
'text-embedding-3-small',
);
const vectors = await embedder.embedText({ text: 'Hello world' });
console.log(vectors);
}
OpenAIEmbedding (found in src/lib/models/providers/openai/openaiEmbedding.ts) wraps the /embeddings endpoint.
Updating and Removing Providers
- PATCH /api/providers/[id] follows the same pattern as POST and triggers
ModelRegistry.updateProvider(). - DELETE /api/providers/[id] triggers
ModelRegistry.removeProvider(), which updatesconfig.jsonand the in-memory list.
Complete Configuration Example
Here is the full workflow from environment setup to generating a response:
# 1️⃣ Set env vars (optional – Vane reads them on startup)
export OPENAI_API_KEY=sk-xxxxxxxxxxxx
export OPENAI_BASE_URL=https://api.openai.com/v1
// 2️⃣ Add the provider via the REST API (client-side)
await fetch('/api/providers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'openai',
name: 'My OpenAI',
config: {
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL,
},
}),
});
// 3️⃣ Load the GPT-4 chat model and generate a response
import ModelRegistry from '@/lib/models/registry';
(async () => {
const registry = new ModelRegistry();
const providers = await registry.getActiveProviders(); // see UI list
const openAi = providers.find(p => p.name === 'My OpenAI');
if (!openAi) throw new Error('Provider not found');
const llm = await registry.loadChatModel(openAi.id, 'gpt-4');
const resp = await llm.generateText({
messages: [{ role: 'user', content: 'Summarize the plot of *Inception*.' }],
});
console.log('LLM answer:', resp.content);
})();
The same pattern works for embedding models or for any other provider (Ollama, Gemini, Groq, etc.) – just replace the "type" and model keys.
Summary
- Three-layer architecture separates UI configuration, runtime registry, and API consumption, making Vane extensible without core code changes.
- Configuration fields are declared via
getProviderConfigFields()in provider implementations and automatically map to environment variables likeOPENAI_API_KEY. - ModelRegistry in
src/lib/models/registry.tsmanages active provider instances and exposesloadChatModel()andloadEmbeddingModel()for runtime model resolution. - Persistence happens in
data/config.jsonviaConfigManager, with REST endpoints at/api/providersenabling dynamic provider management.
Frequently Asked Questions
How do I add a new LLM provider to Vane without restarting the application?
Send a POST request to /api/providers with the provider type and configuration object. The ModelRegistry.addProvider() method in src/lib/models/registry.ts dynamically instantiates the provider, queries its model list, and persists it to config.json without requiring a server restart.
What environment variables does Vane use for OpenAI configuration?
Vane recognizes OPENAI_API_KEY and OPENAI_BASE_URL as defined in the env property of the UIConfigField array within src/lib/models/providers/openai/index.ts. When these variables are present, ConfigManager.initializeFromEnv() automatically creates a provider entry on startup.
How does Vane handle embedding models differently from chat models?
While both are managed through the same provider configuration in src/lib/config/types.ts, they are instantiated separately at runtime. The ModelRegistry class exposes loadChatModel() which returns a BaseLLM implementation (like OpenAILLM), while loadEmbeddingModel() returns a BaseEmbedding implementation (like OpenAIEmbedding), each wrapping distinct API endpoints.
Where does Vane store provider configuration persistently?
Provider configurations are stored in data/config.json (path defined in ConfigManager.configPath) as an array of ConfigModelProvider objects. This schema includes the provider id, type, chatModels, embeddingModels, and encrypted config values, enabling the ModelRegistry to rebuild active provider instances across server restarts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →