File size: 3,637 Bytes
7ff7119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""chat_graph -- 5 chat tool + ReAct + validator.

Topológia:

  START
    → intent_classifier_node    (regex, gyors -- 6 intent)
    → planner_node              (intent → tool-sorrend hint a system prompt-ba)
    → agent_node                (LLM bind_tools, ReAct)
    → tools_condition (cond)
       ├→ tool_node (ToolNode)  (5 tool végrehajtás)
       │    → agent_node (loop)
       ↓ ha nincs több tool_call
    → synthesizer_node          (utolsó AIMessage.content → final_answer)
    → validator_node            (forrás-cite + min 20 char)
    → should_retry (cond)
       ├→ agent_node (max 2 retry)
       ↓ ok
    → END
"""

from __future__ import annotations

from langchain_core.runnables import Runnable
from langgraph.graph import END, START, StateGraph
from langgraph.prebuilt import ToolNode, tools_condition

from config import settings
from graph.states.chat_state import ChatState
from nodes.chat.agent_node import build_agent_node
from nodes.chat.intent_classifier_node import intent_classifier_node
from nodes.chat.planner_node import planner_node
from nodes.chat.synthesizer_node import synthesizer_node
from nodes.chat.validator_node import validator_node
from tools import ChatToolContext, build_tools


def _should_retry(state: ChatState) -> str:
    """Validator → agent (retry) vagy END.

    A validator_node a `messages` listához egy HumanMessage-t fűz, ha nem
    elfogadható a válasz. Ezt detektáljuk: az utolsó message HumanMessage-e?
    """
    messages = state.get("messages") or []
    if not messages:
        return "end"
    last = messages[-1]
    # Ha az utolsó user-instrukció a validator-ből jött (retry kérés)
    if hasattr(last, "type") and last.type == "human":
        # A validator által beszúrt HumanMessage-eket azonosítjuk a content-szubstring-en
        content = str(getattr(last, "content", ""))
        if "A válasz nem elfogadható" in content:
            retry = state.get("validator_retry_count", 0)
            if retry <= settings.validator_max_retries:
                return "retry"
    return "end"


def build_chat_graph(llm: Runnable, context: ChatToolContext, *, checkpointer=None):
    """Compile-olt chat_graph.

    Args:
        llm: a chat-modell (Runnable, configurable_alternatives is OK)
        context: a ChatToolContext (HybridStore + documents map)
        checkpointer: opcionális (SqliteSaver / InMemorySaver)
    """
    tools_list = build_tools(context)
    llm_with_tools = llm.bind_tools(tools_list)

    graph = StateGraph(ChatState)

    graph.add_node("intent", intent_classifier_node)
    graph.add_node("planner", planner_node)
    graph.add_node("agent", build_agent_node(llm_with_tools))
    graph.add_node("tools", ToolNode(tools_list))
    graph.add_node("synthesizer", synthesizer_node)
    graph.add_node("validator", validator_node)

    graph.add_edge(START, "intent")
    graph.add_edge("intent", "planner")
    graph.add_edge("planner", "agent")

    # tools_condition: agent → tools VAGY synthesizer
    graph.add_conditional_edges(
        "agent",
        tools_condition,
        {"tools": "tools", "__end__": "synthesizer"},
    )
    # tool végrehajtás után vissza agent-hez (ReAct loop)
    graph.add_edge("tools", "agent")

    # synthesizer → validator → END vagy retry
    graph.add_edge("synthesizer", "validator")
    graph.add_conditional_edges(
        "validator",
        _should_retry,
        {"retry": "agent", "end": END},
    )

    if checkpointer is not None:
        return graph.compile(checkpointer=checkpointer)
    return graph.compile()