jdmagent / src /jdm_agent /enrich /proposers.py
expAge
feat(phase-6 + mcp): JDM enrichment pipeline + expose verify_claim via MCP
e13590b
"""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
# Décrit la relation au LLM pour cadrer la sémantique.
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]
]