""" Appel du LLM génératif — DeepSeek-V4.1-Flash — via le routeur "Inference Providers" de Hugging Face (https://router.huggingface.co), qui proxy automatiquement vers un provider tiers (Novita à ce jour) tout en n'exigeant qu'un seul jeton HF_TOKEN pour l'authentification et la facturation. Aucune autre clé d'API n'est nécessaire. """ from __future__ import annotations import time from dataclasses import dataclass from functools import lru_cache from core.generation.prompts import build_messages, no_context_message from core.retrieval.hybrid_search import RetrievalResult from config import settings @dataclass class GenerationResult: answer: str used_fallback: bool latency_seconds: float sources: list[dict] @lru_cache(maxsize=1) def _get_client(): from huggingface_hub import InferenceClient if not settings.hf_token: raise RuntimeError("HF_TOKEN manquant : impossible d'appeler le LLM.") return InferenceClient(api_key=settings.hf_token) def generate_answer( question: str, retrieval: RetrievalResult, target_language: str, ) -> GenerationResult: start = time.perf_counter() if not retrieval.is_sufficient: return GenerationResult( answer=no_context_message(target_language), used_fallback=True, latency_seconds=time.perf_counter() - start, sources=[], ) messages = build_messages(question, retrieval.hits, target_language) client = _get_client() completion = client.chat.completions.create( model=settings.llm_model, messages=messages, temperature=settings.llm_temperature, max_tokens=settings.llm_max_tokens, ) answer = completion.choices[0].message.content sources = [ { "file": hit.source_file, "language": hit.language, "page": hit.page, "score": round(hit.score, 3), "title": hit.title, } for hit in retrieval.hits ] return GenerationResult( answer=answer, used_fallback=False, latency_seconds=time.perf_counter() - start, sources=sources, )