"""Chat agent node producing replies using system + RAG contexts + history.""" from typing import Callable, List from langchain_core.messages import BaseMessage, SystemMessage, AIMessage from langchain_core.language_models.chat_models import BaseChatModel from graphs.state import AgentState from graphs.prompts import SYSTEM_PROMPT_TEMPLATE def chat_node(llm: BaseChatModel) -> Callable[[AgentState], AgentState]: """Factory returning a node that generates a reply with RAG context.""" def _run(state: AgentState) -> AgentState: messages = list(state.get("messages", [])) sys_msgs: List[BaseMessage] = [SystemMessage(content=SYSTEM_PROMPT_TEMPLATE)] formation_context = state.get("formation_context", "") prestation_context = state.get("prestation_context", "") project_context = state.get("project_context", "") if project_context: sys_msgs.append( SystemMessage( content=( "CONTEXTE PROJET (extraits des documents du projet; n'utilise rien d'autre):\n\n" f"{project_context}\n\n" "Consignes projet: Ce contenu indique des informations complémentaires à prendre en compte pour répondre à la question. " ) ) ) if formation_context: sys_msgs.append( SystemMessage( content=( "CONTEXTE FORMATIONS (extraits du catalogue formations; n'utilise rien d'autre):\n\n" f"{formation_context}\n\n" "Consignes formations: Utilise exclusivement ce contexte pour recommander les formations. " "Cite la page et la source pour chaque recommandation. " "Une formation = un document." ) ) ) if prestation_context: sys_msgs.append( SystemMessage( content=( "CONTEXTE PRESTATIONS (extraits du catalogue services; n'utilise rien d'autre):\n\n" f"{prestation_context}\n\n" "Consignes prestations: Utilise exclusivement ce contexte pour recommander les prestations. " "Cite la page et la source pour chaque recommandation. " "Un document peut contenir plusieurs prestations." ) ) ) if state.get("voice_mode"): sys_msgs.append(SystemMessage(content=( "IMPORTANT - MODE VOCAL: Ta réponse sera lue à voix haute. " "Réponds de manière concise et naturelle, comme dans une conversation orale. " "N'utilise PAS de markdown, pas de listes à puces, pas de tableaux, pas de gras/italique. " "Pas de numéros de page ni de références documentaires détaillées. " "Limite ta réponse à quelques phrases claires et directes. " "Si la question nécessite des détails, propose de les envoyer par écrit." ))) response = llm.invoke(sys_msgs + messages) return {"messages": messages + [AIMessage(content=response.content)]} return _run