first commit
This commit is contained in:
0
docs/features/plugins/circuit-breaker.mdx
Normal file
0
docs/features/plugins/circuit-breaker.mdx
Normal file
306
docs/features/plugins/jsonparser.mdx
Normal file
306
docs/features/plugins/jsonparser.mdx
Normal file
@@ -0,0 +1,306 @@
|
||||
---
|
||||
title: JSON Parser
|
||||
description: A simple Bifrost plugin that handles partial JSON chunks in streaming responses by making them valid JSON objects.
|
||||
icon: "code-branch"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
When using AI providers that stream JSON responses, the individual chunks often contain incomplete JSON that cannot be parsed directly. This plugin automatically detects and fixes partial JSON chunks by adding the necessary closing braces, brackets, and quotes to make them valid JSON.
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic JSON Completion**: Detects partial JSON and adds missing closing characters
|
||||
- **Streaming Only**: Processes only streaming responses (non-streaming responses are ignored)
|
||||
- **Flexible Usage Modes**: Supports two usage types for different deployment scenarios
|
||||
- **Safe Fallback**: Returns original content if JSON cannot be fixed
|
||||
- **Memory Leak Prevention**: Automatic cleanup of stale accumulated content with configurable intervals
|
||||
- **Zero Dependencies**: Only depends on Go's standard library
|
||||
|
||||
## Usage
|
||||
|
||||
### Usage Types
|
||||
|
||||
The plugin supports two usage types:
|
||||
|
||||
1. **AllRequests**: Processes all streaming responses automatically
|
||||
2. **PerRequest**: Processes only when explicitly enabled via request context
|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/maximhq/bifrost/core"
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
"github.com/maximhq/bifrost/plugins/jsonparser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create the JSON parser plugin for all requests
|
||||
jsonPlugin := jsonparser.NewJsonParserPlugin(jsonparser.PluginConfig{
|
||||
Usage: jsonparser.AllRequests,
|
||||
CleanupInterval: 2 * time.Minute, // Cleanup every 2 minutes
|
||||
MaxAge: 10 * time.Minute, // Remove entries older than 10 minutes
|
||||
})
|
||||
|
||||
// Initialize Bifrost with the plugin
|
||||
client, err := bifrost.Init(context.Background(), schemas.BifrostConfig{
|
||||
Account: &MyAccount{},
|
||||
LLMPlugins: []schemas.LLMPlugin{
|
||||
jsonPlugin,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Use the client normally - JSON parsing happens automatically
|
||||
// in the PostLLMHook for all streaming responses
|
||||
}
|
||||
```
|
||||
|
||||
### PerRequest Mode
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"github.com/maximhq/bifrost/core"
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
"github.com/maximhq/bifrost/plugins/jsonparser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create the JSON parser plugin for per-request control
|
||||
jsonPlugin := jsonparser.NewJsonParserPlugin(jsonparser.PluginConfig{
|
||||
Usage: jsonparser.PerRequest,
|
||||
CleanupInterval: 2 * time.Minute, // Cleanup every 2 minutes
|
||||
MaxAge: 10 * time.Minute, // Remove entries older than 10 minutes
|
||||
})
|
||||
|
||||
// Initialize Bifrost with the plugin
|
||||
client, err := bifrost.Init(context.Background(), schemas.BifrostConfig{
|
||||
Account: &MyAccount{},
|
||||
LLMPlugins: []schemas.LLMPlugin{
|
||||
jsonPlugin,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), jsonparser.EnableStreamingJSONParser, true)
|
||||
|
||||
// Enable JSON parsing for specific requests
|
||||
stream, bifrostErr := client.ChatCompletionStreamRequest(schemas.NewBifrostContext(ctx, schemas.NoDeadline), request)
|
||||
if bifrostErr != nil {
|
||||
// handle error
|
||||
}
|
||||
for chunk := range stream {
|
||||
_ = chunk // handle each streaming chunk
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
// Custom cleanup configuration
|
||||
plugin := jsonparser.NewJsonParserPlugin(jsonparser.PluginConfig{
|
||||
Usage: jsonparser.AllRequests,
|
||||
CleanupInterval: 2 * time.Minute, // Cleanup every 2 minutes
|
||||
MaxAge: 10 * time.Minute, // Remove entries older than 10 minutes
|
||||
})
|
||||
```
|
||||
|
||||
#### Default Values
|
||||
|
||||
- **CleanupInterval**: 5 minutes (how often to run cleanup)
|
||||
- **MaxAge**: 30 minutes (how old entries can be before cleanup)
|
||||
- **Usage**: Must be specified (AllRequests or PerRequest)
|
||||
|
||||
### Context Key for PerRequest Mode
|
||||
|
||||
When using `PerRequest` mode, the plugin checks for the context key `jsonparser.EnableStreamingJSONParser` with a boolean value:
|
||||
|
||||
- `true`: Enable JSON parsing for this request
|
||||
- `false`: Disable JSON parsing for this request
|
||||
- Key not present: Disable JSON parsing for this request
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/maximhq/bifrost/plugins/jsonparser"
|
||||
)
|
||||
|
||||
// Enable JSON parsing for this request
|
||||
ctx := context.WithValue(context.Background(), jsonparser.EnableStreamingJSONParser, true)
|
||||
|
||||
// Disable JSON parsing for this request
|
||||
ctx := context.WithValue(context.Background(), jsonparser.EnableStreamingJSONParser, false)
|
||||
|
||||
// No context key - JSON parsing disabled (default behavior)
|
||||
ctx := context.Background()
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
The plugin implements an optimized `parsePartialJSON` function with the following steps:
|
||||
|
||||
1. **Usage Check**: Determines if processing should occur based on usage type and context
|
||||
2. **Validates Input**: First tries to parse the string as valid JSON
|
||||
3. **Character Analysis**: If invalid, processes the string character-by-character to track:
|
||||
- String boundaries (inside/outside quotes)
|
||||
- Escape sequences
|
||||
- Opening/closing braces and brackets
|
||||
4. **Auto-Completion**: Adds missing closing characters in the correct order
|
||||
5. **Validation**: Verifies the completed JSON is valid
|
||||
6. **Fallback**: Returns original content if completion fails
|
||||
|
||||
### Memory Management
|
||||
|
||||
The plugin automatically manages memory by:
|
||||
|
||||
1. **Accumulating Content**: Stores partial JSON chunks with timestamps for each request
|
||||
2. **Periodic Cleanup**: Runs a background goroutine that removes stale entries based on `MaxAge`
|
||||
3. **Request Completion**: Automatically clears accumulated content when requests complete successfully
|
||||
4. **Configurable Intervals**: Allows customization of cleanup frequency and retention periods
|
||||
|
||||
### Real-Life Streaming Example
|
||||
|
||||
Here's a practical example showing how the JSON parser plugin fixes broken JSON chunks in streaming responses:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/maximhq/bifrost/core"
|
||||
"github.com/maximhq/bifrost/core/schemas"
|
||||
"github.com/maximhq/bifrost/plugins/jsonparser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create JSON parser plugin
|
||||
jsonPlugin := jsonparser.NewJsonParserPlugin(jsonparser.PluginConfig{
|
||||
Usage: jsonparser.AllRequests,
|
||||
CleanupInterval: 2 * time.Minute,
|
||||
MaxAge: 10 * time.Minute,
|
||||
})
|
||||
|
||||
// Initialize Bifrost with the plugin
|
||||
client, err := bifrost.Init(context.Background(), schemas.BifrostConfig{
|
||||
Account: &MyAccount{},
|
||||
LLMPlugins: []schemas.LLMPlugin{jsonPlugin},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer client.Shutdown()
|
||||
|
||||
// Request structured JSON response
|
||||
request := &schemas.BifrostChatRequest{
|
||||
Provider: schemas.OpenAI,
|
||||
Model: "gpt-4o-mini",
|
||||
Input: []schemas.ChatMessage{
|
||||
{
|
||||
Role: schemas.ChatMessageRoleUser,
|
||||
Content: schemas.ChatMessageContent{
|
||||
ContentStr: bifrost.Ptr("Return user profile as JSON: {\"name\": \"John Doe\", \"email\": \"john@example.com\"}"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Stream the response
|
||||
stream, bifrostErr := client.ChatCompletionStreamRequest(schemas.NewBifrostContext(context.Background(), schemas.NoDeadline), request)
|
||||
if bifrostErr != nil {
|
||||
panic(bifrostErr)
|
||||
}
|
||||
|
||||
fmt.Println("Streaming JSON response:")
|
||||
for chunk := range stream {
|
||||
if chunk.BifrostChatResponse != nil && len(chunk.BifrostChatResponse.Choices) > 0 {
|
||||
choice := chunk.BifrostChatResponse.Choices[0]
|
||||
if choice.ChatStreamResponseChoice != nil && choice.ChatStreamResponseChoice.Delta != nil {
|
||||
content := *choice.ChatStreamResponseChoice.Delta.Content
|
||||
fmt.Printf("Chunk: %s\n", content)
|
||||
|
||||
// With JSON parser, you can parse each chunk immediately
|
||||
var jsonData map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(content), &jsonData); err == nil {
|
||||
fmt.Printf("✅ Valid JSON parsed successfully\n")
|
||||
} else {
|
||||
fmt.Printf("❌ Invalid JSON: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Without JSON Parser** (raw streaming chunks):
|
||||
```
|
||||
Chunk 1: `{` ❌ Invalid JSON
|
||||
Chunk 2: `{"name"` ❌ Invalid JSON
|
||||
Chunk 3: `{"name": "John"` ❌ Invalid JSON
|
||||
Chunk 4: `{"name": "John Doe"` ❌ Invalid JSON
|
||||
```
|
||||
|
||||
**With JSON Parser** (processed chunks):
|
||||
```
|
||||
Chunk 1: `{}` ✅ Valid JSON
|
||||
Chunk 2: `{"name": ""}` ✅ Valid JSON
|
||||
Chunk 3: `{"name": "John"}` ✅ Valid JSON
|
||||
Chunk 4: `{"name": "John Doe"}` ✅ Valid JSON
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Function Calling**: Stream tool call arguments as valid JSON throughout the response
|
||||
- **Structured Data**: Stream complex JSON objects (user profiles, product catalogs) progressively
|
||||
- **Real-time Parsing**: Enable client-side JSON parsing at each streaming step without waiting for completion
|
||||
- **API Integration**: Forward streaming JSON to downstream services that expect valid JSON
|
||||
- **Live Updates**: Update UI components with valid JSON data as it streams in
|
||||
|
||||
### Example Transformations
|
||||
|
||||
| Input | Output |
|
||||
|-------|--------|
|
||||
| `{"name": "John"` | `{"name": "John"}` |
|
||||
| `["apple", "banana"` | `["apple", "banana"]` |
|
||||
| `{"user": {"name": "John"` | `{"user": {"name": "John"}}` |
|
||||
| `{"message": "Hello\nWorld"` | `{"message": "Hello\nWorld"}` |
|
||||
| `""` (empty string) | `{}` |
|
||||
| `" "` (whitespace only) | `{}` |
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test suite:
|
||||
|
||||
```bash
|
||||
cd plugins/jsonparser
|
||||
go test -v
|
||||
```
|
||||
|
||||
The tests cover:
|
||||
- Plugin interface compliance
|
||||
- Both usage types (AllRequests and PerRequest)
|
||||
- Context-based enabling/disabling
|
||||
- Streaming responses only (non-streaming responses are ignored)
|
||||
- Various JSON completion scenarios
|
||||
- Edge cases and error conditions
|
||||
- Memory cleanup functionality with real and simulated requests
|
||||
- Configuration options and default values
|
||||
566
docs/features/plugins/mocker.mdx
Normal file
566
docs/features/plugins/mocker.mdx
Normal file
@@ -0,0 +1,566 @@
|
||||
---
|
||||
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),
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user