aigateway

package
v2.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

README

AI Gateway Library

The AI Gateway library provides a simple interface to use the Midsommar AI Gateway as an importable library in standalone applications.

Features

  • LLM Proxying: Secure reverse proxy for multiple LLM providers (OpenAI, Anthropic, Google AI, etc.)
  • Authentication & Authorization: Token-based authentication with app-level access control
  • Budget Enforcement: Real-time budget tracking and limits
  • Request Filtering: JavaScript-based policy enforcement
  • Analytics: Usage tracking and cost calculation
  • Tool Integration: REST API tool execution and MCP (Model Context Protocol) support
  • Data Source Proxying: Vector search capabilities
  • Streaming Support: Real-time streaming responses
  • Hot Reloading: Dynamic configuration updates

Installation

import "github.com/TykTechnologies/midsommar/v2/pkg/aigateway"

Basic Usage

Standalone Server
package main

import (
    "context"
    "log"
    
    "github.com/TykTechnologies/midsommar/v2/analytics"
    "github.com/TykTechnologies/midsommar/v2/pkg/aigateway"
    "github.com/TykTechnologies/midsommar/v2/services"
)

func main() {
    // Initialize your database and services
    db := setupDatabase()
    service := services.NewService(db)
    budgetService := services.NewBudgetService(db, service)

    // Initialize analytics (required for default database analytics)
    ctx := context.Background()
    analytics.InitDefault(ctx, db)

    // Create gateway with interface-based services
    gatewayService := aigateway.NewDatabaseService(service)
    gatewayBudgetService := aigateway.NewDatabaseBudgetService(budgetService)
    
    gateway := aigateway.New(
        gatewayService,
        gatewayBudgetService,
        &aigateway.Config{Port: 9090},
    )

    // Start as standalone server
    log.Println("Starting AI Gateway on :9090")
    if err := gateway.Start(); err != nil {
        log.Fatal(err)
    }
}
Integration with Existing HTTP Server
package main

import (
    "context"
    "log"
    "net/http"
    
    "github.com/TykTechnologies/midsommar/v2/analytics"
    "github.com/TykTechnologies/midsommar/v2/pkg/aigateway"
    "github.com/TykTechnologies/midsommar/v2/services"
)

func main() {
    // Initialize services
    db := setupDatabase()
    service := services.NewService(db)
    budgetService := services.NewBudgetService(db, service)

    // Initialize analytics
    ctx := context.Background()
    analytics.InitDefault(ctx, db)
    
    // Create gateway with interface-based services (note: Port in config is ignored when using as handler)
    gatewayService := aigateway.NewDatabaseService(service)
    gatewayBudgetService := aigateway.NewDatabaseBudgetService(budgetService)
    
    gateway := aigateway.New(
        gatewayService,
        gatewayBudgetService,
        &aigateway.Config{},
    )

    // Mount gateway in existing server
    mux := http.NewServeMux()
    mux.Handle("/ai/", http.StripPrefix("/ai", gateway.Handler()))
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    })

    log.Println("Starting server on :8080 with AI Gateway at /ai/")
    log.Fatal(http.ListenAndServe(":8080", mux))
}
Graceful Shutdown
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
    
    "github.com/TykTechnologies/midsommar/v2/analytics"
    "github.com/TykTechnologies/midsommar/v2/pkg/aigateway"
    "github.com/TykTechnologies/midsommar/v2/services"
)

func main() {
    // Initialize services
    db := setupDatabase()
    service := services.NewService(db)
    budgetService := services.NewBudgetService(db, service)

    // Initialize analytics
    ctx := context.Background()
    analytics.InitDefault(ctx, db)

    // Create gateway with interface-based services
    gatewayService := aigateway.NewDatabaseService(service)
    gatewayBudgetService := aigateway.NewDatabaseBudgetService(budgetService)
    
    gateway := aigateway.New(
        gatewayService,
        gatewayBudgetService,
        &aigateway.Config{Port: 9090},
    )

    // Set up graceful shutdown
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    // Start server in goroutine
    go func() {
        log.Println("Starting AI Gateway microproxy on :9090")
        if err := gateway.Start(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Gateway error: %v", err)
        }
    }()

    // Wait for shutdown signal
    <-ctx.Done()
    log.Println("Shutting down gateway...")

    // Graceful shutdown with timeout
    shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    if err := gateway.Stop(shutdownCtx); err != nil {
        log.Printf("Gateway shutdown error: %v", err)
    }
    log.Println("Gateway stopped")
}

