UI: sidebar + insight panel; metrics retrieval/time; fix InferenceClient provider
Browse files- app/crag/llm_client.py +54 -31
app/crag/llm_client.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
| 1 |
"""Client LLM unique pour tout le pipeline CRAG.
|
| 2 |
|
| 3 |
-
Modèle
|
| 4 |
-
|
| 5 |
-
Un mode "mock" (LLM_PROVIDER=mock) permet de faire tourner l'app, les tests et
|
| 6 |
-
la démo sans token HF ni appel réseau — utile en CI ou pour un premier test
|
| 7 |
-
local avant configuration du Space.
|
| 8 |
"""
|
| 9 |
from __future__ import annotations
|
| 10 |
|
|
|
|
| 11 |
import json
|
| 12 |
import logging
|
| 13 |
|
| 14 |
-
from tenacity import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
from app.core.config import get_settings
|
| 17 |
|
|
@@ -23,12 +26,7 @@ class LLMError(RuntimeError):
|
|
| 23 |
|
| 24 |
|
| 25 |
def _mock_complete(system_prompt: str, user_prompt: str, json_mode: bool) -> str:
|
| 26 |
-
"""Réponses déterministes
|
| 27 |
-
|
| 28 |
-
Volontairement simple : ce n'est PAS un modèle de langage, seulement un
|
| 29 |
-
filet de sécurité pour que `docker build && docker run` fonctionne sans
|
| 30 |
-
HF_TOKEN configuré, et pour des tests unitaires rapides et déterministes.
|
| 31 |
-
"""
|
| 32 |
if json_mode:
|
| 33 |
if "score" in system_prompt.lower() or "pertinence" in system_prompt.lower():
|
| 34 |
return json.dumps({
|
|
@@ -43,35 +41,58 @@ def _mock_complete(system_prompt: str, user_prompt: str, json_mode: bool) -> str
|
|
| 43 |
)
|
| 44 |
|
| 45 |
|
| 46 |
-
|
| 47 |
-
def _hf_complete(system_prompt: str, user_prompt: str, json_mode: bool) -> str:
|
| 48 |
from huggingface_hub import InferenceClient
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
settings = get_settings()
|
| 51 |
if not settings.hf_token:
|
| 52 |
raise LLMError(
|
| 53 |
-
"HF_TOKEN manquant. Définissez HF_TOKEN dans
|
| 54 |
-
"secrets du Space Hugging Face (Settings > Variables and secrets)."
|
| 55 |
)
|
| 56 |
|
| 57 |
-
|
| 58 |
-
if settings.hf_inference_provider:
|
| 59 |
-
# API multi-fournisseurs de Hugging Face ("Inference Providers") :
|
| 60 |
-
# certains modèles volumineux (ex. DeepSeek, grands MoE) ne sont pas
|
| 61 |
-
# servis par l'API serverless HF "historique" mais routés vers un
|
| 62 |
-
# fournisseur tiers (together, novita, fireworks-ai, nebius, ...).
|
| 63 |
-
# Voir https://huggingface.co/docs/inference-providers
|
| 64 |
-
client_kwargs["provider"] = settings.hf_inference_provider
|
| 65 |
-
client = InferenceClient(**client_kwargs)
|
| 66 |
messages = [
|
| 67 |
{"role": "system", "content": system_prompt},
|
| 68 |
{"role": "user", "content": user_prompt},
|
| 69 |
]
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
return completion.choices[0].message.content
|
| 76 |
|
| 77 |
|
|
@@ -82,6 +103,8 @@ def complete(system_prompt: str, user_prompt: str, json_mode: bool = False) -> s
|
|
| 82 |
return _mock_complete(system_prompt, user_prompt, json_mode)
|
| 83 |
try:
|
| 84 |
return _hf_complete(system_prompt, user_prompt, json_mode)
|
| 85 |
-
except
|
|
|
|
|
|
|
| 86 |
logger.error("Échec appel LLM (%s): %s", settings.hf_model_id, exc)
|
| 87 |
raise LLMError(str(exc)) from exc
|
|
|
|
| 1 |
"""Client LLM unique pour tout le pipeline CRAG.
|
| 2 |
|
| 3 |
+
Modèle : deepseek-ai/DeepSeek-V4.1-Flash via huggingface_hub.InferenceClient.
|
| 4 |
+
Mode mock (LLM_PROVIDER=mock) pour CI / démo sans token.
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
from __future__ import annotations
|
| 7 |
|
| 8 |
+
import inspect
|
| 9 |
import json
|
| 10 |
import logging
|
| 11 |
|
| 12 |
+
from tenacity import (
|
| 13 |
+
retry,
|
| 14 |
+
retry_if_exception_type,
|
| 15 |
+
stop_after_attempt,
|
| 16 |
+
wait_exponential,
|
| 17 |
+
)
|
| 18 |
|
| 19 |
from app.core.config import get_settings
|
| 20 |
|
|
|
|
| 26 |
|
| 27 |
|
| 28 |
def _mock_complete(system_prompt: str, user_prompt: str, json_mode: bool) -> str:
|
| 29 |
+
"""Réponses déterministes hors-ligne (tests / démo sans HF_TOKEN)."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
if json_mode:
|
| 31 |
if "score" in system_prompt.lower() or "pertinence" in system_prompt.lower():
|
| 32 |
return json.dumps({
|
|
|
|
| 41 |
)
|
| 42 |
|
| 43 |
|
| 44 |
+
def _build_inference_client(settings):
|
|
|
|
| 45 |
from huggingface_hub import InferenceClient
|
| 46 |
|
| 47 |
+
kwargs: dict = {"token": settings.hf_token}
|
| 48 |
+
# Certaines versions acceptent model= au constructeur, d'autres non.
|
| 49 |
+
init_params = inspect.signature(InferenceClient.__init__).parameters
|
| 50 |
+
if "model" in init_params:
|
| 51 |
+
kwargs["model"] = settings.hf_model_id
|
| 52 |
+
if settings.hf_inference_provider and "provider" in init_params:
|
| 53 |
+
kwargs["provider"] = settings.hf_inference_provider
|
| 54 |
+
elif settings.hf_inference_provider:
|
| 55 |
+
logger.warning(
|
| 56 |
+
"HF_INFERENCE_PROVIDER=%s ignoré : cette version de "
|
| 57 |
+
"huggingface_hub ne supporte pas provider= sur InferenceClient. "
|
| 58 |
+
"Mettez à jour huggingface_hub ou videz la variable.",
|
| 59 |
+
settings.hf_inference_provider,
|
| 60 |
+
)
|
| 61 |
+
return InferenceClient(**kwargs), settings.hf_model_id
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@retry(
|
| 65 |
+
stop=stop_after_attempt(3),
|
| 66 |
+
wait=wait_exponential(multiplier=1, min=1, max=8),
|
| 67 |
+
retry=retry_if_exception_type((TimeoutError, ConnectionError, OSError)),
|
| 68 |
+
reraise=True,
|
| 69 |
+
)
|
| 70 |
+
def _hf_complete(system_prompt: str, user_prompt: str, json_mode: bool) -> str:
|
| 71 |
settings = get_settings()
|
| 72 |
if not settings.hf_token:
|
| 73 |
raise LLMError(
|
| 74 |
+
"HF_TOKEN manquant. Définissez HF_TOKEN dans les secrets du Space."
|
|
|
|
| 75 |
)
|
| 76 |
|
| 77 |
+
client, model_id = _build_inference_client(settings)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
messages = [
|
| 79 |
{"role": "system", "content": system_prompt},
|
| 80 |
{"role": "user", "content": user_prompt},
|
| 81 |
]
|
| 82 |
+
try:
|
| 83 |
+
completion = client.chat_completion(
|
| 84 |
+
messages=messages,
|
| 85 |
+
model=model_id,
|
| 86 |
+
max_tokens=800,
|
| 87 |
+
temperature=0.1 if json_mode else 0.3,
|
| 88 |
+
)
|
| 89 |
+
except TypeError:
|
| 90 |
+
# Anciennes signatures sans model= dans chat_completion
|
| 91 |
+
completion = client.chat_completion(
|
| 92 |
+
messages=messages,
|
| 93 |
+
max_tokens=800,
|
| 94 |
+
temperature=0.1 if json_mode else 0.3,
|
| 95 |
+
)
|
| 96 |
return completion.choices[0].message.content
|
| 97 |
|
| 98 |
|
|
|
|
| 103 |
return _mock_complete(system_prompt, user_prompt, json_mode)
|
| 104 |
try:
|
| 105 |
return _hf_complete(system_prompt, user_prompt, json_mode)
|
| 106 |
+
except LLMError:
|
| 107 |
+
raise
|
| 108 |
+
except Exception as exc: # noqa: BLE001
|
| 109 |
logger.error("Échec appel LLM (%s): %s", settings.hf_model_id, exc)
|
| 110 |
raise LLMError(str(exc)) from exc
|