How to Configure Custom API Endpoints and Models for Different LLM Providers in Prompt‑Optimizer

Prompt‑Optimizer uses a provider registry system where each LLM defines a defaultBaseURL and connectionSchema, allowing you to override endpoints and models via the UI or programmatically through the TextAdapterRegistry.

Prompt‑Optimizer is an open‑source tool designed to refine and optimize prompts across multiple large language models. To support diverse deployment scenarios—from cloud APIs to self‑hosted instances—the codebase provides a flexible architecture to configure custom API endpoints and models for different LLM providers in prompt‑optimizer.

Understanding the Provider Registry Architecture

The core abstraction is the provider registry located in packages/core/src/services/model/defaults.ts. Every supported LLM is defined as a TextProvider object containing:

  • id – the unique registry key (e.g., openai, gemini, anthropic).
  • defaultBaseURL – the canonical endpoint used when no custom URL is provided.
  • connectionSchema – a schema object declaring required fields (typically apiKey) and optional overrides (baseURL, model).

For example, OpenAI’s default configuration is declared as:

// packages/core/src/services/model/defaults.ts
{
  id: 'openai',
  name: 'OpenAI',
  defaultBaseURL: 'https://api.openai.com/v1',
  connectionSchema: {
    required: ['apiKey'],
    optional: ['baseURL', 'model']
  }
}

Overriding Endpoints in the Connection Config UI

The front‑end exposes a Connection Config dialog implemented in packages/ui/src/composables/model/useConnectionConfig.ts. When you select a provider, the composable auto‑populates the Base URL field with the provider’s defaultBaseURL:

// packages/ui/src/composables/model/useConnectionConfig.ts
if (providerMeta?.defaultBaseURL && !currentConfig?.baseURL) {
  result.baseURL = providerMeta.defaultBaseURL
}

You can overwrite this value to point at a custom endpoint—such as a self‑hosted OpenAI‑compatible server or a proxy. The Model selector (useTextModelManager.ts) then queries the effective endpoint to retrieve available models, ensuring that custom models exposed by your endpoint appear in the dropdown.

Programmatically Adding Custom Providers

To add a provider that is not built‑in, extend the registry and register it at runtime.

Step 1: Define Provider Metadata

Create a new TextProvider object in packages/core/src/services/model/defaults.ts or a custom extension file:

export const customProvider: TextProvider = {
  id: 'my-custom-llm',
  name: 'My Custom LLM',
  defaultBaseURL: 'https://api.mycustomllm.com/v1',
  connectionSchema: {
    required: ['apiKey'],
    optional: ['baseURL', 'model'],
    fieldTypes: { apiKey: 'string', baseURL: 'string', model: 'string' }
  },
  staticModels: [{ id: 'my-model-1', name: 'My Model 1' }]
}

Step 2: Register the Provider

Import the registry and register the provider during application initialization:

import { TextAdapterRegistry } from '@/services/llm/adapters/registry'
import { customProvider } from '@/services/model/defaults'

TextAdapterRegistry.getInstance().register(customProvider)

Once registered, the provider appears in the UI automatically because useTextModelManager.ts pulls the full list via:

providers.value = textAdapterRegistry?.getAllProviders?.() || []

Runtime Resolution Flow

Understanding how the configuration flows at runtime ensures you can debug connection issues effectively.

Step Component Action
1 Provider Registry (defaults.ts) Supplies defaultBaseURL and connectionSchema.
2 UI Config (useConnectionConfig.ts) Displays default URL; accepts user override.
3 Adapter (openai-adapter.ts, gemini-adapter.ts) Resolves effective URL: config.baseURL || provider.defaultBaseURL.
4 LLM Service (llm/service.ts) Sends HTTP requests to the resolved endpoint.
5 Model Manager (useTextModelManager.ts) Fetches model list from the effective endpoint.

For example, in the OpenAI adapter (packages/core/src/services/llm/adapters/openai-adapter.ts), the request URL is constructed as:

const baseURL = config.connectionConfig.baseURL || this.getProvider().defaultBaseURL
http.post(`${baseURL}${config.endpoints.chat}`, …)

Code Examples

Using a Custom Endpoint in a Test Script

import { createLLMService } from '@/services/llm/service'

const service = createLLMService({
  providerId: 'openai',
  connectionConfig: { 
    apiKey: 'sk-test-key', 
    baseURL: 'https://my-proxy.example.com/v1' 
  }
})

