Files
opantoantro/main.go
T
Beyhan Ogur c56ae7194c feat: enable SSE streaming support in Anthropic handler
- Updated AnthropicHandler to check for streaming configuration before handling SSE transform.
- Modified handleStreaming to use the original request body instead of forcing stream to true.
- Adjusted the transformation logic in AnthropicToBifrost to respect the original stream setting.
- Added logging for SSE streaming configuration in the main application.
2026-05-11 15:28:56 +03:00

170 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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")
}
log.Printf(" SSE streaming : %v", cfg.Streaming)
if err := app.Listen(addr); err != nil {
log.Fatalf("server error: %v", err)
os.Exit(1)
}
}