How Reasonix's Provider Abstraction Supports Multiple LLM Backends (DeepSeek, Anthropic, OpenAI)

Reasonix implements a uniform Provider interface that abstracts all LLM interactions, enabling the host to swap between DeepSeek, Anthropic, and OpenAI backends without code changes.

The provider abstraction in DeepSeek-Reasonix treats every LLM service as a pluggable extension. By defining common data structures for model catalogs, streaming completions, and error handling, the architecture eliminates provider-specific logic from the host application. This design allows developers to add new backends—proprietary or open-source—by implementing a single Go interface.

Core Provider Interface

The foundation of Reasonix's multi-backend support resides in sdk/go/sdk.go. Here, the Provider interface declares three essential operations:

type Provider interface {
    Catalog(ctx context.Context) ([]ProviderDescriptor, error)
    Stream(ctx context.Context, req ProviderRequest) (<-chan ProviderChunk, error)
    // Additional methods for error normalization and capability negotiation
}

Catalog returns available models wrapped in ProviderDescriptor structs. *Stream initiates a token-by-token completion flow. Each backend adapter—OpenAI, Anthropic, or DeepSeek—implements these methods against its native API.

Backend Implementations

OpenAI Provider (internal/provider/openai/openai.go)

The OpenAI adapter maps Reasonix's generic request format to OpenAI's chat completions endpoint. It handles:

  • Model mapping: Translates Reasonix model identifiers to OpenAI model names
  • Streaming SSE: Converts server-sent events into ProviderChunk values
  • Tool call serialization: Reformats OpenAI function-calling responses into ProviderToolCall structs
func (p *OpenAIProvider) Stream(ctx context.Context, req ProviderRequest) (<-chan ProviderChunk, error) {
    openaiReq := p.toOpenAIChatRequest(req) // Transform to OpenAI schema
    stream, err := p.client.CreateChatCompletionStream(ctx, openaiReq)
    // ... pump chunks through channel as ProviderChunk
}

Anthropic Provider (internal/provider/anthropic/anthropic.go)

Anthropic's implementation mirrors the same contract but targets the Messages API. Key adaptations include:

  • Message format conversion: Reasonix's message array maps to Anthropic's messages parameter
  • System prompt handling: Extracts system messages into Anthropic's top-level system field
  • Streaming protocol: Adapts Anthropic's event-stream format to ProviderChunk emission

Both providers share error normalization logic, converting HTTP status codes and JSON error bodies into standardized ProviderErrorCode values.

DeepSeek Integration

DeepSeek support follows the identical pattern. A DeepSeek provider implements Catalog and Stream against DeepSeek's API specification, returning the same DTOs. The host cannot distinguish between DeepSeek, OpenAI, or Anthropic at runtime—only the providerRef identifier differs.

Extension and Registration

Providers load dynamically through the extension system. internal/extension/providerext/providerext.go registers provider plugins with the host:

// Extension entry point wires provider into host
func NewProviderExtension(p Provider) *Extension {
    return &Extension{
        provider: p,
        // Expose provider capabilities to host RPC layer
    }
}

When the host initializes, it queries each extension's Catalog method and builds a unified model registry. Users select models via providerRef strings like "plugin/openai/gpt-4o" or "plugin/anthropic/claude-3-opus".

Data Structures: Shared Contracts

All providers communicate through types defined in sdk/go/types_generated.go:

Struct Purpose
ProviderDescriptor Model metadata: ID, display name, capabilities
ProviderRequest Completion parameters: messages, temperature, effort, tools
ProviderChunk Streaming output: text delta, tool calls, usage stats, finish reason
ProviderError Normalized failure: error code, message, retryable flag

This shared schema ensures that a ProviderRequest created for OpenAI transports identically to Anthropic—only the concrete Stream implementation interprets field semantics differently.

Runtime Request Flow

  1. Catalog discovery: Host calls Catalog() on all loaded providers, aggregating results into a single model picker
  2. Request routing: User selects a model; host extracts providerRef to locate the correct provider instance
  3. Stream initiation: Host invokes Stream(ctx, req) with a standardized request
  4. Chunk pumping: Provider adapter converts native API responses into ProviderChunk values pushed through a Go channel
  5. Error handling: Provider adapters translate service-specific failures into ProviderError codes the host handles uniformly

Example: Configuring Multiple Providers

package main

import (
    "os"
    "github.com/reasonix/sdk/go"
    "github.com/reasonix/internal/provider/openai"
    "github.com/reasonix/internal/provider/anthropic"
)

func main() {
    // OpenAI backend
    openaiProvider := openai.NewProvider(openai.Config{
        APIKey:  os.Getenv("OPENAI_API_KEY"),
        BaseURL: "https://api.openai.com/v1",
    })
    
    // Anthropic backend
    anthropicProvider := anthropic.NewProvider(anthropic.Config{
        APIKey: os.Getenv("ANTHROPIC_API_KEY"),
    })
    
    // DeepSeek backend (hypothetical custom implementation)
    deepseekProvider := &DeepSeekProvider{
        apiKey: os.Getenv("DEEPSEEK_API_KEY"),
        baseURL: "https://api.deepseek.com/v1",
    }
    
    // Host automatically discovers all three via extension registration
    host, err := reasonix.StartHost(handler, reasonix.Options{
        Extensions: []reasonix.Extension{
            providerext.New(openaiProvider),
            providerext.New(anthropicProvider),
            providerext.New(deepseekProvider),
        },
    })
}

Each provider operates independently. The host manages lifecycle, request multiplexing, and unified telemetry without provider-specific conditionals.

Summary

  • Single interface: The Provider contract in sdk/go/sdk.go abstracts all LLM operations
  • Three reference implementations: OpenAI, Anthropic, and extensible patterns for DeepSeek in internal/provider/
  • Dynamic loading: Extension system in internal/extension/providerext/providerext.go enables runtime provider registration
  • Shared DTOs: sdk/go/types_generated.go defines backend-agnostic data structures
  • Uniform error handling: All adapters normalize failures into ProviderErrorCode values

Frequently Asked Questions

How do I add a custom LLM provider to Reasonix?

Implement the Provider interface from sdk/go/sdk.go with Catalog and Stream methods. Wrap your implementation in providerext.New() and pass it to reasonix.StartHost. See internal/provider/openai/openai.go for a complete reference implementation.

What happens if an LLM backend returns a non-streaming response?

The provider adapter must still return a <-chan ProviderChunk channel. Buffer the complete response and emit it as a single chunk, or implement client-side streaming by chunking the response artificially. The host expects uniform streaming behavior regardless of backend capabilities.

Can I use multiple API keys for different providers simultaneously?

Yes. Each provider instance holds its own configuration including API keys. Initialize separate provider structs with distinct credentials, register all with the extension system, and the host routes requests based on model selection without key conflicts.

How does Reasonix handle provider-specific features like Anthropic's extended thinking?

The ProviderRequest struct includes an Effort field that backends interpret according to their capabilities. Anthropic maps "high" effort to its extended thinking mode; OpenAI might map it to increased temperature sampling. Backend adapters translate generic effort levels into API-specific parameters privately.

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 →