await service.chat({ 
  model: 'gpt-4o', 
  messages: [{ role: 'user', content: 'Hello' }] 
})

Extending the Connection Schema for a Corporate Proxy

// packages/core/src/services/model/defaults.ts
export const corporateOpenAI: TextProvider = {
  id: 'corporate-openai',
  name: 'Corporate OpenAI Gateway',
  defaultBaseURL: 'https://gateway.corp.net/openai/v1',
  connectionSchema: {
    required: ['apiKey', 'workspaceId'],
    optional: ['baseURL', 'model'],
    fieldTypes: { 
      apiKey: 'string', 
      workspaceId: 'string',
      baseURL: 'string', 
      model: 'string' 
    }
  }
}

Key Files

File Role Link
packages/core/src/services/model/defaults.ts Provider default URLs and static model data https://github.com/linshenkx/prompt-optimizer/blob/develop/packages/core/src/services/model/defaults.ts
packages/core/src/services/llm/adapters/openai-adapter.ts Resolves effective baseURL and builds request URLs for OpenAI https://github.com/linshenkx/prompt-optimizer/blob/develop/packages/core/src/services/llm/adapters/openai-adapter.ts
packages/core/src/services/llm/adapters/gemini-adapter.ts Same pattern for Google Gemini https://github.com/linshenkx/prompt-optimizer/blob/develop/packages/core/src/services/llm/adapters/gemini-adapter.ts
packages/ui/src/composables/model/useConnectionConfig.ts UI logic that injects provider defaults and allows overrides https://github.com/linshenkx/prompt-optimizer/blob/develop/packages/ui/src/composables/model/useConnectionConfig.ts
packages/ui/src/composables/model/useTextModelManager.ts Retrieves provider list and model metadata for selection UI https://github.com/linshenkx/prompt-optimizer/blob/develop/packages/ui/src/composables/model/useTextModelManager.ts
packages/core/src/services/adapters/abstract-registry.ts Base class for provider registration (both text and image) https://github.com/linshenkx/prompt-optimizer/blob/develop/packages/core/src/services/adapters/abstract-registry.ts
packages/core/src/services/llm/service.ts Core LLM service that consumes the resolved configuration https://github.com/linshenkx/prompt-optimizer/blob/develop/packages/core/src/services/llm/service.ts

Summary

  • Provider Registry: Each LLM is defined in packages/core/src/services/model/defaults.ts with a defaultBaseURL and connectionSchema.
  • UI Override: The connection dialog (useConnectionConfig.ts) pre-fills the default endpoint but allows you to specify custom URLs for proxies or self-hosted instances.
  • Adapter Resolution: At runtime, adapters like openai-adapter.ts resolve the effective URL using config.baseURL || provider.defaultBaseURL.
  • Extensibility: You can register new providers via TextAdapterRegistry.register() without modifying core UI code, and the model manager will automatically surface them.

Frequently Asked Questions

How do I point Prompt‑Optimizer to a self‑hosted OpenAI‑compatible server?

Enter your custom endpoint in the Base URL field of the Connection Config dialog. The UI pre-fills the standard OpenAI URL (https://api.openai.com/v1), but you can overwrite it with your local address (e.g., http://localhost:8000/v1). The openai-adapter.ts will then route all requests to your server instead of the official API.

Can I add a completely new LLM provider that isn't built‑in?

Yes. Define a new TextProvider object in packages/core/src/services/model/defaults.ts with a unique id, defaultBaseURL, and connectionSchema. Then register it at runtime using TextAdapterRegistry.getInstance().register(customProvider). The UI automatically picks up the new provider via useTextModelManager.ts, which calls getAllProviders().

Where does the UI get the default API endpoint for each provider?

The default endpoints are defined in the provider metadata within packages/core/src/services/model/defaults.ts. When you select a provider in the UI, useConnectionConfig.ts reads the defaultBaseURL property and injects it into the form. If the field is left empty by the user, the system falls back to this default at request time.

What happens if I leave the Base URL field empty in the connection settings?

If the field is empty, the LLM adapter uses the provider’s defaultBaseURL defined in defaults.ts. For example, openai-adapter.ts resolves the effective URL with config.connectionConfig.baseURL || this.getProvider().defaultBaseURL. This ensures the official endpoint is used as a safe fallback while still allowing custom overrides when needed.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →