Files
Loki/backend/app/ollama_client.py
T
Claude cbe9bcd514 Auto-réglage GPU : détection, num_ctx optimal et bouton Réglage auto
Backend :
- agent_config : num_ctx dans la config, envoyé à Ollama (0 = défaut modèle)
- ollama_client : méthode ps() (placement GPU/CPU via /api/ps)
- autotune : placement() lit size vs size_vram pour savoir si le modèle est sur GPU
- routes/config : POST /api/config/auto (détecte GPU + modèle, calcule et applique
  num_ctx/max_tokens, renvoie la détection et le placement)

Frontend :
- client/store : autoTune(), état tuning + résultat
- SettingsView : bouton ⚡ Réglage auto, bannière de détection (GPU/VRAM,
  contexte, placement GPU/CPU), slider Contexte (num_ctx)

Config :
- GPU_VRAM_MB / GPU_NAME pour déclarer la VRAM si Ollama est distant

Tests : recommandation (8B sur 12 Go -> ctx 32768), route /auto + persistance,
transmission num_ctx aux options Ollama, placement via /api/ps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
2026-06-30 05:54:30 +00:00

88 lines
3.3 KiB
Python

"""Client HTTP léger pour l'API Ollama.
On utilise httpx directement (plutôt que le SDK) pour garder le contrôle
total sur le streaming et n'embarquer aucune dépendance superflue.
"""
from __future__ import annotations
import json
from typing import AsyncIterator
import httpx
from .config import settings
class OllamaClient:
"""Enveloppe asynchrone autour de l'API REST d'Ollama."""
def __init__(self, host: str | None = None) -> None:
self.host = (host or settings.ollama_host).rstrip("/")
async def ping(self) -> dict:
"""Vérifie la connexion et renvoie la version d'Ollama."""
async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client:
resp = await client.get(f"{self.host}/api/version")
resp.raise_for_status()
return resp.json()
async def list_models(self) -> list[dict]:
"""Liste les modèles installés localement (/api/tags)."""
async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client:
resp = await client.get(f"{self.host}/api/tags")
resp.raise_for_status()
return resp.json().get("models", [])
async def ps(self) -> list[dict]:
"""Modèles actuellement chargés et leur répartition VRAM/CPU (/api/ps)."""
async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client:
resp = await client.get(f"{self.host}/api/ps")
resp.raise_for_status()
return resp.json().get("models", [])
async def show(self, name: str) -> dict:
"""Métadonnées détaillées d'un modèle (/api/show)."""
async with httpx.AsyncClient(timeout=15.0, follow_redirects=True) as client:
resp = await client.post(f"{self.host}/api/show", json={"name": name})
resp.raise_for_status()
return resp.json()
async def pull_model(self, name: str) -> AsyncIterator[dict]:
"""Télécharge un modèle en streamant la progression (/api/pull)."""
async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
async with client.stream(
"POST", f"{self.host}/api/pull", json={"name": name}
) as resp:
resp.raise_for_status()
async for line in resp.aiter_lines():
if line.strip():
yield json.loads(line)
async def chat(
self,
model: str,
messages: list[dict],
*,
tools: list[dict] | None = None,
options: dict | None = None,
stream: bool = True,
) -> AsyncIterator[dict]:
"""Conversation avec le modèle, en streaming token par token."""
payload: dict = {"model": model, "messages": messages, "stream": stream}
if tools:
payload["tools"] = tools
if options:
payload["options"] = options
async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
async with client.stream(
"POST", f"{self.host}/api/chat", json=payload
) as resp:
resp.raise_for_status()
async for line in resp.aiter_lines():
if line.strip():
yield json.loads(line)
ollama = OllamaClient()