dimercia / app.py
dieumercimvemba's picture
Update app.py
cd72ff2 verified
Raw
History Blame Contribute Delete
4.71 kB
import uvicorn
import json
import asyncio
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, StreamingResponse
from llama_cpp import Llama
app = FastAPI(
title="Dimercia AI",
version="0.1.4",
description="API OpenAI compatible souveraine - Anti-Timeout pour Cline"
)
# --- Initialisation globale ---
MODEL_PATH = "/app/models/qwen2.5-coder-1.5b-instruct-q4_k_m.gguf"
print("Chargement de Dimercia AI v0.1.4 en mémoire RAM...")
llm = Llama(
model_path=MODEL_PATH,
n_ctx=16384, # 16k conserve un parfait équilibre sur CPU
n_threads=4, # Exploite à fond le calcul parallèle
verbose=False
)
print("Dimercia AI v0.1.4 est prêt.")
@app.get("/")
def home():
return {
"name": "Dimercia AI",
"version": "0.1.4",
"status": "running"
}
@app.get("/v1/models")
def models():
return {
"object": "list",
"data": [
{"id": "dimercia-coder", "object": "model", "owned_by": "dimercia"}
]
}
@app.post("/v1/chat/completions")
async def chat(request: Request):
try:
body = await request.json()
except Exception:
return JSONResponse(status_code=400, content={"detail": "JSON invalide"})
# --- Nettoyage adaptatif du Payload ---
raw_messages = body.get("messages", [])
cleaned_messages = []
for msg in raw_messages:
role = msg.get("role", "user")
raw_content = msg.get("content", "")
if isinstance(raw_content, list):
text_pieces = [piece.get("text", "") if isinstance(piece, dict) else str(piece) for piece in raw_content]
content = "".join(text_pieces)
else:
content = str(raw_content)
cleaned_messages.append({"role": role, "content": content})
temperature = body.get("temperature", 0.2)
max_tokens = body.get("max_tokens", 512)
stream = body.get("stream", False)
temp_val = float(temperature) if temperature is not None else 0.2
tokens_val = int(max_tokens) if max_tokens is not None else 512
# --- Gestion du mode STREAMING (Avec système anti-timeout) ---
if stream:
async def chunk_generator():
# Étape 1 : Lancer la création de l'itérateur dans un thread séparé (non-bloquant)
task = asyncio.create_task(asyncio.to_thread(
llm.create_chat_completion,
messages=cleaned_messages,
temperature=temp_val,
max_tokens=tokens_val,
stream=True
))
# Étape 2 : Tant que llama.cpp calcule le prefill, on envoie des pings invisibles à Cline
while not task.done():
# Envoi d'un chunk de commentaire SSE pour garder la connexion ouverte
yield ": heartbeat\n\n"
await asyncio.sleep(1.0) # Attendre 1 seconde avant le prochain ping
try:
iterator = task.result()
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
yield "data: [DONE]\n\n"
return
# Étape 3 : Consommer les tokens normalement dès qu'ils sont prêts
def get_next_chunk(it):
try:
return next(it)
except StopIteration:
return None
except Exception as ex:
return ex
while True:
chunk = await asyncio.to_thread(get_next_chunk, iterator)
if chunk is None:
break
if isinstance(chunk, Exception):
yield f"data: {json.dumps({'error': str(chunk)})}\n\n"
break
if "model" in chunk:
chunk["model"] = "dimercia-coder"
yield f"data: {json.dumps(chunk)}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(chunk_generator(), media_type="text/event-stream")
# --- Gestion du mode STANDARD ---
else:
try:
response = await asyncio.to_thread(
llm.create_chat_completion,
messages=cleaned_messages,
temperature=temp_val,
max_tokens=tokens_val,
stream=False
)
if isinstance(response, dict):
response["model"] = "dimercia-coder"
return response
except Exception as e:
return JSONResponse(status_code=500, content={"detail": str(e)})
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)