Stabilise Ollama et les benchmarks longs

This commit is contained in:
Michael committed 2026-07-07 15:56:04 +02:00
1 parent c9b6c8bb3e
commit 803b28a39a
3 files changed
+62 -22

No files matched your search

+12 -1
View File
@@ -6,6 +6,7 @@ respect d'un format. Score /100, stocké en base et affiché dans l'UI.
"""
from __future__ import annotations
import asyncio
import json
import re
import subprocess
@@ -189,14 +190,24 @@ async def run_bench(model: str) -> AsyncIterator[dict]:
details = []
for name, fn in TASKS:
yield {"type": "task_start", "task": name}
task = asyncio.create_task(fn(model))
try:
score, detail = await fn(model)
while not task.done():
done, _ = await asyncio.wait({task}, timeout=10)
if not done:
# Empêche OpenResty/Nginx de fermer le SSE pendant une longue
# génération d'un gros modèle.
yield {"type": "heartbeat", "task": name}
score, detail = await task
except (OllamaError, httpx.HTTPError, OSError) as exc:
score, detail = 0, f"erreur : {str(exc)[:80]}"
except Exception as exc:
# Une épreuve défaillante ne doit pas couper silencieusement le SSE :
# elle vaut zéro et les autres épreuves continuent.
score, detail = 0, f"épreuve interrompue : {str(exc)[:80]}"
finally:
if not task.done():
task.cancel()
total += score
details.append({"task": name, "score": score, "detail": detail})
yield {"type": "task_done", "task": name, "score": score, "detail": detail}
+17 -6
View File
@@ -5,6 +5,7 @@ total sur le streaming et n'embarquer aucune dépendance superflue.
"""
from __future__ import annotations
import asyncio
import json
from typing import AsyncIterator
@@ -126,12 +127,22 @@ class OllamaClient:
# laisse jusqu'à dix minutes pour les gros modèles ou un stockage lent.
timeout = httpx.Timeout(connect=10.0, read=600.0, write=30.0, pool=10.0)
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
resp = await client.post(
f"{self.host}/api/generate",
json={"model": model, "keep_alive": keep_alive, "stream": False},
)
resp.raise_for_status()
return resp.json()
for attempt in range(2):
resp = await client.post(
f"{self.host}/api/generate",
json={"model": model, "keep_alive": keep_alive, "stream": False},
)
try:
# Inclut le corps JSON d'Ollama dans l'erreur (OOM, runner…),
# contrairement à raise_for_status qui ne montrait que « 500 ».
await _raise_for_stream_status(resp)
except OllamaError:
if resp.status_code >= 500 and attempt == 0:
await asyncio.sleep(2)
continue
raise
return resp.json()
raise OllamaError("préchargement interrompu sans réponse")
async def chat(
self,
+33 -15
View File
@@ -205,11 +205,21 @@ export async function runBench(
model: string,
onProgress: (task: string, score: number | null, detail?: string) => void
): Promise<BenchResult | null> {
const res = await fetch("/api/bench", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model }),
});
let res: Response;
try {
res = await fetch("/api/bench", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model }),
});
} catch (err) {
const raw = err instanceof Error ? err.message : "connexion impossible";
throw new Error(
/network error|failed to fetch|load failed/i.test(raw)
? "connexion au benchmark interrompue par le réseau ou le reverse proxy"
: raw
);
}
if (!res.ok) {
throw await apiError(res, `benchmark refusé (${res.status})`);
}
@@ -238,18 +248,26 @@ export async function runBench(
final = { score: payload.score, details: payload.details, at: Date.now() / 1000 };
};
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
const events = buffer.split("\n\n");
buffer = events.pop() ?? "";
for (const block of events) {
if (block.trim()) dispatch(block);
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
const events = buffer.split("\n\n");
buffer = events.pop() ?? "";
for (const block of events) {
if (block.trim()) dispatch(block);
}
}
buffer += decoder.decode();
if (buffer.trim()) dispatch(buffer);
} catch (err) {
const raw = err instanceof Error ? err.message : "connexion interrompue";
if (/network error|failed to fetch|load failed/i.test(raw)) {
throw new Error("flux du benchmark interrompu par le reverse proxy");
}
throw err;
}
buffer += decoder.decode();
if (buffer.trim()) dispatch(buffer);
if (!final) throw new Error("le benchmark s'est interrompu avant le résultat");
return final;
}