Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,11 @@ func (bifrost *Bifrost) tryRequest(req *schemas.BifrostRequest, ctx context.Cont
}
}

// GetDropExcessRequests returns the current value of DropExcessRequests
func (bifrost *Bifrost) GetDropExcessRequests() bool {
return bifrost.dropExcessRequests.Load()
}

// UpdateDropExcessRequests updates the DropExcessRequests setting at runtime.
// This allows for hot-reloading of this configuration value.
func (bifrost *Bifrost) UpdateDropExcessRequests(value bool) {
Expand Down
215 changes: 213 additions & 2 deletions docs/contributing/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ type Provider interface {
}
```

### **Meta Configuration Support**

Some providers require additional configuration beyond API keys. Bifrost supports this through meta configs:

```go
// In core/schemas/meta/yourprovider.go
type YourProviderMetaConfig struct {
// Add provider-specific fields
Endpoint string `json:"endpoint"` // e.g., Custom API endpoint
Region string `json:"region"` // e.g., Cloud region
ProjectID string `json:"project_id"` // e.g., Cloud project identifier

// ... other fields (check /core/schemas/provider.go)
}
```

### **Provider Structure Template**

```go
Expand Down Expand Up @@ -488,6 +504,197 @@ func providerRequiresKey(providerKey schemas.ModelProvider) bool {
}
```

## 🌐 **Integration with HTTP Transport**

The HTTP transport layer requires specific changes to handle provider configuration, meta configs, and model patterns.

### **1. Provider Recognition**

Update `transports/bifrost-http/integrations/utils.go`:

```go
var validProviders = map[schemas.ModelProvider]bool{
// ... existing providers
schemas.YourProvider: true, // Add this line
}

// Add model patterns
func isYourProviderModel(model string) bool {
yourProviderPatterns := []string{
"your-provider-pattern", "your-model-prefix", "yourprovider/",
}
return matchesAnyPattern(model, yourProviderPatterns)
}

// Add pattern check
func GetProviderFromModel(model string) schemas.ModelProvider {
// ... existing checks
if isYourProviderModel(modelLower) {
return schemas.YourProvider
}
}
```

### **2. Meta Configuration Support**

If your provider needs additional configuration beyond API keys, you'll need to implement meta config support:

1. **Define Meta Config Structure** (`core/schemas/meta/yourprovider.go`):

```go
type YourProviderMetaConfig struct {
Endpoint string `json:"endpoint"` // Custom API endpoint
Region string `json:"region"` // Cloud region
ProjectID string `json:"project_id"` // Project identifier
}

func (c *YourProviderMetaConfig) GetType() string {
return "yourprovider"
}
```

2. **Update Store Meta Config Processing** (`transports/bifrost-http/lib/store.go`):
Comment thread
Pratham-Mishra04 marked this conversation as resolved.

Add your provider to these three functions:

```go
// A. Add to parseMetaConfig
func (s *ConfigStore) parseMetaConfig(rawMetaConfig json.RawMessage, provider schemas.ModelProvider) (*schemas.MetaConfig, error) {
switch provider {
// ... existing cases
case schemas.YourProvider:
var config meta.YourProviderMetaConfig
if err := json.Unmarshal(rawMetaConfig, &config); err != nil {
return nil, fmt.Errorf("failed to unmarshal meta config: %w", err)
}
var metaConfig schemas.MetaConfig = &config
return &metaConfig, nil
}
return nil, fmt.Errorf("unsupported provider for meta config: %s", provider)
}

// B. Add to processMetaConfigEnvVars
func (s *ConfigStore) processMetaConfigEnvVars(rawMetaConfig json.RawMessage, provider schemas.ModelProvider) (json.RawMessage, error) {
switch provider {
// ... existing cases
case schemas.YourProvider:
var config meta.YourProviderMetaConfig
if err := json.Unmarshal(rawMetaConfig, &config); err != nil {
return nil, fmt.Errorf("failed to unmarshal meta config: %w", err)
}

// Process each field that might contain env vars
endpoint, envVar, err := s.processEnvValue(config.Endpoint)
if err != nil {
return nil, err
}
if envVar != "" {
s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{
EnvVar: envVar,
Provider: string(provider),
KeyType: "meta_config",
ConfigPath: fmt.Sprintf("providers.%s.meta_config.endpoint", provider),
})
}
config.Endpoint = endpoint

// Process other fields similarly...

return json.Marshal(config)
}
return rawMetaConfig, nil
}

