Files
gpt-load/internal/container/container.go
T

230 lines
6.8 KiB
Go

// Package container assembles the 2.0 dependency graph with dig.
package container
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/dig"
"gorm.io/gorm"
"gpt-load/internal/app"
"gpt-load/internal/catalog"
"gpt-load/internal/channel"
"gpt-load/internal/control"
"gpt-load/internal/dialect"
"gpt-load/internal/execution"
bifrostexecutor "gpt-load/internal/execution/bifrost"
"gpt-load/internal/gateway"
"gpt-load/internal/health"
"gpt-load/internal/httplifecycle"
"gpt-load/internal/platform/config"
"gpt-load/internal/platform/encryption"
"gpt-load/internal/platform/httpclient"
"gpt-load/internal/platform/httproute"
"gpt-load/internal/platform/redact"
"gpt-load/internal/pricing"
"gpt-load/internal/ratelimit"
"gpt-load/internal/requestlog"
"gpt-load/internal/state"
stateloader "gpt-load/internal/state/loader"
"gpt-load/internal/storage"
"gpt-load/internal/telemetry"
"gpt-load/internal/webui"
"github.com/sirupsen/logrus"
)
// BuildContainer creates the 2.0 runtime foundation dependency graph.
func BuildContainer() (*dig.Container, error) {
dependencyContainer := dig.New()
providers := []any{
config.Load,
func(cfg *config.Config) (encryption.Service, error) {
return encryption.NewServiceWithKeyFile(cfg.EncryptionKey, cfg.DataDir)
},
func(cfg *config.Config) (*gorm.DB, error) {
db, err := storage.OpenWithSource(cfg.DatabaseDSN, cfg.DatabaseMetadata.Source)
if err == nil {
logrus.WithField("event", "startup.database_open").Info("database opened")
}
return db, err
},
httplifecycle.NewCoordinator,
app.NewEngineWithLifecycle,
webui.NewServer,
state.NewCredentialRegistry,
channel.NewRegistry,
control.NewPriceRuntime,
control.NewCatalogBootstrap,
func(bootstrap *control.CatalogBootstrap) *catalog.Runtime { return bootstrap.Runtime },
health.NewStatsStore,
health.NewMutationCoordinator,
ratelimit.NewAccessKeyRPM,
func(limiter *ratelimit.AccessKeyRPM) gateway.AccessKeyRPMLimiter {
return limiter
},
func(manager *state.Manager) requestlog.RetentionPolicyProvider {
return retentionSnapshotProvider{manager: manager}
},
func(runtime *control.PriceRuntime) gateway.PriceTableProvider {
return priceRuntimeProvider{runtime: runtime}
},
requestlog.NewService,
func(service *requestlog.Service) telemetry.RequestLogSink {
return service
},
func(service *requestlog.Service) control.RequestLogReader {
return service
},
func(service *requestlog.Service) control.UsageStatReader {
return service
},
func(service *requestlog.Service) control.HomeStatisticsReader {
return service
},
func(service *requestlog.Service) control.RequestLogStatsReader {
return service
},
func(service *requestlog.Service) control.RequestLogCleaner {
return service
},
func(service *requestlog.Service) app.RequestLogRuntime {
return service
},
func(
cfg *config.Config,
registry *state.CredentialRegistry,
stats *health.StatsStore,
) app.RuntimeStateCheckpoint {
return app.NewFileRuntimeStateCheckpoint(cfg.DataDir, registry, stats)
},
control.NewRuntime,
func(runtime *control.Runtime) app.ControlRuntime { return runtime },
httpclient.NewHTTPClientManager,
func(manager *httpclient.HTTPClientManager) *catalog.Client {
return catalog.NewClient(manager, "")
},
redact.New,
func(manager *httpclient.HTTPClientManager) *http.Client {
return manager.GetClient(&httpclient.Config{
ConnectTimeout: 15 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
ResponseHeaderTimeout: 120 * time.Second,
DisableCompression: true,
WriteBufferSize: 32 * 1024,
ReadBufferSize: 32 * 1024,
ForceAttemptHTTP2: true,
TLSHandshakeTimeout: 15 * time.Second,
ExpectContinueTimeout: time.Second,
})
},
dialect.NewOpenAI,
dialect.NewOpenAIResponses,
dialect.NewAnthropic,
dialect.NewGemini,
func(
openAI *dialect.OpenAI,
openAIResponses *dialect.OpenAIResponses,
anthropic *dialect.Anthropic,
gemini *dialect.Gemini,
) dialect.Set {
return dialect.NewSet(openAI, openAIResponses, anthropic, gemini)
},
func(registry *channel.Registry) (*bifrostexecutor.RuntimeManager, error) {
return bifrostexecutor.NewManagedRuntime(registry)
},
func(runtime *bifrostexecutor.RuntimeManager) *state.Manager {
manager := state.NewManager()
manager.SetSnapshotReconciler(providerRuntimeSnapshotReconciler{runtime: runtime})
return manager
},
func(runtime *bifrostexecutor.RuntimeManager) execution.Executor { return runtime },
func(runtime *bifrostexecutor.RuntimeManager) app.ExecutionRuntime { return runtime },
gateway.NewExecutionForwarder,
func(forwarder *gateway.ExecutionForwarder) gateway.AttemptForwarder { return forwarder },
gateway.NewHandlerWithLifecycle,
control.NewService,
control.NewCatalogSyncCoordinator,
func(service *control.Service) app.StartupBootstrap { return service },
func(service *control.Service) app.StartupRecovery { return service },
control.NewServer,
newHTTPRegistry,
func(
db *gorm.DB,
manager *state.Manager,
registry *state.CredentialRegistry,
channelRegistry *channel.Registry,
encryptionService encryption.Service,
) app.RuntimeStateLoader {
return stateloader.NewWithCredentialValidation(
db,
manager,
registry,
channelRegistry,
encryptionService,
)
},
app.NewApp,
}
for _, provider := range providers {
if err := dependencyContainer.Provide(provider); err != nil {
return nil, err
}
}
if err := dependencyContainer.Invoke(func(
engine *gin.Engine,
registry *httproute.Registry,
) error {
return registry.Bind(engine)
}); err != nil {
return nil, fmt.Errorf("register HTTP routes: %w", err)
}
return dependencyContainer, nil
}
type providerRuntimeSnapshotReconciler struct {
runtime *bifrostexecutor.RuntimeManager
}
func (reconciler providerRuntimeSnapshotReconciler) ReconcileConfigSnapshot(snapshot *state.ConfigSnapshot) error {
if reconciler.runtime == nil {
return fmt.Errorf("reconcile provider runtimes: runtime manager is unavailable")
}
targets := make([]channel.ResolvedTarget, 0)
if snapshot != nil {
targets = make([]channel.ResolvedTarget, 0, len(snapshot.Groups))
for _, group := range snapshot.Groups {
targets = append(targets, group.ResolvedTarget)
}
}
return reconciler.runtime.Reconcile(targets)
}
func newHTTPRegistry(
gatewayHandler *gateway.Handler,
controlServer *control.Server,
webUIServer *webui.Server,
) (*httproute.Registry, error) {
return httproute.NewRegistry(
app.HTTPModule(),
controlServer.HTTPModule(),
gatewayHandler.HTTPModule(),
webUIServer.HTTPModule(),
)
}
type priceRuntimeProvider struct {
runtime *control.PriceRuntime
}
func (provider priceRuntimeProvider) Load() *pricing.Table {
return provider.runtime.Load()
}