SAP-ERP-AI-Agent / src /graph_builder.py
daisysooyeon's picture
deploy: SAP ERP AI Agent (HF Spaces docker)
50efdc6
Raw
History Blame Contribute Delete
4.55 kB
"""
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", "์‘๋‹ต ์—†์Œ"))