--- title: "Mocker" description: "Mock AI provider responses for testing, development, and simulation purposes." icon: "mask" --- ## Quick Start ### Minimal Configuration The simplest way to use the Mocker plugin is with no configuration - it will create a default catch-all rule: ```go package main import ( "context" bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" mocker "github.com/maximhq/bifrost/plugins/mocker" ) func main() { // Create plugin with minimal config plugin, err := mocker.NewMockerPlugin(mocker.MockerConfig{ Enabled: true, // Default rule will be created automatically }) if err != nil { panic(err) } // Initialize Bifrost with the plugin client, initErr := bifrost.Init(context.Background(), schemas.BifrostConfig{ Account: &yourAccount, LLMPlugins: []schemas.LLMPlugin{plugin}, }) if err != nil { panic(err) } defer client.Shutdown() // All chat and responses requests will now return: "This is a mock response from the Mocker plugin" // Chat completion request chatResponse, _ := client.ChatCompletionRequest(schemas.NewBifrostContext(context.Background(), schemas.NoDeadline), &schemas.BifrostChatRequest{ Provider: schemas.OpenAI, Model: "gpt-4", Input: []schemas.ChatMessage{ { Role: schemas.ChatMessageRoleUser, Content: schemas.ChatMessageContent{ ContentStr: bifrost.Ptr("Hello!"), }, }, }, }) // Responses request responsesResponse, _ := client.ResponsesRequest(schemas.NewBifrostContext(context.Background(), schemas.NoDeadline), &schemas.BifrostResponsesRequest{ Provider: schemas.OpenAI, Model: "gpt-4o", Input: []schemas.ResponsesMessage{ { Role: bifrost.Ptr(schemas.ResponsesInputMessageRoleUser), Content: &schemas.ResponsesMessageContent{ ContentStr: bifrost.Ptr("Hello!"), }, }, }, }) } ``` ### Custom Response ```go plugin, err := mocker.NewMockerPlugin(mocker.MockerConfig{ Enabled: true, Rules: []mocker.MockRule{ { Name: "openai-mock", Enabled: true, Probability: 1.0, // Always trigger Conditions: mocker.Conditions{ Providers: []string{"openai"}, }, Responses: []mocker.Response{ { Type: mocker.ResponseTypeSuccess, Content: &mocker.SuccessResponse{ Message: "Hello! This is a custom mock response for OpenAI.", Usage: &mocker.Usage{ PromptTokens: 15, CompletionTokens: 25, TotalTokens: 40, }, }, }, }, }, }, }) ``` ### Responses Request Example The mocker plugin automatically handles both chat completion and responses requests with the same configuration: ```go // This rule will work for both ChatCompletionRequest and ResponsesRequest { Name: "universal-mock", Enabled: true, Probability: 1.0, Conditions: mocker.Conditions{ MessageRegex: stringPtr("(?i).*hello.*"), }, Responses: []mocker.Response{ { Type: mocker.ResponseTypeSuccess, Content: &mocker.SuccessResponse{ Message: "Hello! I'm a mock response that works for both request types.", }, }, }, } ``` ## Installation Add the plugin to your project: ```bash go get github.com/maximhq/bifrost/plugins/mocker ``` Import in your code: ```go import mocker "github.com/maximhq/bifrost/plugins/mocker" ``` ## Basic Usage ### Creating the Plugin ```go config := mocker.MockerConfig{ Enabled: true, DefaultBehavior: mocker.DefaultBehaviorPassthrough, // "passthrough", "success", "error" Rules: []mocker.MockRule{ // Your rules here }, } plugin, err := mocker.NewMockerPlugin(config) if err != nil { log.Fatal(err) } ``` ### Adding to Bifrost ```go client, initErr := bifrost.Init(context.Background(), schemas.BifrostConfig{ Account: &yourAccount, LLMPlugins: []schemas.LLMPlugin{plugin}, Logger: bifrost.NewDefaultLogger(schemas.LogLevelInfo), }) ``` ### Disabling the Plugin ```go config := mocker.MockerConfig{ Enabled: false, // All requests pass through to real providers } ``` ## Supported Request Types The Mocker plugin supports the following Bifrost request types: - **Chat Completion Requests** (`ChatCompletionRequest`) - Standard chat-based interactions - **Responses Requests** (`ResponsesRequest`) - OpenAI-compatible responses API format - **Skip Context Key** - Use `"skip-mocker"` context key to bypass mocking per request ### Skip Mocker for Specific Requests You can skip the mocker plugin for specific requests by adding a context key: ```go import "github.com/maximhq/bifrost/core/schemas" // Create context that skips mocker ctx := context.WithValue(context.Background(), schemas.BifrostContextKey("skip-mocker"), true) // This request will bypass the mocker and go to the real provider response, err := client.ChatCompletionRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), request) ``` ## Key Features ### Template Variables Create dynamic responses using templates: ```go Response{ Type: mocker.ResponseTypeSuccess, Content: &mocker.SuccessResponse{ MessageTemplate: stringPtr("Hello from {{provider}} using model {{model}}!"), }, } ``` **Available Variables:** - `{{provider}}` - Provider name (e.g., "openai", "anthropic") - `{{model}}` - Model name (e.g., "gpt-4", "claude-3") - `{{faker.*}}` - Fake data generation (see Configuration Reference) ### Weighted Response Selection Configure multiple responses with different probabilities: ```go Responses: []mocker.Response{ { Type: mocker.ResponseTypeSuccess, Weight: 0.8, // 80% chance Content: &mocker.SuccessResponse{ Message: "Success response", }, }, { Type: mocker.ResponseTypeError, Weight: 0.2, // 20% chance Error: &mocker.ErrorResponse{ Message: "Rate limit exceeded", Type: stringPtr("rate_limit"), Code: stringPtr("429"), }, }, } ``` ### Latency Simulation Add realistic delays to responses: ```go // Fixed latency Latency: &mocker.Latency{ Type: mocker.LatencyTypeFixed, Min: 250 * time.Millisecond, } // Variable latency Latency: &mocker.Latency{ Type: mocker.LatencyTypeUniform, Min: 100 * time.Millisecond, Max: 500 * time.Millisecond, } ``` ### Advanced Matching #### Regex Message Matching ```go Conditions: mocker.Conditions{ MessageRegex: stringPtr(`(?i).*support.*|.*help.*`), } ``` #### Request Size Filtering ```go Conditions: mocker.Conditions{ RequestSize: &mocker.SizeRange{ Min: 100, // bytes Max: 1000, // bytes }, } ``` ### Faker Data Generation Create realistic test data using faker variables: ```go { Name: "user-profile-example", Responses: []mocker.Response{ { Type: mocker.ResponseTypeSuccess, Content: &mocker.SuccessResponse{ MessageTemplate: stringPtr(`User Profile: - Name: {{faker.name}} - Email: {{faker.email}} - Company: {{faker.company}} - Address: {{faker.address}}, {{faker.city}} - Phone: {{faker.phone}} - User ID: {{faker.uuid}} - Join Date: {{faker.date}} - Premium Account: {{faker.boolean}}`), }, }, }, } ``` ### Statistics and Monitoring Get runtime statistics for monitoring: ```go stats := plugin.GetStatistics() fmt.Printf("Plugin enabled: %v\n", stats.Enabled) fmt.Printf("Total requests: %d\n", stats.TotalRequests) fmt.Printf("Mocked requests: %d\n", stats.MockedRequests) // Rule-specific stats for ruleName, ruleStats := range stats.Rules { fmt.Printf("Rule %s: %d triggers\n", ruleName, ruleStats.Triggers) } ``` ## Configuration Reference ### MockerConfig | Field | Type | Default | Description | |-------|------|---------|-------------| | `Enabled` | `bool` | `false` | Enable/disable the entire plugin | | `DefaultBehavior` | `string` | `"passthrough"` | Action when no rules match: `"passthrough"`, `"success"`, `"error"` | | `GlobalLatency` | `*Latency` | `nil` | Global latency applied to all rules | | `Rules` | `[]MockRule` | `[]` | List of mock rules evaluated in priority order | ### MockRule | Field | Type | Default | Description | |-------|------|---------|-------------| | `Name` | `string` | - | Unique rule name for identification | | `Enabled` | `bool` | `true` | Enable/disable this specific rule | | `Priority` | `int` | `0` | Higher numbers = higher priority | | `Probability` | `float64` | `1.0` | Activation probability (0.0=never, 1.0=always) | | `Conditions` | `Conditions` | `{}` | Matching conditions (empty = match all) | | `Responses` | `[]Response` | - | Possible responses (weighted random selection) | | `Latency` | `*Latency` | `nil` | Rule-specific latency override | ### Conditions | Field | Type | Description | |-------|------|-------------| | `Providers` | `[]string` | Match specific providers: `["openai", "anthropic"]` | | `Models` | `[]string` | Match specific models: `["gpt-4", "claude-3"]` | | `MessageRegex` | `*string` | Regex pattern to match message content | | `RequestSize` | `*SizeRange` | Request size constraints in bytes | ### Response | Field | Type | Description | |-------|------|-------------| | `Type` | `string` | Response type: `"success"` or `"error"` | | `Weight` | `float64` | Weight for random selection (default: 1.0) | | `Content` | `*SuccessResponse` | Required if `Type="success"` | | `Error` | `*ErrorResponse` | Required if `Type="error"` | | `AllowFallbacks` | `*bool` | Control fallback behavior (`nil`=allow, `false`=block) | ### SuccessResponse | Field | Type | Description | |-------|------|-------------| | `Message` | `string` | Static response message | | `MessageTemplate` | `*string` | Template with variables: `{{provider}}`, `{{model}}`, `{{faker.*}}` | | `Model` | `*string` | Override model name in response | | `Usage` | `*Usage` | Token usage information | | `FinishReason` | `*string` | Completion reason (default: `"stop"`) | | `CustomFields` | `map[string]interface{}` | Additional metadata fields | ### ErrorResponse | Field | Type | Description | |-------|------|-------------| | `Message` | `string` | Error message to return | | `Type` | `*string` | Error type (e.g., `"rate_limit"`, `"auth_error"`) | | `Code` | `*string` | Error code (e.g., `"429"`, `"401"`) | | `StatusCode` | `*int` | HTTP status code | ### Latency | Field | Type | Description | |-------|------|-------------| | `Type` | `string` | Latency type: `"fixed"` or `"uniform"` | | `Min` | `time.Duration` | Minimum/exact latency (use `time.Millisecond`) | | `Max` | `time.Duration` | Maximum latency (required for `"uniform"`) | **Important**: Use Go's `time.Duration` constants: - ✅ Correct: `100 * time.Millisecond` - ❌ Wrong: `100` (nanoseconds, barely noticeable) ### Faker Variables #### Personal Information - `{{faker.name}}` - Full name - `{{faker.first_name}}` - First name only - `{{faker.last_name}}` - Last name only - `{{faker.email}}` - Email address - `{{faker.phone}}` - Phone number #### Location - `{{faker.address}}` - Street address - `{{faker.city}}` - City name - `{{faker.state}}` - State/province - `{{faker.zip_code}}` - Postal code #### Business - `{{faker.company}}` - Company name - `{{faker.job_title}}` - Job title #### Text and Data - `{{faker.lorem_ipsum}}` - Lorem ipsum text - `{{faker.lorem_ipsum:10}}` - Lorem ipsum with 10 words - `{{faker.uuid}}` - UUID v4 - `{{faker.hex_color}}` - Hex color code #### Numbers and Dates - `{{faker.integer}}` - Random integer (1-100) - `{{faker.integer:10,50}}` - Random integer between 10-50 - `{{faker.float}}` - Random float (0-100, 2 decimals) - `{{faker.float:1,10}}` - Random float between 1-10 - `{{faker.boolean}}` - Random boolean - `{{faker.date}}` - Date (YYYY-MM-DD format) - `{{faker.datetime}}` - Datetime (YYYY-MM-DD HH:MM:SS format) ## Best Practices ### Rule Organization ```go // Use priority to control rule evaluation order rules := []mocker.MockRule{ {Name: "specific-error", Priority: 100, Conditions: /* specific */}, {Name: "general-success", Priority: 50, Conditions: /* general */}, {Name: "catch-all", Priority: 0, Conditions: /* empty */}, } ``` ### Development vs Production ```go // Development: High mock rate config := mocker.MockerConfig{ Enabled: true, Rules: []mocker.MockRule{ {Probability: 1.0}, // Always mock }, } // Production: Occasional testing config := mocker.MockerConfig{ Enabled: true, Rules: []mocker.MockRule{ {Probability: 0.1}, // 10% mock rate }, } ``` ### Performance Considerations - Place specific conditions before general ones (higher priority) - Use simple string matching over complex regex when possible - Keep response templates reasonably sized - Consider disabling debug logging in production ### Testing Your Configuration ```go func validateMockerConfig(config mocker.MockerConfig) error { _, err := mocker.NewMockerPlugin(config) return err } // Test before deployment if err := validateMockerConfig(yourConfig); err != nil { log.Fatalf("Invalid mocker configuration: %v", err) } ``` ## Common Issues ### Plugin Not Triggering 1. Check if plugin is enabled: `Enabled: true` 2. Verify rule is enabled: `rule.Enabled: true` 3. Check probability: `Probability: 1.0` for testing 4. Verify conditions match your request ### Latency Not Working Use `time.Duration` constants, not raw integers: ```go // ❌ Wrong: 100 nanoseconds (barely noticeable) Min: 100 // ✅ Correct: 100 milliseconds Min: 100 * time.Millisecond ``` ### Regex Not Matching Test your regex pattern and ensure proper escaping: ```go // Case-insensitive matching MessageRegex: stringPtr(`(?i).*help.*`) // Escape special characters MessageRegex: stringPtr(`\$\d+\.\d+`) // Match $12.34 ``` ### Controlling Fallbacks ```go Response{ Type: mocker.ResponseTypeError, AllowFallbacks: boolPtr(false), // Block fallbacks Error: &mocker.ErrorResponse{ Message: "Authentication failed", }, } ``` ### Skip Mocker Not Working Ensure you're using the correct context key format: ```go // ✅ Correct ctx := context.WithValue(context.Background(), schemas.BifrostContextKey("skip-mocker"), true) // ❌ Wrong ctx := context.WithValue(context.Background(), "skip-mocker", true) ``` ### Responses Request Issues If responses requests aren't being mocked: 1. Verify the plugin supports `ResponsesRequest` (version 1.2.13+) 2. Check that your regex patterns match the message content 3. Ensure the request type is `schemas.ResponsesRequest` ### Debug Mode Enable debug logging to troubleshoot: ```go client, initErr := bifrost.Init(context.Background(), schemas.BifrostConfig{ Account: &account, LLMPlugins: []schemas.LLMPlugin{plugin}, Logger: bifrost.NewDefaultLogger(schemas.LogLevelDebug), }) ```