feat: add OpenAI-compatible /v1/chat/completions endpoint

This commit is contained in:
Roland Tannous 2026-03-06 07:48:09 +00:00
commit d910759121
4 changed files with 44 additions and 2 deletions

View file

@ -96,6 +96,11 @@ app.include_router(auth_router, prefix="/api/auth", tags=["auth"])
app.include_router(training_router, prefix="/api/train", tags=["training"])
app.include_router(models_router, prefix="/api/models", tags=["models"])
app.include_router(inference_router, prefix="/api/inference", tags=["inference"])
# OpenAI-compatible endpoints: mount the same inference router at /v1
# so external tools (Open WebUI, SillyTavern, etc.) can use the
# standard /v1/chat/completions path.
app.include_router(inference_router, prefix="/v1", tags=["openai-compat"])
app.include_router(datasets_router, prefix="/api/datasets", tags=["datasets"])
app.include_router(data_recipe_router, prefix="/api/data-recipe", tags=["data-recipe"])
app.include_router(export_router, prefix="/api/export", tags=["export"])

View file

@ -785,3 +785,40 @@ async def openai_chat_completions(
backend.reset_generation_state()
logger.error(f"Error during OpenAI completion: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
# =====================================================================
# OpenAI-Compatible Models Listing (/models → /v1/models)
# =====================================================================
@router.get("/models")
async def openai_list_models(
current_subject: str = Depends(get_current_subject),
):
"""
OpenAI-compatible model listing endpoint.
Returns the currently loaded model in the format expected by
OpenAI-compatible clients (``GET /v1/models``).
"""
models = []
# Check GGUF backend
llama_backend = get_llama_cpp_backend()
if llama_backend.is_loaded:
models.append({
"id": llama_backend.model_identifier,
"object": "model",
"owned_by": "local",
})
# Check Unsloth backend
backend = get_inference_backend()
if backend.active_model_name:
models.append({
"id": backend.active_model_name,
"object": "model",
"owned_by": "local",
})
return {"object": "list", "data": models}

View file

@ -99,7 +99,7 @@ export async function* streamChatCompletions(
payload: OpenAIChatCompletionsRequest,
signal: AbortSignal,
): AsyncGenerator<OpenAIChatChunk> {
const response = await authFetch("/api/inference/chat/completions", {
const response = await authFetch("/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),

View file

@ -202,7 +202,7 @@ async function generateTitleWithModel(payload: {
return joined.length > 60 ? joined.slice(0, 60).trimEnd() : joined;
}
const response = await authFetch("/api/inference/chat/completions", {
const response = await authFetch("/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({