govon-runtime / src /inference /graph /builder.py
GovOn Deploy
sync: PR#584 RAG removal + ReAct architecture
1635ec4
"""GovOn LangGraph StateGraph ๋นŒ๋”.
v4 ์•„ํ‚คํ…์ฒ˜: ReAct + ToolNode ๊ธฐ๋ฐ˜.
LLM์ด ์ž์œจ์ ์œผ๋กœ ๋„๊ตฌ ํ˜ธ์ถœ์„ ๊ฒฐ์ •ํ•˜๋ฉฐ, ์ •์  planner/executor๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.
Graph topology:
START โ†’ session_load โ†’ agent โ†’ [route_agent]
โ”œโ”€โ”€ (no tool_calls) โ†’ persist โ†’ END
โ”œโ”€โ”€ (all Tier 0) โ†’ tools โ†’ agent โ†’ ...
โ””โ”€โ”€ (needs approval) โ†’ approval_wait โ†’ [route_after_approval]
โ”œโ”€โ”€ (approved) โ†’ tools โ†’ agent โ†’ ...
โ”œโ”€โ”€ (cancelled) โ†’ persist โ†’ END
โ””โ”€โ”€ (rejected) โ†’ agent โ†’ ...
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from langgraph.graph import END, START, StateGraph
from langgraph.prebuilt import ToolNode
from .nodes import (
make_agent_node,
make_approval_wait_node,
make_persist_node,
make_session_load_node,
route_after_approval,
)
from .state import ApprovalStatus, GovOnGraphState
if TYPE_CHECKING:
from langgraph.graph.state import CompiledStateGraph
from src.inference.session_context import SessionStore
def _make_route_agent(approval_map: dict[str, bool]):
"""agent ๋…ธ๋“œ ์ดํ›„ ๋ผ์šฐํŒ… ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
Parameters
----------
approval_map : dict[str, bool]
{tool_name: requires_approval} ๋งคํ•‘.
Returns
-------
Callable
state๋ฅผ ๋ฐ›์•„ ๋‹ค์Œ ๋…ธ๋“œ ์ด๋ฆ„์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ผ์šฐํŒ… ํ•จ์ˆ˜.
"""
def route_agent(state: GovOnGraphState) -> str:
messages = state.get("messages", [])
if not messages:
return "persist"
last = messages[-1]
tool_calls = getattr(last, "tool_calls", None)
if not tool_calls:
return "persist"
needs_approval = any(approval_map.get(tc["name"], False) for tc in tool_calls)
return "approval_wait" if needs_approval else "tools"
return route_agent
def build_govon_graph(
*,
llm,
tools: list,
session_store: "SessionStore",
checkpointer: Optional[object] = None,
) -> "CompiledStateGraph":
"""GovOn ReAct StateGraph๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์ปดํŒŒ์ผํ•œ๋‹ค.
Parameters
----------
llm : BaseChatModel
๋„๊ตฌ ๋ฐ”์ธ๋”ฉ ๊ฐ€๋Šฅํ•œ LLM ์ธ์Šคํ„ด์Šค (ChatOpenAI ๋“ฑ).
tools : list[StructuredTool]
build_all_tools()๋กœ ์ƒ์„ฑ๋œ ๋„๊ตฌ ๋ชฉ๋ก.
session_store : SessionStore
์„ธ์…˜ ์ €์žฅ์†Œ. session_load / persist ๋…ธ๋“œ์—์„œ ์‚ฌ์šฉ.
checkpointer : optional
LangGraph checkpoint ์ €์žฅ์†Œ.
None์ด๋ฉด MemorySaver๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
Returns
-------
CompiledStateGraph
์ปดํŒŒ์ผ๋œ LangGraph.
"""
from langgraph.checkpoint.memory import MemorySaver
from .tools import get_tool_approval_map
tool_node = ToolNode(tools)
approval_map = get_tool_approval_map(tools)
graph = StateGraph(GovOnGraphState)
# --- ๋…ธ๋“œ ๋“ฑ๋ก ---
graph.add_node("session_load", make_session_load_node(session_store))
graph.add_node("agent", make_agent_node(llm, tools))
graph.add_node("approval_wait", make_approval_wait_node(approval_map))
graph.add_node("tools", tool_node)
graph.add_node("persist", make_persist_node(session_store))
# --- ์—ฃ์ง€ ---
graph.add_edge(START, "session_load")
graph.add_edge("session_load", "agent")
graph.add_conditional_edges(
"agent",
_make_route_agent(approval_map),
{
"tools": "tools",
"approval_wait": "approval_wait",
"persist": "persist",
},
)
graph.add_conditional_edges(
"approval_wait",
route_after_approval,
{
"tools": "tools",
"agent": "agent",
"persist": "persist",
},
)
graph.add_edge("tools", "agent")
graph.add_edge("persist", END)
# --- ์ปดํŒŒ์ผ ---
saver = checkpointer if checkpointer is not None else MemorySaver()
return graph.compile(checkpointer=saver)