feat: implement AI model API proxy service

This commit is contained in:
2026-03-11 18:22:16 +08:00
commit 26738973bd
33 changed files with 4607 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
"""OpenAI native endpoint (passthrough)."""
import time
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from ..models import ClientKey
from ..middleware.auth import get_current_client
from ..services.router import ModelRouter
from ..services.key_selector import key_selector
from ..services.logger import async_logger, LogEntry
from ..providers.litellm_wrapper import LiteLLMWrapper
router = APIRouter(prefix="/v1/openai", tags=["openai"])
class ChatMessage(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
model: str
messages: List[ChatMessage]
stream: bool = False
temperature: Optional[float] = None
max_tokens: Optional[int] = None
@router.post("/chat/completions")
async def openai_chat(
request: ChatRequest,
client: ClientKey = Depends(get_current_client)
):
"""OpenAI native chat completions (passthrough)."""
start_time = time.time()
# Get OpenAI provider
provider = await ModelRouter.get_provider("openai")
if not provider:
raise HTTPException(status_code=503, detail={"error": "OpenAI provider not configured"})
# Get next API key
provider_key = await key_selector.get_next_key(provider.id)
if not provider_key:
raise HTTPException(status_code=503, detail={"error": "No available OpenAI API key"})
messages = [{"role": m.role, "content": m.content} for m in request.messages]
kwargs = {}
if request.temperature is not None:
kwargs["temperature"] = request.temperature
if request.max_tokens is not None:
kwargs["max_tokens"] = request.max_tokens
try:
response, prompt_tokens, completion_tokens = await LiteLLMWrapper.chat_completion(
provider=provider,
api_key=provider_key.key,
model=request.model,
messages=messages,
stream=request.stream,
**kwargs
)
if request.stream:
return StreamingResponse(
LiteLLMWrapper.stream_response(
response, client.id, provider.id, request.model, start_time
),
media_type="text/event-stream"
)
else:
latency_ms = int((time.time() - start_time) * 1000)
await async_logger.log(LogEntry(
client_key_id=client.id,
provider_id=provider.id,
model=request.model,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
latency_ms=latency_ms,
success=True
))
return response
except Exception as e:
latency_ms = int((time.time() - start_time) * 1000)
await async_logger.log(LogEntry(
client_key_id=client.id,
provider_id=provider.id,
model=request.model,
latency_ms=latency_ms,
success=False,
error_message=str(e)
))
raise HTTPException(status_code=500, detail={"error": str(e)})
@router.get("/models")
async def openai_models(client: ClientKey = Depends(get_current_client)):
"""List OpenAI models."""
return {
"object": "list",
"data": [
{"id": "gpt-4o", "object": "model", "owned_by": "openai"},
{"id": "gpt-4o-mini", "object": "model", "owned_by": "openai"},
{"id": "o1", "object": "model", "owned_by": "openai"},
{"id": "o1-mini", "object": "model", "owned_by": "openai"},
]
}