first commit

This commit is contained in:
Beyhan Ogur
2026-05-11 15:08:50 +03:00
commit a408821410
47 changed files with 4670 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
# AnthropicHandler
**Özet:** `/anthropic/*` route'unu karşılar (`handlers/anthropic.go:75`). Anthropic Messages API formatını OpenAI Chat Completions formatına çevirir (Bifrost), upstream'e iletir ve yanıtı geri Anthropic formatına dönüştürür.
**Kütüphaneler:** Fiber v3, GORM, Go `encoding/json`
**Bağlantılar:** [[Main]], [[BifrostTransform]], [[ProxyEngine]], [[RequestLog]], [[Config]], [[Anthropic Flow]]
## Davranış
1. `GET /v1/models` → [[AnthropicHandler#modelsList|modelleri listele]]
2. `HEAD` → 404 (DeepSeek uyumluluğu)
3. Empty body → varsayılan "optoant gateway ready" yanıtı
4. `x-api-key``Authorization: Bearer` dönüşümü (Claude Code uyumluluğu)
5. Gerekirse `OPENAI_KEY`'den auto-inject
6. [[BifrostTransform#AnthropicToBifrost|AnthropicToBifrost()]] ile dönüşüm
7. [[ProxyEngine]] ile `OPENAI_BACKEND/v1/chat/completions`'a forward
8. Başarılıysa (200): [[BifrostTransform#BifrostToAnthropic|BifrostToAnthropic()]] → Anthropic format
9. Hata durumunda: raw passthrough
10. DB log (fire-and-forget)
## Endpoint
```
POST /anthropic/v1/messages
x-api-key: <api-key>
# veya
Authorization: Bearer <api-key>
```
## Modeller (`modelsList()`)
| ID | Display Name |
|---|---|
| `deepseek-v4-flash` | DeepSeek V4 Flash |
| `deepseek-v4-pro` | DeepSeek V4 Pro |
## Ek Fonksiyonlar
| Fonksiyon | Açıklama |
|---|---|
| `infoPage()` | `GET /anthropic` (boş path) için HTML bilgi sayfası |
| `modelsList()` | `GET /anthropic/v1/models` için model listesi |
## Edge Case'ler
- Geçersiz Anthropic formatı → uyarı log'u + raw passthrough
- Upstream hatası`502 Bad Gateway`
- Yanıt dönüşüm hatası → raw yanıtı forward et
+46
View File
@@ -0,0 +1,46 @@
# Anthropic Flow
**Özet:** Anthropic Messages API formatındaki isteklerin gateway üzerinden Bifrost dönüşümüyle OpenAI formatına çevrilip upstream'e iletilmesi ve yanıtın geri Anthropic formatına dönüştürülmesi.
**Kütüphaneler:** Fiber v3, Go `net/http`, `encoding/json`
**Bağlantılar:** [[AnthropicHandler]], [[BifrostTransform]], [[ProxyEngine]], [[RequestLog]], [[Index]]
## Akış Diyagramı
```mermaid
sequenceDiagram
participant Client
participant Gateway
participant Bifrost as BifrostTransform
participant Proxy
participant Upstream
participant DB
Client->>Gateway: POST /anthropic/v1/messages
Note over Gateway: AnthropicHandler
Gateway->>Gateway: x-api-key → Authorization: Bearer
Gateway->>Gateway: API key auto-inject (gerekirse)
Gateway->>Bifrost: AnthropicToBifrost()
Bifrost-->>Gateway: OpenAI Chat Completions format
Gateway->>Proxy: Forward() to /v1/chat/completions
Proxy->>Upstream: POST <BACKEND>/v1/chat/completions
Upstream-->>Proxy: 200 OK + OpenAI format response
Proxy-->>Gateway: ForwardResult
Gateway->>Bifrost: BifrostToAnthropic()
Bifrost-->>Gateway: Anthropic Messages format
Gateway->>DB: go db.Create(logEntry)
Gateway-->>Client: 200 OK (Anthropic format)
```
## Edge Case'ler
| Senaryo | Davranış |
|---|---|
| **HEAD isteği** | 404 (DeepSeek uyumluluğu) |
| **Empty body** | Varsayılan "optoant gateway ready" yanıtı |
| **Geçersiz Anthropic formatı** | Raw passthrough + uyarı log'u |
| **Yanıt dönüşüm hatası** | Raw yanıtı forward et (passthrough) |
| **GET /v1/models** | DeepSeek model listesi (Anthropic formatında) |
| **Boş path /anthropic** | HTML info sayfası (`infoPage()`) |
| **Upstream hatası** | 502 Bad Gateway |
+61
View File
@@ -0,0 +1,61 @@
# BifrostTransform
**Özet:** Anthropic Messages API formatı ile OpenAI Chat Completions formatı arasında çift yönlü dönüşüm yapan katman (`internal/transform/anthropic_bifrost.go:74`). "Bifrost" (İskandinav mitolojisindeki köprü) adı, two-way dönüşümü simgeler.
**Kütüphaneler:** Go `encoding/json`, `strings`
**Bağlantılar:** [[AnthropicHandler]], [[Index]]
## Struct'lar
### Anthropic Format (Giriş/Çıkış)
| Struct | Alanlar |
|---|---|
| `AnthropicRequest` | `Model`, `MaxTokens`, `Messages []AnthropicMessage`, `System`, `Stream` |
| `AnthropicMessage` | `Role`, `Content` |
| `AnthropicResponse` | `ID`, `Type`, `Role`, `Content []AnthropicContent`, `Model`, `StopReason`, `StopSequence` |
| `AnthropicContent` | `Type`, `Text` |
### OpenAI / Bifrost Format (Ara Katman)
| Struct | Alanlar |
|---|---|
| `BifrostRequest` | `Model`, `Messages []BifrostMessage`, `MaxTokens`, `Stream` |
| `BifrostMessage` | `Role`, `Content` |
| `BifrostResponse` | `ID`, `Object`, `Created`, `Model`, `Choices[{Message{Role,Content}, FinishReason, Index}]` |
## Dönüşüm Detayları
### Anthropic → OpenAI (`AnthropicToBifrost()`)
```
Anthropic Request OpenAI Request
───────────────── ─────────────
model: "claude-3-5-sonnet" → model: "Anthropic/claude-3-5-sonnet"
system: "Be helpful" → messages: [{role:"system", ...}, ...]
messages: [{role, content}] → messages: [{role, content}]
max_tokens: 1024 → max_tokens: 1024
stream: true → stream: true
```
### OpenAI → Anthropic (`BifrostToAnthropic()`)
```
OpenAI Response Anthropic Response
─────────────── ─────────────────
id: "chatcmpl-xxx" → id: "chatcmpl-xxx"
choices[].message.content → content: [{type:"text", text:"..."}]
choices[].finish_reason → stop_reason: "stop"/"end_turn"
model → model (same)
```
## Model Prefix Tahmini (`guessProvider()`)
| Model içerir | Prefix | Örnek |
|---|---|---|
| `deepseek` | `DeepSeek/` | `deepseek-v4``DeepSeek/deepseek-v4` |
| `gpt`, `openai` | `OpenAI/` | `gpt-4``OpenAI/gpt-4` |
| `claude`, `anthropic` | `Anthropic/` | `claude-3``Anthropic/claude-3` |
| `gemini` | `Google/` | `gemini-pro``Google/gemini-pro` |
| diğer | `DeepSeek/` (varsayılan) | — |
+95
View File
@@ -0,0 +1,95 @@
# ClaudeCode_Setup
**Özet:** Claude Code CLI'ı bu LLM Gateway üzerinden kullanmak için gerekli yapılandırma. Claude Code, Anthropic Messages API formatında konuşur; gateway `/anthropic/*` endpoint'i ile bunu OpenAI formatına çevirip herhangi bir OpenAI-compatible backend'e (DeepSeek, vb.) iletir.
**Kütüphaneler:** Claude Code CLI, Go Fiber v3
**Bağlantılar:** [[AnthropicHandler]], [[BifrostTransform]], [[Anthropic Flow]], [[Config]], [[Index]]
## Nasıl Çalışır
```
Claude Code → x-api-key → Gateway /anthropic/v1/messages → AnthropicToBifrost()
OpenAI Chat Completions
OPENAI_BACKEND/v1/chat/completions
BifrostToAnthropic()
Claude Code ← Anthropic Messages API ← Gateway ← ← ← ← ← ←
```
## Claude Code Yapılandırması
Claude Code'u gateway'e yönlendirmek için iki yol var:
### 1. Environment Variable (Önerilen)
```bash
export ANTHROPIC_BASE_URL="http://localhost:8000/anthropic"
export ANTHROPIC_API_KEY="<upstream-api-key>"
claude
```
### 2. Claude Code Settings JSON
`~/.claude/settings.json`:
```json
{
"anthropicBaseUrl": "http://localhost:8000/anthropic",
"apiKey": "<upstream-api-key>"
}
```
## Gateway Konfigürasyonu (`.env`)
```env
PORT=8000
OPENAI_BACKEND=https://api.deepseek.com
OPENAI_KEY=sk-your-deepseek-api-key
REQUEST_TIMEOUT_SECONDS=30
```
| Değişken | Değer | Açıklama |
|---|---|---|
| `OPENAI_BACKEND` | `https://api.deepseek.com` | Upstream LLM API (OpenAI-compatible) |
| `OPENAI_KEY` | `sk-...` | Upstream API anahtarı (auto-inject) |
## Test
Gateway çalışırken Claude Code'un gönderdiği formatta test:
```bash
curl -X POST "http://localhost:8000/anthropic/v1/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: sk-test-key" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 256,
"messages": [{"role": "user", "content": "Merhaba, 2+2 nedir?"}]
}'
```
Başarılı yanıt formatı:
```json
{
"id": "chatcmpl-xxx",
"type": "message",
"role": "assistant",
"content": [{"type": "text", "text": "4"}],
"model": "DeepSeek/deepseek-v4",
"stop_reason": "end_turn"
}
```
## Önemli Notlar
- Claude Code `x-api-key` header'ı ile authentication yapar → Gateway bunu `Authorization: Bearer`'a çevirir
- `anthropic-version` header'ı olduğu gibi upstream'e iletilir
- Model adı otomatik prefix alır: `claude-sonnet-4-20250514``Anthropic/claude-sonnet-4-20250514`
- **Streaming (`stream: true`) henüz desteklenmez** — response tamamlanana kadar bekler
- Claude Code `--no-stream` flag'i ile streaming'i kapatarak kullanılabilir: `claude --no-stream`
+30
View File
@@ -0,0 +1,30 @@
# Config
**Özet:** Tüm runtime konfigürasyonu `.env` dosyasından okur (`config/config.go:9`). Hiçbir varsayılan IP veya port sabit kodlanmamıştır — tüm değerler `os.Getenv()` ile okunur.
**Kütüphaneler:** Go `os`, `strconv`
**Bağlantılar:** [[Main]], [[Index]]
## Parametreler
| Değişken | Varsayılan | Config Alanı | Açıklama |
|---|---|---|---|
| `PORT` | `8000` | `Port` | HTTP dinleme portu |
| `OPENAI_BACKEND` | `https://api.deepseek.com` | `OpenAIBackend` | Upstream LLM backend base URL |
| `OPENAI_KEY` | `""` | `OpenAIApiKey` | Opsiyonel API anahtarı (otomatik enjekte) |
| `DB_MODE` | `pgs` | `DBMode` | Veritabanı motoru: `sqlite` veya `pgs` |
| `DB_PATH` | `data/gateway.db` | `DBPath` | SQLite veritabanı dosya yolu (`DB_MODE=sqlite` iken) |
| `LOG_LEVEL` | `info` | `LogLevel` | Konsol log seviyesi: `debug` (her şey), `info` (özet), `warn` (sadece hatalar) |
| `POSTGRES_DSN` / `DATABASE_DSN` | `""` | `PostgresDSN` | PostgreSQL bağlantı DSN (`DB_MODE=pgs` iken) |
| `REQUEST_TIMEOUT_SECONDS` | `30` | `RequestTimeoutSeconds` | Upstream istek zaman aşımı (saniye) |
| `OPENAI_MODEL` | `""` | `OpenAIModel` | Varsayılan model adı (istekte model yoksa enjekte edilir) |
## Önemli Detaylar
- `DB_MODE=sqlite``DB_PATH` değeri SQLite dosya yolu olarak kullanılır (varsayılan: `data/gateway.db`)
- `DB_MODE=pgs` veya tanımsız → PostgreSQL bağlantısı (mevcut davranış)
- `POSTGRES_DSN` ve `DATABASE_DSN` çifti desteklenir (birincil: `POSTGRES_DSN`)
- `OPENAI_KEY` tanımlanmışsa, client `Authorization` header'ı göndermediğinde otomatik enjekte edilir
- Tüm değerler `os.Getenv()` ile okunur
- Config struct'ı main.go'da bir kez yüklenir ve tüm handler'lara pointer olarak geçilir
+60
View File
@@ -0,0 +1,60 @@
# DockerSetup
**Özet:** Multi-stage Docker build ile optimize edilmiş imaj üretimi ve docker-compose ile PostgreSQL + Gateway orchestration'ı.
**Kütüphaneler:** Docker, docker-compose, Alpine Linux
**Bağlantılar:** [[Main]], [[Config]], [[Index]]
## Dockerfile (`docker/Dockerfile`)
**Stage 1 — Build:** `golang:1.24-alpine`
- Dependency caching için önce `go.mod` + `go.sum` kopyalanır
- `CGO_ENABLED=0` ile statik binary
- `-ldflags="-w -s"` ile boyut optimizasyonu
**Stage 2 — Runtime:** `alpine:3.21`
- Sadece binary + `ca-certificates` + `tzdata`
- Non-root `gateway` kullanıcısı (güvenlik)
- Port 8000 expose
## docker-compose (`docker/docker-compose.yml`)
| Servis | İmaj/Rol | Özellikler |
|---|---|---|
| `app` | LLM Gateway (build) | `.env`'den config, postgres'e bağımlı |
| `postgres` | `postgres:16-alpine` | Healthcheck, persistent volume `pgdata` |
### Servis Detayları (app)
- Port mapping: `${PORT:-8000}:8000`
- Environment: `PORT`, `OPENAI_BACKEND`, `DATABASE_DSN`, `REQUEST_TIMEOUT_SECONDS`
- `depends_on` ile postgres healthcheck bekler
- `restart: unless-stopped`
## Kullanım
```bash
# Tüm stack'i başlat
docker compose -f docker/docker-compose.yml up -d
# Sadece PostgreSQL (geliştirme)
docker compose -f docker/docker-compose.yml up -d postgres
# Port override
PORT=9000 docker compose -f docker/docker-compose.yml up -d
# Sadece imaj build
docker build -t optoant-gateway:latest -f docker/Dockerfile .
```
## Alternatif Derleme
```bash
# build.sh ile production binary
./build.sh
# Çıktı: ./gateway
# Veya direkt Go build
CGO_ENABLED=0 go build -ldflags="-w -s" -o gateway ./main.go
```
+29
View File
@@ -0,0 +1,29 @@
# HealthHandler
**Özet:** `/health` endpoint'i ile servis durumu, veritabanı bağlantısı ve aktif konfigürasyon bilgilerini döndürür (`handlers/health.go:27`).
**Kütüphaneler:** Fiber v3, GORM
**Bağlantılar:** [[Main]], [[Config]], [[Index]]
## Yanıt Formatı
```json
{
"status": "ok",
"database": "ok" | "unreachable" | "disabled",
"config": {
"openai_backend": "https://api.deepseek.com",
"port": "8000"
}
}
```
## Davranış
| Durum | `database` değeri |
|---|---|
| DB bağlantısı yok (DSN boş) | `"disabled"` |
| DB var, ping başarılı | `"ok"` |
| DB var, ping başarısız | `"unreachable"` |
| Her durumda `status` | `"ok"` (servis ayakta) |
+86
View File
@@ -0,0 +1,86 @@
# LLM Gateway — Mimari Haritası
**Özet:** Bu proje, birden fazla LLM sağlayıcısını (OpenAI-compatible, Anthropic) tek bir gateway üzerinden proxy'leyen, istekleri dönüştüren ve loglayan bir Go hizmetidir. Go 1.26 + Fiber v3 + GORM + PostgreSQL / SQLite (`DB_MODE` ile seçimli) teknolojileriyle inşa edilmiştir.
**Kütüphaneler:** Go 1.26, Fiber v3, GORM, PostgreSQL, SQLite, godotenv, swaggo
**Bağlantılar:** [[Main]], [[Config]], [[HealthHandler]], [[OpenAIHandler]], [[AnthropicHandler]], [[ProxyEngine]], [[BifrostTransform]], [[RequestLog]], [[SwaggerUI]], [[Testing]], [[DockerSetup]], [[OpenAI_Flow]], [[Anthropic_Flow]], [[ClaudeCode_Setup]]
---
## Katman Mimarisi
| Katman | Dosya | Açıklama |
|--------|-------|----------|
| **Giriş** | `main.go:38` | Fiber v3 app başlatma, DB bağlantısı, route tanımları |
| **Konfigürasyon** | `config/config.go:9` | `.env` tabanlı runtime config (port, backend, DSN) |
| **Handler** | `handlers/*.go` | HTTP handler fonksiyonları (health, openai, anthropic) |
| **Proxy** | `internal/proxy/proxy.go:14` | Generic HTTP forward motoru |
| **Transform** | `internal/transform/anthropic_bifrost.go:10` | Anthropic ↔ OpenAI format dönüştürücü |
| **Model** | `models/request_log.go:13` | GORM veritabanı modeli |
## API Endpoint'leri
| Method | Path | Handler | Açıklama |
|--------|------|---------|----------|
| `GET` | `/health` | [[HealthHandler]] | Servis durumu, DB sağlığı, config bilgisi |
| `ALL` | `/v1/*` | [[OpenAIHandler]] | OpenAI-compatible direct passthrough proxy |
| `ALL` | `/anthropic`, `/anthropic/*` | [[AnthropicHandler]] | Anthropic Messages API → OpenAI dönüşüm proxy |
| `GET` | `/swagger`, `/swagger/*` | Inline Fiber | CDN'den yüklenen Swagger UI |
## Veri Akışları
- [[OpenAI Flow]] — `/v1/*` isteklerinin upstream'e direkt iletilmesi
- [[Anthropic Flow]] — `/anthropic/*` isteklerinin Bifrost dönüşümüyle upstream'e iletilmesi
## Claude Code Entegrasyonu
- [[ClaudeCode_Setup]] — Claude Code CLI'ı gateway üzerinden kullanma
## Altyapı ve Geliştirme
- [[DockerSetup]] — Multi-stage Docker build + docker-compose (app + postgres)
- [[SwaggerUI]] — `/swagger/` adresinde OpenAPI dokümantasyonu
- [[Testing]] — `httptest.Server` ile mock upstream testleri
- `.air.toml` — Air hot-reload (geliştirme)
- `build.sh` — Production binary derleme scripti
- [[RequestLog]] — GORM + PostgreSQL/SQLite istek loglama
## Veritabanı Şeması
| Tablo | Model | Amaç |
|-------|-------|------|
| `request_logs` | [[RequestLog]] | Tüm proxy isteklerinin log kaydı |
## Dönüşüm Katmanı
| Yön | Fonksiyon | Açıklama |
|-----|-----------|----------|
| Anthropic → OpenAI | `AnthropicToBifrost()` | Mesaj API → Chat Completions dönüşümü |
| OpenAI → Anthropic | `BifrostToAnthropic()` | Chat Completions → Mesaj API dönüşümü |
Desteklenen model prefix'leri: `DeepSeek/`, `OpenAI/`, `Anthropic/`, `Google/`
## Proje Haritası
```
.
├── main.go # Giriş noktası
├── config/config.go # Konfigürasyon
├── models/request_log.go # Veritabanı modeli
├── handlers/
│ ├── health.go # Health check
│ ├── openai.go # OpenAI proxy
│ └── anthropic.go # Anthropic proxy
├── internal/
│ ├── proxy/proxy.go # Forward motoru
│ └── transform/anthropic_bifrost.go # Format dönüştürücü
├── tests/openai_test.go # Unit testler
├── docker/
│ ├── Dockerfile # Multi-stage build
│ └── docker-compose.yml # Orchestration
├── build.sh # Derleme scripti
└── docs/
├── swagger.json/yaml # Swagger spec
└── wiki/ # Bu wiki (Obsidian Knowledge Graph)
```
+36
View File
@@ -0,0 +1,36 @@
# Main
**Özet:** Uygulamanın giriş noktası (`main.go:38`). Fiber v3 web framework'ünü başlatır, `DB_MODE`'a göre PostgreSQL veya SQLite bağlantısını kurar, AutoMigrate çalıştırır ve tüm route'ları tanımlar.
**Kütüphaneler:** Fiber v3, GORM, PostgreSQL, SQLite, godotenv
**Bağlantılar:** [[Index]], [[Config]], [[RequestLog]], [[OpenAIHandler]], [[AnthropicHandler]], [[HealthHandler]], [[SwaggerUI]], [[DockerSetup]]
## Başlangıç Adımları
1. `.env` dosyası yüklenir (`godotenv.Load()`)
2. [[Config]] okunur (`config.Load()`)
3. `DB_MODE` kontrol edilir — PostgreSQL DSN veya SQLite bağlantısı + AutoMigrate (`RequestLog`)
4. Fiber v3 app oluşturulur (`fiber.Config{AppName: "LLM Gateway v1.0", ServerHeader: "optoant-gateway"}`)
5. Route'lar tanımlanır:
- `GET /health` → [[HealthHandler]]
- `GET /swagger/swagger.json` → statik dosya serve
- `GET /swagger``/swagger/` redirect
- `GET /swagger/*` → Swagger UI HTML (CDN)
- `ALL /v1/*` → [[OpenAIHandler]] (direct passthrough)
- `ALL /anthropic`, `/anthropic/*` → [[AnthropicHandler]] (Bifrost dönüşümü)
6. `app.Listen(addr)` ile server başlatılır
## Önemli Detaylar
- DB bağlantısı başarısız olursa uygulama **devam eder** (DB'siz çalışabilir)
- Tüm backend istekleri `.env`'deki `OPENAI_BACKEND` adresine gider
- OpenAI endpoint'i direkt passthrough yaparken, Anthropic endpoint'i format dönüşümü yapar
- Swagger UI **CDN'den** yüklenir (offline çalışmaz)
- API key auto-injection: `OPENAI_KEY` env'i tanımlıysa, client Authorization göndermezse otomatik eklenir
## Geliştirme
- **Air hot-reload:** `.air.toml` ile canlı yeniden derleme (`air`)
- **Production build:** `build.sh` ile `CGO_ENABLED=0` optimize binary
- **Docker:** `docker/Dockerfile` multi-stage + `docker-compose.yml`
+36
View File
@@ -0,0 +1,36 @@
# OpenAIHandler
**Özet:** `/v1/*` route'unu karşılar (`handlers/openai.go:29`), gelen istekleri olduğu gibi `OPENAI_BACKEND` adresine iletir. OpenAI-compatible bir proxy'dir.
**Kütüphaneler:** Fiber v3, GORM, Go `net/http`
**Bağlantılar:** [[Main]], [[ProxyEngine]], [[RequestLog]], [[Config]], [[OpenAI Flow]]
## Davranış
1. Gelen isteği alır, header'ları `net/http.Header` formatına çevirir (`fiberToHTTPHeaders()`)
2. Eğer `OPENAI_KEY` tanımlanmışsa ve client Authorization göndermemişse, otomatik ekler
3. [[ProxyEngine]] ile isteği `OPENAI_BACKEND + originalURL` adresine forward eder
4. Response header'larını kopyalar (`copyResponseHeaders()` — Transfer-Encoding, Content-Length, Connection atlanır)
5. Yanıtı olduğu gibi client'a döndürür
6. [[RequestLog]]'a fire-and-forget goroutine ile kayıt ekler
## Endpoint
```
ANY /v1/chat/completions
ANY /v1/models
Authorization: Bearer <api-key>
```
## Yardımcı Fonksiyonlar
| Fonksiyon | Açıklama |
|---|---|
| `fiberToHTTPHeaders(c fiber.Ctx) http.Header` | Fiber header'larını `net/http` formatına çevirir |
| `copyResponseHeaders(c fiber.Ctx, headers http.Header)` | Upstream response header'larını Fiber response'a kopyalar (hop-by-hop atlanır) |
## Hata Durumu
- Upstream hatasında → `502 Bad Gateway` + hata mesajı
- Loglama hatası → ana isteği etkilemez (fire-and-forget)
+38
View File
@@ -0,0 +1,38 @@
# OpenAI Flow
**Özet:** OpenAI-compatible isteklerin gateway üzerinden direkt passthrough akışı.
**Kütüphaneler:** Fiber v3, Go `net/http`
**Bağlantılar:** [[OpenAIHandler]], [[ProxyEngine]], [[RequestLog]], [[Index]]
## Akış Diyagramı
```mermaid
sequenceDiagram
participant Client
participant Gateway
participant Proxy
participant Upstream
participant DB
Client->>Gateway: POST /v1/chat/completions
Note over Gateway: OpenAIHandler
Gateway->>Gateway: fiberToHTTPHeaders()
Gateway->>Gateway: API key auto-inject (gerekirse)
Gateway->>Gateway: copyResponseHeaders()
Gateway->>Proxy: Forward(method, targetURL, headers, body, timeout)
Proxy->>Upstream: POST <OPENAI_BACKEND>/v1/chat/completions
Upstream-->>Proxy: 200 OK + response body
Proxy-->>Gateway: ForwardResult{StatusCode, Body, Headers}
Gateway->>Gateway: copyResponseHeaders() (skip hop-by-hop)
Gateway->>DB: go db.Create(logEntry) — fire-and-forget
Gateway-->>Client: 200 OK + original response body
```
## Önemli Noktalar
- İstek body'si **olduğu gibi** iletilir (dönüşüm yok)
- Response header'ları kopyalanırken `Transfer-Encoding`, `Content-Length`, `Connection` atlanır
- Upstream hatasında client `502 Bad Gateway` alır
- DB loglama bir goroutine içinde çalışır, asla ana isteği bloke etmez
+41
View File
@@ -0,0 +1,41 @@
# ProxyEngine
**Özet:** HTTP isteklerini upstream backend'e ileten merkezi proxy motoru (`internal/proxy/proxy.go:24`). Tüm header'ları korur, hassas header'ları log'da maskeler, hop-by-hop header'ları otomatik atlar.
**Kütüphaneler:** Go `net/http`, `context`, `io`
**Bağlantılar:** [[OpenAIHandler]], [[AnthropicHandler]], [[Index]]
## Struct
```go
type ForwardResult struct {
StatusCode int
Body []byte
Headers http.Header
}
```
## Fonksiyonlar
### `Forward(ctx, method, targetURL, headers, body, timeoutSec) *ForwardResult`
- HTTP isteğini hedef URL'ye iletir
- Context üzerinden timeout yönetimi (`time.Duration(timeoutSec) * time.Second`)
- Hop-by-hop header'ları atlar: `Connection`, `TE`, `Trailers`, `Transfer-Encoding`, `Upgrade`
- Body'yi log için okur, sonra tekrar sarar
- `ForwardResult{StatusCode, Body, Headers}` döndürür
### `MaskSensitiveHeaders(headers) http.Header`
- Header'ları clone'lar
- `Authorization``Bearer [REDACTED]`
- `X-Api-Key``[REDACTED]`
- Sadece loglama amaçlı kullanılır; upstream'e orijinal header gider
### `truncateForLog(s, maxLen) string`
- Uzun string'leri `maxLen` (2000) karakterde kırpar, `...[TRUNCATED]` ekler
## Güvenlik
- Hassas header'lar upstream'e **olduğu gibi** iletilir ama **log'da maskelenir**
- Hop-by-hop header'lar otomatik atlanır (proxy zincirini bozmamak için)
- Sensitive header masking sadece log çıktısı içindir
+33
View File
@@ -0,0 +1,33 @@
# RequestLog
**Özet:** Her proxy'lenen isteğin kaydını tutan GORM modeli (`models/request_log.go:13`). Hassas header'lar (Authorization) asla düz metin olarak saklanmaz. Loglama fire-and-forget goroutine ile yapılır.
**Kütüphaneler:** GORM, PostgreSQL, Go `time`
**Bağlantılar:** [[Main]], [[OpenAIHandler]], [[AnthropicHandler]], [[Index]]
## Tablo: `request_logs`
| Column | GORM Tipi | JSON | Açıklama |
|--------|-----------|------|----------|
| `id` | `uint` (PK) | `id` | Otomatik artan |
| `created_at` | `time.Time` | `created_at` | Oluşturulma zamanı |
| `updated_at` | `time.Time` | `updated_at` | Güncellenme zamanı |
| `deleted_at` | `gorm.DeletedAt` | (gizli) | Soft delete index |
| `endpoint` | `string(512)` | `endpoint` | İstek yolu |
| `method` | `string(16)` | `method` | HTTP metodu |
| `client_ip` | `string(64)` | `client_ip` | İstemci IP |
| `request_body` | `text` | `request_body` | Body (2000 char limit) |
| `response_status` | `int` | `response_status` | HTTP yanıt kodu |
| `latency_ms` | `int64` | `latency_ms` | İşlem süresi (ms) |
## Sabitler
- `maxBodyLength = 2000``TruncateBody()` ile body bu limite kırpılır
## Önemli Detaylar
- `TruncateBody()` body'yi 2000 karakterde kırpar, sonuna `…[truncated]` ekler
- Loglama **fire-and-forget** (`go db.Create()`): loglama hatası ana isteği etkilemez
- Hassas header'lar (Authorization, X-Api-Key) veritabanına yazılmaz
- DB bağlantısı yoksa loglama tamamen atlanır
+40
View File
@@ -0,0 +1,40 @@
# SwaggerUI
**Özet:** Fiber v3 native Swagger UI entegrasyonu (`main.go:79`). OpenAPI spec `docs/swagger.json` dosyasından statik olarak servis edilir, UI CDN'den (unpkg) yüklenir.
**Kütüphaneler:** swaggo/swag, Swagger UI 5.x (CDN)
**Bağlantılar:** [[Main]], [[Index]]
## Route'lar
| Path | Açıklama |
|---|---|
| `GET /swagger` | `/swagger/`'a redirect |
| `GET /swagger/` | Swagger UI HTML (CDN) |
| `GET /swagger/swagger.json` | Statik OpenAPI spec dosyası |
## Kullanım
```bash
# Swagger spec'i yenile
swag init
# Swagger UI'a tarayıcıdan eriş
open http://localhost:8000/swagger/
```
## Swagger Annotation'ları
- `main.go:1-19` — Genel API bilgisi (title, version, host, security)
- `handlers/health.go:21-27``/health` endpoint
- `handlers/openai.go:20-28``/v1/{path}` endpoint
- `handlers/anthropic.go:65-73``/anthropic/{path}` endpoint
## Önemli Detaylar
- Swagger UI **unpkg CDN'inden** yüklenir (offline çalışmaz)
- Spec statik dosyadır: `docs/swagger.json`
- `docs/swagger.yaml` da mevcuttur (aynı spec)
- `swag init` her endpoint/değişiklik sonrası çalıştırılmalıdır
- API key: `Authorization: Bearer` (Swagger UI'da "Authorize" butonu)
+71
View File
@@ -0,0 +1,71 @@
# Testing
**Özet:** Fiber v3 test framework'ü ile mock upstream sunucuları kullanarak proxy mantığının test edilmesi. Transform katmanı unit testleri `internal/transform`, entegrasyon testleri `tests/` paketinde.
**Kütüphaneler:** Go `testing`, `net/http/httptest`, Fiber v3 test (`app.Test()`), `encoding/json`
**Bağlantılar:** [[OpenAIHandler]], [[AnthropicHandler]], [[BifrostTransform]], [[ProxyEngine]], [[Index]]
## Test Stratejisi
- `mockUpstream(t, status, body)``httptest.Server` ile sabit yanıt dönen test sunucusu
- `newTestApp(cfg)` — OpenAI handler bağlı Fiber test uygulaması (DB'siz)
- `newAnthropicTestApp(cfg)` — Anthropic handler bağlı Fiber test uygulaması (DB'siz)
- Gerçek upstream'e ihtiyaç yok, mock sunucu kullanılır
- `app.Test(req, fiber.TestConfig{Timeout: -1})` ile Fiber native HTTP test
## Transform Unit Testleri (`internal/transform`)
### Anthropic → OpenAI (`AnthropicToBifrost`)
| Test | Açıklama |
|---|---|
| `TestAnthropicToBifrost_Basic` | Temel dönüşüm, model prefix + max_tokens + messages |
| `TestAnthropicToBifrost_WithSystem` | `system` alanının `messages[0]`'a eklenmesi |
| `TestAnthropicToBifrost_ModelPrefix` | 6 model için prefix tahmini (claude→Anthropic/, deepseek→DeepSeek/, vb.) |
| `TestAnthropicToBifrost_StreamPassthrough` | `stream: true` korunur |
| `TestAnthropicToBifrost_InvalidJSON` | Geçersiz JSON → hata döner |
### OpenAI → Anthropic (`BifrostToAnthropic`)
| Test | Açıklama |
|---|---|
| `TestBifrostToAnthropic_Basic` | Temel dönüşüm, content/text/stop_reason kontrolü |
| `TestBifrostToAnthropic_EmptyFinishReason` | Boş `finish_reason``end_turn` |
| `TestBifrostToAnthropic_MultipleChoices` | 2 choice'ın 2 content block'a dönüşümü |
| `TestBifrostToAnthropic_InvalidJSON` | Geçersiz JSON → hata döner |
## Entegrasyon Testleri (`tests/anthropic_test.go`, `tests/openai_test.go`)
| Test | Açıklama |
|---|---|
| `TestAnthropicProxy_Success` | Full Bifrost döngüsü: Anthropic→OpenAI→forward→OpenAI→Anthropic |
| `TestAnthropicProxy_InvalidFormatPassthrough` | Geçersiz Anthropic formatı → raw passthrough |
| `TestAnthropicProxy_UpstreamError` | Upstream kapalı → 502 Bad Gateway |
| `TestAnthropicProxy_ModelsList` | `GET /anthropic/v1/models` → DeepSeek model listesi |
| `TestAnthropicProxy_EmptyBody` | Boş body → varsayılan "ready" yanıtı |
| `TestAnthropicProxy_HEAD` | `HEAD` → 404 |
| `TestAnthropicProxy_xApiKeyConversion` | `x-api-key` header'ının `Authorization: Bearer`'a dönüşümü |
| `TestAnthropicProxy_DefaultModelInjection` | İstekte model yoksa `OPENAI_MODEL` enjekte edilir (zaten "/" var, prefix eklenmez) |
| `TestAnthropicProxy_DefaultModelInjection_NopWhenModelExists` | İstekte model varsa `OPENAI_MODEL` override etmez |
| `TestAnthropicProxy_TransformErrorFallsbackToPassthrough` | Upstream yanıtı bozuksa raw passthrough |
| `TestOpenAIProxy_Success` | OpenAI handler başarılı proxy |
| `TestOpenAIProxy_DefaultModelInjection` | OpenAI handler'da model yoksa enjekte |
| `TestOpenAIProxy_DefaultModelInjection_ExistingModel` | OpenAI handler'da var olan model korunur |
| `TestOpenAIProxy_UpstreamError` | OpenAI handler upstream hatasında 502 |
## Çalıştırma
```bash
# Tüm testler
go test ./tests/ -v
# Sadece transform unit testleri
go test ./internal/transform/ -v
```
## Notlar
- Tüm testler DB'siz çalışır (`db = nil`) — loglama katmanı test edilmez
- Fiber v3'ün `app.Test()` metodu ile entegre test
- Mock upstream her test için ayrı `httptest.Server` oluşturur (izolasyon)