// C. Add to GetProviderConfig for redaction
func (s *ConfigStore) GetProviderConfig(provider schemas.ModelProvider) (*ProviderConfig, error) {
// ... existing code ...

if configCopy.MetaConfig != nil {
switch m := (*configCopy.MetaConfig).(type) {
// ... existing cases
case *meta.YourProviderMetaConfig:
config := *m

// Redact or show env vars for each field
path := fmt.Sprintf("providers.%s.meta_config.endpoint", provider)
if envVar, ok := envVarsByPath[path]; ok {
config.Endpoint = "env." + envVar
} else {
config.Endpoint = RedactKey(config.Endpoint)
}

// Handle other fields...

var metaConfig schemas.MetaConfig = &config
configCopy.MetaConfig = &metaConfig
}
}

return &configCopy, nil
}
```

### **3. Testing HTTP Transport Integration**

Add integration tests in `tests/transports-integrations/`:

```python
# tests/integrations/test_yourprovider.py

def test_yourprovider_config():
config = {
"provider": "yourprovider",
"meta_config": {
"endpoint": "env.YOURPROVIDER_ENDPOINT",
"region": "us-east-1"
}
}
# Test config validation
response = client.post("/v1/providers", json=config)
assert response.status_code == 200

def test_yourprovider_models():
# Test model pattern recognition
response = client.post("/v1/chat/completions", json={
"model": "yourprovider/model-name",
"messages": [{"role": "user", "content": "Hello"}]
})
assert response.status_code == 200
```

Run the tests:

```bash
cd tests/transports-integrations
python -m pytest tests/integrations/ -v
```

### **4. Configuration Example**

Document the configuration format for users:

```json
{
"providers": {
"yourprovider": {
"keys": [
{
"value": "env.YOURPROVIDER_API_KEY",
"models": ["*"]
}
],
"meta_config": {
"endpoint": "env.YOURPROVIDER_ENDPOINT",
"region": "env.YOURPROVIDER_REGION",
"project_id": "env.YOURPROVIDER_PROJECT_ID"
}
}
}
}
```

Note: API key handling is automatic - you only need to implement the meta config processing if your provider requires additional configuration beyond API keys.

---

## 📚 **Documentation Requirements**
Expand Down Expand Up @@ -539,7 +746,6 @@ result, err := client.ChatCompletionRequest(ctx, &schemas.BifrostRequest{
},
},
})
```

## Features

