# How to Implement Custom LLM Providers Beyond OpenAI Compatibility in Reasonix

> Learn to implement custom LLM providers in Reasonix beyond OpenAI compatibility. Create a package, register it, and expose configuration keys for seamless integration.

- Repository: [YHH/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix)
- Tags: how-to-guide
- Published: 2026-08-11

---

**Implementing custom LLM providers in Reasonix requires creating a package that satisfies the `Provider` interface defined in [`internal/provider/provider.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/provider.go), registering it via the `Register` function in an `init()` block, and exposing configuration keys in the TOML schema.**

Reasonix uses a clean provider abstraction to support any language model backend, whether it follows the OpenAI chat completion protocol or a completely custom format. This architecture lives in the `esengine/DeepSeek-Reasonix` repository and enables plug-and-play integration of new LLM services without modifying core engine code.

## The Provider Abstraction in Reasonix

All LLM drivers in Reasonix implement a common interface defined in **[`internal/provider/provider.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/provider.go)**. This file contains four essential components that every custom provider must interact with:

- **`Config`** – a struct holding connection parameters including `APIKey`, `Model`, `BaseURL`, and provider-specific fields
- **`Provider`** – the interface that abstracts chat generation, tool handling, and streaming capabilities
- **`Factory`** – a function type `func(cfg Config) (Provider, error)` that constructs concrete provider instances
- **`Register(kind string, f Factory)`** – a global registry function that maps a short identifier string to your factory

The registration mechanism uses a package-level map populated during `init()` execution. When Reasonix starts, it looks up the `kind` value from the configuration file and invokes the matching factory to instantiate the provider.

## Built-In Provider Examples

Reasonix ships with two reference implementations that demonstrate the registration pattern:

| Provider | File | Registration Key | Compatibility |
|----------|------|------------------|---------------|
| OpenAI | [`internal/provider/openai/openai.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/openai/openai.go) | `"openai"` | Native OpenAI API |
| Anthropic | [`internal/provider/anthropic/anthropic.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/anthropic/anthropic.go) | `"anthropic"` | Native Anthropic Messages API |

Both providers register themselves using identical patterns. The OpenAI driver calls `provider.Register("openai", New)` in its `init()` function, while the Anthropic driver registers with `provider.Register("anthropic", New)`. Despite speaking completely different protocols, they plug into the same engine interface.

Services with OpenAI-compatible endpoints—such as **vLLM**, **Ollama**, or **Azure OpenAI**—work immediately by setting `kind = "openai"` and pointing `base_url` at the service URL.

## Step-by-Step: Creating a Non-OpenAI-Compatible Provider

Follow these seven steps to implement custom LLM providers beyond OpenAI compatibility in Reasonix:

### 1. Create a New Package

Create a directory under `internal/provider` for your implementation:

```bash
mkdir internal/provider/myprovider
touch internal/provider/myprovider/myprovider.go

```

### 2. Define the Provider Struct

Structure your provider to hold any client objects, authentication tokens, or service-specific configuration:

```go
type client struct {
    endpoint string
    token    string
    httpClient *http.Client
}

```

### 3. Implement the Provider Interface

The exact method signatures are defined in [`internal/provider/provider.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/provider.go). At minimum, implement:

- `Chat(ctx context.Context, msgs []Message, opts Options) (ChatResult, error)` – the core generation method
- `Tools() []Tool` – returns supported tool definitions (may return `nil`)
- Optional methods like `ModelInfo()`, `Close()`, or streaming handlers

### 4. Write the Constructor Factory

Your `New` function must match the `Factory` signature and validate required configuration:

```go
func New(cfg provider.Config) (provider.Provider, error) {
    if cfg.BaseURL == "" {
        return nil, errors.New("myprovider: base_url is required")
    }
    // Initialize and return your client
}

```

### 5. Register in init()

Add registration to ensure your provider is available at runtime:

```go
func init() {
    provider.Register("myprovider", New)
}

```

### 6. Expose Configuration Keys

Add fields to the TOML schema so users can configure your provider:

```toml
[model]
kind = "myprovider"
model = "my-model/1.0"
base_url = "https://api.myprovider.com/v1"
api_key_env = "MYPROVIDER_API_KEY"

```

### 7. Add Tests and Documentation

Reference [`internal/provider/openai/openai_test.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/openai/openai_test.go) for test patterns. Verify your provider handles edge cases in request translation, response parsing, and error propagation.

## Complete Implementation Example

Here's a minimal skeleton for [`internal/provider/myprovider/myprovider.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/myprovider/myprovider.go):

