Documentation Index
Fetch the complete documentation index at: https://www.getmaxim.ai/docs/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Adding a new provider to Bifrost enables users to access different AI models through a unified interface. This guide walks you through the entire process from design to deployment.
Prerequisites
Required Skills
- Go Programming - Intermediate level proficiency
- HTTP/REST APIs - Understanding of API communication
- JSON Processing - Request/response serialization
- Testing - Unit and integration test writing
Development Environment
- Go 1.23+ - Latest Go version
- Provider API Access - API keys for testing
- Git - Version control familiarity
- Testing Tools - Go test framework knowledge
Provider Interface
Core Interface Definition
Every provider must implement the Provider interface:
type Provider interface {
// GetProviderKey returns the unique provider identifier
GetProviderKey() ModelProvider
// ChatCompletion performs chat completion requests
ChatCompletion(ctx context.Context, model, key string, messages []BifrostMessage, params *ModelParameters) (*BifrostResponse, *BifrostError)
// TextCompletion performs text completion requests (optional)
TextCompletion(ctx context.Context, model, key string, text string, params *ModelParameters) (*BifrostResponse, *BifrostError)
}
Provider Structure Template
package providers
import (
"context"
"fmt"
"net/http"
"time"
"github.com/maximhq/bifrost/core/schemas"
)
// YourProviderProvider implements the Provider interface for YourProvider
type YourProviderProvider struct {
config *schemas.ProviderConfig
client *http.Client
logger schemas.Logger
}
// NewYourProviderProvider creates a new YourProvider provider instance
func NewYourProviderProvider(config *schemas.ProviderConfig, logger schemas.Logger) *YourProviderProvider {
return &YourProviderProvider{
config: config,
client: &http.Client{
Timeout: time.Duration(config.NetworkConfig.TimeoutSeconds) * time.Second,
},
logger: logger,
}
}
// GetProviderKey returns the provider identifier
func (p *YourProviderProvider) GetProviderKey() schemas.ModelProvider {
return schemas.YourProvider // Add this to schemas/bifrost.go
}
Step-by-Step Implementation
Step 1: Add Provider Constant
First, add your provider to the core schemas:
// In core/schemas/bifrost.go
const (
OpenAI ModelProvider = "openai"
Anthropic ModelProvider = "anthropic"
// ... existing providers
YourProvider ModelProvider = "yourprovider" // Add this line
)
Step 2: Implement Chat Completion
func (p *YourProviderProvider) ChatCompletion(
ctx context.Context,
model, key string,
messages []schemas.BifrostMessage,
params *schemas.ModelParameters,
) (*schemas.BifrostResponse, *schemas.BifrostError) {
// 1. Build provider-specific request
providerRequest := p.buildChatRequest(model, messages, params)
// 2. Make HTTP request
resp, err := p.makeRequest(ctx, key, providerRequest)
if err != nil {
return nil, p.handleError(err)
}
// 3. Parse and convert response
bifrostResponse, err := p.parseChatResponse(resp)
if err != nil {
return nil, p.handleError(err)
}
return bifrostResponse, nil
}
Step 3: Request Translation
Convert Bifrost requests to provider-specific format:
type YourProviderChatRequest struct {
Model string `json:"model"`
Messages []YourProviderMessage `json:"messages"`
Temperature *float64 `json:"temperature,omitempty"`
MaxTokens *int `json:"max_tokens,omitempty"`
// Add provider-specific fields
}
func (p *YourProviderProvider) buildChatRequest(
model string,
messages []schemas.BifrostMessage,
params *schemas.ModelParameters,
) *YourProviderChatRequest {
req := &YourProviderChatRequest{
Model: model,
Messages: p.convertMessages(messages),
}
// Apply parameters
if params != nil {
req.Temperature = params.Temperature
req.MaxTokens = params.MaxTokens
// Map other parameters
}
return req
}
func (p *YourProviderProvider) convertMessages(messages []schemas.BifrostMessage) []YourProviderMessage {
var providerMessages []YourProviderMessage
for _, msg := range messages {
providerMsg := YourProviderMessage{
Role: string(msg.Role),
}
// Handle different content types
if msg.Content.ContentStr != nil {
providerMsg.Content = *msg.Content.ContentStr
} else if msg.Content.ContentBlocks != nil {
// Handle multi-modal content
providerMsg.Content = p.convertContentBlocks(*msg.Content.ContentBlocks)
}
providerMessages = append(providerMessages, providerMsg)
}
return providerMessages
}
Step 4: HTTP Communication
func (p *YourProviderProvider) makeRequest(
ctx context.Context,
apiKey string,
request interface{},
) (*YourProviderResponse, error) {
// Serialize request
requestBody, err := json.Marshal(request)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
// Create HTTP request
httpReq, err := http.NewRequestWithContext(
ctx,
"POST",
"https://api.yourprovider.com/v1/chat/completions",
bytes.NewBuffer(requestBody),
)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
// Set headers
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
httpReq.Header.Set("User-Agent", "Bifrost/1.0")
// Execute request
httpResp, err := p.client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer httpResp.Body.Close()
// Handle HTTP errors
if httpResp.StatusCode != http.StatusOK {
return nil, p.handleHTTPError(httpResp)
}
// Parse response
var response YourProviderResponse
if err := json.NewDecoder(httpResp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &response, nil
}
Step 5: Response Translation
Convert provider responses to Bifrost format:
func (p *YourProviderProvider) parseChatResponse(resp *YourProviderResponse) (*schemas.BifrostResponse, error) {
bifrostResp := &schemas.BifrostResponse{
ID: resp.ID,
Object: "chat.completion",
Model: resp.Model,
Created: int(time.Now().Unix()),
Usage: schemas.LLMUsage{
PromptTokens: resp.Usage.PromptTokens,
CompletionTokens: resp.Usage.CompletionTokens,
TotalTokens: resp.Usage.TotalTokens,
},
ExtraFields: schemas.BifrostResponseExtraFields{
Provider: p.GetProviderKey(),
RawResponse: resp,
},
}
// Convert choices
for i, choice := range resp.Choices {
bifrostChoice := schemas.BifrostResponseChoice{
Index: i,
FinishReason: choice.FinishReason,
Message: schemas.BifrostMessage{
Role: schemas.ModelChatMessageRole(choice.Message.Role),
Content: schemas.MessageContent{
ContentStr: &choice.Message.Content,
},
},
}
// Handle tool calls if supported
if len(choice.Message.ToolCalls) > 0 {
bifrostChoice.Message.AssistantMessage = &schemas.AssistantMessage{
ToolCalls: &choice.Message.ToolCalls,
}
}
bifrostResp.Choices = append(bifrostResp.Choices, bifrostChoice)
}
return bifrostResp, nil
}
Step 6: Error Handling
func (p *YourProviderProvider) handleError(err error) *schemas.BifrostError {
return &schemas.BifrostError{
IsBifrostError: false,
Error: schemas.ErrorField{
Message: err.Error(),
Error: err,
},
}
}
func (p *YourProviderProvider) handleHTTPError(resp *http.Response) error {
var errorResp YourProviderErrorResponse
if err := json.NewDecoder(resp.Body).Decode(&errorResp); err != nil {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
}
return &schemas.BifrostError{
IsBifrostError: false,
StatusCode: &resp.StatusCode,
Error: schemas.ErrorField{
Type: &errorResp.Error.Type,
Code: &errorResp.Error.Code,
Message: errorResp.Error.Message,
},
}
}
Testing Your Provider
Unit Tests
package providers
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestYourProviderProvider_ChatCompletion(t *testing.T) {
tests := []struct {
name string
model string
messages []schemas.BifrostMessage
params *schemas.ModelParameters
wantErr bool
}{
{
name: "successful chat completion",
model: "your-model-name",
messages: []schemas.BifrostMessage{
{
Role: schemas.ModelChatMessageRoleUser,
Content: schemas.MessageContent{
ContentStr: stringPtr("Hello, world!"),
},
},
},
params: nil,
wantErr: false,
},
// Add more test cases
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
provider := NewYourProviderProvider(testConfig, testLogger)
result, err := provider.ChatCompletion(
context.Background(),
tt.model,
"test-api-key",
tt.messages,
tt.params,
)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, tt.model, result.Model)
assert.NotEmpty(t, result.Choices)
}
})
}
}
Integration Tests
Create integration tests in tests/core-providers/yourprovider_test.go:
func TestYourProviderIntegration(t *testing.T) {
// Skip if no API key
apiKey := os.Getenv("YOURPROVIDER_API_KEY")
if apiKey == "" {
t.Skip("YOURPROVIDER_API_KEY not set")
}
// Test with real API
scenarios := []scenarios.TestScenario{
scenarios.SimpleChatScenario(),
scenarios.MultiTurnConversationScenario(),
scenarios.ToolCallScenario(),
// Add provider-specific scenarios
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
err := scenario.Run(t, schemas.YourProvider, "your-model-name")
assert.NoError(t, err)
})
}
}
Integration with Bifrost Core
Register Your Provider
Add your provider to the core factory function in core/bifrost.go:
func (bifrost *Bifrost) createProviderFromProviderKey(providerKey schemas.ModelProvider, config *schemas.ProviderConfig) (schemas.Provider, error) {
switch providerKey {
case schemas.OpenAI:
return providers.NewOpenAIProvider(config, bifrost.logger), nil
case schemas.Anthropic:
return providers.NewAnthropicProvider(config, bifrost.logger), nil
// ... existing providers
case schemas.YourProvider:
return providers.NewYourProviderProvider(config, bifrost.logger), nil
default:
return nil, fmt.Errorf("unsupported provider: %s", providerKey)
}
}
Update Key Requirements
If your provider requires API keys, update the key checking logic:
func providerRequiresKey(providerKey schemas.ModelProvider) bool {
return providerKey != schemas.Vertex &&
providerKey != schemas.Ollama
// YourProvider requires keys by default
}
Documentation Requirements
Provider Documentation
Create comprehensive documentation including:
- Setup Guide - How to get API keys and configure
- Supported Features - What capabilities are available
- Model List - Supported models and their capabilities
- Examples - Real usage examples
- Limitations - Known limitations or differences
Example Documentation Template
# YourProvider Integration
## Configuration
### API Key Setup
1. Create account at YourProvider
2. Generate API key
3. Configure in Bifrost
### Supported Models
- `your-model-v1` - Fast, general purpose
- `your-model-v2` - Advanced reasoning
- `your-model-multimodal` - Vision and text
## Examples
### Basic Chat Completion
```go
client, initErr := bifrost.Init(schemas.BifrostConfig{
Account: &account,
})
result, err := client.ChatCompletionRequest(ctx, &schemas.BifrostRequest{
Provider: schemas.YourProvider,
Model: "your-model-v1",
Input: schemas.RequestInput{
ChatCompletionInput: &[]schemas.BifrostMessage{
{Role: "user", Content: "Hello!"},
},
},
})
```
## Features
### Supported Features
- โ
Chat completions
- โ
Function calling
- โ
Streaming responses
- โ Image generation
### Parameter Mapping
| Bifrost Parameter | YourProvider Parameter | Notes |
| ----------------- | ---------------------- | ---------- |
| temperature | temperature | 0.0-2.0 |
| max_tokens | max_tokens | Up to 4096 |
โ
Submission Checklist
Before submitting your provider implementation:
Code Quality
- Interface Implementation - Correctly implements Provider interface
- Error Handling - Proper error handling and BifrostError creation
- Type Conversion - Accurate Bifrost โ Provider type mapping
- HTTP Communication - Robust API communication with retries
- Code Style - Follows Go conventions and Bifrost patterns
Testing
- Unit Tests - Comprehensive unit test coverage (>80%)
- Integration Tests - Real API integration tests
- Error Scenarios - Tests for various error conditions
- Parameter Testing - Tests for all supported parameters
- Edge Cases - Tests for edge cases and boundary conditions
Documentation
- Code Comments - Clear function and complex logic documentation
- User Documentation - Setup and usage guides
- Examples - Working code examples
- Feature Matrix - Clear documentation of supported features
- Migration Guide - If replacing existing integrations
Integration
- Core Integration - Properly integrated with bifrost.go factory
- Schema Updates - Provider constant added to schemas
- Key Handling - Proper API key requirement configuration
- Configuration - Standard provider configuration support
HTTP Transport Integration
- Provider Recognition - Added to
validProviders map in transports/bifrost-http/integrations/utils.go
- Model Patterns - Added patterns to appropriate
is*Model() functions in utils.go
- Transport Tests - All tests pass in
tests/transports-integrations/ directory
- Multi-Provider Support - Verified
ParseModelString correctly handles your provider prefix
Required Updates in utils.go:
// 1. Add to validProviders map
var validProviders = map[schemas.ModelProvider]bool{
// ... existing providers
schemas.YourProvider: true, // Add this line
}
// 2. Add model patterns to appropriate function
func isYourProviderModel(model string) bool {
yourProviderPatterns := []string{
"your-provider-pattern", "your-model-prefix", "yourprovider/",
}
return matchesAnyPattern(model, yourProviderPatterns)
}
// 3. Add pattern check to GetProviderFromModel
func GetProviderFromModel(model string) schemas.ModelProvider {
// ... existing checks
// Your Provider Models
if isYourProviderModel(modelLower) {
return schemas.YourProvider
}
// ... rest of function
}
Test Your Integration:
# Run HTTP transport integration tests
cd tests/transports-integrations
python -m pytest tests/integrations/ -v
Advanced Features
Streaming Support
If your provider supports streaming responses:
func (p *YourProviderProvider) ChatCompletionStream(
ctx context.Context,
model, key string,
messages []schemas.BifrostMessage,
params *schemas.ModelParameters,
) (<-chan *schemas.BifrostResponse, <-chan error) {
responseChan := make(chan *schemas.BifrostResponse)
errorChan := make(chan error, 1)
go func() {
defer close(responseChan)
defer close(errorChan)
// Implement streaming logic
stream, err := p.createStream(ctx, key, request)
if err != nil {
errorChan <- err
return
}
for event := range stream {
if event.Error != nil {
errorChan <- event.Error
return
}
response := p.convertStreamEvent(event)
responseChan <- response
}
}()
return responseChan, errorChan
}
Multi-Modal Support
For providers that support images or other media:
func (p *YourProviderProvider) convertContentBlocks(blocks []schemas.ContentBlock) interface{} {
var content []YourProviderContent
for _, block := range blocks {
switch block.Type {
case schemas.ContentBlockTypeText:
content = append(content, YourProviderContent{
Type: "text",
Text: *block.Text,
})
case schemas.ContentBlockTypeImage:
content = append(content, YourProviderContent{
Type: "image_url",
ImageURL: YourProviderImageURL{
URL: block.ImageURL.URL,
Detail: block.ImageURL.Detail,
},
})
}
}
return content
}
Function Calling
For providers with function calling capabilities:
func (p *YourProviderProvider) convertTools(tools *[]schemas.Tool) []YourProviderTool {
if tools == nil {
return nil
}
var providerTools []YourProviderTool
for _, tool := range *tools {
providerTools = append(providerTools, YourProviderTool{
Type: "function",
Function: YourProviderFunction{
Name: tool.Function.Name,
Description: tool.Function.Description,
Parameters: tool.Function.Parameters,
},
})
}
return providerTools
}
Next Steps
- Fork the Repository - Create your development environment
- Choose a Provider - Select a provider you want to integrate
- Study Existing Examples - Look at OpenAI or Anthropic implementations
- Start with Basic Implementation - Get chat completion working first
- Add Advanced Features - Streaming, tools, multi-modal support
- Test Thoroughly - Write comprehensive tests
- Document Everything - Create clear documentation
- Submit Pull Request - Follow the contribution guidelines
Ready to build your provider? ๐
Check out the existing provider implementations in core/providers/ for reference, and donโt hesitate to ask questions in GitHub Discussions if you need help!