Expand All @@ -556,7 +762,7 @@ result, err := client.ChatCompletionRequest(ctx, &schemas.BifrostRequest{
| ----------------- | ---------------------- | ---------- |
| temperature | temperature | 0.0-2.0 |
| max_tokens | max_tokens | Up to 4096 |
````
```

---

Expand Down Expand Up @@ -740,6 +946,7 @@ func (p *YourProviderProvider) convertTools(tools *[]schemas.Tool) []YourProvide
return providerTools
}
```
````

---

Expand All @@ -759,3 +966,7 @@ func (p *YourProviderProvider) convertTools(tools *[]schemas.Tool) []YourProvide
**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](https://github.com/maximhq/bifrost/discussions) if you need help!

```

```
Comment thread
Pratham-Mishra04 marked this conversation as resolved.
73 changes: 54 additions & 19 deletions transports/bifrost-http/handlers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package handlers
import (
"encoding/json"
"fmt"
"os"
"slices"

"github.com/fasthttp/router"
bifrost "github.com/maximhq/bifrost/core"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/transports/bifrost-http/lib"
"github.com/valyala/fasthttp"
)

Expand All @@ -16,50 +17,84 @@ import (
type ConfigHandler struct {
client *bifrost.Bifrost
logger schemas.Logger
store *lib.ConfigStore
configPath string
}

// NewConfigHandler creates a new handler for configuration management.
// It requires the Bifrost client, a logger, and the path to the config file to be reloaded.
func NewConfigHandler(client *bifrost.Bifrost, logger schemas.Logger, configPath string) *ConfigHandler {
func NewConfigHandler(client *bifrost.Bifrost, logger schemas.Logger, store *lib.ConfigStore, configPath string) *ConfigHandler {
return &ConfigHandler{
client: client,
logger: logger,
store: store,
configPath: configPath,
}
}

// RegisterRoutes registers the configuration-related routes.
// It adds the `PUT /config` endpoint.
func (h *ConfigHandler) RegisterRoutes(r *router.Router) {
r.PUT("/config", h.handleReloadConfig)
r.GET("/config", h.GetConfig)
r.PUT("/config", h.handleUpdateConfig)
r.POST("/config/save", h.SaveConfig)
}

// handleReloadConfig re-reads the configuration file and applies updatable settings.
// GetConfig handles GET /config - Get the current configuration
func (h *ConfigHandler) GetConfig(ctx *fasthttp.RequestCtx) {
config := h.store.ClientConfig
SendJSON(ctx, config, h.logger)
}

// handleUpdateConfig updates the core configuration settings.
// Currently, it supports hot-reloading of the `drop_excess_requests` setting.
// Note that settings like `prometheus_labels` cannot be changed at runtime.
func (h *ConfigHandler) handleReloadConfig(ctx *fasthttp.RequestCtx) {
var config struct {
BifrostSettings struct {
DropExcessRequests *bool `json:"drop_excess_requests,omitempty"`
} `json:"bifrost_settings"`
}
func (h *ConfigHandler) handleUpdateConfig(ctx *fasthttp.RequestCtx) {
var req lib.ClientConfig

data, err := os.ReadFile(h.configPath)
if err != nil {
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to read config file: %v", err), h.logger)
if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
SendError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("Invalid request format: %v", err), h.logger)
return
}

if err := json.Unmarshal(data, &config); err != nil {
SendError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("failed to parse config file: %v", err), h.logger)
return
// Get current config with proper locking
currentConfig := h.store.ClientConfig
updatedConfig := currentConfig

if req.DropExcessRequests != currentConfig.DropExcessRequests {
h.client.UpdateDropExcessRequests(req.DropExcessRequests)
updatedConfig.DropExcessRequests = req.DropExcessRequests
}

if !slices.Equal(req.PrometheusLabels, currentConfig.PrometheusLabels) {
updatedConfig.PrometheusLabels = req.PrometheusLabels
}

if config.BifrostSettings.DropExcessRequests != nil {
h.client.UpdateDropExcessRequests(*config.BifrostSettings.DropExcessRequests)
if req.InitialPoolSize != currentConfig.InitialPoolSize {
updatedConfig.InitialPoolSize = req.InitialPoolSize
}

// Update the store with the new config
h.store.ClientConfig = updatedConfig

ctx.SetStatusCode(fasthttp.StatusOK)
SendJSON(ctx, map[string]interface{}{"status": "config reloaded", "drop_excess_requests": config.BifrostSettings.DropExcessRequests}, h.logger)
SendJSON(ctx, map[string]any{
"status": "success",
"message": "Configuration updated successfully",
}, h.logger)
}

// SaveConfig handles POST /config/save - Persist current configuration to JSON file
func (h *ConfigHandler) SaveConfig(ctx *fasthttp.RequestCtx) {
// Save current configuration back to the original JSON file
if err := h.store.SaveConfig(); err != nil {
h.logger.Warn(fmt.Sprintf("Failed to save configuration: %v", err))
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("Failed to save configuration: %v", err), h.logger)
return
}

SendJSON(ctx, map[string]any{
"status": "success",
"message": "Configuration saved successfully",
}, h.logger)
}
Loading