Spaces:
Running
Running
| """ | |
| 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", "์๋ต ์์")) | |