Analytics Configuration

The AI Gateway supports pluggable analytics backends, allowing you to send usage data to different destinations.

Default Database Analytics

By default, the gateway uses database-based analytics:

import "github.com/TykTechnologies/midsommar/v2/analytics"

// Initialize database analytics (required)
analytics.InitDefault(ctx, db)

// Create gateway - uses default database analytics
gateway := aigateway.New(gatewayService, budgetService, config)
HTTP Analytics for External Control Planes

Send analytics to external APIs or control planes:

// Send analytics to external HTTP endpoint
httpAnalytics := aigateway.NewHTTPAnalyticsHandler("https://my-control-plane.com/api")

gateway := aigateway.NewWithAnalytics(
    gatewayService,
    budgetService, 
    httpAnalytics,
    config,
)

The HTTP analytics handler sends POST requests with JSON payloads:

{
  "type": "llm_usage",
  "llm_id": 1,
  "model": "gpt-4",
  "vendor": "openai",
  "prompt_tokens": 100,
  "response_tokens": 50,
  "total_tokens": 150,
  "cost": 2500,
  "currency": "USD",
  "timestamp": "2025-01-01T12:00:00Z",
  "app_id": 1,
  "user_id": 42
}
Custom Analytics Implementation

Implement your own analytics backend:

import "github.com/TykTechnologies/midsommar/v2/analytics"

type MyCustomAnalytics struct {
    // Your custom fields
}

func (m *MyCustomAnalytics) RecordChatRecord(record *models.LLMChatRecord) {
    // Send to message queue, file, etc.
}

func (m *MyCustomAnalytics) RecordChatLogEntry(log *models.LLMChatLogEntry) {
    // Handle chat logs
}

func (m *MyCustomAnalytics) RecordProxyLog(log *models.ProxyLog) {
    // Handle proxy logs  
}

func (m *MyCustomAnalytics) RecordToolCall(name string, timestamp time.Time, execTime int, toolID uint) {
    // Handle tool calls
}

func (m *MyCustomAnalytics) SetAsGlobalHandler() {
    analytics.SetHandler(m)
}

// Use custom analytics
customAnalytics := &MyCustomAnalytics{}
gateway := aigateway.NewWithAnalytics(
    gatewayService,
    budgetService,
    customAnalytics, 
    config,
)
Multiple Analytics Backends

You can also combine multiple analytics backends:

type MultiAnalytics struct {
    handlers []analytics.AnalyticsHandler
}

func (m *MultiAnalytics) RecordChatRecord(record *models.LLMChatRecord) {
    for _, handler := range m.handlers {
        handler.RecordChatRecord(record)
    }
}

// ... implement other methods similarly

// Use multiple backends
multi := &MultiAnalytics{
    handlers: []analytics.AnalyticsHandler{
        analytics.NewDatabaseHandler(ctx, db),           // Local database
        aigateway.NewHTTPAnalyticsHandler(controlPlaneURL), // External API  
    },
}

gateway := aigateway.NewWithAnalytics(gatewayService, budgetService, multi, config)
Gin
import "github.com/gin-gonic/gin"

router := gin.Default()
router.Any("/ai/*path", gin.WrapH(gateway.Handler()))
Gorilla Mux
import "github.com/gorilla/mux"

router := mux.NewRouter()
router.PathPrefix("/ai/").Handler(gateway.Handler())
Chi
import "github.com/go-chi/chi/v5"

router := chi.NewRouter()
router.Mount("/ai", gateway.Handler())

API Interface

type Gateway interface {
    // Start starts the gateway as an HTTP server on the configured port
    Start() error

    // Stop gracefully stops the gateway server  
    Stop(ctx context.Context) error

    // Handler returns an http.Handler for integration with existing servers
    Handler() http.Handler

    // Reload reloads the gateway configuration (LLMs, datasources, filters)
    Reload() error
}

Configuration Hot Reloading

The gateway supports hot reloading of configuration without restarting:

