2025-04-15 16:36:40 +08:00
|
|
|
import io
|
2025-02-13 18:10:46 +08:00
|
|
|
import os
|
|
|
|
|
import uuid
|
|
|
|
|
import yaml
|
|
|
|
|
import logging
|
|
|
|
|
import time
|
|
|
|
|
import json
|
|
|
|
|
import asyncio
|
2025-04-15 16:36:40 +08:00
|
|
|
from pydub import AudioSegment
|
2025-04-08 18:07:54 +08:00
|
|
|
from fastapi import FastAPI, Request, HTTPException, File, Form, UploadFile
|
|
|
|
|
from fastapi.responses import JSONResponse, StreamingResponse
|
2025-02-20 18:43:45 +08:00
|
|
|
from backend import (
|
|
|
|
|
OpenAIProxyBackend,
|
|
|
|
|
LlmClientBackend,
|
|
|
|
|
VisionModelBackend,
|
|
|
|
|
ASRClientBackend,
|
|
|
|
|
TtsClientBackend,
|
|
|
|
|
ChatCompletionRequest,
|
|
|
|
|
CompletionRequest,
|
|
|
|
|
Message,
|
|
|
|
|
)
|
2025-11-21 09:53:12 +08:00
|
|
|
from services.memory_check import MemoryChecker
|
2025-03-17 19:55:00 +08:00
|
|
|
from services.model_list import GetModelList
|
|
|
|
|
|
2025-02-13 18:10:46 +08:00
|
|
|
logging.basicConfig(
|
|
|
|
|
level=logging.DEBUG,
|
|
|
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
|
|
|
handlers=[
|
|
|
|
|
logging.StreamHandler(),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
logger = logging.getLogger("api")
|
|
|
|
|
app = FastAPI(title="OpenAI Compatible API Server")
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
def __init__(self):
|
2025-03-27 19:12:54 +08:00
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
config_path = os.path.join(current_dir, "config", "config.yaml")
|
|
|
|
|
with open(config_path) as f:
|
2025-02-13 18:10:46 +08:00
|
|
|
self.data = yaml.safe_load(f)
|
2025-04-09 15:56:50 +08:00
|
|
|
|
|
|
|
|
tiktoken_cache_dir = os.path.join(current_dir, "cache")
|
|
|
|
|
os.environ["TIKTOKEN_CACHE_DIR"] = tiktoken_cache_dir
|
2025-02-13 18:10:46 +08:00
|
|
|
|
|
|
|
|
config = Config()
|
|
|
|
|
|
|
|
|
|
@app.middleware("http")
|
|
|
|
|
async def auth_middleware(request: Request, call_next):
|
|
|
|
|
if request.url.path.startswith("/v1"):
|
|
|
|
|
api_key = request.headers.get("Authorization", "").replace("Bearer ", "")
|
2025-03-17 19:55:00 +08:00
|
|
|
# if api_key != os.getenv("API_KEY"):
|
|
|
|
|
# return JSONResponse(
|
|
|
|
|
# status_code=401,
|
|
|
|
|
# content={"error": "Invalid authentication credentials"}
|
|
|
|
|
# )
|
2025-02-13 18:10:46 +08:00
|
|
|
return await call_next(request)
|
|
|
|
|
|
|
|
|
|
class ModelDispatcher:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.backends = {}
|
2025-11-21 09:53:12 +08:00
|
|
|
self.memory_checker = MemoryChecker(
|
|
|
|
|
host=config.data["server"]["host"],
|
|
|
|
|
port=config.data["server"]["port"]
|
|
|
|
|
)
|
2025-03-18 18:09:19 +08:00
|
|
|
self.lock = asyncio.Lock()
|
2025-11-27 16:36:26 +08:00
|
|
|
self.total_memory = None
|
|
|
|
|
self.current_used_memory = 0
|
2025-02-13 18:10:46 +08:00
|
|
|
|
2025-11-21 09:53:12 +08:00
|
|
|
async def _ensure_memory_available(self, required_mem: int):
|
|
|
|
|
if required_mem <= 0:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
cmm_info = await self.memory_checker.get_cmminfo()
|
2025-11-27 16:36:26 +08:00
|
|
|
external_remain = cmm_info["data"]["remain"]
|
2025-11-21 09:53:12 +08:00
|
|
|
|
2025-11-27 16:36:26 +08:00
|
|
|
if self.total_memory is None:
|
|
|
|
|
self.total_memory = cmm_info["data"].get("total", external_remain)
|
|
|
|
|
logger.info(f"Memory Manager Initialized | Total Capacity: {self.total_memory}")
|
|
|
|
|
|
|
|
|
|
internal_remain = self.total_memory - self.current_used_memory
|
|
|
|
|
|
|
|
|
|
remain_mem = min(internal_remain, external_remain)
|
|
|
|
|
|
|
|
|
|
logger.debug(f"Memory Check | Required: {required_mem} | "
|
|
|
|
|
f"External Remain: {external_remain} | Internal Remain: {internal_remain} | "
|
|
|
|
|
f"Effective Available: {remain_mem}")
|
2025-11-21 09:53:12 +08:00
|
|
|
|
|
|
|
|
if remain_mem >= required_mem:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
needed_mem = required_mem - remain_mem
|
|
|
|
|
reclaimable_mem = 0
|
|
|
|
|
models_to_unload = []
|
|
|
|
|
|
|
|
|
|
for model_name, backend in self.backends.items():
|
|
|
|
|
if reclaimable_mem >= needed_mem:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
model_conf = config.data["models"].get(model_name, {})
|
|
|
|
|
mem_used = model_conf.get("memory_required", 0)
|
|
|
|
|
|
|
|
|
|
reclaimable_mem += mem_used
|
|
|
|
|
models_to_unload.append(model_name)
|
|
|
|
|
|
|
|
|
|
if remain_mem + reclaimable_mem < required_mem:
|
|
|
|
|
total_reclaimable = sum([config.data["models"].get(m, {}).get("memory_required", 0) for m in self.backends])
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=503,
|
|
|
|
|
detail=f"Insufficient Memory Resource. Required: {required_mem}, "
|
|
|
|
|
f"Available: {remain_mem}, Total Reclaimable: {total_reclaimable}. "
|
|
|
|
|
f"Cannot satisfy request even after unloading."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for model_name in models_to_unload:
|
|
|
|
|
logger.info(f"Unloading model '{model_name}' to free memory...")
|
|
|
|
|
backend = self.backends.pop(model_name)
|
2025-11-27 16:36:26 +08:00
|
|
|
|
|
|
|
|
model_conf = config.data["models"].get(model_name, {})
|
|
|
|
|
mem_freed = model_conf.get("memory_required", 0)
|
|
|
|
|
self.current_used_memory -= mem_freed
|
|
|
|
|
if self.current_used_memory < 0:
|
|
|
|
|
self.current_used_memory = 0
|
|
|
|
|
|
2025-11-21 09:53:12 +08:00
|
|
|
if backend:
|
|
|
|
|
await backend.close()
|
|
|
|
|
|
|
|
|
|
# await asyncio.sleep(0.1)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
if isinstance(e, HTTPException):
|
|
|
|
|
raise e
|
|
|
|
|
logger.error(f"Memory management error: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Memory check failed: {str(e)}")
|
|
|
|
|
|
2025-03-18 18:09:19 +08:00
|
|
|
async def get_backend(self, model_name):
|
|
|
|
|
async with self.lock:
|
2025-11-21 09:53:12 +08:00
|
|
|
if model_name in self.backends:
|
|
|
|
|
backend = self.backends.pop(model_name)
|
|
|
|
|
self.backends[model_name] = backend
|
|
|
|
|
return backend
|
2025-11-27 16:36:26 +08:00
|
|
|
|
2025-11-21 09:53:12 +08:00
|
|
|
model_config = config.data["models"].get(model_name)
|
|
|
|
|
if model_config is None:
|
|
|
|
|
return None
|
2025-11-27 16:36:26 +08:00
|
|
|
|
2025-11-21 09:53:12 +08:00
|
|
|
required_mem = model_config.get("memory_required", 0)
|
2025-11-27 16:36:26 +08:00
|
|
|
|
2025-11-21 09:53:12 +08:00
|
|
|
await self._ensure_memory_available(required_mem)
|
2025-11-27 16:36:26 +08:00
|
|
|
|
2025-11-21 09:53:12 +08:00
|
|
|
logger.info(f"Loading model: {model_name} (Mem Required: {required_mem})")
|
|
|
|
|
|
2025-11-27 16:36:26 +08:00
|
|
|
backend_instance = None
|
2025-11-21 09:53:12 +08:00
|
|
|
if model_config["type"] == "openai_proxy":
|
2025-11-27 16:36:26 +08:00
|
|
|
backend_instance = OpenAIProxyBackend(model_config)
|
2025-11-21 09:53:12 +08:00
|
|
|
elif model_config["type"] in ("llm", "vlm"):
|
2025-11-27 16:36:26 +08:00
|
|
|
backend_instance = LlmClientBackend(model_config)
|
2025-11-21 09:53:12 +08:00
|
|
|
elif model_config["type"] == "vision_model":
|
2025-11-27 16:36:26 +08:00
|
|
|
backend_instance = VisionModelBackend(model_config)
|
2025-11-21 09:53:12 +08:00
|
|
|
elif model_config["type"] == "tts":
|
2025-11-27 16:36:26 +08:00
|
|
|
backend_instance = TtsClientBackend(model_config)
|
2025-11-21 09:53:12 +08:00
|
|
|
elif model_config["type"] == "asr":
|
2025-11-27 16:36:26 +08:00
|
|
|
backend_instance = ASRClientBackend(model_config)
|
2025-11-21 09:53:12 +08:00
|
|
|
else:
|
|
|
|
|
return None
|
2025-11-27 16:36:26 +08:00
|
|
|
|
|
|
|
|
self.backends[model_name] = backend_instance
|
|
|
|
|
self.current_used_memory += required_mem
|
|
|
|
|
|
2025-03-18 18:09:19 +08:00
|
|
|
return self.backends.get(model_name)
|
2025-02-13 18:10:46 +08:00
|
|
|
|
2025-03-17 19:55:00 +08:00
|
|
|
async def initialize():
|
|
|
|
|
global config
|
|
|
|
|
model_list = GetModelList(
|
|
|
|
|
host=config.data["server"]["host"],
|
|
|
|
|
port=config.data["server"]["port"]
|
|
|
|
|
)
|
|
|
|
|
await model_list.get_model_list(required_mem=0)
|
|
|
|
|
config = Config()
|
|
|
|
|
dispatcher = ModelDispatcher()
|
|
|
|
|
return dispatcher
|
|
|
|
|
|
|
|
|
|
_dispatcher = asyncio.run(initialize())
|
2025-02-13 18:10:46 +08:00
|
|
|
|
|
|
|
|
@app.post("/v1/chat/completions")
|
|
|
|
|
async def chat_completions(request: Request, body: ChatCompletionRequest):
|
2025-03-18 18:09:19 +08:00
|
|
|
backend = await _dispatcher.get_backend(body.model)
|
2025-02-13 18:10:46 +08:00
|
|
|
if not backend:
|
|
|
|
|
raise HTTPException(
|
2025-04-14 11:03:56 +08:00
|
|
|
status_code=400,
|
2025-02-13 18:10:46 +08:00
|
|
|
detail=f"Unsupported model: {body.model}"
|
|
|
|
|
)
|
|
|
|
|
|
2025-04-08 18:07:54 +08:00
|
|
|
try:
|
2025-02-13 18:10:46 +08:00
|
|
|
if body.stream:
|
|
|
|
|
chunk_generator = await backend.generate(body)
|
|
|
|
|
if not chunk_generator:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=500,
|
|
|
|
|
detail="Failed to generate stream response"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def format_stream():
|
|
|
|
|
try:
|
|
|
|
|
async for chunk in chunk_generator:
|
|
|
|
|
if isinstance(chunk, dict):
|
|
|
|
|
chunk_dict = chunk
|
|
|
|
|
else:
|
|
|
|
|
chunk_dict = chunk.model_dump()
|
|
|
|
|
|
|
|
|
|
json_chunk = json.dumps(chunk_dict, ensure_ascii=False)
|
|
|
|
|
yield f"data: {json_chunk}\n\n"
|
2025-02-18 17:13:25 +08:00
|
|
|
except asyncio.CancelledError:
|
2025-02-20 17:40:30 +08:00
|
|
|
logger.warning("Client disconnected early, terminating inference...")
|
2025-02-18 17:13:25 +08:00
|
|
|
if backend and isinstance(backend, LlmClientBackend):
|
2025-04-11 11:18:14 +08:00
|
|
|
current_task = asyncio.current_task()
|
|
|
|
|
if current_task in backend._active_tasks:
|
|
|
|
|
current_task.cancel()
|
2025-02-18 17:13:25 +08:00
|
|
|
raise
|
|
|
|
|
finally:
|
2025-02-20 17:40:30 +08:00
|
|
|
logger.debug("Stream connection closed")
|
2025-02-13 18:10:46 +08:00
|
|
|
return StreamingResponse(
|
|
|
|
|
format_stream(),
|
|
|
|
|
media_type="text/event-stream"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
response = await backend.generate(body)
|
|
|
|
|
return JSONResponse(content=response)
|
|
|
|
|
|
|
|
|
|
except HTTPException as he:
|
|
|
|
|
raise he
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Processing error: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
2025-02-13 18:32:46 +08:00
|
|
|
@app.post("/v1/completions")
|
|
|
|
|
async def create_completion(request: Request, body: CompletionRequest):
|
|
|
|
|
chat_request = ChatCompletionRequest(
|
|
|
|
|
model=body.model,
|
|
|
|
|
messages=[Message(role="user", content=body.prompt)],
|
|
|
|
|
temperature=body.temperature,
|
|
|
|
|
max_tokens=body.max_tokens,
|
2025-02-18 11:40:03 +08:00
|
|
|
top_p=body.top_p,
|
2025-02-13 18:32:46 +08:00
|
|
|
stream=body.stream
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-18 18:09:19 +08:00
|
|
|
backend = await _dispatcher.get_backend(chat_request.model)
|
2025-02-13 18:32:46 +08:00
|
|
|
if not backend:
|
|
|
|
|
raise HTTPException(status_code=400, detail=f"Unsupported model: {chat_request.model}")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if body.stream:
|
|
|
|
|
chunk_generator = await backend.generate(chat_request)
|
|
|
|
|
|
|
|
|
|
async def convert_stream():
|
|
|
|
|
async for chunk in chunk_generator:
|
2025-02-20 17:40:30 +08:00
|
|
|
# Convert format and serialize to JSON string
|
2025-02-13 18:32:46 +08:00
|
|
|
completion_chunk = {
|
|
|
|
|
"id": chunk.get("id", f"cmpl-{uuid.uuid4()}"),
|
|
|
|
|
"object": "text_completion.chunk",
|
|
|
|
|
"created": chunk.get("created", int(time.time())),
|
|
|
|
|
"model": chat_request.model,
|
|
|
|
|
"choices": [{
|
|
|
|
|
"text": chunk["choices"][0]["delta"].get("content", ""),
|
|
|
|
|
"index": 0,
|
|
|
|
|
"logprobs": None,
|
|
|
|
|
"finish_reason": chunk["choices"][0].get("finish_reason")
|
|
|
|
|
}]
|
|
|
|
|
}
|
|
|
|
|
yield f"data: {json.dumps(completion_chunk)}\n\n"
|
|
|
|
|
|
|
|
|
|
yield "data: [DONE]\n\n"
|
|
|
|
|
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
convert_stream(),
|
|
|
|
|
media_type="text/event-stream"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
chat_response = await backend.generate(chat_request)
|
|
|
|
|
return JSONResponse({
|
|
|
|
|
"id": f"cmpl-{uuid.uuid4()}",
|
|
|
|
|
"object": "text_completion",
|
|
|
|
|
"created": int(time.time()),
|
|
|
|
|
"model": chat_request.model,
|
|
|
|
|
"choices": [{
|
|
|
|
|
"text": chat_response["choices"][0]["message"]["content"],
|
|
|
|
|
"index": 0,
|
|
|
|
|
"logprobs": None,
|
|
|
|
|
"finish_reason": "stop"
|
|
|
|
|
}],
|
|
|
|
|
"usage": chat_response.get("usage", {
|
|
|
|
|
"prompt_tokens": 0,
|
|
|
|
|
"completion_tokens": 0,
|
|
|
|
|
"total_tokens": 0
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Completion error: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
2025-02-20 18:43:45 +08:00
|
|
|
@app.post("/v1/audio/speech")
|
2025-11-21 09:53:12 +08:00
|
|
|
async def create_speech(request: Request):
|
2025-02-20 18:43:45 +08:00
|
|
|
try:
|
|
|
|
|
request_data = await request.json()
|
2025-04-14 14:16:27 +08:00
|
|
|
model = request_data.get("model")
|
2025-11-05 16:54:54 +08:00
|
|
|
voice = request_data.get("voice", "prompt_data")
|
2025-04-14 14:16:27 +08:00
|
|
|
response_format = request_data.get("response_format", "mp3")
|
|
|
|
|
if not model:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=400,
|
|
|
|
|
detail="Model is required for speech generation"
|
|
|
|
|
)
|
|
|
|
|
backend = await _dispatcher.get_backend(model)
|
2025-02-20 18:43:45 +08:00
|
|
|
if not backend:
|
2025-04-14 14:16:27 +08:00
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=400,
|
|
|
|
|
detail=f"Unsupported model: {model}"
|
|
|
|
|
)
|
|
|
|
|
input_text = request_data.get("input")
|
|
|
|
|
if not input_text:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=400,
|
|
|
|
|
detail="Input text is required for speech generation"
|
|
|
|
|
)
|
2025-02-21 18:02:24 +08:00
|
|
|
audio_stream = backend.generate_speech(
|
2025-04-14 14:16:27 +08:00
|
|
|
input_text=input_text,
|
|
|
|
|
voice=voice,
|
|
|
|
|
format=response_format
|
2025-02-20 18:43:45 +08:00
|
|
|
)
|
|
|
|
|
return StreamingResponse(
|
2025-02-21 18:02:24 +08:00
|
|
|
audio_stream,
|
2025-04-14 14:16:27 +08:00
|
|
|
media_type=f"audio/{response_format}",
|
|
|
|
|
headers={"Content-Disposition": f'attachment; filename="speech.{response_format}"'}
|
2025-02-20 18:43:45 +08:00
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Speech generation error: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
@app.post("/v1/audio/transcriptions")
|
|
|
|
|
async def create_transcription(
|
|
|
|
|
file: UploadFile = File(...),
|
|
|
|
|
model: str = Form(...),
|
|
|
|
|
language: str = Form(None),
|
|
|
|
|
prompt: str = Form(""),
|
|
|
|
|
response_format: str = Form("json")
|
|
|
|
|
):
|
2025-04-14 11:03:56 +08:00
|
|
|
backend = await _dispatcher.get_backend(model)
|
|
|
|
|
if not backend:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=400,
|
|
|
|
|
detail=f"Unsupported model: {model}"
|
|
|
|
|
)
|
|
|
|
|
try:
|
2025-02-20 18:43:45 +08:00
|
|
|
audio_data = await file.read()
|
2025-04-15 16:36:40 +08:00
|
|
|
audio = AudioSegment.from_file(io.BytesIO(audio_data), format=file.filename.split('.')[-1])
|
|
|
|
|
|
|
|
|
|
target_sample_rate = 16000
|
|
|
|
|
target_channels = 1
|
|
|
|
|
target_sample_width = 2
|
|
|
|
|
|
|
|
|
|
if audio.frame_rate != target_sample_rate or audio.channels != target_channels or audio.sample_width != target_sample_width:
|
|
|
|
|
audio = audio.set_frame_rate(target_sample_rate).set_channels(target_channels).set_sample_width(target_sample_width)
|
|
|
|
|
|
|
|
|
|
segment_duration_ms = 30 * 1000
|
|
|
|
|
segments = [audio[i:i + segment_duration_ms] for i in range(0, len(audio), segment_duration_ms)]
|
|
|
|
|
|
|
|
|
|
transcription_results = []
|
|
|
|
|
for segment in segments:
|
|
|
|
|
segment_data = io.BytesIO()
|
|
|
|
|
segment.export(segment_data, format="wav")
|
|
|
|
|
segment_data.seek(0)
|
|
|
|
|
|
|
|
|
|
transcription = await backend.create_transcription(
|
|
|
|
|
segment_data.read(),
|
|
|
|
|
language=language,
|
|
|
|
|
prompt=prompt
|
|
|
|
|
)
|
|
|
|
|
transcription_results.append(transcription)
|
|
|
|
|
|
|
|
|
|
full_transcription = " ".join(transcription_results)
|
|
|
|
|
|
2025-02-20 18:43:45 +08:00
|
|
|
return JSONResponse(content={
|
2025-04-15 16:36:40 +08:00
|
|
|
"text": full_transcription,
|
2025-02-20 18:43:45 +08:00
|
|
|
"task": "transcribe",
|
|
|
|
|
"language": language,
|
2025-04-15 16:36:40 +08:00
|
|
|
"duration": len(audio) / 1000.0,
|
|
|
|
|
"segments": len(segments),
|
|
|
|
|
"sample_rate": target_sample_rate,
|
|
|
|
|
"channels": target_channels,
|
|
|
|
|
"bit_depth": target_sample_width * 8
|
2025-02-20 18:43:45 +08:00
|
|
|
})
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Transcription error: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
@app.post("/v1/audio/translations")
|
|
|
|
|
async def create_translation(
|
|
|
|
|
file: UploadFile = File(...),
|
|
|
|
|
model: str = Form(...),
|
|
|
|
|
prompt: str = Form(""),
|
|
|
|
|
response_format: str = Form("json")
|
|
|
|
|
):
|
|
|
|
|
try:
|
2025-03-18 18:09:19 +08:00
|
|
|
backend = await _dispatcher.get_backend(model)
|
2025-02-20 18:43:45 +08:00
|
|
|
if not backend:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Unsupported model")
|
|
|
|
|
audio_data = await file.read()
|
|
|
|
|
|
|
|
|
|
translation = await backend.create_translation(
|
|
|
|
|
audio_data,
|
|
|
|
|
prompt=prompt
|
|
|
|
|
)
|
|
|
|
|
return JSONResponse(content={
|
|
|
|
|
"text": translation,
|
|
|
|
|
"task": "translate",
|
|
|
|
|
"duration": 0
|
|
|
|
|
})
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Translation error: {str(e)}")
|
|
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
2025-03-17 19:55:00 +08:00
|
|
|
@app.get("/v1/models")
|
|
|
|
|
async def list_models():
|
|
|
|
|
models_info = []
|
2025-03-25 11:36:12 +08:00
|
|
|
for model_name in config.data["models"].keys():
|
2025-03-17 19:55:00 +08:00
|
|
|
model_config = config.data["models"].get(model_name, {})
|
|
|
|
|
models_info.append({
|
|
|
|
|
"id": model_name,
|
|
|
|
|
"object": "model",
|
|
|
|
|
"created": model_config.get("created", 0),
|
|
|
|
|
"owned_by": model_config.get("owner", "user"),
|
|
|
|
|
"permission": [],
|
|
|
|
|
"root": model_config.get("root", "")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"data": models_info,
|
|
|
|
|
"object": "list"
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-13 18:10:46 +08:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
import uvicorn
|
|
|
|
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|
|
|
logging.getLogger().handlers[0].flush()
|