Documentation
¶
Overview ¶
Package extensions provides a plugin system for extending backend functionality. It defines the Extension interface for lifecycle management, route registration, and hooks for generation events, along with an ExtensionRegistry for managing multiple extensions.
Interceptor Pattern ¶
The package provides a ProviderInterceptor pattern for wrapping provider calls with cross-cutting concerns. Unlike hooks (which observe), interceptors control the execution flow and can modify requests/responses or short-circuit calls.
Example usage:
// Create a chain of interceptors chain := NewInterceptorChain() chain.Add(NewLoggingInterceptor()) chain.Add(NewTimeoutInterceptor(5 * time.Second)) chain.Add(NewCachingInterceptor()) // Execute with the chain resp, err := chain.Execute(ctx, req, providerFunc)
Interceptors execute in order, each calling next() to proceed to the next interceptor or the actual provider. An interceptor can skip calling next() to short-circuit execution (e.g., return a cached response).
The InterceptorRegistry provides a way to manage named interceptors globally:
registry := NewInterceptorRegistry()
registry.Register("logger", NewLoggingInterceptor())
registry.Register("timeout", NewTimeoutInterceptor(5 * time.Second))
Example interceptors included in the test suite:
- LoggingInterceptor: Logs before/after provider calls
- TimeoutInterceptor: Enforces timeouts on provider calls
- CachingInterceptor: Caches responses based on request prompts
- MetricsInterceptor: Tracks call counts and durations
Per-Request Extension Configuration ¶
Extensions can be configured or disabled on a per-request basis using the "extension_config" metadata key in GenerateRequest. This allows fine-grained control over extension behavior for specific requests.
Example usage:
// Create a request with per-extension configuration
req := &GenerateRequest{
Prompt: "Hello, world!",
Metadata: map[string]interface{}{
"extension_config": map[string]interface{}{
"caching": map[string]interface{}{
"enabled": false, // Disable caching for this request
},
"logging": map[string]interface{}{
"enabled": true,
"level": "debug", // Use debug level logging
},
"metrics": map[string]interface{}{
"interval": 30, // Custom metrics interval (enabled implicitly)
},
},
},
}
Extensions can check their configuration using helper functions:
func (e *MyCachingExtension) BeforeGenerate(ctx context.Context, req *GenerateRequest) error {
// Check if extension is enabled for this request
if !IsExtensionEnabled(req.Metadata, e.Name()) {
return nil // Skip processing
}
// Get custom configuration
config, enabled := GetExtensionConfig(req.Metadata, e.Name())
if enabled && config != nil {
// Use custom config values
if ttl, ok := config["ttl"].(int); ok {
// Use custom TTL
}
}
// Process request...
return nil
}
Default Behavior ¶
By default, extensions are enabled unless explicitly disabled. An extension is considered enabled in the following cases:
- No metadata is provided
- No "extension_config" key in metadata
- No configuration for the specific extension
- Extension config exists without an "enabled" field
- Extension config has "enabled": true
An extension is disabled only when:
- Extension config has "enabled": false
Security Considerations ¶
Security-critical extensions (e.g., authentication, authorization, rate limiting) should IGNORE disable requests and always execute their logic. These extensions should check the configuration but enforce their own security policies:
func (e *AuthExtension) BeforeGenerate(ctx context.Context, req *GenerateRequest) error {
// Always run authentication, regardless of request config
// Security extensions should ignore the enabled flag
return e.authenticate(ctx, req)
}
Backward Compatibility ¶
Extensions opt-in to checking per-request configuration. Existing extensions that don't check IsExtensionEnabled() will continue to work normally, processing all requests as before. This ensures backward compatibility with existing extension implementations.
Index ¶
- Constants
- func GetCapabilities(ext interface{}) []string
- func GetExtensionConfig(metadata map[string]interface{}, extensionName string) (config map[string]interface{}, enabled bool)
- func GetExtensionType(ext interface{}) string
- func GetPriority(ext interface{}) int
- func HasCapability(ext interface{}, capability string) bool
- func IsExtensionEnabled(metadata map[string]interface{}, extensionName string) bool
- type AfterGenerateHook
- type BaseExtension
- func (b *BaseExtension) AfterGenerate(ctx context.Context, req *GenerateRequest, resp *GenerateResponse) error
- func (b *BaseExtension) BeforeGenerate(ctx context.Context, req *GenerateRequest) error
- func (b *BaseExtension) Dependencies() []string
- func (b *BaseExtension) OnProviderError(ctx context.Context, provider types.Provider, err error) error
- func (b *BaseExtension) OnProviderSelected(ctx context.Context, provider types.Provider) error
- func (b *BaseExtension) Priority() int
- func (b *BaseExtension) RegisterRoutes(r RouteRegistrar) error
- func (b *BaseExtension) Shutdown(ctx context.Context) error
- type BeforeGenerateHook
- type DependencyDeclarer
- type Extension
- type ExtensionConfig
- type ExtensionMeta
- type ExtensionRegistry
- type GenerateRequest
- type GenerateResponse
- type Initializable
- type InterceptorChain
- type InterceptorRegistry
- type PriorityProvider
- type ProviderErrorHandler
- type ProviderFunc
- type ProviderInterceptor
- type ProviderSelectionHook
- type RouteProvider
- type RouteRegistrar
Constants ¶
const ( PrioritySecurity = 100 // Security-critical extensions (auth, rate limiting) PriorityCache = 200 // Caching extensions PriorityTransform = 500 // Transform and business logic (default) PriorityLogging = 900 // Logging and auditing extensions )
Priority constants define the execution order for extensions. Lower priority values execute first. Priority ranges:
- 0-199: Critical/Security extensions (e.g., authentication, rate limiting)
- 200-399: Caching and data layer extensions
- 400-599: Transform and business logic extensions
- 600-799: Monitoring and metrics extensions
- 800-999: Logging and auditing extensions
const ExtensionConfigKey = "extension_config"
ExtensionConfigKey is the metadata key for per-request extension configuration. Request metadata should contain this key with a map of extension names to their configs.
Variables ¶
This section is empty.
Functions ¶
func GetCapabilities ¶ added in v1.0.3
func GetCapabilities(ext interface{}) []string
GetCapabilities returns a list of capability names that the given extension implements. This is useful for debugging and introspection.
func GetExtensionConfig ¶ added in v1.0.3
func GetExtensionConfig(metadata map[string]interface{}, extensionName string) (config map[string]interface{}, enabled bool)
GetExtensionConfig extracts configuration for a specific extension from request metadata. It returns the extension's configuration map and a boolean indicating if the extension is enabled.
The function looks for configuration in metadata[ExtensionConfigKey][extensionName]. If no configuration is found, the extension is considered enabled by default (enabled=true). If configuration exists with {"enabled": false}, the extension is disabled (enabled=false). If configuration exists with other settings, the extension is enabled with those settings.
Example metadata structure:
{
"extension_config": {
"caching": {"enabled": false},
"logging": {"enabled": true, "level": "debug"},
"metrics": {"interval": 60} // enabled=true implicitly
}
}
Returns:
- config: The extension's configuration map (may be nil if no config found)
- enabled: Whether the extension is enabled for this request
func GetExtensionType ¶ added in v1.0.3
func GetExtensionType(ext interface{}) string
GetExtensionType returns the concrete type name of an extension. This is useful for debugging and logging.
func GetPriority ¶ added in v1.0.3
func GetPriority(ext interface{}) int
GetPriority returns the priority of an extension. It checks for PriorityProvider capability first, then falls back to Extension interface. If neither is implemented, returns the default priority (PriorityTransform = 500).
func HasCapability ¶ added in v1.0.3
HasCapability checks if an extension implements a specific capability. The capability name should match the interface name (e.g., "BeforeGenerateHook").
func IsExtensionEnabled ¶ added in v1.0.3
IsExtensionEnabled checks if an extension is enabled for this request. It's a convenience wrapper around GetExtensionConfig that only returns the enabled status.
Default behavior: extensions are enabled unless explicitly disabled in metadata.
Example usage:
if !IsExtensionEnabled(req.Metadata, "caching") {
// Skip caching logic
return nil
}
Security Note: Security-critical extensions (e.g., authentication, authorization) should ignore disable requests and always execute their logic regardless of this setting.
Types ¶
type AfterGenerateHook ¶ added in v1.0.3
type AfterGenerateHook interface {
AfterGenerate(ctx context.Context, req *GenerateRequest, resp *GenerateResponse) error
}
AfterGenerateHook defines extensions that need to process responses after generation. Implement this to modify, log, or analyze responses from providers.
type BaseExtension ¶
type BaseExtension struct{}
BaseExtension provides default implementations for optional methods
func (*BaseExtension) AfterGenerate ¶
func (b *BaseExtension) AfterGenerate(ctx context.Context, req *GenerateRequest, resp *GenerateResponse) error
func (*BaseExtension) BeforeGenerate ¶
func (b *BaseExtension) BeforeGenerate(ctx context.Context, req *GenerateRequest) error
func (*BaseExtension) Dependencies ¶
func (b *BaseExtension) Dependencies() []string
func (*BaseExtension) OnProviderError ¶
func (*BaseExtension) OnProviderSelected ¶
func (*BaseExtension) Priority ¶ added in v1.0.3
func (b *BaseExtension) Priority() int
func (*BaseExtension) RegisterRoutes ¶
func (b *BaseExtension) RegisterRoutes(r RouteRegistrar) error
type BeforeGenerateHook ¶ added in v1.0.3
type BeforeGenerateHook interface {
BeforeGenerate(ctx context.Context, req *GenerateRequest) error
}
BeforeGenerateHook defines extensions that need to intercept requests before generation. Implement this to modify or validate requests before they're sent to providers.
type DependencyDeclarer ¶ added in v1.0.3
type DependencyDeclarer interface {
Dependencies() []string
}
DependencyDeclarer defines extensions that depend on other extensions. Implement this to ensure your extension initializes after its dependencies.
type Extension ¶
type Extension interface {
Name() string
Version() string
Description() string
Dependencies() []string
Priority() int
Initialize(config map[string]interface{}) error
Shutdown(ctx context.Context) error
RegisterRoutes(registrar RouteRegistrar) error
BeforeGenerate(ctx context.Context, req *GenerateRequest) error
AfterGenerate(ctx context.Context, req *GenerateRequest, resp *GenerateResponse) error
OnProviderError(ctx context.Context, provider types.Provider, err error) error
OnProviderSelected(ctx context.Context, provider types.Provider) error
}
Extension defines the interface for backend extensions
type ExtensionConfig ¶
type ExtensionConfig struct {
Enabled bool `yaml:"enabled"`
Config map[string]interface{} `yaml:"config"`
}
ExtensionConfig is a local type until backendtypes is ready
type ExtensionMeta ¶ added in v1.0.3
ExtensionMeta defines the core metadata required for all extensions. All extensions must implement this interface at minimum.
type ExtensionRegistry ¶
type ExtensionRegistry interface {
Register(ext Extension) error
Get(name string) (Extension, bool)
List() []Extension
Initialize(configs map[string]ExtensionConfig) error
Shutdown(ctx context.Context) error
}
ExtensionRegistry manages extension lifecycle
func NewRegistry ¶
func NewRegistry() ExtensionRegistry
type GenerateRequest ¶
type GenerateRequest struct {
Provider string `json:"provider,omitempty"`
Model string `json:"model,omitempty"`
Prompt string `json:"prompt"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
Stream bool `json:"stream,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
GenerateRequest is a local type until backendtypes is ready.
Metadata can contain "extension_config" key with per-extension settings:
{
"extension_config": {
"caching": {"enabled": false},
"logging": {"enabled": true, "level": "debug"},
"metrics": {"interval": 60}
}
}
Extensions can use GetExtensionConfig() or IsExtensionEnabled() to check their configuration. By default, extensions are enabled unless explicitly disabled via {"enabled": false}.
type GenerateResponse ¶
type GenerateResponse struct {
Content string `json:"content"`
Model string `json:"model"`
Provider string `json:"provider"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
GenerateResponse is a local type until backendtypes is ready
type Initializable ¶ added in v1.0.3
type Initializable interface {
Initialize(config map[string]interface{}) error
Shutdown(ctx context.Context) error
}
Initializable defines extensions that need initialization and cleanup. Implement this interface if your extension needs setup/teardown.
type InterceptorChain ¶ added in v1.0.3
type InterceptorChain struct {
// contains filtered or unexported fields
}
InterceptorChain manages a chain of interceptors and executes them in order.
func NewInterceptorChain ¶ added in v1.0.3
func NewInterceptorChain() *InterceptorChain
NewInterceptorChain creates a new empty interceptor chain.
func (*InterceptorChain) Add ¶ added in v1.0.3
func (c *InterceptorChain) Add(interceptor ProviderInterceptor)
Add appends an interceptor to the chain. Interceptors are executed in the order they are added.
func (*InterceptorChain) Execute ¶ added in v1.0.3
func (c *InterceptorChain) Execute(ctx context.Context, req *GenerateRequest, provider ProviderFunc) (*GenerateResponse, error)
Execute runs the interceptor chain followed by the provider function. Each interceptor is called in order, and each must call next() to proceed. If an interceptor doesn't call next(), execution is short-circuited.
type InterceptorRegistry ¶ added in v1.0.3
type InterceptorRegistry interface {
// Register adds an interceptor to the registry with a unique name.
Register(name string, interceptor ProviderInterceptor) error
// Get retrieves an interceptor by name.
Get(name string) (ProviderInterceptor, bool)
// List returns all registered interceptor names.
List() []string
// Unregister removes an interceptor from the registry.
Unregister(name string) error
}
InterceptorRegistry manages named interceptors for the application.
func NewInterceptorRegistry ¶ added in v1.0.3
func NewInterceptorRegistry() InterceptorRegistry
NewInterceptorRegistry creates a new interceptor registry.
type PriorityProvider ¶ added in v1.0.3
type PriorityProvider interface {
Priority() int
}
PriorityProvider defines extensions that specify execution priority. Implement this to control the order in which your extension's hooks are called. Lower priority values execute first.
type ProviderErrorHandler ¶ added in v1.0.3
type ProviderErrorHandler interface {
OnProviderError(ctx context.Context, provider types.Provider, err error) error
}
ProviderErrorHandler defines extensions that handle provider errors. Implement this to add custom error handling, logging, or retry logic.
type ProviderFunc ¶ added in v1.0.3
type ProviderFunc func(ctx context.Context, req *GenerateRequest) (*GenerateResponse, error)
ProviderFunc is the signature for a provider call that can be intercepted. It takes a context and request, and returns a response or error.
type ProviderInterceptor ¶ added in v1.0.3
type ProviderInterceptor interface {
// Intercept processes a provider call and optionally calls next to proceed.
// Interceptors can:
// - Modify the request before calling next
// - Modify the response after calling next
// - Skip calling next to short-circuit (e.g., return cached response)
// - Handle errors from next
Intercept(ctx context.Context, req *GenerateRequest, next ProviderFunc) (*GenerateResponse, error)
}
ProviderInterceptor defines the interface for intercepting provider calls. Interceptors can modify requests, responses, or control the flow of execution. They must call next() to proceed to the next interceptor or the actual provider.
type ProviderSelectionHook ¶ added in v1.0.3
type ProviderSelectionHook interface {
OnProviderSelected(ctx context.Context, provider types.Provider) error
}
ProviderSelectionHook defines extensions that react to provider selection. Implement this to log, validate, or modify behavior based on selected provider.
type RouteProvider ¶ added in v1.0.3
type RouteProvider interface {
RegisterRoutes(registrar RouteRegistrar) error
}
RouteProvider defines extensions that provide HTTP routes. Implement this to add custom API endpoints to the backend.
type RouteRegistrar ¶
type RouteRegistrar interface {
Handle(pattern string, handler http.Handler)
HandleFunc(pattern string, handler http.HandlerFunc)
}
RouteRegistrar allows extensions to register custom routes