// After updating database configurations
if err := gateway.Reload(); err != nil {
    log.Printf("Failed to reload configuration: %v", err)
}

Supported Endpoints

When using the gateway, the following endpoints are available:

LLM Endpoints
  • POST /llm/rest/{llmSlug}/{path} - REST API calls to LLMs
  • POST /llm/stream/{llmSlug}/{path} - Streaming calls to LLMs
Tool Endpoints
  • GET|POST|PUT|DELETE /tools/{toolSlug} - Tool operation calls
  • POST /tools/{toolSlug}/mcp - MCP StreamableHTTP transport
  • GET /tools/{toolSlug}/mcp/sse - MCP SSE transport
  • POST /tools/{toolSlug}/mcp/message - MCP message endpoint
Data Source Endpoints
  • POST /datasource/{dsSlug} - Vector search queries
Metadata Endpoints
  • GET /.well-known/oauth-protected-resource - OAuth2 resource metadata

Dependencies

The AI Gateway requires:

  • Database: For storing LLM configurations, apps, users, and analytics
  • Services Layer: Business logic and database operations (services.Service)
  • Budget Service: Budget enforcement and tracking (services.BudgetService)

Example Microproxy Service

Here's a complete example of a standalone microproxy service:

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "strconv"
    "syscall"
    "time"

    "github.com/TykTechnologies/midsommar/v2/analytics"
    "github.com/TykTechnologies/midsommar/v2/pkg/aigateway"
    "github.com/TykTechnologies/midsommar/v2/services"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func main() {
    // Database setup
    dsn := os.Getenv("DATABASE_URL")
    if dsn == "" {
        log.Fatal("DATABASE_URL environment variable required")
    }
    
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatalf("Failed to connect to database: %v", err)
    }

    // Services setup
    service := services.NewService(db)
    budgetService := services.NewBudgetService(db, service)

    // Initialize analytics
    ctx := context.Background()
    analytics.InitDefault(ctx, db)

    // Gateway setup with interface-based services
    gatewayService := aigateway.NewDatabaseService(service)
    gatewayBudgetService := aigateway.NewDatabaseBudgetService(budgetService)

    port := 9090
    if portStr := os.Getenv("PORT"); portStr != "" {
        if p, err := strconv.Atoi(portStr); err == nil {
            port = p
        }
    }
    
    gateway := aigateway.New(
        gatewayService,
        gatewayBudgetService,
        &aigateway.Config{Port: port},
    )

    // Graceful shutdown setup
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    // Start server
    go func() {
        log.Printf("Starting AI Gateway microproxy on :%d", port)
        if err := gateway.Start(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Gateway error: %v", err)
        }
    }()

    // Wait for shutdown
    <-ctx.Done()
    log.Println("Received shutdown signal...")

    // Graceful shutdown
    shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    if err := gateway.Stop(shutdownCtx); err != nil {
        log.Printf("Gateway shutdown error: %v", err)
        os.Exit(1)
    }
    
    log.Println("Gateway stopped gracefully")
}

Environment Variables

The gateway respects these environment variables:

  • DEBUG_HTTP_PROXY=true - Enables request/response logging for debugging
  • DATABASE_URL - Database connection string (required)
  • PORT - Server port (optional, defaults to config)

Use Cases

  1. Standalone Microproxy: Deploy as a dedicated AI gateway service
  2. API Gateway Integration: Add AI capabilities to existing API gateways
  3. Application Integration: Embed AI gateway into web applications
  4. Development/Testing: Local AI proxy for development environments
  5. Multi-tenant SaaS: AI gateway with tenant isolation and billing

Future Enhancements

  • Custom Configuration Providers: Support for file-based, API-based configurations
  • Plugin System: Extensible middleware and vendor support
  • Observability: Enhanced metrics, tracing, and monitoring
  • Caching: Request/response caching for improved performance
  • Load Balancing: Multiple upstream instances for high availability

Documentation

Overview

