# How Fabric Handles Different AI Provider APIs: A Deep Dive into the Vendor Architecture

> Fabric unifies AI provider APIs like OpenAI, Anthropic, Gemini, and Azure with a single vendor interface. Discover its pluggable architecture for seamless integration.

- Repository: [Daniel Miessler 🛡️/fabric](https://github.com/danielmiessler/fabric)
- Tags: architecture
- Published: 2026-02-28

---

**Fabric abstracts every AI service behind a universal `Vendor` interface, enabling seamless integration of OpenAI, Anthropic, Gemini, Azure, Ollama, and any OpenAI-compatible API through a single, pluggable architecture.**

The open-source Fabric project by Daniel Miessler solves the fragmentation of AI provider APIs by treating every service as an interchangeable plugin. Whether you are calling GPT-4, Claude, or a local Ollama instance, Fabric uses a consistent internal contract to handle authentication, model discovery, and streaming chat completions.

## The Vendor Interface: Fabric's Universal API Contract

At the heart of Fabric's provider-agnostic design is the `ai.Vendor` interface defined in [`internal/plugins/ai/vendor.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/vendor.go). This contract mandates four core operations that every AI provider must implement:

- **Model discovery** via `ListModels()`
- **Synchronous chat** via `Send()`
- **Streaming chat** via `SendStream()`
- **Raw-mode detection** via `NeedsRawMode()`

By enforcing this interface, Fabric ensures that the CLI, Streamlit UI, and core logic remain completely decoupled from provider-specific implementation details. When you execute `fabric --vendor OpenAI --model gpt-4o`, the system resolves the vendor name to an interface implementation and invokes `SendStream()` without knowing whether the underlying transport is HTTP, gRPC, or a local Unix socket.

## Vendor Registration and Discovery

### The VendorsManager Registry

The `VendorsManager` struct in [`internal/plugins/ai/vendors.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/vendors.go) maintains a **case-insensitive registry** of all loaded vendors. It handles three critical responsibilities:

1. **Parallel model loading**: When `readModels()` is invoked, it spawns a goroutine per vendor to call `ListModels()` concurrently, significantly reducing startup latency when querying dozens of providers.
2. **Vendor resolution**: `FindByName(name)` performs case-insensitive lookups, allowing users to type `--vendor openai`, `--vendor OpenAI`, or `--vendor OPENAI` interchangeably.
3. **Model normalization**: Results are sorted and deduplicated, producing a `VendorsModels` map that maps vendor names to their available model slices.

### PluginRegistry Initialization

During application startup, [`internal/core/plugin_registry.go`](https://github.com/danielmiessler/fabric/blob/main/internal/core/plugin_registry.go) executes `NewPluginRegistry()` to instantiate every built-in vendor. The initialization sequence follows a specific pattern:

1. Create a slice of vendor objects (`OpenAI`, `Anthropic`, `Gemini`, `Bedrock`, `Ollama`, etc.)
2. Iterate over the static `openai_compatible.ProviderMap` to generate generic clients for OpenAI-compatible services (GitHub Models, Perplexity, Groq, etc.)
3. **Sort the final list alphabetically** to ensure deterministic behavior when the `--vendor` flag is omitted
4. Register all vendors with the `VendorsManager`

This design means adding support for a new AI provider requires only implementing the `Vendor` interface and appending the instance to the registry slice.

## Supporting OpenAI-Compatible Providers

Fabric includes a powerful abstraction layer for services that expose an OpenAI-style HTTP API. The `openai_compatible` package in [`internal/plugins/ai/openai_compatible/providers_config.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/openai_compatible/providers_config.go) defines a `ProviderConfig` struct that specifies:

- **Base URL**: The endpoint root (e.g., `https://api.groq.com/openai/v1`)
- **Model list endpoint**: Optional path for fetching available models
- **Responses API support**: Boolean flag indicating whether the provider implements the newer OpenAI Responses API versus the legacy Chat Completions endpoint

The generic `Client` in this package acts as a shim. When `SendStream()` is called, it checks `ImplementsResponses`:

- If **true**: Streams via `Responses.NewStreaming()`
- If **false**: Falls back to classic `Chat.Completions.NewStreaming()`

This allows Fabric to support dozens of OpenAI-compatible providers (Groq, Together AI, Fireworks, etc.) without writing provider-specific code, while still accommodating providers that implement newer API versions.

## Provider-Specific Implementations

While the OpenAI-compatible wrapper covers most services, Fabric maintains dedicated implementations for providers with unique authentication schemes or non-standard protocols:

- **OpenAI** ([`internal/plugins/ai/openai/openai.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/openai/openai.go)): The reference implementation, handling raw-mode detection for models like MiniMax that require unmodified system prompts, and implementing fallback logic for model listing when the SDK fails.
- **Gemini** ([`internal/plugins/ai/gemini/gemini.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/gemini/gemini.go)): Uses the OpenAI-compatible wrapper since Google's Gemini API adopted the OpenAI schema, but includes specific configuration for Google's authentication headers.
- **Azure AI Gateway** ([`internal/plugins/ai/azureaigateway/azureaigateway.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/azureaigateway/azureaigateway.go)): Demonstrates request shaping for Azure's token-based authentication and regional endpoint management, distinct from standard OpenAI bearer tokens.
- **Ollama** ([`internal/plugins/ai/ollama/ollama.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/ollama/ollama.go)): Handles local Unix socket connections and localhost HTTP endpoints, with special logic for listing locally available models.
- **Amazon Bedrock**: Implements AWS Signature Version 4 authentication for invoking models hosted on AWS infrastructure.

Each implementation embeds `plugins.PluginBase` to inherit configuration management (API keys, endpoints) through the `.env` file generated during `fabric --setup`.

## Runtime Vendor Selection and Model Discovery

### CLI Flags and Configuration

Users interact with the vendor system through flags defined in [`internal/cli/flags.go`](https://github.com/danielmiessler/fabric/blob/main/internal/cli/flags.go):

- `--vendor`: Specifies the provider name (case-insensitive)
- `--model`: Selects the specific model ID
- `--listvendors`: Displays all registered vendors alphabetically

When the `--vendor` flag is omitted, Fabric defaults to the first vendor in the alphabetically sorted list stored in `VendorsAll`.

### Model Discovery Process

When executing `fabric --listmodels`, the following sequence occurs:

1. `VendorsManager.readModels()` spawns concurrent goroutines for each vendor
2. Each goroutine calls `Vendor.ListModels()`
3. For OpenAI-compatible providers, `Client.ListModels()` first attempts the official SDK, then falls back to `FetchModelsDirectly()` for providers like GitHub Models that return non-standard JSON
4. Results are normalized, sorted, and stored in the `VendorsModels` map

### Raw-Mode and API Version Handling

Fabric handles edge cases through vendor-specific hooks:

- **Raw Mode**: Certain models (e.g., MiniMax) require payloads without Fabric's default "system" role transformation. The `NeedsRawMode(model string) bool` hook in [`internal/plugins/ai/openai/openai.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/openai/openai.go) allows providers to enforce unmodified request bodies.
- **Responses API vs Chat Completions**: When `ProviderConfig.ImplementsResponses` is true, `Client.SendStream` in [`internal/plugins/ai/openai/openai.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/openai/openai.go) routes to `Responses.NewStreaming()`; otherwise it uses the classic Chat Completions endpoint.

## Adding a Custom AI Provider to Fabric

Extending Fabric to support a new AI service requires implementing the `Vendor` interface and registering the instance. Here is a complete skeleton:

```go
// internal/plugins/ai/myprovider/client.go
package myprovider

import (
	"context"
	"github.com/danielmiessler/fabric/internal/plugins"
	"github.com/danielmiessler/fabric/internal/plugins/ai"
	"github.com/danielmiessler/fabric/internal/chat"
	"github.com/danielmiessler/fabric/internal/domain"
)

type Client struct {
	*plugins.PluginBase
	apiKey string
}

func NewClient() ai.Vendor {
	c := &Client{}
	c.PluginBase = plugins.NewVendorPluginBase("MyProvider", c.configure)
	return c
}

func (c *Client) configure() error {
	// Load API key from environment or .env file
	c.apiKey = c.GetEnvVariable("MYPROVIDER_API_KEY")
	return nil
}

func (c *Client) ListModels() ([]string, error) {
	// Return available models
	return []string{"myprovider-large", "myprovider-small"}, nil
}

func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (string, error) {
	// Implement synchronous chat
	return "Response from MyProvider", nil
}

func (c *Client) SendStream(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, handler func(string)) error {
	// Implement streaming chat
	handler("Streaming response chunk")
	return nil
}

func (c *Client) NeedsRawMode(model string) bool {
	// Return true if model requires unmodified payloads
	return false
}

```

To register the provider, modify [`internal/core/plugin_registry.go`](https://github.com/danielmiessler/fabric/blob/main/internal/core/plugin_registry.go):

```go
import "github.com/danielmiessler/fabric/internal/plugins/ai/myprovider"

func NewPluginRegistry() (*PluginRegistry, error) {
    // ... existing vendor initialization ...
    vendors = append(vendors, myprovider.NewClient())
    // ... rest of registration ...
}

```

Once registered, the provider appears in `fabric --listvendors` and accepts the `--vendor MyProvider` flag immediately.

## Summary

Fabric achieves AI provider interoperability through a clean abstraction layer that decouples the application from vendor-specific implementations:

- **The `Vendor` interface** in [`internal/plugins/ai/vendor.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/vendor.go) defines the contract for model listing, chat completion, and streaming.
- **The `VendorsManager`** maintains a case-insensitive registry and parallelizes model discovery across all providers.
- **The `openai_compatible` wrapper** enables zero-code integration for any service exposing an OpenAI-style HTTP API, with automatic fallback between Responses and Chat Completions endpoints.
- **Provider-specific implementations** handle unique authentication schemes (AWS Bedrock, Azure AI Gateway) and local deployments (Ollama).
- **Runtime selection** via `--vendor` and `--model` flags resolves through the registry without hardcoded provider logic.

This architecture allows developers to add new AI providers by implementing a single Go interface, with the CLI, model discovery, and UI automatically supporting the new service.

## Frequently Asked Questions

### How does Fabric support new OpenAI-compatible providers without code changes?

Fabric reads a static `ProviderMap` in [`internal/plugins/ai/openai_compatible/providers_config.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/openai_compatible/providers_config.go) that defines base URLs and capabilities for services like Groq, Perplexity, and Fireworks. When `NewPluginRegistry` initializes, it iterates over this map and creates a generic `Client` for each entry. These clients automatically handle model listing, streaming, and API version detection (Responses vs Chat Completions), allowing new OpenAI-compatible endpoints to be added by updating the configuration map rather than writing new Go code.

### What is the difference between the Vendor interface and the openai_compatible wrapper?

The `Vendor` interface in [`internal/plugins/ai/vendor.go`](https://github.com/danielmiessler/fabric/blob/main/internal/plugins/ai/vendor.go) is the top-level abstraction that all providers must satisfy, including methods for `ListModels`, `Send`, `SendStream`, and `NeedsRawMode`. The `openai_compatible` wrapper is a specific implementation of this interface located in `internal/plugins/ai/openai_compatible/`. It provides a reusable HTTP client for any service mimicking OpenAI's REST schema. Native providers like Azure AI Gateway or AWS Bedrock implement the `Vendor` interface directly without using the wrapper, while Gemini, Groq, and Perplexity use the wrapper to avoid code duplication.

### How does Fabric handle model discovery for providers with non-standard APIs?

When `fabric --listmodels` executes, the `VendorsManager.readModels()` method spawns a goroutine for each registered vendor. For OpenAI-compatible providers, the `Client.ListModels()` method first attempts to use the official OpenAI SDK. If that fails or returns non-standard JSON (as seen with GitHub Models), it falls back to `FetchModelsDirectly()`, which performs a raw HTTP GET to the provider's model list endpoint and parses the response manually. This dual-approach strategy ensures Fabric can discover available models even when providers deviate from the standard OpenAI response format.

### Can I use multiple AI providers in the same Fabric command?

Fabric commands target a single vendor at a time via the `--vendor` flag. However, you can switch providers between invocations without restarting the application. The `VendorsManager` maintains all registered providers in memory, so running `fabric --vendor OpenAI --model gpt-4o` followed by `fabric --vendor Ollama --model llama3` resolves each command to the correct implementation instantly. For workflows requiring multiple providers simultaneously, you would invoke the Fabric CLI separately for each vendor and aggregate the outputs externally, as the internal architecture assumes a single vendor context per request to maintain clean separation of concerns in the `Send` and `SendStream` methods.