How Fabric Handles Different AI Provider APIs: A Deep Dive into the Vendor Architecture
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. 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 maintains a case-insensitive registry of all loaded vendors. It handles three critical responsibilities:
- Parallel model loading: When
readModels()is invoked, it spawns a goroutine per vendor to callListModels()concurrently, significantly reducing startup latency when querying dozens of providers. - Vendor resolution:
FindByName(name)performs case-insensitive lookups, allowing users to type--vendor openai,--vendor OpenAI, or--vendor OPENAIinterchangeably. - Model normalization: Results are sorted and deduplicated, producing a
VendorsModelsmap that maps vendor names to their available model slices.
PluginRegistry Initialization
During application startup, internal/core/plugin_registry.go executes NewPluginRegistry() to instantiate every built-in vendor. The initialization sequence follows a specific pattern:
- Create a slice of vendor objects (
OpenAI,Anthropic,Gemini,Bedrock,Ollama, etc.) - Iterate over the static
openai_compatible.ProviderMapto generate generic clients for OpenAI-compatible services (GitHub Models, Perplexity, Groq, etc.) - Sort the final list alphabetically to ensure deterministic behavior when the
--vendorflag is omitted - 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 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): 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): 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): 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): 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:
--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:
VendorsManager.readModels()spawns concurrent goroutines for each vendor- Each goroutine calls
Vendor.ListModels() - For OpenAI-compatible providers,
Client.ListModels()first attempts the official SDK, then falls back toFetchModelsDirectly()for providers like GitHub Models that return non-standard JSON - Results are normalized, sorted, and stored in the
VendorsModelsmap
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) boolhook ininternal/plugins/ai/openai/openai.goallows providers to enforce unmodified request bodies. - Responses API vs Chat Completions: When
ProviderConfig.ImplementsResponsesis true,Client.SendStreamininternal/plugins/ai/openai/openai.goroutes toResponses.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:
// 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:
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
Vendorinterface ininternal/plugins/ai/vendor.godefines the contract for model listing, chat completion, and streaming. - The
VendorsManagermaintains a case-insensitive registry and parallelizes model discovery across all providers. - The
openai_compatiblewrapper 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
--vendorand--modelflags 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 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →