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,108 @@
"""Anthropic 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/anthropic", tags=["anthropic"])
class Message(BaseModel):
role: str
content: str
class AnthropicRequest(BaseModel):
model: str
messages: List[Message]
max_tokens: int = 1024
stream: bool = False
temperature: Optional[float] = None
@router.post("/messages")
async def anthropic_messages(
request: AnthropicRequest,
client: ClientKey = Depends(get_current_client)
):
"""Anthropic native messages endpoint."""
start_time = time.time()
# Get Anthropic provider
provider = await ModelRouter.get_provider("anthropic")
if not provider:
raise HTTPException(status_code=503, detail={"error": "Anthropic 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 Anthropic API key"})
messages = [{"role": m.role, "content": m.content} for m in request.messages]
kwargs = {"max_tokens": request.max_tokens}
if request.temperature is not None:
kwargs["temperature"] = request.temperature
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 anthropic_models(client: ClientKey = Depends(get_current_client)):
"""List Anthropic models."""
return {
"object": "list",
"data": [
{"id": "claude-3-opus-20240229", "object": "model", "owned_by": "anthropic"},
{"id": "claude-3-sonnet-20240229", "object": "model", "owned_by": "anthropic"},
{"id": "claude-3-haiku-20240307", "object": "model", "owned_by": "anthropic"},
]
}