first commit
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
// Package main is the entry point for the LLM Gateway / Proxy service.
|
||||
//
|
||||
// @title LLM Gateway API
|
||||
// @version 1.0
|
||||
// @description OpenAI-compatible and Anthropic-compatible LLM proxy/gateway with Bifrost mapping.
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
//
|
||||
// @contact.name optoant
|
||||
// @contact.email admin@optoant.local
|
||||
//
|
||||
// @license.name MIT
|
||||
// @license.url https://opensource.org/licenses/MIT
|
||||
//
|
||||
// @host localhost:8000
|
||||
// @BasePath /
|
||||
//
|
||||
// @securityDefinitions.apikey BearerAuth
|
||||
// @in header
|
||||
// @name Authorization
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/joho/godotenv"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
gormlogger "gorm.io/gorm/logger"
|
||||
|
||||
"optoant/config"
|
||||
"optoant/handlers"
|
||||
"optoant/internal/logger"
|
||||
"optoant/models"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Load .env file if present (silently ignore if missing)
|
||||
_ = godotenv.Load()
|
||||
|
||||
cfg := config.Load()
|
||||
|
||||
// Logger verbosity from LOG_LEVEL env (debug, info, warn)
|
||||
logger.SetLevel(cfg.LogLevel)
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Database setup (GORM — PostgreSQL or SQLite based on DB_MODE)
|
||||
// ------------------------------------------------------------------
|
||||
var db *gorm.DB
|
||||
if cfg.PostgresDSN != "" || cfg.DBMode == "sqlite" {
|
||||
var dialector gorm.Dialector
|
||||
dbEngine := "postgres"
|
||||
|
||||
switch cfg.DBMode {
|
||||
case "sqlite":
|
||||
dsn := cfg.DBPath
|
||||
if dsn == "" {
|
||||
dsn = "data/gateway.db"
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(dsn), 0755); err != nil {
|
||||
log.Printf("⚠️ Failed to create DB directory: %v", err)
|
||||
}
|
||||
dialector = sqlite.Open(dsn)
|
||||
dbEngine = "sqlite"
|
||||
default:
|
||||
dialector = postgres.Open(cfg.PostgresDSN)
|
||||
}
|
||||
|
||||
var err error
|
||||
db, err = gorm.Open(dialector, &gorm.Config{
|
||||
Logger: gormlogger.Default.LogMode(gormlogger.Warn),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("⚠️ DB connection failed (continuing without DB): %v", err)
|
||||
db = nil
|
||||
} else {
|
||||
if err := db.AutoMigrate(&models.RequestLog{}); err != nil {
|
||||
log.Printf("⚠️ AutoMigrate failed: %v", err)
|
||||
} else {
|
||||
log.Printf("✅ Database connected and migrated (%s)", dbEngine)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Println("ℹ️ No POSTGRES_DSN/DATABASE_DSN set — running without database logging")
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Fiber v3 application
|
||||
// ------------------------------------------------------------------
|
||||
app := fiber.New(fiber.Config{
|
||||
AppName: "LLM Gateway v1.0",
|
||||
ServerHeader: "optoant-gateway",
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Swagger UI at /swagger/* — Fiber v3 native, no v2 dependency
|
||||
// Swagger UI is loaded from CDN; spec is served from /swagger/swagger.json
|
||||
// ------------------------------------------------------------------
|
||||
app.Get("/swagger/swagger.json", func(c fiber.Ctx) error {
|
||||
return c.SendFile("./docs/swagger.json")
|
||||
})
|
||||
app.Get("/swagger", func(c fiber.Ctx) error {
|
||||
return c.Redirect().To("/swagger/")
|
||||
})
|
||||
app.Get("/swagger/*", func(c fiber.Ctx) error {
|
||||
html := `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>LLM Gateway — Swagger UI</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
SwaggerUIBundle({
|
||||
url: "/swagger/swagger.json",
|
||||
dom_id: "#swagger-ui",
|
||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
||||
layout: "StandaloneLayout",
|
||||
deepLinking: true,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
return c.Type("html").SendString(html)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Routes
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// Health check
|
||||
app.Get("/health", handlers.HealthHandler(cfg, db))
|
||||
|
||||
// OpenAI-compatible: forward all /v1/* methods to OPENAI_BACKEND
|
||||
app.All("/v1/*", handlers.OpenAIHandler(cfg, db))
|
||||
|
||||
// Anthropic-compatible: forward all /anthropic/* to backend or Bifrost
|
||||
app.All("/anthropic", handlers.AnthropicHandler(cfg, db))
|
||||
app.All("/anthropic/*", handlers.AnthropicHandler(cfg, db))
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Start server
|
||||
// ------------------------------------------------------------------
|
||||
addr := fmt.Sprintf(":%s", cfg.Port)
|
||||
log.Printf("🚀 LLM Gateway starting on %s", addr)
|
||||
log.Printf(" Log level : %s", cfg.LogLevel)
|
||||
log.Printf(" Upstream backend: %s", cfg.OpenAIBackend)
|
||||
log.Printf(" OpenAI endpoint : /v1/* (direct passthrough)")
|
||||
log.Printf(" Anthropic endpoint: /anthropic/* (Anthropic↔OpenAI transform)")
|
||||
if db != nil {
|
||||
log.Printf(" DB logging : enabled")
|
||||
}
|
||||
|
||||
if err := app.Listen(addr); err != nil {
|
||||
log.Fatalf("server error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user