async def cadrer_sans_outils(
moteur: Any, modele: str, messages: list[Message]
) -> dict[str, Any] | None:
"""Un modèle sans fonctions peut produire les données d'une carte, sans agir."""
schema = schema_questions()["function"]["parameters"]
schema["properties"]["questions"]["minItems"] = 0
schema["properties"] = {
"subject": {"type": "string", "description": "Sujet de la dernière demande."},
"knownDetails": {
"type": "array",
"items": {"type": "string"},
"maxItems": 12,
"description": "Précisions explicitement fournies pour CE SUJET seulement.",
},
**schema["properties"],
}
schema["required"] = ["subject", "knownDetails", "questions"]
consigne = (
"Décide si la DERNIÈRE demande de l'utilisateur nécessite des précisions "
"avant de la réaliser. Tu prépares uniquement les données d'un formulaire, "
"sans exécuter la demande ni aucune action. Pour une création ou un "
"programme encore vague, choisis toi-même le nombre de questions utiles, "
"chacune avec 2 à 4 choix courts. Aucun quota : peu pour un besoin simple, "
"davantage pour un projet complexe. Chaque question est affichée séparément. "
"Ne répète pas une question sous une autre forme et arrête-toi dès que "
"les informations nécessaires sont couvertes. "
"Respecte le périmètre donné : si l'utilisateur indique qu'une seule "
"précision manque, demande seulement celle-ci, sans ajouter des "
"préférences facultatives. "
"Respecte la langue de l'utilisateur. N'invente pas ses "
"réponses et ne redemande pas des informations déjà données POUR CE SUJET. "
"Un niveau ou un objectif donné pour une autre activité ne vaut pas "
"automatiquement pour la nouvelle. Par exemple, un programme "
"d'apprentissage sans niveau ni temps disponible nécessite un cadrage : "
'{"questions":[{"title":"Quel est ton niveau dans ce domaine ?",'
'"options":[{"label":"Débutant"},{"label":"J’ai déjà des bases"}]}]}. '
"Pour une question factuelle, une demande déjà précise, des réponses "
"à un questionnaire ou une demande d'avancer sans questions, renvoie "
"une liste questions vide. Ne mets jamais de noms d’outils ou de "
"fonctions dans les textes du formulaire. L'introduction est facultative ; "
"ne répète "
"pas une question dans l'introduction. Réponds uniquement avec du JSON "
"conforme au schéma suivant :\n" + json.dumps(schema, ensure_ascii=False)
)
derniere_demande = next(
(m.content or "" for m in reversed(messages) if m.role == Role.USER), ""
)
travail = [
*messages,
Message(role=Role.SYSTEM, content=consigne),
Message(
role=Role.USER,
content=(
"Évalue uniquement le besoin de précisions pour cette demande "
"du dialogue, puis fournis le JSON du formulaire :\n"
+ json.dumps(derniere_demande, ensure_ascii=False)
+ "\nIdentifie d'abord le sujet de CETTE demande dans subject, "
"puis les seules précisions déjà connues POUR CE SUJET dans "
"knownDetails. Les réponses sur une autre activité ne "
"décrivent pas automatiquement celle-ci. Complète ensuite "
"les questions utiles pour personnaliser le résultat."
),
),
]
fragments: list[str] = []
taille = 0
arret = None
async with aclosing(
moteur.stream_full(
travail,
model=modele,
temperature=0.0,
max_tokens=CADRAGE_MAX_JETONS,
format=schema,
)
) as flux:
async for morceau in flux:
if morceau.tool_calls:
raise ValueError("Un formulaire ne doit pas demander d'action.")
if morceau.content:
taille += len(morceau.content.encode("utf-8"))
if taille > QUESTIONNAIRE_MAX_OCTETS:
raise ValueError("Le questionnaire est trop long.")
fragments.append(morceau.content)
if morceau.finish_reason:
arret = morceau.finish_reason
if arret != "stop":
raise ValueError("Le questionnaire n'a pas été terminé.")
decision = _Decision.model_validate_json("".join(fragments))
if not decision.questions:
return None
return valider_questions(
decision.model_dump_json(exclude={"subject", "knownDetails"})
)