Package aigateway provides a simple interface to use the Midsommar AI Gateway as an importable library in standalone applications.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	Port int

	// TLSEnabled must be set when the host serves this gateway over HTTPS on
	// Port. The OpenAI-compatible endpoints (/ai/ and the unified router) call
	// back into /llm/call/ over a loopback connection to Port, and that hop has
	// to match the listener's scheme or the server rejects it as plain HTTP.
	TLSEnabled bool

	// LLMTimeout is the timeout for upstream LLM HTTP requests.
	// Defaults to 5 minutes if zero, suitable for long-running agentic workloads.
	LLMTimeout time.Duration

	// UnifiedRouterBasePath moves the OpenRouter-style single-endpoint ingress
	// ({base}/chat/completions, {base}/completions, {base}/models) off its default
	// "/v1". Set it when embedding the gateway in a host that already owns "/v1",
	// or that mounts this handler under a prefix of its own.
	UnifiedRouterBasePath string

	// DisableUnifiedRouter turns the unified ingress off entirely. The per-route
	// endpoints (/ai/, /llm/, /anthropic/) are unaffected.
	DisableUnifiedRouter bool

	// ServerTiming adds Server-Timing headers/trailers splitting each LLM
	// request into gateway and upstream time. Off by default.
	ServerTiming bool
}

Config represents the configuration for the AI Gateway

type ConfigProvider

type ConfigProvider interface {
	// GetLLMs returns the list of active LLM configurations
	GetLLMs(ctx context.Context) ([]models.LLM, error)

	// GetDatasources returns the list of active datasource configurations
	GetDatasources(ctx context.Context) ([]models.Datasource, error)

	// GetFilters returns the list of filters to be applied
	GetFilters(ctx context.Context) ([]models.Filter, error)

	// Watch returns a channel that signals when configuration changes occur.
	// This enables hot reloading of the gateway configuration.
	Watch() <-chan struct{}
}

ConfigProvider is an interface that allows custom configuration sources for the AI Gateway. This enables users to provide LLM configurations, datasources, and filters from various sources (database, files, APIs, etc.)

type Gateway

type Gateway interface {
	// Start starts the gateway as an HTTP server on the configured port
	Start() error

	// Stop gracefully stops the gateway server
	Stop(ctx context.Context) error

	// Handler returns an http.Handler that can be integrated into existing HTTP servers
	Handler() http.Handler

	// UnifiedRouterBasePath reports the path prefix the OpenRouter-style unified
	// ingress is served under ("/v1" by default), or "" when it is disabled.
	// Hosts mounting Handler() behind their own router forward this prefix.
	UnifiedRouterBasePath() string

	// Reload reloads the gateway configuration (LLMs, datasources, filters)
	Reload() error

	// AddResponseHook adds a response hook for REST request processing (REST-only, not streaming)
	AddResponseHook(hook proxy.ResponseHook)

	// SetAuthHooks sets authentication lifecycle hooks (replaces SetPostAuthCallback)
	SetAuthHooks(hooks *proxy.AuthHooks)

	// SetPostAuthCallback is deprecated, use SetAuthHooks instead
	SetPostAuthCallback(callback proxy.PostAuthCallback)

	// SetRouteResolver installs the router implementation for the /ai/ chain
	// and the unified ingress (Model Routers). Nil disables routers.
	SetRouteResolver(resolver proxy.RouteResolver)
}

Gateway represents an AI Gateway instance that can proxy requests to LLM providers, enforce authentication, budgets, and policies, and provide analytics.

func New

func New(
	gatewayService services.ServiceInterface,
	budgetService services.BudgetServiceInterface,
	config *Config,
) Gateway

New creates a new Gateway instance using the unified services interface with default database analytics. This approach allows for flexible backend implementations (database, file, API, etc.).

Parameters:

  • gatewayService: Service interface for configuration, authentication, and pricing
  • budgetService: Budget interface for spending validation and tracking
  • config: Gateway configuration including port settings

Example with database backend:

db := setupDatabase()
service := services.NewService(db)
budgetService := services.NewBudgetService(db, service)

gateway := aigateway.New(
	service,           // directly use services.Service
	budgetService,     // directly use services.BudgetService
	&aigateway.Config{Port: 9090},
)
gateway.Start()

Example with file-based backend:

gateway := aigateway.New(
	fileGatewayService,    // implements services.ServiceInterface
	fileBudgetService,     // implements services.BudgetServiceInterface
	&aigateway.Config{Port: 9090},
)

