Spaces:
Runtime error
Runtime error
Commit History
21 days ago
-
feat(embeddings): voyage-4-large remplace EmbeddingGemma (API Voyage, halfvec 1024)
Mesure sur tout le corpus (32 303 morceaux) et le jeu relu de 102 questions : en recherche dense, MRR@20 0,758 contre 0,537 pour EmbeddingGemma en francais, 0,680 contre 0,537 en anglais. Par le chemin du chat lui-meme (mots-cles en plein texte, question entiere vectorisee, 8 extraits envoyes au modele), l'article attendu figure parmi les extraits 92 % du temps en francais contre 61 % avec Gemma (MRR@8 0,504 contre 0,336), et 87 % en anglais contre 56 % (0,538 contre 0,282). Vectoriser la question entiere plutot que ses mots-cles gagne a lui seul +0,087 de MRR@8 en francais [IC 95 % +0,035 ; +0,143] et +0,127 en anglais [+0,067 ; +0,191]. - embedding_service.py : VoyageProvider (httpx, connexion gardee : 0,42 s par question contre 1,14 s avec une connexion neuve). Son corps de requete reproduit les vecteurs deja calcules du corpus : un test voyage_live le prouve, cosinus >= 0,999 sur deux questions et deux documents. Lots de 900 textes et 90 000 jetons estimes, refus definit…57c8117
26 days ago
-
fix(eval): relecture vraiment a l'aveugle, budget au pire cas, mesures justes
Releves par la revue adversariale du lot personas. - Relecture a l'aveugle : les etiquettes etaient tirees au hasard, mais les reponses ecrites dans l'ordre des variantes ; citoyen toujours premier, temoin toujours dernier. Elles suivent desormais l'ordre des etiquettes. - Budget : il porte sur le pire cas (une reponse coupee demande une suite, une grille illisible est redemandee), soit deux appels par tache ; 96 par defaut pour les memes 48 taches. Un nombre negatif est refuse (`--comparaisons -1` retranchait un appel et en lancait trois). - Le reclassement par le modele est coupe : il ajoutait un appel par question hors budget, et en faisait dans --dry-run. - distinction ne compare que des profils de meme modele et memes consignes ; similarite ne tient plus « a b » et « c d » pour identiques ; les suffixes (quater, septies) sont ceux du chat ; les renvois vers un avocat reconnaissent « vous adresser a un avocat », « l'avocat de votre choix », « an attorney ». …84f70cd -
feat(prompts): quatre personas vraiment distincts, un profil general, registre adapte hors du droit
Les quatre profils ne differaient que de quatre ou cinq lignes de role, et deux poussaient a inventer (« jurisprudence pertinente si disponible », « contexte historique ») alors que le corpus n'en contient pas. Chaque profil dit desormais qui est l'interlocuteur, ce qu'il attend et comment lui repondre : - citoyen : mots simples, demarche, delai, autorite ; avocat si l'enjeu est serieux ; - avocat : vocabulaire exact, conditions, exceptions, sanctions, articulation des textes ; ni jurisprudence ni renvoi vers un avocat ; - entrepreneur : obligation, etapes, delais, montants ; obligatoire et facultatif distingues ; - etudiant : notion definie, raisonnement, place dans la hierarchie des normes ; - general (n'a pas repondu) : ne presume rien du lecteur. Le bloc partage (longueur, grammaire de citation, interdits) ne change pas. Un profil inconnu recoit le profil general, avec un avertissement, et non plus citoyen en silence. Les reponses conversationnelles portent une ligne d'audi…406ec90 -
feat(eval): comparer_mistral mesure les profils, avec temoin de bruit et budget borne
- --personas : chaque question posee sous chaque profil, avec le MEME contexte ; alias acceptes, profil sans prompt refuse (sinon le repli de get_system_prompt ferait mesurer un autre profil). - --temoin : le premier profil rejoue, plancher de bruit a 0,7. - --budget : au-dela, rien n'est lance, avant meme la cle d'API. - --questions-libres avec --dry-run : la recherche du chat, sans appel au modele, montre si une suggestion a une reponse dans le corpus. - Mesures : forme_relue (citations que le chat sait relire en sources), traits (listes, renvois, definitions), similarite et distinction entre profils. La regle de decision des nouveaux prompts est ecrite dans la docstring, avant la mesure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>df97adf -
fix(recherche): la passe floue compare les titres mot par mot
« peine de mort au cameroun » rendait en tete des decrets de pret, a egalite (0,959), devant les 11 articles du Code penal sur la peine de mort. La passe floue sur les titres, faite pour rattraper les fautes de frappe, comparait la requete ENTIERE : « de », « au » et « cameroun » font plus de la moitie de ses trigrammes, et tout titre en « ... de ... au Cameroun » passait le seuil (0,516 pour 0,5). Etiquetes correspondance de titre, ces decrets passaient devant toute correspondance d'article. - Chaque mot significatif (sans mots vides, ni « cameroun », « loi », « decret »...) doit se retrouver dans le titre, meme mal orthographie. Les fautes de frappe (« nominaton », « code miner ») restent rattrapees. - Le mode entre dans la cle du cache : une recherche texte et une recherche hybride de la meme requete se servaient l'une l'autre. - scripts/eval/evaluer_recherche.py rejoue la requete de la page sur les 102 questions relues et 10 requetes temoins. Mesure (102 questions), mode …baaafb1
30 days ago
-
feat(eval): comparer_mistral mesure aussi un plus petit modele et un prompt renforce
--modeles et --consignes font varier le modele et le prompt systeme du chat : « actuelles » (la production) ou « renforcees » (cinq regles de fond en tete). Les etiquettes de la relecture a l'aveugle suivent le nombre de variantes, et la synthese compte les citations orphelines des comparaisons : une case remplie ne vaut que si ses citations tiennent. Mesure du 07/10 (20 questions, 4 comparaisons) : - ministral-3b-2512 : 90,9 % de citations justes (22) avec le prompt actuel, 68,8 % (32) avec le renforce ; 1,7 a 1,8 s par reponse ; - ministral-14b-2512 : 96,2 % (26) et 96,4 % (28) ; 3,0 a 3,3 s ; - comparaisons : le 3b remplit les 28 cases, avec 37 articles absents des extraits ; le 14b en remplit 22, avec 20. Regle fixee avant la mesure : le 3b passait a 94,6 % de citations justes au moins. Rate avec les deux prompts : ministral-14b-2512 reste. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>b16c746 -
docs(config): .env.example aligne sur Settings, seed aux 14 domaines, README a jour
.env.example : - chaque reglage de Mistral et de Groq y figure, avec les plafonds verifies du palier gratuit ; MISTRAL_MODEL suit le defaut de config.py (14b) ; INTENT_CLASSIFIER n'annonce plus « local » ; - plus aucun commentaire en fin de ligne : docker --env-file le garde dans la valeur, et « groq # groq | local » etait refuse au demarrage ; - l'extraction des PDF annonce Docling, son moteur par defaut. tests/test_core/test_env_example.py verifie que chaque cle est un reglage (Settings ignore en silence une cle inconnue), qu'aucune ligne ne porte de commentaire, que les valeurs actives se chargent et que chaque reglage des fournisseurs est documente. scripts/seed_database.py semait cinq categories aux identifiants fixes et aux noms d'avant la migration 013 : sur une base migree, une quinzieme categorie, et des lois rattachees a l'identifiant 1, 2 ou 3, quel qu'il soit. Il cree les 14 domaines manquants par leur nom et rattache ses lois par le nom. Textes perimes : « 12 cat…deb8048 -
fix(intention): budget de sortie suffisant, nominations reconnues comme du droit
Mesure du classement d'intention par Groq (qwen3.8-27b) sur les 122 messages de tests/fixtures/intent_eval.json : - a 40 jetons de sortie, 2 messages sur 60 revenaient VIDES : le modele depense des jetons invisibles avant son verdict (128 factures pour un verdict de 15), et Groq repond alors 400 json_validate_failed. Ces messages partaient en « juridique » par defaut. Le plafond passe a 256 ; le quota compte l'usage reel. GroqService traduit desormais json_validate_failed en GroqReponseInvalideError : un lot de classement des lois se redecoupe au lieu d'arreter le reclassement comme une panne ; - 3 questions de droit sur 72 partaient en hors sujet : un nom de personne, « les universites d'Etat et leurs recteurs », « who was appointed governor of the Littoral region? ». Le corpus est fait aux deux tiers de decrets de nomination ; les consignes le disent maintenant en toutes lettres. Apres correction, sur les 122 : 121 justes (99,2 %), rappel « juridique » 1,000 (ex…c84fadf -
feat(eval): comparaison ministral-14b / 8b, et jeu d'evaluation par le modele du chat
scripts/eval/comparer_mistral.py mesure les deux modeles sur 20 questions du jeu d'evaluation relu et 4 comparaisons de regimes, au plus 48 appels : meme contexte pour les deux (la recherche est faite une fois), aucun appel a Groq, modele de secours coupe. Mesures : premier morceau, duree, jetons, fin, articles cites presents dans les extraits, JSON lisible et lignes remplies. Les reponses sont ecrites anonymisees (X/Y tires au sort) pour une relecture a l'aveugle des ajouts non sources. La regle de decision est ecrite avant la mesure : on garde le 14b, sauf si le 8b egale la justesse des citations a 2 points pres sans ajouter plus de contenu non source. generate_eval_set.py passe par get_llm_service() au lieu de Gemini, avec un systeme neutre : sans lui, l'instruction du chat exigeait « Sources: » au milieu d'un JSON. MistralService.generate_stream accepte un rappel `usage` (jetons du flux, modele qui a repondu), pour les mesures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.…f565440 -
feat(intention): classement par Groq court et en cache, plus de classement local
Le classement local par embeddings est retire (intent_local.py, intent_examples.py, leurs tests et le prechauffage au demarrage). INTENT_CLASSIFIER accepte « groq » (defaut) ou « llm » (le modele du chat) ; l'ancienne valeur « gemini » est ramenee a « llm », et « local » est refuse avec la raison plutot qu'avec l'erreur generique de pydantic. Le mode groq menage le quota du jour (200 000 jetons, 1 000 requetes) : - consignes courtes (~300 jetons au lieu de ~870 : le chat plafonnait vers 215 questions par jour), schema strict, sortie de 40 jetons ; - deux derniers echanges seulement, 200 caracteres chacun ; - cache des verdicts (2 000 entrees, 24 h) : un message identique ne coute plus de requete ; un repli n'y entre jamais. Quand Groq ne peut pas repondre dans les temps — quota epuise (aucune requete n'est alors envoyee), attente du limiteur au-dela de 0,5 s, delai GROQ_INTENTION_TIMEOUT_S (3 s) depasse, panne — la question part aussitot en « juridique », avec une regle `defaut-g…21d412f
-
feat(extraction): Docling en local, OCR pleine page, cache reprenable
Docling remplace l'API Gemini, payante, comme extracteur par defaut (PDF_EXTRACTION_ENGINE=docling ; « gemini » reste disponible). - Chaque page est rasterisee (pypdfium2, 216 dpi) et passe par l'OCR pleine page : 42 % des pages n'ont pas de couche texte, et docling-parse rend NOIRES les 59 pages de scans masques (MRC) du corpus. - OCR RapidOCR sur torch, donc sur le GPU : mesure sur 97 pages contre Gemini et LlamaParse, il lit les numeros d'article la ou Tesseract en fausse (« ARTICLE 16 » lu « 13 »), a 3,8-4 s par page. - Conversion par lots de 8 pages, chaque lot mis en cache aussitot, brut : une interruption ne coute que le lot en cours, et ameliorer le nettoyage n'oblige jamais a refaire l'OCR. L'empreinte du cache porte les versions, les revisions des modeles et le rendu ; les lots se relisent quelle que soit leur taille. - Un lot en echec est rejoue ; au bout de TENTATIVES_MAX, il est repris page a page, et seule la page fautive est abandonnee et signalee. - F…69b13a2 -
feat(embeddings): EmbeddingGemma par defaut, et une panne qui se voit
EMBEDDING_PROVIDER vaut desormais gemma : les embeddings sont calcules en local, gratuitement, et Gemini ne sert plus qu'au chat. `gemini` reste disponible, a 768 dimensions lui aussi. LA PANNE LA PLUS PROBABLE DU SEMANTIQUE ETAIT LA SEULE INVISIBLE. Modele absent, fichier tronque, vecteurs d'un autre modele : dans chacun de ces cas, la recherche hybride avalait l'erreur et repondait en plein texte seul, pendant que GET /search/health disait « healthy ». Trois filets : - le modele est charge AU DEMARRAGE, et une question est reellement encodee. Un echec n'empeche pas l'API de demarrer — plein texte, chat et comptes s'en passent — mais il est journalise, et le service est ECARTE : garde, il retenterait de charger 300 Mo a chaque question ; - la sante passe en « degraded » si le service manque, si des vecteurs d'un AUTRE fournisseur sont en base (leurs cosinus ne veulent rien dire), ou si aucun vecteur n'est exploitable alors qu'il y a des articles a vectoriser. Ce dernier …7a1d560 -
style: ordre des imports et import inutilise laisses par mes commits du jour
ruff signalait trois ecarts dans des fichiers touches par 153dd7c et ad3ae33 : deux blocs d'imports mal ordonnes et un `Optional` importe sans usage. Aucun changement de comportement. Restent deux signalements anterieurs, dans tests/test_main.py et tests/test_migrations/test_email_tokens_schema.py, laisses en l'etat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>a9c1b19 -
feat(embeddings): schema en 768 dimensions, index HNSW sur la colonne
La colonne articles.embedding passe de vector(3072) a vector(768) : la sortie native d'EmbeddingGemma, et une dimension que gemini-embedding-001 produit aussi. Un seul schema pour les deux fournisseurs. Le fournisseur par defaut reste gemini : ce commit ne change que le schema et ce qui en depend. LA MIGRATION DETRUIT TOUS LES VECTEURS (c4d5e6f7a8b9). C'est inevitable : un vecteur 3072 n'a pas d'equivalent dans l'espace d'un autre modele. Ici les bases etaient vides. Une base peuplee devra etre regeneree ensuite, et comme le conteneur lance `alembic upgrade head` a chaque demarrage, deployer ce code declenche la purge. La procedure est a ecrire avant tout deploiement. L'index HNSW se pose directement sur la colonne, sans le detour par une expression halfvec. Les anciens index sont supprimes AVANT l'ALTER : laisse en place, l'index halfvec survit a la conversion et ne sert plus a rien. UN PIEGE TROUVE EN CHEMIN, mesure par EXPLAIN. La recherche garde deux etages : l'index fournit des…195c1c3 -
chore(eval): banc de comparaison des extracteurs PDF
Mesure trois choses contre la couche texte du PDF, page par page : la pagination (le critere qui a fait abandonner LlamaParse), la densite, et l'ancrage lexical avec detection des numeros d'article inventes. Moteurs : Gemini (chemin de production), Docling, Chandra 2 via Datalab, DeepSeek. Reference mesuree sur le Code Minier, pages 21 a 32 : pagination 12/12, 97,4 % d'ancrage, aucun article invente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>ad3ae33
-
feat(extraction): remplacer LlamaParse par Gemini multimodal
La cle LlamaParse ayant ete retiree, l'ingestion de tout nouveau PDF levait et marquait le document `refused` — le bon echec, mais l'ingestion etait a l'arret. LE MOTIF DU CHANGEMENT EST LA PAGINATION, PAS LE PRIX. LlamaParse rend le document en un bloc sans signaler ses coupures de page ; `_split_markdown_pages` les reconstruisait en coupant sur les lignes de trois tirets, et sa propre docstring admettait la limite : « la pagination ne devient exacte qu'avec un moteur qui renvoie un numero de page explicite ». Mesure en base : les 710 articles portent tous `page_number = 1`, et sur les 27 lois, 271 pages de PDF ne donnent que 27 pages extraites — couverture moyenne 0,60. Un lien `?article=35` ouvre donc le PDF a la premiere page. MESURE AVANT D'ECRIRE, sur le Code Minier dont la sortie LlamaParse est en base : densite 2352 car/page contre 2405 — equivalent, rien n'est perdu marqueurs de page 1 par page contre 1 pour 69 pages lot pages 21 a 32 numerote 21…ca48b04 -
fix(qualite): ruff devient une vraie barriere, et trouve un schema mort et casse
Ruff tournait sans jeu de regles declare : 1332 signalements, dont 98 faux positifs sur le motif `Depends()` de FastAPI et 615 modernisations de style. Un controle que personne ne peut passer n'est pas un controle. Le jeu retenu ne garde que ce qui signale un defaut reel (F, E4, E7, E9, I, RUF100), avec des exceptions nommees la ou l'ordre des imports EST la protection : env.py doit poser sys.path avant d'importer les modeles, conftest doit surcharger DATABASE_URL avant d'importer l'application. Ce que le controle a alors trouve : - UN SCHEMA MORT ET CASSE. `LawDetailResponse` existait deja, exposant `List[ArticleResponse]`. Il n'etait branche sur aucune route, et il etait doublement inutilisable : ArticleResponse porte le CONTENU integral de chaque article — pour le Code Minier, 166 Ko servis deux fois — et son `number` etait plafonne a 20 caracteres alors que la colonne en fait 64 depuis qu'elle accueille « QUATRE-VINGT-DIX-SEPTIEME ». Le plafond du schema est corrige, et…7435134
-
feat(pipeline): chunk_refiner enfin cable, avec kind, embed et embed_text
599 lignes ecrites, testees, appelees par personne. Le raffinage tourne desormais entre le decoupage et la persistance. Trois colonnes sur articles (migration b7c8d9e0f1a2) : - kind : article, legal_basis, preamble, boilerplate, roster, table, fragment, continuation ; - embed : faut-il vectoriser ce chunk ; - embed_text : le texte REELLEMENT envoye au modele. Ce que cela change, mesure sur le corpus reel de 849 articles : 62 % font moins de 120 caracteres, et l'echantillon montre ce qu'ils sont — des lignes de liste nominative ("100. DOURLAI STANISLAS 0769502K"), des fragments de tableau, un separateur "---". Ils partaient tous au vectoriel, consommant du quota et saturant les resultats semantiques de textes qui ne repondent a aucune question. Ils restent en base, affichables et trouvables en plein texte ; ils ne sont plus vectorises. embed_text prefixe le contenu de l'en-tete du document — reference, titre, date, categorie, section, page. Sans lui, "Article 3.- La depense…8da83c7 -
test(eval): harnais de mesure de la recuperation
Sans mesure, le choix de la dimension et les poids de fusion se decident sur les chiffres MTEB d'un fournisseur et sur une constante tiree d'un papier TREC. Ce harnais permet de trancher sur CE corpus. scripts/eval/metrics.py — recall@k, MRR et intervalle de confiance bootstrap. Fonctions pures, definitions ecrites noir sur blanc dans le module : avec un seul article pertinent par question, recall@k est un taux de succes, et MRR est la metrique principale parce que le RAG n'envoie que 8 articles au modele — ce qui compte est que le bon soit HAUT, pas seulement present. Le module dit aussi qu'avec 60 questions l'erreur type est d'environ 6 points, et le rapport affiche l'intervalle a cote de chaque nombre : un ecart de 2 points n'est pas un resultat. scripts/eval/generate_eval_set.py — echantillonnage stratifie (60 % normatif, 20 % gros textes, 20 % aleatoire, formules d'execution exclues), generation de deux questions par article, et surtout les garde-fous qui ouvrent la boucle ferme…4f32045