# How Dexter Supports Multiple LLM Providers: OpenAI, Anthropic, and Google

> Dexter seamlessly integrates with OpenAI, Anthropic, and Google LLM providers. Discover how Dexter's provider registry and prefix-based resolution simplify multi-provider LLM access for your applications.

- Repository: [Virat Singh/dexter](https://github.com/virattt/dexter)
- Tags: deep-dive
- Published: 2026-02-16

---

**Dexter supports multiple LLM providers through a provider registry, model factories, and prefix-based resolution that automatically routes requests to OpenAI, Anthropic, Google, or Ollama based on the model name.**

The `virattt/dexter` repository implements a clean abstraction layer that lets you switch between LLM providers without changing your application code. By leveraging a centralized registry in [`src/providers.ts`](https://github.com/virattt/dexter/blob/main/src/providers.ts) and factory methods in [`src/model/llm.ts`](https://github.com/virattt/dexter/blob/main/src/model/llm.ts), Dexter resolves the correct provider at runtime based on model name prefixes like `claude-`, `gemini-`, or `gpt-`.

## The Three-Pillar Architecture

Dexter’s multi-provider support rests on three tightly-coupled components that handle declaration, instantiation, and routing.

### Provider Registry

The registry in [`src/providers.ts`](https://github.com/virattt/dexter/blob/main/src/providers.ts) declares every supported provider with metadata required for routing and configuration. Each entry includes the provider ID, display name, model prefix, environment variable for the API key, and a designated fast model for quick tasks.

```typescript
// src/providers.ts
export interface ProviderDef {
  id: string;
  displayName: string;
  modelPrefix: string;
  envVar: string;
  fastModel: string;
}

export const PROVIDERS: ProviderDef[] = [
  {
    id: 'anthropic',
    displayName: 'Anthropic',
    modelPrefix: 'claude-',
    envVar: 'ANTHROPIC_API_KEY',
    fastModel: 'claude-haiku-4-5'
  },
  {
    id: 'google',
    displayName: 'Google',
    modelPrefix: 'gemini-',
    envVar: 'GOOGLE_API_KEY',
    fastModel: 'gemini-1.5-flash'
  },
  // ... OpenAI, Ollama entries
];

```

### Model Factories

The factory pattern in [`src/model/llm.ts`](https://github.com/virattt/dexter/blob/main/src/model/llm.ts) maps each provider ID to a LangChain chat class constructor. When `getChatModel()` receives a provider ID, it selects the appropriate factory, instantiates the class (e.g., `ChatAnthropic`, `ChatGoogleGenerativeAI`, `ChatOpenAI`), and injects the API key from the corresponding environment variable.

```typescript
// src/model/llm.ts
import { ChatAnthropic } from '@langchain/anthropic';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ChatOpenAI } from '@langchain/openai';
import { ChatOllama } from '@langchain/community/chat_models/ollama';

const MODEL_FACTORIES: Record<string, (apiKey: string, model: string) => BaseChatModel> = {
  anthropic: (key, model) => new ChatAnthropic({ apiKey: key, modelName: model }),
  google: (key, model) => new ChatGoogleGenerativeAI({ apiKey: key, model: model }),
  openai: (key, model) => new ChatOpenAI({ apiKey: key, modelName: model }),
  ollama: (key, model) => new ChatOllama({ baseUrl: process.env.OLLAMA_BASE_URL, model: model }),
};

```

### Resolution and Dispatch

The `resolveProvider()` function in [`src/providers.ts`](https://github.com/virattt/dexter/blob/main/src/providers.ts) determines which provider to use by matching the model name against the `modelPrefix` of each registered provider. If no prefix matches, it defaults to OpenAI. The `getChatModel()` function in [`src/model/llm.ts`](https://github.com/virattt/dexter/blob/main/src/model/llm.ts) then uses this provider ID to select the correct factory and return a configured `BaseChatModel` instance.

```typescript
// src/providers.ts
export function resolveProvider(model: string): ProviderDef {
  const provider = PROVIDERS.find(p => model.startsWith(p.modelPrefix));
  return provider || defaultProvider; // defaultProvider is OpenAI
}

// src/model/llm.ts
export function getChatModel(model: string): BaseChatModel {
  const provider = resolveProvider(model);
  const apiKey = getApiKey(provider.envVar);
  const factory = MODEL_FACTORIES[provider.id];
  return factory(apiKey, model);
}

```

## How Provider Selection Works

When you invoke a model through Dexter, the system follows a deterministic routing flow:

1. **Model name supplied** – You pass a string like `"claude-3-5-sonnet-20240620"`, `"gemini-1.5-flash"`, or `"gpt-4o"`.
2. **Prefix matching** – `resolveProvider()` iterates through the `PROVIDERS` array and selects the first entry where `model.startsWith(provider.modelPrefix)` is true.
3. **Fallback to OpenAI** – If no prefix matches, Dexter uses the `defaultProvider` (OpenAI) to maintain backward compatibility.
4. **Factory instantiation** – `getChatModel()` retrieves the API key from `process.env[provider.envVar]`, selects the corresponding factory from `MODEL_FACTORIES`, and instantiates the LangChain chat class.
5. **Ready for inference** – The returned `BaseChatModel` is passed to the agent loop or direct invocation methods.

## Provider-Specific Optimizations

Dexter handles unique features of each provider without exposing complexity to the caller.

### Anthropic Prompt Caching

For Anthropic models, Dexter constructs explicit `SystemMessage` and `HumanMessage` objects with `cache_control: {type: 'ephemeral'}` to enable Anthropic’s prompt-caching optimization. This reduces token costs for repeated system prompts across multi-turn conversations.

```typescript
// src/model/llm.ts
function buildAnthropicMessages(systemPrompt: string, userPrompt: string) {
  return [
    new SystemMessage({
      content: systemPrompt,
      additional_kwargs: { cache_control: { type: 'ephemeral' } }
    }),
    new HumanMessage({
      content: userPrompt,
      additional_kwargs: { cache_control: { type: 'ephemeral' } }
    })
  ];
}

```

### Fast-Model Shortcuts

Dexter can automatically downgrade to a cheaper, faster model for lightweight tasks by referencing the `fastModel` field in the provider definition. The `getFastModel()` utility returns the provider’s designated fast variant while keeping the same provider configuration.

```typescript
// src/model/llm.ts
export function getFastModel(providerId: string, currentModel: string): string {
  const provider = PROVIDERS.find(p => p.id === providerId);
  return provider ? provider.fastModel : currentModel;
}

```

## Code Examples

The following examples demonstrate how to invoke different providers using Dexter’s unified interface.

```typescript
import { callLlm } from '@/model/llm';

// OpenAI (default behavior)
await callLlm('What is the current S&P 500 price?');
// Uses model "gpt-5.2" → OpenAI ChatOpenAI instance

// Anthropic – model name starts with "claude-"
await callLlm('Summarize Apple’s Q4 earnings.', {
  model: 'claude-3-5-sonnet-20240620',
});
// resolveProvider() picks Anthropic, factory creates ChatAnthropic,
// buildAnthropicMessages adds cache_control for prompt caching

// Google Gemini
await callLlm('Explain the concept of discounted cash flow.', {
  model: 'gemini-1.5-flash',
});
// resolveProvider() picks Google, factory creates ChatGoogleGenerativeAI

// Using a fast-model shortcut
import { getFastModel } from '@/model/llm';
const fast = getFastModel('anthropic', 'claude-3-5-sonnet-20240620');
// fast === 'claude-haiku-4-5'
await callLlm('Briefly list Apple’s top 5 products.', { model: fast });

```

## Summary

- **Provider Registry**: [`src/providers.ts`](https://github.com/virattt/dexter/blob/main/src/providers.ts) declares all supported LLMs with routing prefixes, API key environment variables, and fast-model aliases.
- **Factory Pattern**: [`src/model/llm.ts`](https://github.com/virattt/dexter/blob/main/src/model/llm.ts) maps provider IDs to LangChain constructors (`ChatOpenAI`, `ChatAnthropic`, `ChatGoogleGenerativeAI`, `ChatOllama`).
- **Prefix Routing**: `resolveProvider()` selects the correct backend by matching model name prefixes (`claude-`, `gemini-`, `gpt-`), falling back to OpenAI by default.
- **Provider-Specific Logic**: Dexter injects Anthropic-specific `cache_control` metadata for prompt caching while using generic templates for OpenAI and Google.
- **Fast Model Switching**: `getFastModel()` allows runtime downgrades to cheaper provider-specific variants without changing invocation code.

## Frequently Asked Questions

### How does Dexter determine which LLM provider to use?

Dexter determines the provider by inspecting the model name string you provide. The `resolveProvider()` function in [`src/providers.ts`](https://github.com/virattt/dexter/blob/main/src/providers.ts) iterates through the `PROVIDERS` array and matches the model name against each provider’s `modelPrefix` (e.g., `claude-` for Anthropic, `gemini-` for Google). The first match wins; if no prefix matches, Dexter defaults to OpenAI.

### Can I use local models like Ollama with Dexter?

Yes. Dexter includes Ollama support through the same factory pattern used for cloud providers. The `MODEL_FACTORIES` record in [`src/model/llm.ts`](https://github.com/virattt/dexter/blob/main/src/model/llm.ts) includes an `ollama` entry that instantiates `ChatOllama` from `@langchain/community`, reading the base URL from `process.env.OLLAMA_BASE_URL`. You invoke it by passing an Ollama model name, though the specific prefix logic depends on your local model naming convention.

### What happens if I don’t specify a model name when calling Dexter?

If you omit the model parameter, Dexter uses its internal default model configuration, which typically points to an OpenAI model like `gpt-5.2`. Because OpenAI is designated as the `defaultProvider` in the provider registry, any unmatched or unspecified model falls back to the OpenAI factory, ensuring the system remains functional even without explicit configuration.

### How does Dexter handle provider-specific features like Anthropic’s prompt caching?

Dexter handles provider-specific features through conditional logic in the `callLlm` function within [`src/model/llm.ts`](https://github.com/virattt/dexter/blob/main/src/model/llm.ts). When the resolved provider is Anthropic, Dexter constructs explicit `SystemMessage` and `HumanMessage` objects with `cache_control: {type: 'ephemeral'}` in the `additional_kwargs`. This enables Anthropic’s prompt-caching optimization. For other providers like OpenAI and Google, Dexter uses a generic `ChatPromptTemplate` that works with their standard APIs.