Adding a new provider
This guide will walk you through creating a provider for DeepIntShield, testing locally, adding it to frontend and CI/CD.
- Fork and Clone:
- Fork the repository: https://github.com/maximhq/deepintshield/
- Clone your fork:
git clone https://github.com/<your_github_username>/deepintshield/
- Initialize:
- Run
make devat the root of the project to set up dependencies and tools.
- Run
Provider Structure
Section titled “Provider Structure”DeepIntShield acts as a gateway:
- Receives a request in a standard format (defined in
core/schemas/). - Converts it to the provider-specific format.
- Sends the request to the provider’s API.
- Receives the provider’s response.
- Converts it back to the standard DeepIntShield response format.
To implement a new provider, first step is to add the provider name in core/schemas/deepintshield.go
- Add it in const declaration of
ModelProvidertype in the format[ProviderName] ModelProvider = "[providername]" - Then, add it in the StandardProviders array in the same file and if needed add in SupportedBaseProviders.
Next, you will create a directory in core/providers/ and populate it with specific files following our strict conventions.
Directory Structure
Section titled “Directory Structure”The directory structure differs based on whether the provider is OpenAI API compatible:
Non-OpenAI-compatible Providers
Section titled “Non-OpenAI-compatible Providers”If the provider has a custom API format (not OpenAI-compatible), create a new folder core/providers/[provider_name]/.
Complete Reference Structure (see core/providers/huggingface/):
core/providers/└─ [provider_name]/ # e.g., huggingface/ ├── [provider_name].go # Main provider implementation (REQUIRED) ├── [provider_name]_test.go # Provider automated tests (REQUIRED) ├── types.go # ALL provider-specific types/structs (REQUIRED) ├── utils.go # ALL utility functions and constants (REQUIRED) ├── errors.go # Error handling (if supported) ├── chat.go # Converters for Chat Completion (if supported) ├── speech.go # Converters for Text-to-Speech (if supported) ├── transcription.go # Converters for Speech-to-Text (if supported) ├── embedding.go # Converters for Embeddings (if supported) ├── images.go # Converters for Images (if supported) ├── batches.go # Converters for Batches (if supported) ├── files.go # Converters for Files (if supported) ├── models.go # Converters for List Models (if supported) └── responses.go # Converters for Response Models (if supported)File Creation Order (CRITICAL):
- Create
types.goFIRST - Define all provider-specific request/response structures - Create
utils.goSECOND - Define constants, base URLs, and helper functions - Create feature files (
chat.go,embedding.go, etc.) THIRD - Implement converters - Create
[provider_name].goFOURTH - Wire everything together - Create
[provider_name]_test.goLAST - Add comprehensive tests
OpenAI-compatible Providers
Section titled “OpenAI-compatible Providers”If the provider is OpenAI API compatible, you only need a minimal structure:
Minimal Reference Structure (see core/providers/cerebras/):
core/providers/└─ [provider_name]/ # e.g., cerebras/ ├── [provider_name].go # Main provider implementation (REQUIRED) └── [provider_name]_test.go # Provider automated tests (REQUIRED)These providers reuse the OpenAI converter logic from core/providers/openai/.
File Conventions & Responsibilities
Section titled “File Conventions & Responsibilities”We enforce strict separation of concerns to keep providers maintainable and consistent. Each file has a specific purpose and must follow these rules.
1. types.go (The Data Layer)
Section titled “1. types.go (The Data Layer)”CRITICAL RULE: All provider-specific structs (Request/Response DTOs) MUST go here. NEVER define types in other files.
Naming Convention:
- Prefix ALL types with the provider name in PascalCase:
[ProviderName][StructName] - Examples:
HuggingFaceChatRequest,HuggingFaceModel,HuggingFaceToolCall
JSON Tag Requirements:
- Use
jsontags that exactly match the provider’s API field names - Use
omitemptyfor optional fields - Use pointers for nullable fields to distinguish between “not set” and “zero value”
Organization:
- Group related types together with comments (e.g.,
// # CHAT TYPES,// # MODELS TYPES) - Define request types before response types
- Keep nested types near their parent types
Generic Example Structure:
package providername
import "encoding/json"
// # MODELS TYPES
// ProviderNameModel represents a model from the provider's catalogtype ProviderNameModel struct { ID string `json:"id"` Name string `json:"name"` Description *string `json:"description,omitempty"` CreatedAt string `json:"created_at"`}
// # CHAT TYPES
// ProviderNameChatRequest represents the request payload for chat completiontype ProviderNameChatRequest struct { Model string `json:"model" validate:"required"` Messages []ProviderNameChatMessage `json:"messages"` MaxTokens *int `json:"max_tokens,omitempty"` Temperature *float64 `json:"temperature,omitempty"` TopP *float64 `json:"top_p,omitempty"` Stream *bool `json:"stream,omitempty"` Tools []ProviderNameTool `json:"tools,omitempty"` ToolChoice json.RawMessage `json:"tool_choice,omitempty"` // flexible: enum or object}
// ProviderNameChatMessage represents a single message in a chattype ProviderNameChatMessage struct { Role *string `json:"role,omitempty"` Content json.RawMessage `json:"content,omitempty"` // flexible: string or []content items Name *string `json:"name,omitempty"` ToolCalls []ProviderNameToolCall `json:"tool_calls,omitempty"`}Key Points:
- Use
json.RawMessagefor fields that can be multiple types (string or object/array) - Use pointers (
*float64,*bool) for optional fields - Add validation tags when appropriate (
validate:"required") - Include comments for complex or non-obvious types
2. utils.go (The Helper Layer)
Section titled “2. utils.go (The Helper Layer)”CRITICAL RULE: All shared utility functions, constants, and configuration helpers MUST go here.
Constants Naming Convention:
- Use camelCase for unexported constants:
defaultInferenceBaseURL - Use SCREAMING_SNAKE_CASE for exported constants:
INFERENCE_PROVIDERS - Group related constants together
Function Naming Convention:
- Use camelCase for unexported helpers:
convertTypeToLowerCase,parseErrorResponse - Use PascalCase for exported utilities:
ConfigureProxy,BuildHeaders
Required Contents:
- Base URLs and API endpoints
- Default values and limits
- Provider-specific constants (like model names, inference providers)
- HTTP request helpers (headers, authentication)
- Error handling utilities
- Data transformation helpers
Generic Example Structure:
package providername
import ( "context" "strings" "time"
providerUtils "github.com/maximhq/deepintshield/core/providers/utils" schemas "github.com/maximhq/deepintshield/core/schemas" "github.com/valyala/fasthttp")
const ( defaultBaseURL = "https://api.provider.com")
const ( defaultLimit = 100 maxLimit = 500)
// Helper to parse provider-specific model formatfunc parseModelString(model string) (string, string) { parts := strings.Split(model, "/") if len(parts) == 2 { return parts[0], parts[1] } return "", model}
// Helper to convert type fields to lowercase in JSON schemasfunc convertTypeToLowerCase(schema map[string]interface{}) { // Implementation for schema normalization...}Organization Tips:
- Group constants by category (URLs, limits, enums)
- Document the source/reason for constants (API docs, limits)
- Keep helper functions focused and single-purpose
- Include error handling in utility functions
3. [provider_name].go (The Controller Layer)
Section titled “3. [provider_name].go (The Controller Layer)”CRITICAL RULE: This is the orchestration layer. It coordinates the request flow but delegates all conversion logic to feature files.
Naming Convention:
- Provider struct:
[ProviderName]Provider(e.g.,HuggingFaceProvider) - Constructor:
New[ProviderName]Provider(config *schemas.ProviderConfig, logger schemas.Logger) - Methods: Match interface exactly:
ChatCompletion,ChatCompletionStream,ListModels, etc.
Required Struct Fields (in order):
type [ProviderName]Provider struct { logger schemas.Logger // ALWAYS first client *fasthttp.Client // HTTP client networkConfig schemas.NetworkConfig // Network settings sendBackRawResponse bool // Debug flag customProviderConfig *schemas.CustomProviderConfig // Optional}Constructor Requirements:
- Accept
*schemas.ProviderConfigandschemas.Logger - Call
config.CheckAndSetDefaults() - Initialize
fasthttp.Clientwith timeouts and limits - Configure proxy using
providerUtils.ConfigureProxy - Set default BaseURL if not provided
- Trim trailing slashes from BaseURL
- Pre-warm response pools if using sync.Pool
- Return provider instance (and error for OpenAI-compatible providers)
Generic Example Structure:
package providername
import ( "context" "strings" "sync" "time"
"github.com/bytedance/sonic" providerUtils "github.com/maximhq/deepintshield/core/providers/utils" schemas "github.com/maximhq/deepintshield/core/schemas" "github.com/valyala/fasthttp")
// ProviderNameProvider implements the Provider interfacetype ProviderNameProvider struct { logger schemas.Logger client *fasthttp.Client networkConfig schemas.NetworkConfig sendBackRawResponse bool customProviderConfig *schemas.CustomProviderConfig}
// Response pools for memory efficiency (optional but recommended)var chatResponsePool = sync.Pool{ New: func() any { return &ProviderNameChatResponse{} },}
// NewProviderNameProvider creates a new provider instancefunc NewProviderNameProvider(config *schemas.ProviderConfig, logger schemas.Logger) *ProviderNameProvider { config.CheckAndSetDefaults()
client := &fasthttp.Client{ ReadTimeout: time.Second * time.Duration(config.NetworkConfig.DefaultRequestTimeoutInSeconds), WriteTimeout: time.Second * time.Duration(config.NetworkConfig.DefaultRequestTimeoutInSeconds), MaxConnsPerHost: 5000, MaxIdleConnDuration: 30 * time.Second, MaxConnWaitTimeout: 10 * time.Second, }
// Configure proxy if provided client = providerUtils.ConfigureProxy(client, config.ProxyConfig, logger)
// Set default BaseURL if not provided if config.NetworkConfig.BaseURL == "" { config.NetworkConfig.BaseURL = defaultBaseURL } config.NetworkConfig.BaseURL = strings.TrimRight(config.NetworkConfig.BaseURL, "/")
// Pre-warm response pools (optional optimization) for i := 0; i < config.ConcurrencyAndBufferSize.Concurrency; i++ { chatResponsePool.Put(&ProviderNameChatResponse{}) }
return &ProviderNameProvider{ logger: logger, client: client, networkConfig: config.NetworkConfig, sendBackRawResponse: config.SendBackRawResponse, customProviderConfig: config.CustomProviderConfig, }}
// GetProviderKey returns the provider identifierfunc (provider *ProviderNameProvider) GetProviderKey() schemas.ModelProvider { return schemas.ProviderName}Method Implementation Pattern (STRICT ORDER):
- Validation: Check request validity (optional, usually done in converter)
- Convert Request: Call
To[Provider][Feature]Request()from feature file - Build HTTP Request: Construct URL, headers, body
- Execute Request: Use
provider.client.Do()or streaming logic - Handle Errors: Parse and convert provider errors to
schemas.DeepIntShieldError - Convert Response: Call
ToDeepIntShield[Feature]Response()from feature file - Return Result: Return DeepIntShield response or error
Example Method (generic pattern):
func (p *ProviderNameProvider) ChatCompletion( ctx context.Context, key schemas.Key, request *schemas.DeepIntShieldChatRequest,) (*schemas.DeepIntShieldChatResponse, *schemas.DeepIntShieldError) { // 1. Convert Request providerReq := ToProviderNameChatCompletionRequest(request)
// 2. Build HTTP Request body, err := sonic.Marshal(providerReq) if err != nil { return nil, &schemas.DeepIntShieldError{/* ... */} }
req := fasthttp.AcquireRequest() defer fasthttp.ReleaseRequest(req)
req.SetRequestURI(p.networkConfig.BaseURL + "/v1/chat/completions") req.Header.SetMethod("POST") req.Header.Set("Authorization", "Bearer "+key.Value) req.Header.Set("Content-Type", "application/json") req.SetBody(body)
// 3. Execute Request resp := fasthttp.AcquireResponse() defer fasthttp.ReleaseResponse(resp)
if err := p.client.Do(req, resp); err != nil { return nil, &schemas.DeepIntShieldError{/* ... */} }
// 4. Handle Errors if resp.StatusCode() != 200 { return nil, parseErrorResponse(resp.Body()) }
// 5. Convert Response var providerResp ProviderNameChatResponse if err := sonic.Unmarshal(resp.Body(), &providerResp); err != nil { return nil, &schemas.DeepIntShieldError{/* ... */} }
return ToDeepIntShieldChatResponse(&providerResp)}4. Feature Files (chat.go, embedding.go, speech.go, etc.) (The Converter Layer)
Section titled “4. Feature Files (chat.go, embedding.go, speech.go, etc.) (The Converter Layer)”CRITICAL RULE: These files contain pure transformation functions ONLY. No HTTP calls, no logging, no side effects.
File Naming Convention:
chat.go- Chat completion convertersembedding.go- Embedding convertersspeech.go- Text-to-speech converterstranscription.go- Speech-to-text convertersmodels.go- List models convertersresponses.go- Response format converters
Function Naming Convention (STRICT):
- To Provider Format:
To[ProviderName][Feature]Request(bifrostReq *schemas.DeepIntShield[Feature]Request) *[ProviderName][Feature]Request - To DeepIntShield Format:
ToDeepIntShield[Feature]Response(providerResp *[ProviderName][Feature]Response) (*schemas.DeepIntShield[Feature]Response, *schemas.DeepIntShieldError)
Examples:
ToHuggingFaceChatCompletionRequestToDeepIntShieldChatResponseToHuggingFaceEmbeddingRequestToDeepIntShieldEmbeddingResponse
Required Converter Pairs (if feature is supported):
- Request converter: DeepIntShield → Provider
- Response converter: Provider → DeepIntShield
Real Example from core/providers/huggingface/chat.go:
package huggingface
import ( "encoding/json" "fmt"
"github.com/bytedance/sonic" schemas "github.com/maximhq/deepintshield/core/schemas")
// ToHuggingFaceChatCompletionRequest converts a DeepIntShield chat request to HuggingFace formatfunc ToHuggingFaceChatCompletionRequest(bifrostReq *schemas.DeepIntShieldChatRequest) *HuggingFaceChatRequest { if bifrostReq == nil || bifrostReq.Input == nil { return nil }
// Convert messages from DeepIntShield format to HuggingFace format hfMessages := make([]HuggingFaceChatMessage, 0, len(bifrostReq.Input)) for _, msg := range bifrostReq.Input { hfMsg := HuggingFaceChatMessage{}
// Set role if msg.Role != "" { role := string(msg.Role) hfMsg.Role = &role }
// Set name if present if msg.Name != nil { hfMsg.Name = msg.Name }
// Convert content (can be string or structured blocks) if msg.Content != nil { if msg.Content.ContentStr != nil { // Simple string content contentJSON, _ := sonic.Marshal(*msg.Content.ContentStr) hfMsg.Content = json.RawMessage(contentJSON) } else if msg.Content.ContentBlocks != nil { // Structured content blocks (text, images, etc.) contentItems := make([]HuggingFaceContentItem, 0, len(msg.Content.ContentBlocks)) for _, block := range msg.Content.ContentBlocks { item := HuggingFaceContentItem{} blockType := string(block.Type) item.Type = &blockType
switch block.Type { case schemas.ChatContentBlockTypeText: if block.Text != nil { item.Text = block.Text } case schemas.ChatContentBlockTypeImage: if block.ImageURLStruct != nil { item.ImageURL = &HuggingFaceImageRef{ URL: block.ImageURLStruct.URL, } } } contentItems = append(contentItems, item) } contentJSON, _ := sonic.Marshal(contentItems) hfMsg.Content = json.RawMessage(contentJSON) } }
// Handle tool calls for assistant messages if msg.ChatAssistantMessage != nil && len(msg.ChatAssistantMessage.ToolCalls) > 0 { hfToolCalls := make([]HuggingFaceToolCall, 0, len(msg.ChatAssistantMessage.ToolCalls)) for _, tc := range msg.ChatAssistantMessage.ToolCalls { hfToolCall := HuggingFaceToolCall{ ID: tc.ID, Type: tc.Type, Function: HuggingFaceFunction{ Name: *tc.Function.Name, Arguments: tc.Function.Arguments, }, } hfToolCalls = append(hfToolCalls, hfToolCall) } hfMsg.ToolCalls = hfToolCalls }
hfMessages = append(hfMessages, hfMsg) }
// Build the request hfReq := &HuggingFaceChatRequest{ Model: bifrostReq.Model, Messages: hfMessages, }
// Map parameters if bifrostReq.Params != nil { params := bifrostReq.Params
// Map standard parameters if params.Temperature != nil { hfReq.Temperature = params.Temperature } if params.MaxTokens != nil { hfReq.MaxTokens = params.MaxTokens } // ... other standard parameters
// Handle provider-specific ExtraParams if params.ExtraParams != nil { if customParam, ok := params.ExtraParams["custom_param"].(string); ok { hfReq.CustomParam = &customParam } } }
return hfReq}Generic Example - Embedding Converter:
package providername
import ( "github.com/maximhq/deepintshield/core/schemas")
// ToProviderNameEmbeddingRequest converts a DeepIntShield embedding request to provider formatfunc ToProviderNameEmbeddingRequest(bifrostReq *schemas.DeepIntShieldEmbeddingRequest) *ProviderNameEmbeddingRequest { if bifrostReq == nil { return nil }
providerReq := &ProviderNameEmbeddingRequest{ Model: bifrostReq.Model, }
// Convert input if bifrostReq.Input != nil { if bifrostReq.Input.Text != nil { providerReq.Input = *bifrostReq.Input.Text } else if bifrostReq.Input.Texts != nil { providerReq.Input = bifrostReq.Input.Texts } }
// Map provider-specific parameters from ExtraParams if bifrostReq.Params != nil && bifrostReq.Params.ExtraParams != nil { if normalize, ok := bifrostReq.Params.ExtraParams["normalize"].(bool); ok { providerReq.Normalize = &normalize } }
return providerReq}Generic Example - List Models Converter:
package providername
import ( "strings" schemas "github.com/maximhq/deepintshield/core/schemas")
// ToDeepIntShieldListModelsResponse converts provider models list to DeepIntShield formatfunc ToDeepIntShieldListModelsResponse( providerResp *ProviderNameListModelsResponse, providerKey schemas.ModelProvider,) *schemas.DeepIntShieldListModelsResponse { if providerResp == nil { return nil }
bifrostResponse := &schemas.DeepIntShieldListModelsResponse{ Data: make([]schemas.Model, 0, len(providerResp.Models)), }
for _, model := range providerResp.Models { // Determine supported methods based on model capabilities supported := determineSupportedMethods(model) if len(supported) == 0 { continue }
newModel := schemas.Model{ ID: model.ID, Name: &model.Name, SupportedMethods: supported, }
bifrostResponse.Data = append(bifrostResponse.Data, newModel) }
return bifrostResponse}
// Helper to determine which DeepIntShield methods a model supportsfunc determineSupportedMethods(model ProviderNameModel) []string { methods := []string{}
// Logic to derive supported methods from model metadata // This varies by provider
return methods}Converter Best Practices:
- Always check for nil inputs at the start
- Pre-allocate slices with known capacity for performance
- Handle optional fields using pointers in types
- Use ExtraParams for provider-specific fields not in standard schema
- Document complex conversions with inline comments
- Keep functions pure - no side effects, no external state
- Return errors when conversion fails (for response converters)
OpenAI-compatible Providers
Section titled “OpenAI-compatible Providers”If you are implementing a provider that is strictly OpenAI API compatible, the implementation is significantly simpler. You reuse all the conversion logic from core/providers/openai/.
When to Use This Approach:
- Provider’s API is 100% OpenAI-compatible
- Same request/response formats
- Same endpoint paths (
/v1/chat/completions,/v1/completions, etc.) - Only differences are: base URL, authentication, and possibly some extra headers
Complete Reference: core/providers/cerebras/cerebras.go
Step 1: Create the Provider File
Section titled “Step 1: Create the Provider File”Create core/providers/[provider_name]/[provider_name].go:
// Package cerebras implements the Cerebras LLM provider.package cerebras
import ( "context" "strings" "time"
"github.com/maximhq/deepintshield/core/providers/openai" providerUtils "github.com/maximhq/deepintshield/core/providers/utils" schemas "github.com/maximhq/deepintshield/core/schemas" "github.com/valyala/fasthttp")
// CerebrasProvider implements the Provider interface for Cerebras's API.type CerebrasProvider struct { logger schemas.Logger // Logger for provider operations client *fasthttp.Client // HTTP client for API requests networkConfig schemas.NetworkConfig // Network configuration including extra headers sendBackRawResponse bool // Whether to include raw response in DeepIntShieldResponse}
// NewCerebrasProvider creates a new Cerebras provider instance.// It initializes the HTTP client with the provided configuration and sets up response pools.func NewCerebrasProvider(config *schemas.ProviderConfig, logger schemas.Logger) (*CerebrasProvider, error) { config.CheckAndSetDefaults()
client := &fasthttp.Client{ ReadTimeout: time.Second * time.Duration(config.NetworkConfig.DefaultRequestTimeoutInSeconds), WriteTimeout: time.Second * time.Duration(config.NetworkConfig.DefaultRequestTimeoutInSeconds), MaxConnsPerHost: 5000, MaxIdleConnDuration: 30 * time.Second, MaxConnWaitTimeout: 10 * time.Second, }
// Configure proxy if provided client = providerUtils.ConfigureProxy(client, config.ProxyConfig, logger)
// Set default BaseURL if not provided if config.NetworkConfig.BaseURL == "" { config.NetworkConfig.BaseURL = "https://api.cerebras.ai" } config.NetworkConfig.BaseURL = strings.TrimRight(config.NetworkConfig.BaseURL, "/")
return &CerebrasProvider{ logger: logger, client: client, networkConfig: config.NetworkConfig, sendBackRawResponse: config.SendBackRawResponse, }, nil}
// GetProviderKey returns the provider identifier for Cerebras.func (provider *CerebrasProvider) GetProviderKey() schemas.ModelProvider { return schemas.Cerebras}Step 2: Implement Required Methods Using OpenAI Handlers
Section titled “Step 2: Implement Required Methods Using OpenAI Handlers”For each supported feature, delegate to the corresponding OpenAI handler:
Chat Completion (Non-Streaming):
// ChatCompletion performs a chat completion request to the Cerebras API.func (provider *CerebrasProvider) ChatCompletion( ctx context.Context, key schemas.Key, request *schemas.DeepIntShieldChatRequest,) (*schemas.DeepIntShieldChatResponse, *schemas.DeepIntShieldError) { return openai.HandleOpenAIChatCompletionRequest( ctx, provider.client, provider.networkConfig.BaseURL+providerUtils.GetPathFromContext(ctx, "/v1/chat/completions"), request, key, provider.networkConfig.ExtraHeaders, providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse), provider.GetProviderKey(), provider.logger, )}Chat Completion (Streaming):
// ChatCompletionStream performs a streaming chat completion request to the Cerebras API.// It supports real-time streaming of responses using Server-Sent Events (SSE).func (provider *CerebrasProvider) ChatCompletionStream( ctx context.Context, postHookRunner schemas.PostHookRunner, key schemas.Key, request *schemas.DeepIntShieldChatRequest,) (chan *schemas.DeepIntShieldStream, *schemas.DeepIntShieldError) { var authHeader map[string]string if key.Value != "" { authHeader = map[string]string{"Authorization": "Bearer " + key.Value} }
// Use shared OpenAI-compatible streaming logic return openai.HandleOpenAIChatCompletionStreaming( ctx, provider.client, provider.networkConfig.BaseURL+"/v1/chat/completions", request, authHeader, provider.networkConfig.ExtraHeaders, providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse), provider.GetProviderKey(), postHookRunner, nil, // customStreamParser - use nil for standard OpenAI format provider.logger, )}Text Completion (Non-Streaming):
// TextCompletion performs a text completion request to Cerebras's API.func (provider *CerebrasProvider) TextCompletion( ctx context.Context, key schemas.Key, request *schemas.DeepIntShieldTextCompletionRequest,) (*schemas.DeepIntShieldTextCompletionResponse, *schemas.DeepIntShieldError) { return openai.HandleOpenAITextCompletionRequest( ctx, provider.client, provider.networkConfig.BaseURL+providerUtils.GetPathFromContext(ctx, "/v1/completions"), request, key, provider.networkConfig.ExtraHeaders, provider.GetProviderKey(), providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse), provider.logger, )}Text Completion (Streaming):
// TextCompletionStream performs a streaming text completion request to Cerebras's API.func (provider *CerebrasProvider) TextCompletionStream( ctx context.Context, postHookRunner schemas.PostHookRunner, key schemas.Key, request *schemas.DeepIntShieldTextCompletionRequest,) (chan *schemas.DeepIntShieldStream, *schemas.DeepIntShieldError) { var authHeader map[string]string if key.Value != "" { authHeader = map[string]string{"Authorization": "Bearer " + key.Value} }
return openai.HandleOpenAITextCompletionStreaming( ctx, provider.client, provider.networkConfig.BaseURL+"/v1/completions", request, authHeader, provider.networkConfig.ExtraHeaders, providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse), provider.GetProviderKey(), postHookRunner, nil, // customStreamParser provider.logger, )}List Models:
// ListModels performs a list models request to Cerebras's API.func (provider *CerebrasProvider) ListModels( ctx context.Context, keys []schemas.Key, request *schemas.DeepIntShieldListModelsRequest,) (*schemas.DeepIntShieldListModelsResponse, *schemas.DeepIntShieldError) { return openai.HandleOpenAIListModelsRequest( ctx, provider.client, request, provider.networkConfig.BaseURL+providerUtils.GetPathFromContext(ctx, "/v1/models"), keys, provider.networkConfig.ExtraHeaders, provider.GetProviderKey(), providerUtils.ShouldSendBackRawResponse(ctx, provider.sendBackRawResponse), provider.logger, )}Step 3: Implement Unsupported Methods
Section titled “Step 3: Implement Unsupported Methods”For features not supported by the provider, return appropriate errors:
// Embedding is not supported by Cerebrasfunc (provider *CerebrasProvider) Embedding( ctx context.Context, key schemas.Key, request *schemas.DeepIntShieldEmbeddingRequest,) (*schemas.DeepIntShieldEmbeddingResponse, *schemas.DeepIntShieldError) { return nil, &schemas.DeepIntShieldError{ StatusCode: http.StatusNotImplemented, Message: "Embedding is not supported by Cerebras", Type: "unsupported_feature", }}
// Speech is not supported by Cerebrasfunc (provider *CerebrasProvider) Speech( ctx context.Context, key schemas.Key, request *schemas.DeepIntShieldSpeechRequest,) (*schemas.DeepIntShieldSpeechResponse, *schemas.DeepIntShieldError) { return nil, &schemas.DeepIntShieldError{ StatusCode: http.StatusNotImplemented, Message: "Speech synthesis is not supported by Cerebras", Type: "unsupported_feature", }}
// SpeechStream is not supported by Cerebrasfunc (provider *CerebrasProvider) SpeechStream( ctx context.Context, postHookRunner schemas.PostHookRunner, key schemas.Key, request *schemas.DeepIntShieldSpeechRequest,) (chan *schemas.DeepIntShieldStream, *schemas.DeepIntShieldError) { return nil, &schemas.DeepIntShieldError{ StatusCode: http.StatusNotImplemented, Message: "Speech synthesis streaming is not supported by Cerebras", Type: "unsupported_feature", }}
// Transcription is not supported by Cerebrasfunc (provider *CerebrasProvider) Transcription( ctx context.Context, key schemas.Key, request *schemas.DeepIntShieldTranscriptionRequest,) (*schemas.DeepIntShieldTranscriptionResponse, *schemas.DeepIntShieldError) { return nil, &schemas.DeepIntShieldError{ StatusCode: http.StatusNotImplemented, Message: "Transcription is not supported by Cerebras", Type: "unsupported_feature", }}
// TranscriptionStream is not supported by Cerebrasfunc (provider *CerebrasProvider) TranscriptionStream( ctx context.Context, postHookRunner schemas.PostHookRunner, key schemas.Key, request *schemas.DeepIntShieldTranscriptionRequest,) (chan *schemas.DeepIntShieldStream, *schemas.DeepIntShieldError) { return nil, &schemas.DeepIntShieldError{ StatusCode: http.StatusNotImplemented, Message: "Transcription streaming is not supported by Cerebras", Type: "unsupported_feature", }}
// Responses is not supported by Cerebrasfunc (provider *CerebrasProvider) Responses( ctx context.Context, key schemas.Key, request *schemas.DeepIntShieldResponsesRequest,) (*schemas.DeepIntShieldResponsesResponse, *schemas.DeepIntShieldError) { return nil, &schemas.DeepIntShieldError{ StatusCode: http.StatusNotImplemented, Message: "Responses is not supported by Cerebras", Type: "unsupported_feature", }}
// ResponsesStream is not supported by Cerebrasfunc (provider *CerebrasProvider) ResponsesStream( ctx context.Context, postHookRunner schemas.PostHookRunner, key schemas.Key, request *schemas.DeepIntShieldResponsesRequest,) (chan *schemas.DeepIntShieldStream, *schemas.DeepIntShieldError) { return nil, &schemas.DeepIntShieldError{ StatusCode: http.StatusNotImplemented, Message: "Responses streaming is not supported by Cerebras", Type: "unsupported_feature", }}Key Points for OpenAI-compatible Providers
Section titled “Key Points for OpenAI-compatible Providers”Constructor Differences:
- Returns
(*[ProviderName]Provider, error)instead of just*[ProviderName]Provider - Must set a default
BaseURLspecific to the provider - Must trim trailing slashes from
BaseURL
URL Construction:
- Use
provider.networkConfig.BaseURL + "/v1/[endpoint]"for direct paths - Use
providerUtils.GetPathFromContext(ctx, "/v1/[endpoint]")when path might be overridden in context
Authentication Headers:
- Create
authHeader map[string]stringwithAuthorization: Bearer {key} - Pass to OpenAI handlers separately from
ExtraHeaders
Custom Stream Parsers:
- Pass
nilforcustomStreamParserif using standard OpenAI SSE format - Only implement custom parser if provider uses non-standard streaming format
Error Handling:
- OpenAI handlers return
*schemas.DeepIntShieldError- propagate directly - For unsupported features, return custom error with
StatusNotImplemented
Advantages of This Approach:
- Automatic updates - benefits from OpenAI handler improvements
- Consistent behavior - same conversion logic as OpenAI
- Easy maintenance - only provider-specific config in your file
Implementation Steps
Section titled “Implementation Steps”Follow this exact order when implementing a new provider.
For Non-OpenAI-compatible Providers
Section titled “For Non-OpenAI-compatible Providers”Phase 1: Research & Planning (Before Writing Code)
Section titled “Phase 1: Research & Planning (Before Writing Code)”-
Study the Provider’s API Documentation:
- Identify all supported endpoints (chat, embeddings, speech, etc.)
- Note authentication method (API key, bearer token, custom headers)
- Document base URL and endpoint paths
- List all request/response fields
- Identify provider-specific parameters not in OpenAI schema
-
Create a Mapping Document (recommended):
# Provider: [ProviderName]## Authentication- Method: Bearer token / API key in header- Header name: Authorization / X-API-Key## Base URL- Production: https://api.provider.com- Staging: https://staging.provider.com (if applicable)## Endpoints- Chat Completions: POST /v1/chat/completions- Embeddings: POST /v1/embeddings- Models: GET /v1/models## Request Fields### Chat Completions- model (required): string- messages (required): array- temperature (optional): float- max_tokens (optional): int- [provider_specific_field] (optional): type## Response Fields### Chat Completions- id: string- choices: array- usage: object- [provider_specific_field]: type
Phase 2: Create Directory Structure
Section titled “Phase 2: Create Directory Structure”- Create Provider Directory:
Terminal window mkdir -p core/providers/[provider_name]cd core/providers/[provider_name]
Phase 3: Define Types (types.go)
Section titled “Phase 3: Define Types (types.go)”-
Create
types.go- Define ALL Provider-Specific Types:Order of Type Definitions:
package [provider_name]import "encoding/json"// # MODELS TYPES// Define model-related types firsttype [ProviderName]Model struct { ... }type [ProviderName]ListModelsResponse struct { ... }// # CHAT TYPES// Define chat-related typestype [ProviderName]ChatRequest struct { ... }type [ProviderName]ChatResponse struct { ... }type [ProviderName]ChatMessage struct { ... }type [ProviderName]ChatChoice struct { ... }// # EMBEDDING TYPES// Define embedding-related typestype [ProviderName]EmbeddingRequest struct { ... }type [ProviderName]EmbeddingResponse struct { ... }// # SPEECH TYPES (if applicable)// Define speech-related types// # TRANSCRIPTION TYPES (if applicable)// Define transcription-related types// # ERROR TYPES// Define error response typestype [ProviderName]ErrorResponse struct { ... }Type Naming Checklist:
- ✅ All types prefixed with provider name:
HuggingFaceChatRequest - ✅ JSON tags match provider API exactly:
json:"model_name" - ✅ Optional fields use
omitempty:json:"temperature,omitempty" - ✅ Nullable fields use pointers:
*float64,*string - ✅ Flexible fields use
json.RawMessage:Content json.RawMessage - ✅ Required fields have validation tags:
validate:"required"
- ✅ All types prefixed with provider name:
Phase 4: Define Utilities (utils.go)
Section titled “Phase 4: Define Utilities (utils.go)”-
Create
utils.go- Define Constants and Helper Functions:Order of Definitions:
package [provider_name]import ("context""encoding/json""fmt"providerUtils "github.com/maximhq/deepintshield/core/providers/utils"schemas "github.com/maximhq/deepintshield/core/schemas""github.com/valyala/fasthttp")// 1. BASE URLs (ALWAYS FIRST)const (defaultBaseURL = "https://api.provider.com"alternateURL = "https://alternate.provider.com")// 2. DEFAULT VALUES AND LIMITSconst (defaultTimeout = 60maxRequestSize = 1024 * 1024 * 10 // 10MBdefaultModelLimit = 100maxConcurrentCalls = 5000)// 3. PROVIDER-SPECIFIC ENUMS/CONSTANTSconst (providerVersion = "v1"apiVersion = "2024-01")// 4. CUSTOM TYPES FOR CONSTANTS (if needed)type inferenceProvider stringconst (providerA inferenceProvider = "provider-a"providerB inferenceProvider = "provider-b")// 5. HELPER FUNCTIONS// Function to build authentication headersfunc buildAuthHeaders(apiKey string) map[string]string { ... }// Function to parse error responsesfunc parseErrorResponse(body []byte) *schemas.DeepIntShieldError { ... }// Function to validate model namesfunc validateModelName(model string) error { ... }// Function to split composite model identifiersfunc splitModelProvider(model string) (provider, modelName string) { ... }Utility Function Checklist:
- ✅ All base URLs defined as constants
- ✅ Helper functions use camelCase (unexported) or PascalCase (exported)
- ✅ Error handling utilities included
- ✅ HTTP header builders included
- ✅ Constants grouped logically with comments
Phase 5: Implement Converters (Feature Files)
Section titled “Phase 5: Implement Converters (Feature Files)”-
Create Feature Files in Order of Complexity (simplest first):
a. Create
models.go(if supported):package [provider_name]import ("fmt"schemas "github.com/maximhq/deepintshield/core/schemas")// ToDeepIntShieldListModelsResponse converts provider models to DeepIntShield formatfunc (response *[ProviderName]ListModelsResponse) ToDeepIntShieldListModelsResponse(providerKey schemas.ModelProvider,) *schemas.DeepIntShieldListModelsResponse {if response == nil {return nil}bifrostResponse := &schemas.DeepIntShieldListModelsResponse{Data: make([]schemas.Model, 0, len(response.Models)),}for _, model := range response.Models {// Validationif model.ID == "" {continue}// Conversion logicbifrostModel := schemas.Model{ID: fmt.Sprintf("%s/%s", providerKey, model.ID),Name: &model.Name,SupportedMethods: deriveSupportedMethods(model),}bifrostResponse.Data = append(bifrostResponse.Data, bifrostModel)}return bifrostResponse}// Helper function to determine supported methodsfunc deriveSupportedMethods(model [ProviderName]Model) []string {// Implementation}b. Create
embedding.go(if supported):package [provider_name]import schemas "github.com/maximhq/deepintshield/core/schemas"// To[ProviderName]EmbeddingRequest converts DeepIntShield request to provider formatfunc To[ProviderName]EmbeddingRequest(bifrostReq *schemas.DeepIntShieldEmbeddingRequest,) *[ProviderName]EmbeddingRequest {if bifrostReq == nil {return nil}providerReq := &[ProviderName]EmbeddingRequest{Model: bifrostReq.Model,}// Convert inputif bifrostReq.Input != nil {if bifrostReq.Input.Text != nil {providerReq.Input = *bifrostReq.Input.Text} else if bifrostReq.Input.Texts != nil {providerReq.Input = bifrostReq.Input.Texts}}// Map parametersif bifrostReq.Params != nil {// Standard parametersif bifrostReq.Params.Dimensions != nil {providerReq.Dimensions = bifrostReq.Params.Dimensions}// Provider-specific parameters from ExtraParamsif bifrostReq.Params.ExtraParams != nil {if val, ok := bifrostReq.Params.ExtraParams["provider_param"].(string); ok {providerReq.ProviderParam = &val}}}return providerReq}// ToDeepIntShieldEmbeddingResponse converts provider response to DeepIntShield formatfunc ToDeepIntShieldEmbeddingResponse(providerResp *[ProviderName]EmbeddingResponse,) (*schemas.DeepIntShieldEmbeddingResponse, *schemas.DeepIntShieldError) {if providerResp == nil {return nil, &schemas.DeepIntShieldError{Message: "Provider response is nil",Type: "invalid_response",}}bifrostResp := &schemas.DeepIntShieldEmbeddingResponse{Data: make([]schemas.EmbeddingData, 0, len(providerResp.Data)),}for i, embedding := range providerResp.Data {bifrostResp.Data = append(bifrostResp.Data, schemas.EmbeddingData{Index: i,Embedding: embedding.Values,})}// Map usage if availableif providerResp.Usage != nil {bifrostResp.Usage = &schemas.Usage{PromptTokens: providerResp.Usage.InputTokens,TotalTokens: providerResp.Usage.TotalTokens,}}return bifrostResp, nil}c. Create
chat.go(most complex):package [provider_name]import ("encoding/json""github.com/bytedance/sonic"schemas "github.com/maximhq/deepintshield/core/schemas")// To[ProviderName]ChatCompletionRequest converts DeepIntShield chat request to provider formatfunc To[ProviderName]ChatCompletionRequest(bifrostReq *schemas.DeepIntShieldChatRequest,) *[ProviderName]ChatRequest {if bifrostReq == nil || bifrostReq.Input == nil {return nil}// Convert messagesproviderMessages := make([][ProviderName]ChatMessage, 0, len(bifrostReq.Input))for _, msg := range bifrostReq.Input {providerMsg := [ProviderName]ChatMessage{}// Set roleif msg.Role != "" {role := string(msg.Role)providerMsg.Role = &role}// Set name if presentif msg.Name != nil {providerMsg.Name = msg.Name}// Convert content (can be string or structured)if msg.Content != nil {if msg.Content.ContentStr != nil {// Simple string contentcontentJSON, _ := sonic.Marshal(*msg.Content.ContentStr)providerMsg.Content = json.RawMessage(contentJSON)} else if msg.Content.ContentBlocks != nil {// Structured content (text, images, etc.)contentItems := make([][ProviderName]ContentItem, 0, len(msg.Content.ContentBlocks))for _, block := range msg.Content.ContentBlocks {item := [ProviderName]ContentItem{}blockType := string(block.Type)item.Type = &blockTypeswitch block.Type {case schemas.ChatContentBlockTypeText:if block.Text != nil {item.Text = block.Text}case schemas.ChatContentBlockTypeImage:if block.ImageURLStruct != nil {item.ImageURL = &[ProviderName]ImageRef{URL: block.ImageURLStruct.URL,}}}contentItems = append(contentItems, item)}contentJSON, _ := sonic.Marshal(contentItems)providerMsg.Content = json.RawMessage(contentJSON)}}// Handle tool calls for assistant messagesif msg.ChatAssistantMessage != nil && len(msg.ChatAssistantMessage.ToolCalls) > 0 {providerToolCalls := make([][ProviderName]ToolCall, 0, len(msg.ChatAssistantMessage.ToolCalls))for _, tc := range msg.ChatAssistantMessage.ToolCalls {providerToolCall := [ProviderName]ToolCall{ID: tc.ID,Type: tc.Type,Function: [ProviderName]Function{Name: *tc.Function.Name,Arguments: tc.Function.Arguments,},}providerToolCalls = append(providerToolCalls, providerToolCall)}providerMsg.ToolCalls = providerToolCalls}// Handle tool call responsesif msg.ChatToolMessage != nil && msg.ChatToolMessage.ToolCallID != nil {providerMsg.ToolCallID = msg.ChatToolMessage.ToolCallID}providerMessages = append(providerMessages, providerMsg)}// Build the requestproviderReq := &[ProviderName]ChatRequest{Model: bifrostReq.Model,Messages: providerMessages,}// Map parametersif bifrostReq.Params != nil {params := bifrostReq.Params// Standard parametersif params.Temperature != nil {providerReq.Temperature = params.Temperature}if params.MaxTokens != nil {providerReq.MaxTokens = params.MaxTokens}if params.TopP != nil {providerReq.TopP = params.TopP}if params.FrequencyPenalty != nil {providerReq.FrequencyPenalty = params.FrequencyPenalty}if params.PresencePenalty != nil {providerReq.PresencePenalty = params.PresencePenalty}if params.Stop != nil {providerReq.Stop = params.Stop}if params.Seed != nil {providerReq.Seed = params.Seed}// Tool/Function calling - omitted for brevity; see complete provider examples}return providerReq}
Key conversion patterns to implement:
// Request Converter - Maps DeepIntShield standard to provider formatfunc To[ProviderName][Feature]Request(bifrostReq) *[ProviderName]Request { // 1. Nil check // 2. Convert messages/input // 3. Map standard parameters (temp, max_tokens, etc.) // 4. Map tools/functions if supported // 5. Map ExtraParams to provider-specific fields return providerReq}
// Response Converter - Maps provider format back to DeepIntShieldfunc ToDeepIntShield[Feature]Response(providerResp) (*schemas.DeepIntShieldResponse, *schemas.DeepIntShieldError) { // 1. Nil check with error return // 2. Convert choices/results // 3. Convert messages/content // 4. Convert tool calls if present // 5. Convert usage/metadata return bifrostResp, nil}Converter Checklist for Each Feature File:
- ✅ Request converter:
To[ProviderName][Feature]Request - ✅ Response converter:
ToDeepIntShield[Feature]Response - ✅ Nil checks at start of every function
- ✅ Pre-allocate slices with capacity
- ✅ Handle all optional fields with nil checks
- ✅ Map ExtraParams to provider-specific fields
- ✅ Return errors for response converters
- ✅ Document complex transformations
💡 Tip: See actual implementation examples in
core/providers/huggingface/,core/providers/anthropic/, or other existing providers for complete patterns.
Phase 6: Implement Provider (provider_name.go)
Section titled “Phase 6: Implement Provider (provider_name.go)”-
Create
[provider_name].go- Wire Everything Together:See detailed structure in “File Conventions & Responsibilities” section above.
Implementation Checklist:
- ✅ Package comment at top
- ✅ All imports organized (stdlib, external, internal)
- ✅ Provider struct with correct field order
- ✅ Response pools (if using sync.Pool)
- ✅ Constructor with proper initialization
- ✅
GetProviderKey()method - ✅ All interface methods implemented
- ✅ Each method follows the strict order: convert → execute → handle errors → convert back
Phase 7: Add Tests
Section titled “Phase 7: Add Tests”-
Create
[provider_name]_test.go:See “Adding Automated Tests” section below for complete details.
For OpenAI-compatible Providers
Section titled “For OpenAI-compatible Providers”For OpenAI-compatible providers, follow the simpler structure shown in the “OpenAI-compatible Providers” section above.
Implementation Checklist:
- ✅ Create
[provider_name].goonly - ✅ Import
github.com/maximhq/deepintshield/core/providers/openai - ✅ Implement constructor returning
(*Provider, error) - ✅ Set default BaseURL specific to provider
- ✅ Delegate all methods to
openai.HandleOpenAI*functions - ✅ Return errors for unsupported features
- ✅ Create
[provider_name]_test.go
Adding to UI
Section titled “Adding to UI”Once your provider is implemented and tested, you need to integrate it into the DeepIntShield UI and CI/CD pipelines.
Step 1: Update UI Constants
Section titled “Step 1: Update UI Constants”a. Add Model Placeholder (ui/lib/constants/config.ts)
Section titled “a. Add Model Placeholder (ui/lib/constants/config.ts)”Add a model placeholder example for your provider to help users understand the expected model format:
export const ModelPlaceholders = { openai: "e.g. gpt-4, gpt-3.5-turbo", anthropic: "e.g. claude-3-opus, claude-3-sonnet", // ... other providers [providername]: "e.g. model-1, model-2", // Add your provider here};Example:
huggingface: "e.g. google/gemma-2-2b-it, nebius/Qwen/Qwen3-Embedding-8B",b. Set Key Requirement (ui/lib/constants/config.ts)
Section titled “b. Set Key Requirement (ui/lib/constants/config.ts)”Specify whether your provider requires an API key:
export const isKeyRequiredByProvider: Record<ProviderName, boolean> = { openai: true, anthropic: true, // ... other providers [providername]: true, // Set to true if API key is required, false otherwise};Example:
huggingface: true, // HuggingFace requires API keyollama: false, // Ollama doesn't require API key (local)Step 2: Add Provider Icon (ui/lib/constants/icons.tsx)
Section titled “Step 2: Add Provider Icon (ui/lib/constants/icons.tsx)”Create an SVG icon for your provider. You can use the provider’s official brand icon or a placeholder.
export const ProviderIcons = { // ... existing providers
[providername]: ({ size = "md", className = "" }: IconProps) => { const resolvedSize = resolveSize(size);
return ( <svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"> <title>ProviderName</title> {/* Add your SVG path here */} <path d="..." fill="#HexColor"></path> </svg> ); },} as const;Tips:
- Get the official icon from the provider’s brand assets or press kit
- Ensure the SVG is properly formatted and viewBox is set to “0 0 24 24”
- Use the provider’s brand color for the fill attribute
- Keep the icon simple and recognizable at small sizes
Step 3: Register Provider Name (ui/lib/constants/logs.ts)
Section titled “Step 3: Register Provider Name (ui/lib/constants/logs.ts)”a. Add to Known Providers List
Section titled “a. Add to Known Providers List”export const KnownProvidersNames = [ "anthropic", "azure", "bedrock", // ... other providers "[providername]", // Add your provider name (lowercase)] as const;b. Add Provider Label
Section titled “b. Add Provider Label”export const ProviderLabels: Record<ProviderName, string> = { anthropic: "Anthropic", azure: "Azure", bedrock: "AWS Bedrock", // ... other providers [providername]: "ProviderName", // Add display name (proper capitalization)} as const;Example:
huggingface: "HuggingFace",cerebras: "Cerebras",Step 4: Update OpenAPI Specification (docs/openapi/openapi.json)
Section titled “Step 4: Update OpenAPI Specification (docs/openapi/openapi.json)”Add your provider to the API documentation’s provider enum:
{ "type": "string", "enum": [ "openai", "anthropic", "azure", "bedrock", // ... other providers "[providername]" ], "description": "AI model provider", "example": "openai"}Location: Search for the "AI model provider" description in docs/openapi/openapi.json and add your provider to the enum array.
Step 5: Update Configuration Schema (transports/config.schema.json)
Section titled “Step 5: Update Configuration Schema (transports/config.schema.json)”a. Add Provider to Providers Object
Section titled “a. Add Provider to Providers Object”{ "providers": { "type": "object", "properties": { "openai": { "$ref": "#/$defs/provider" }, "anthropic": { "$ref": "#/$defs/provider" }, // ... other providers "[providername]": { "$ref": "#/$defs/provider" } } }}b. Add to Fallback Provider Enum
Section titled “b. Add to Fallback Provider Enum”{ "fallbacks": { "items": { "properties": { "provider": { "type": "string", "enum": [ "openai", "anthropic", // ... other providers "[providername]" ] } } } }}Location: Search for "fallbacks" in transports/config.schema.json and add your provider to both locations.
Step 6: Update UI README (ui/README.md)
Section titled “Step 6: Update UI README (ui/README.md)”Add your provider to the list of supported providers:
## Provider Configuration
Manage all your AI providers from a unified interface:
- **Supported Providers**: OpenAI, Azure, Anthropic, AWS Bedrock, Cohere, Google Vertex AI, Mistral, Ollama, Parasail, Elevenlabs, SGLang, Cerebras, Groq, Gemini, OpenRouter, ProviderNameExample:
- **Supported Providers**: OpenAI, Azure, Anthropic, AWS Bedrock, Cohere, Google Vertex AI, Mistral, Ollama, Parasail, Elevenlabs, SGLang, Cerebras, Groq, Gemini, OpenRouter, HuggingFaceStep 7: Register Provider in Core (core/deepintshield.go)
Section titled “Step 7: Register Provider in Core (core/deepintshield.go)”a. Add Provider Import
Section titled “a. Add Provider Import”import ( // ... existing imports "github.com/maximhq/deepintshield/core/providers/[providername]")b. Add Case to createBaseProvider
Section titled “b. Add Case to createBaseProvider”func (deepintshield *DeepIntShield) createBaseProvider(providerKey schemas.ModelProvider, config *schemas.ProviderConfig) (schemas.Provider, error) { // ... existing cases
case schemas.ProviderName: return providername.NewProviderNameProvider(config, deepintshield.logger), nil
default: return nil, fmt.Errorf("unsupported provider: %s", targetProviderKey)}For OpenAI-compatible providers (returns error):
case schemas.ProviderName: return providername.NewProviderNameProvider(config, deepintshield.logger)For non-OpenAI-compatible providers (no error):
case schemas.ProviderName: return providername.NewProviderNameProvider(config, deepintshield.logger), nilStep 8: Add CI/CD Environment Variables
Section titled “Step 8: Add CI/CD Environment Variables”Add your provider’s API key to all GitHub Actions workflow files that run tests.
Files to Update:
Section titled “Files to Update:”.github/workflows/pr-tests.yml.github/workflows/release-pipeline.yml(multiple jobs)
Changes Required:
Section titled “Changes Required:”Add the environment variable to the env: section:
env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # ... other API keys PROVIDER_NAME_API_KEY: ${{ secrets.PROVIDER_NAME_API_KEY }}Example from pr-tests.yml:
env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} HUGGING_FACE_API_KEY: ${{ secrets.HUGGING_FACE_API_KEY }}Locations in release-pipeline.yml:
core-releasejobframework-releasejobplugins-releasejobdeepintshield-http-releasejob
Note: Repository maintainers need to add the actual secret value in GitHub repository settings under Settings > Secrets and variables > Actions.
UI Integration Checklist
Section titled “UI Integration Checklist”Before submitting your PR, verify all UI changes:
- ✅ Model placeholder added to
ui/lib/constants/config.ts - ✅ Key requirement set in
ui/lib/constants/config.ts - ✅ Provider icon added to
ui/lib/constants/icons.tsx - ✅ Provider name added to
ui/lib/constants/logs.ts(KnownProvidersNames) - ✅ Provider label added to
ui/lib/constants/logs.ts(ProviderLabels) - ✅ Provider added to OpenAPI spec enum (
docs/openapi/openapi.json) - ✅ Provider added to config schema (
transports/config.schema.json) - 2 locations - ✅ Provider listed in UI README (
ui/README.md) - ✅ Provider import added to
core/deepintshield.go - ✅ Provider case added to
createBaseProviderincore/deepintshield.go - ✅ Environment variable added to
.github/workflows/pr-tests.yml - ✅ Environment variable added to
.github/workflows/release-pipeline.yml(4 jobs)
Creating Provider Documentation
Section titled “Creating Provider Documentation”MANDATORY: Every new provider must have comprehensive documentation in the docs directory. This documentation helps users understand how the provider works, what parameters it supports, and any special considerations.
Documentation File Location
Section titled “Documentation File Location”Create a new MDX file at: docs/providers/supported-providers/[provider_name].mdx
Example: For a provider named “example”, create: docs/providers/supported-providers/example.mdx
Documentation Structure
Section titled “Documentation Structure”Your provider documentation should follow this structure for consistency. Reference complete examples:
- Groq:
docs/providers/supported-providers/groq.mdx(OpenAI-compatible provider) - Bedrock:
docs/providers/supported-providers/bedrock.mdx(Custom API provider with multiple features) - Cerebras:
docs/providers/supported-providers/cerebras.mdx(OpenAI-compatible, simple) - Mistral:
docs/providers/supported-providers/mistral.mdx(Transcription + chat support) - Ollama:
docs/providers/supported-providers/ollama.mdx(Local-first infrastructure)
Required Sections
Section titled “Required Sections”1. Front Matter (Frontmatter)
Section titled “1. Front Matter (Frontmatter)”---title: "[Provider Full Name]"description: "[Brief description] - parameter mapping, [key features], and [auth method]"icon: "[icon letter or emoji]"---Example:
---title: "Groq"description: "Groq API conversion guide - OpenAI-compatible format, parameter handling, text completion fallback, streaming, and tool support"icon: "g"---2. Overview Section
Section titled “2. Overview Section”Start with a brief overview explaining:
- What the provider is and its key characteristics
- How DeepIntShield converts requests to/from this provider’s format
- List of major transformation features
Template:
## Overview
[Provider Name] is a **[type: OpenAI-compatible/custom API/local-first]** provider offering [key features]. DeepIntShield converts requests to [Provider]'s expected format with [specific features]. Key characteristics:- **[Feature 1]** - brief description- **[Feature 2]** - brief description- **[Feature 3]** - brief description3. Supported Operations Table
Section titled “3. Supported Operations Table”Create a table showing which operations are supported:
### Supported Operations
| Operation | Non-Streaming | Streaming | Endpoint | Notes ||-----------|---------------|-----------|----------|-------|| Chat Completions | ✅ | ✅ | `/v1/chat/completions` | || Text Completions | ❌ | ❌ | Not supported | || Embeddings | ✅ | ❌ | `/v1/embeddings` | || List Models | ✅ | ❌ | `/v1/models` | |4. Feature Sections (One per Supported Feature)
Section titled “4. Feature Sections (One per Supported Feature)”For each major feature (Chat Completions, Embeddings, etc.):
a. Request Parameters
Section titled “a. Request Parameters”## Request Parameters
### Parameter Mapping
| Parameter | Transformation | Notes ||-----------|----------------|-------|| `max_completion_tokens` | Direct pass-through | Minimum X tokens || `temperature` | Renamed to `temp` | Provider-specific name |Include:
- OpenAI parameter name
- How it’s transformed for the provider (renamed, dropped, etc.)
- Any special notes or constraints
b. Filtered/Dropped Parameters
Section titled “b. Filtered/Dropped Parameters”### Filtered Parameters
Removed for [Provider] compatibility:- `prompt_cache_key` - Not supported- `store` - Not supportedc. Special Features
Section titled “c. Special Features”### [Feature Name]
Document any provider-specific features like:- Reasoning/thinking support- Special authentication- Unique parameters- Format conversionsd. Message Conversion
Section titled “d. Message Conversion”## Message Conversion
Content types supported:- ✅ Text content- ✅ Images (URL and base64)- ❌ Audio inpute. Response Conversion
Section titled “e. Response Conversion”## Response Conversion
Field mapping from provider format back to DeepIntShield standard.5. Streaming Section (If Supported)
Section titled “5. Streaming Section (If Supported)”## Streaming
[Provider] uses **[protocol: SSE/WebSocket/custom]** streaming with:- Request configuration: stream: true- Event format: [description]- End marker: [description]6. Authentication Section
Section titled “6. Authentication Section”## Authentication
**[Authentication Type]:**Authorization: [format]
[Additional details about key management, etc.]7. Configuration Section
Section titled “7. Configuration Section”## Configuration
**HTTP Settings:**- **Base URL**: `[default URL]` (default)- **API Version**: [version info]- **Max Connections**: 5000 per host- **Idle Timeout**: 60 seconds8. Caveats/Important Notes
Section titled “8. Caveats/Important Notes”Use collapsible accordion sections for limitations:
## Caveats
<details><summary>[Caveat Title]</summary>
**Severity**: [High/Medium/Low]**Behavior**: [What happens]**Impact**: [What breaks/changes]**Code**: [File references if relevant]
</details>
<details><summary>[Another Caveat]</summary>
...
</details>Common caveats to document:
- Unsupported content types (images, audio, etc.)
- Parameter limitations
- Streaming restrictions
- Special handling required
- Breaking behavioral differences from OpenAI standard
9. Warnings/Notes
Section titled “9. Warnings/Notes”Use special callouts for important information:
<Aside type="caution">**Unsupported Operations**: [List operations], [List operations].</Aside>
<Aside type="note">[Provider] requires [special setup/configuration].</Aside>
<Aside type="tip">[Helpful tip for using this provider effectively].</Aside>Code Examples in Documentation
Section titled “Code Examples in Documentation”Include examples in both formats where applicable:
<Tabs><Tab title="Gateway">
`````bashcurl -X POST http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "[provider]/[model]", "messages": [{"role": "user", "content": "Hello"}] }'</Tab> <Tab title=“Go SDK”>
response, err := client.ChatCompletion(ctx, &schemas.DeepIntShieldChatRequest{ Provider: schemas.ProviderName, Model: "model-name", Input: messages,})</Tab> </Tabs>
### Documentation Formatting Standards
- **Markdown**: Use standard MDX syntax compatible with the documentation site- **Code blocks**: Always specify language (bash, go, json, yaml)- **Tables**: Use pipes for alignment and clarity- **Sections**: Use H1 (#) for main provider, H2 (##) for major sections, H3 (###) for subsections- **Lists**: Use bullets (-) for unordered, numbers (1.) for ordered- **Emphasis**: Use **bold** for important terms, `code` for inline code
### Documentation Checklist
Before submitting your documentation:
- ✅ File created: `docs/providers/supported-providers/[provider_name].mdx`- ✅ Front matter with title, description, and icon- ✅ Overview section explaining the provider- ✅ Supported Operations table (accurate for your implementation)- ✅ Parameter mapping documented for each supported feature- ✅ Filtered parameters listed- ✅ Message conversion explained (content types)- ✅ Tool/function support documented (if applicable)- ✅ Response conversion patterns explained- ✅ Streaming behavior documented (if supported)- ✅ Authentication method clearly explained- ✅ Configuration section with base URL and settings- ✅ Caveats documented with severity ratings- ✅ Code examples for Gateway and Go SDK (where applicable)- ✅ All special features explained- ✅ Links to reference existing implementations (if applicable)- ✅ Warnings for unsupported features
---
## Adding Automated Tests
Testing is **MANDATORY** for all providers. Tests ensure your provider works correctly and continues to work as the codebase evolves.
---
### Test File Structure
Create `core/providers/[provider_name]/[provider_name]_test.go`:
**Example Test File Structure**:
```gopackage providername_test
import ( "os" "testing"
"github.com/maximhq/deepintshield/core/internal/llmtests" "github.com/maximhq/deepintshield/core/schemas")
func TestProviderName(t *testing.T) { t.Parallel()
// Check for API key - skip if not available if os.Getenv("PROVIDER_API_KEY") == "" { t.Skip("Skipping tests because PROVIDER_API_KEY is not set") }
// Initialize test client client, ctx, cancel, err := llmtests.SetupTest() if err != nil { t.Fatalf("Error initializing test setup: %v", err) } defer cancel()
// Configure test scenarios testConfig := llmtests.ComprehensiveTestConfig{ Provider: schemas.ProviderName, ChatModel: "model-name", Fallbacks: []schemas.Fallback{ {Provider: schemas.ProviderName, Model: "fallback-model"}, }, Scenarios: llmtests.TestScenarios{ SimpleChat: true, CompletionStream: true, ToolCalls: true, TextCompletion: false, // Not supported Embedding: false, // Not supported ListModels: true, // ... configure based on provider capabilities }, }
// Run all tests t.Run("ProviderNameTests", func(t *testing.T) { llmtests.RunAllComprehensiveTests(t, client, ctx, testConfig) })
client.Shutdown()}Test Configuration Requirements
Section titled “Test Configuration Requirements”Environment Variables:
- REQUIRED:
[PROVIDER_NAME]_API_KEY- API key for the provider - Optional:
PROVIDER_BASE_URL- Custom base URL for testing
Example:
export CEREBRAS_API_KEY="your-api-key-here"export HUGGING_FACE_API_KEY="your-hf-token-here"Package Declaration:
- Use
package [provider_name]_test(note the_testsuffix) - This ensures tests don’t access unexported functions (tests external behavior)
Test Scenarios Configuration
Section titled “Test Scenarios Configuration”The llmtests.TestScenarios struct defines which tests to run. Set each field based on provider capabilities:
Core Test Scenarios
Section titled “Core Test Scenarios”| Scenario | Enable if… |
|---|---|
SimpleChat | Provider supports basic chat completion |
CompletionStream | Provider supports streaming chat |
TextCompletion | Provider supports text completions (legacy) |
TextCompletionStream | Provider supports streaming text completions |
ToolCalls | Provider supports function/tool calling |
ToolCallsStreaming | Provider supports streaming with tool calls |
Embedding | Provider supports text embeddings |
ListModels | Provider has a list models endpoint |
ImageURL | Provider accepts image URLs in messages |
ImageBase64 | Provider accepts base64-encoded images |
Model Configuration
Section titled “Model Configuration”ChatModel (REQUIRED if any chat scenario is enabled):
ChatModel: "llama-3.3-70b", // Primary model for chat testsTextModel (REQUIRED if any text completion scenario is enabled):
TextModel: "llama3.1-8b", // Model for text completion testsEmbeddingModel (REQUIRED if Embedding scenario is enabled):
EmbeddingModel: "text-embedding-ada-002", // Model for embedding testsFallbacks (OPTIONAL but recommended):
Fallbacks: []schemas.Fallback{ {Provider: schemas.Cerebras, Model: "llama3.1-8b"}, {Provider: schemas.Cerebras, Model: "gpt-oss-120b"},},- Fallbacks are tested if primary model fails
- Tests that fallback mechanism works correctly
Running Tests
Section titled “Running Tests”Run all tests for your provider:
cd core/providers/[provider_name]go test -vRun with API key:
PROVIDER_API_KEY="your-key" go test -vRun specific test:
go test -v -run TestProviderNameRun with timeout (for slow providers):
go test -v -timeout 5mSkip integration tests (if API key not set):
go test -v -shortTest Checklist
Section titled “Test Checklist”Before submitting your provider, ensure:
- ✅ Test file named
[provider_name]_test.go - ✅ Package is
[provider_name]_test - ✅
t.Parallel()called at start - ✅ API key check with
t.Skip()if not available - ✅ All supported scenarios enabled in config
- ✅ All unsupported scenarios disabled (set to
false) - ✅ Appropriate models specified (ChatModel, TextModel, EmbeddingModel)
- ✅ Fallback models configured (at least 1-2)
- ✅
client.Shutdown()called at end - ✅ Tests pass locally with valid API key
- ✅ Tests skip gracefully without API key
Common Test Failures and Solutions
Section titled “Common Test Failures and Solutions”Test hangs indefinitely:
- Solution: Add timeout:
go test -v -timeout 2m - Cause: Provider not responding or network issue
“API key not set” skip message:
- Solution: Export the required environment variable
- Not a failure: Tests correctly skip when credentials unavailable
“Unsupported feature” errors:
- Solution: Set the scenario to
falseinTestScenarios - Cause: Test trying to run unsupported feature
“Model not found” errors:
- Solution: Update ChatModel/TextModel to valid model for provider
- Cause: Model name incorrect or not available
Streaming tests fail but non-streaming pass:
- Solution: Check streaming implementation in provider
- Cause: SSE parsing error or incorrect stream handling
Tool calling tests fail:
- Solution: Verify tool/function conversion in
chat.go - Cause: Tool format doesn’t match provider’s expected structure
CI/CD Integration
Section titled “CI/CD Integration”After your tests pass locally, ensure they’ll run in the CI/CD pipeline:
GitHub Actions Setup
Section titled “GitHub Actions Setup”Required Secret: Your provider’s API key must be added to GitHub repository secrets by a maintainer:
- Secret name:
PROVIDER_NAME_API_KEY(uppercase, underscores) - Example:
HUGGING_FACE_API_KEY,CEREBRAS_API_KEY
Workflow Files: The API key environment variable should already be added if you followed Step 8 in “Adding to UI”. Verify it’s present in:
.github/workflows/pr-tests.yml.github/workflows/release-pipeline.yml(4 jobs)
Test Execution: Tests will automatically run on:
- Pull requests (PR tests workflow)
- Release builds (release pipeline workflow)
- Manual workflow triggers
Skipping Tests:
If the API key secret is not set, your tests will be skipped (not fail) thanks to the t.Skip() check in your test file.
Final Pre-Submission Checklist
Section titled “Final Pre-Submission Checklist”Before creating a pull request, verify everything is complete:
Provider Implementation:
- ✅ Provider code follows file structure conventions
- ✅ All supported features implemented correctly
- ✅ Error handling properly converts to
schemas.DeepIntShieldError - ✅ OpenAI handlers used if provider is compatible
- ✅ Code is well-commented and documented
Tests:
- ✅ Test file created:
[provider_name]_test.go - ✅ All supported scenarios enabled
- ✅ All unsupported scenarios disabled
- ✅ Tests pass locally with valid API key
- ✅ Tests skip gracefully without API key
- ✅ Appropriate models configured
Schema & Core:
- ✅ Provider added to
core/schemas/deepintshield.go(ModelProvider type + arrays) - ✅ Provider registered in
core/deepintshield.go(import + case)
UI Integration:
- ✅ All 7 UI files updated (config.ts, icons.tsx, logs.ts, etc.)
- ✅ Provider icon looks good and is recognizable
- ✅ Model placeholders are helpful examples
CI/CD:
- ✅ Environment variables added to workflow files
- ✅ API key secret name follows convention
Documentation:
- ✅ Provider-specific parameters documented (if any)
- ✅ Example usage added (optional but helpful)
- ✅ Any special setup instructions noted