```go
package myprovider

import (
    "context"
    "errors"
    "net/http"

    "github.com/esengine/DeepSeek-Reasonix/main-v2/internal/provider"
)

type client struct {
    endpoint string
    token    string
    httpClient *http.Client
}

// New satisfies the provider.Factory signature.
func New(cfg provider.Config) (provider.Provider, error) {
    if cfg.BaseURL == "" {
        return nil, errors.New("myprovider: base_url is required")
    }
    if cfg.APIKey == "" {
        return nil, errors.New("myprovider: api_key is required")
    }
    return &client{
        endpoint:   cfg.BaseURL,
        token:      cfg.APIKey,
        httpClient: &http.Client{Timeout: 30 * time.Second},
    }, nil
}

// Chat implements the core generation call for your custom LLM.
func (c *client) Chat(ctx context.Context, msgs []provider.Message, opts provider.Options) (provider.ChatResult, error) {
    // 1. Translate msgs to your provider's native request format
    // 2. Execute HTTP request using c.httpClient
    // 3. Parse response and populate provider.ChatResult
    // 4. Map usage statistics, finish reasons, and content
    
    return provider.ChatResult{
        Content: "response from custom provider",
        Usage: provider.Usage{
            PromptTokens:     0,
            CompletionTokens: 0,
            TotalTokens:      0,
        },
    }, nil
}

// Tools reports tool definitions supported by this backend.
func (c *client) Tools() []provider.Tool {
    // Return nil or populate with provider.Tool structs
    return nil
}

// ModelInfo exposes metadata about the configured model.
func (c *client) ModelInfo() provider.ModelInfo {
    return provider.ModelInfo{
        Name:   "my-model/1.0",
        Vendor: "MyProvider Inc.",
    }
}

func init() {
    // Register under the short name used in TOML configuration.
    provider.Register("myprovider", New)
}

```

## Configuration Usage

Once compiled, your provider works identically to built-in options:

```toml
[model]
kind = "myprovider"
model = "my-model/1.0"
base_url = "https://api.myprovider.com/v1"
api_key_env = "MYPROVIDER_API_KEY"

```

The engine instantiates your driver through `provider.New`, which looks up `"myprovider"` in the global registry and invokes your factory with the parsed `Config`. All Reasonix features—tool calling, post-LLM hooks, effort tracking, and streaming—function automatically because they operate on the abstract `Provider` interface.

## Key Source Files

| File | Purpose |
|------|---------|
| [`internal/provider/provider.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/provider.go) | Core abstraction: `Config`, `Provider` interface, `Register` function |
| [`internal/provider/openai/openai.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/openai/openai.go) | Reference for OpenAI-compatible implementations |
| [`internal/provider/anthropic/anthropic.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/anthropic/anthropic.go) | Reference for non-OpenAI protocol implementations |
| [`internal/provider/responses/responses.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/responses/responses.go) | Pattern for response-only or mock providers |

## Summary

- **Provider interface** in [`internal/provider/provider.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/provider.go) abstracts all LLM backends behind unified method signatures
- **Factory pattern** with `Register(kind, factory)` enables runtime discovery without code changes
- **OpenAI-compatible services** reuse the existing driver by overriding `base_url`
- **Custom protocols** require a new package implementing `Chat()`, `Tools()`, and registration
- **Zero core modifications** needed—add your package, compile, and configure via TOML

## Frequently Asked Questions

### What is the minimum interface my custom provider must implement?

Your provider must implement `Chat(context.Context, []Message, Options) (ChatResult, error)` and `Tools() []Tool` as defined in [`internal/provider/provider.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/provider.go). Optional methods like `ModelInfo()`, `Close()`, or streaming handlers extend functionality but aren't strictly required for basic operation.

### Can I use a custom provider with existing OpenAI-compatible tools?

Yes. Your provider's `Tools()` method returns `provider.Tool` definitions that Reasonix serializes according to your driver's native format. The engine handles tool invocation and result integration; you only translate between Reasonix's tool schema and your LLM's expected format.

### How does Reasonix handle provider errors and retries?

Providers return standard Go errors from `Chat()` and other methods. Reasonix's execution layer applies retry policies, circuit breakers, and fallback logic at the engine level without provider involvement. Your driver should propagate transient errors (timeouts, 5xx responses) and permanent errors (authentication failures, invalid requests) appropriately.

### Where should I add configuration validation for my custom provider?

Validate required fields in your factory's `New` function before returning the provider instance. This ensures fast failure during startup rather than mid-execution. Reference the OpenAI driver's validation of `APIKey` and `Model` in [`internal/provider/openai/openai.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/provider/openai/openai.go) for patterns.