| """Proposition de candidats pour combler un gap (basée sur LLM).""" |
| from __future__ import annotations |
|
|
| from typing import Any, List |
|
|
| from pydantic import BaseModel, Field |
|
|
| from jdm_agent.client.relations import describe_relation, parse_relation_definitions |
| from jdm_agent.enrich.models import Candidate, Gap |
|
|
|
|
| PROPOSER_PROMPT = """Tu es un expert en linguistique française et en construction de \ |
| graphes lexico-sémantiques pour JeuxDeMots (JDM). |
| |
| Ton rôle : pour un gap donné (term, relation), proposer **5 à 10 termes cibles** \ |
| plausibles qui complètent le triplet. Chaque proposition doit être : |
| - linguistiquement correcte |
| - factuelle (vérifiable, non-créative) |
| - en français standard, EN MINUSCULES |
| - au singulier (sauf si la cible est intrinsèquement pluriel) |
| - à l'infinitif pour les verbes |
| |
| Pour CHAQUE candidat, fournis : |
| - `target` : le terme cible |
| - `confidence` : 0.0–1.0 de ta certitude (1.0 = totalement standard) |
| - `rationale` : 1 phrase courte expliquant pourquoi |
| |
| Renvoie SEULEMENT la liste JSON, pas de commentaire libre. |
| |
| Si tu ne connais pas la relation ou si elle est trop floue, renvoie une liste vide. |
| """ |
|
|
|
|
| class _RawCandidate(BaseModel): |
| target: str |
| confidence: float = Field(0.5, ge=0.0, le=1.0) |
| rationale: str = "" |
|
|
|
|
| class _CandidateList(BaseModel): |
| candidates: List[_RawCandidate] = Field(default_factory=list) |
|
|
|
|
| def propose_candidates(gap: Gap, llm: Any, max_candidates: int = 10) -> List[Candidate]: |
| """Demande au LLM de proposer des cibles pour combler le gap. |
| |
| Args: |
| gap: le trou identifié. |
| llm: instance LangChain BaseChatModel. |
| max_candidates: limite haute. |
| |
| Returns: |
| Liste de Candidate (non encore validés). |
| """ |
| from langchain_core.messages import HumanMessage, SystemMessage |
|
|
| |
| rel_docs = parse_relation_definitions() |
| rel_desc = describe_relation(gap.relation, rel_docs) |
|
|
| user_msg = ( |
| f"Gap à combler :\n" |
| f" term = {gap.term!r}\n" |
| f" relation = {gap.relation!r}\n" |
| f" contexte = {gap.detail}\n\n" |
| f"Description de la relation :\n{rel_desc}\n\n" |
| f"Propose jusqu'à {max_candidates} cibles pertinentes." |
| ) |
|
|
| structured = llm.with_structured_output(_CandidateList) |
| try: |
| out: _CandidateList = structured.invoke([ |
| SystemMessage(content=PROPOSER_PROMPT), |
| HumanMessage(content=user_msg), |
| ]) |
| except Exception: |
| return [] |
|
|
| return [ |
| Candidate( |
| term=gap.term, relation=gap.relation, |
| target=rc.target, confidence=rc.confidence, |
| rationale=rc.rationale, source="llm", |
| ) |
| for rc in out.candidates[:max_candidates] |
| ] |
|
|