Spaces:
Running
Running
File size: 4,552 Bytes
50efdc6 | 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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | """
src/graph_builder.py
LangGraph StateGraph ํฉํ ๋ฆฌ โ ๊ทธ๋ํ ๊ตฌ์กฐ ์ ์ ๋ฐ ์ปดํ์ผ
"""
import uuid
import argparse
from src.logging_config import setup_logging
setup_logging() # Must be called before any other src.* imports
from langgraph.graph import StateGraph, END
from langgraph.types import Send
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
from src.config import get_config
from src.graph.state import AgentState
from src.graph.router import router_node
from src.graph.worker_a import worker_a_node
from src.graph.worker_b import worker_b_node
from src.graph.human_loop import human_loop_node
from src.graph.synthesizer import synthesizer_node
from src.preprocess import preprocess_email
def _auto_approve_node(state: AgentState) -> dict:
"""HITL ๋นํ์ฑ(ํ๊ฐ ์ ์ฉ) ๊ฒฝ๋ก์์ worker_a์ PENDING_APPROVAL์ ์น์ธ๋จ(SUCCESS)์ผ๋ก
ํ์ํ๋ค. ์ด์์ human_loop๊ณผ ๋ฌ๋ฆฌ DB(SQLite) UPDATE๋ ์ ๋ ์ํํ์ง ์๋๋ค โ
'์น์ธ๋์๋ค๊ณ ๊ฐ์ 'ํ์ ๋์ ๋ต์ฅ ํ์ง๋ง ์ธก์ ํ๊ธฐ ์ํจ์ด๋ค.
(golden_response๋ PENDING_APPROVAL์ 'completed successfully'๋ก ๋ ๋ํ๋ฏ๋ก ์ ํฉ)."""
if state.get("erp_action_status") == "PENDING_APPROVAL":
return {
"erp_action_status": "SUCCESS",
"requires_human_approval": False,
"human_approved": True,
}
return {}
def _build_state_graph(*, hitl: bool = True) -> StateGraph:
"""StateGraph ๊ตฌ์กฐ๋ง ๋น๋ (์ฒดํฌํฌ์ธํฐ ์์) โ sync/async ๊ณต์ฉ.
hitl=True : worker_a โ (PENDING_APPROVAL์ด๋ฉด) human_loop โ synthesizer.
hitl=False : ํ๊ฐ ์ ์ฉ โ worker_a โ auto_approve(์น์ธ ๊ฐ์ , DB ๋ฏธ๋ณ๊ฒฝ) โ synthesizer.
"""
builder = StateGraph(AgentState)
builder.add_node("router", router_node)
builder.add_node("worker_a", worker_a_node)
builder.add_node("worker_b", worker_b_node)
builder.add_node("synthesizer", synthesizer_node)
if hitl:
builder.add_node("human_loop", human_loop_node)
else:
builder.add_node("auto_approve", _auto_approve_node)
builder.set_entry_point("router")
def route_after_router(state: AgentState):
intent = state["intent"]
if intent == "ACTION_ONLY":
return [Send("worker_a", state)]
elif intent == "QA_ONLY":
return [Send("worker_b", state)]
else: # BOTH
return [Send("worker_a", state), Send("worker_b", state)]
builder.add_conditional_edges("router", route_after_router, ["worker_a", "worker_b"])
if hitl:
_hitl_config_enabled = get_config().feature_flags.human_in_the_loop
def route_after_worker_a(state: AgentState) -> str:
if state.get("erp_action_status") == "PENDING_APPROVAL" and _hitl_config_enabled:
return "human_loop"
return "synthesizer"
builder.add_conditional_edges("worker_a", route_after_worker_a)
builder.add_edge("human_loop", "synthesizer")
else:
# ํ๊ฐ ์ ์ฉ: ์น์ธ์ ๊ฐ์ (PENDING_APPROVALโSUCCESS, DB ๋ฏธ๋ณ๊ฒฝ)ํ ๋ค ํฉ์ฑ.
builder.add_edge("worker_a", "auto_approve")
builder.add_edge("auto_approve", "synthesizer")
builder.add_edge("worker_b", "synthesizer")
builder.add_edge("synthesizer", END)
return builder
def build_graph():
"""SqliteSaver(sync)๋ก ๊ทธ๋ํ ๋น๋ โ CLI / ํ
์คํธ ์ ์ฉ"""
checkpoint_db = get_config().paths.checkpoint_db
conn = sqlite3.connect(checkpoint_db, check_same_thread=False)
memory = SqliteSaver(conn)
return _build_state_graph().compile(checkpointer=memory)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="SAP ERP AI Agent")
parser.add_argument("--input", required=True, help="์ด๋ฉ์ผ ํ
์คํธ ์
๋ ฅ")
args = parser.parse_args()
graph = build_graph()
thread_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}
# LangGraph ์ง์
์ ์ด๋ฉ์ผ ์ ์ฒ๋ฆฌ โ ๋ฐ์ ์/์์ ์/์์ฒญ ์ฌํญ/์ํฐํฐ ๊ตฌ์กฐํ
email_ctx = preprocess_email(args.input)
result = graph.invoke(
{
"user_input": args.input,
"email_context": email_ctx.model_dump(),
"error_messages": [],
"requires_human_approval": False,
"human_approved": None,
},
config=config,
)
print("=== ์ต์ข
์๋ต ===")
print(result.get("final_response", "์๋ต ์์"))
|