func NewWithAnalytics

func NewWithAnalytics(
	gatewayService services.ServiceInterface,
	budgetService services.BudgetServiceInterface,
	analyticsHandler analytics.AnalyticsHandler,
	config *Config,
) Gateway

NewWithAnalytics creates a new Gateway instance with a custom analytics handler. This allows full control over where analytics data is sent (HTTP API, message queue, etc.).

Parameters:

  • gatewayService: Service interface for configuration, authentication, and pricing
  • budgetService: Budget service interface for spending validation and tracking
  • analyticsHandler: Analytics handler for recording usage data (nil uses existing global handler)
  • config: Gateway configuration including port settings

Example with HTTP analytics:

gateway := aigateway.NewWithAnalytics(
	service,           // implements services.ServiceInterface
	budgetService,     // implements services.BudgetServiceInterface
	aigateway.NewHTTPAnalyticsHandler("https://my-control-plane/api"),
	&aigateway.Config{Port: 9090},
)

type HTTPAnalyticsHandler

type HTTPAnalyticsHandler struct {
	// contains filtered or unexported fields
}

HTTPAnalyticsHandler sends analytics to an HTTP endpoint

func NewHTTPAnalyticsHandler

func NewHTTPAnalyticsHandler(endpoint string) *HTTPAnalyticsHandler

NewHTTPAnalyticsHandler creates a new HTTP analytics handler

func (*HTTPAnalyticsHandler) RecordChatLogEntry

func (h *HTTPAnalyticsHandler) RecordChatLogEntry(_ context.Context, log *models.LLMChatLogEntry)

RecordChatLogEntry implements analytics.AnalyticsHandler

func (*HTTPAnalyticsHandler) RecordChatRecord

func (h *HTTPAnalyticsHandler) RecordChatRecord(_ context.Context, record *models.LLMChatRecord)

RecordChatRecord implements analytics.AnalyticsHandler

func (*HTTPAnalyticsHandler) RecordChatRecordsBatch

func (h *HTTPAnalyticsHandler) RecordChatRecordsBatch(_ context.Context, records []*models.LLMChatRecord)

RecordChatRecordsBatch implements batch recording for HTTP analytics

func (*HTTPAnalyticsHandler) RecordComplianceEvents

func (h *HTTPAnalyticsHandler) RecordComplianceEvents(_ context.Context, events []*models.ComplianceEvent)

RecordComplianceEvents implements analytics.AnalyticsHandler (no-op for HTTP handler)

func (*HTTPAnalyticsHandler) RecordProxyLog

func (h *HTTPAnalyticsHandler) RecordProxyLog(_ context.Context, log *models.ProxyLog)

RecordProxyLog implements analytics.AnalyticsHandler

func (*HTTPAnalyticsHandler) RecordProxyLogsBatch

func (h *HTTPAnalyticsHandler) RecordProxyLogsBatch(_ context.Context, logs []*models.ProxyLog)

RecordProxyLogsBatch implements batch recording for HTTP analytics

func (*HTTPAnalyticsHandler) RecordToolCall

func (h *HTTPAnalyticsHandler) RecordToolCall(_ context.Context, name string, timestamp time.Time, execTime int, toolID uint)

RecordToolCall implements analytics.AnalyticsHandler

func (*HTTPAnalyticsHandler) SetAsGlobalHandler

func (h *HTTPAnalyticsHandler) SetAsGlobalHandler()

SetAsGlobalHandler sets this handler as the global analytics handler

type ServiceProvider

type ServiceProvider interface {
	// GetActiveLLMs returns the list of active LLM configurations
	GetActiveLLMs() ([]models.LLM, error)

	// GetActiveDatasources returns the list of active datasource configurations
	GetActiveDatasources() ([]models.Datasource, error)

	// CallToolOperation executes a tool operation
	CallToolOperation(toolID uint, operationID string, params map[string][]string, payload map[string]interface{}, headers map[string][]string) (interface{}, error)

	// GetToolBySlug retrieves a tool by its slug identifier
	GetToolBySlug(slug string) (*models.Tool, error)
}

ServiceProvider is an interface that provides the core service operations needed by the gateway. This allows users to provide custom implementations or wrap existing services.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL