Nimbus-Bank-Triage / src /graph.py
MrNoOne07's picture
Upload folder using huggingface_hub
331f4c6 verified
Raw
History Blame Contribute Delete
5.27 kB
"""
Nimbus Bank Triage β€” LangGraph StateGraph Assembly
Wires the six agents into a sequential pipeline with:
- An injection short-circuit after security_in
- A conditional Critic β†’ Drafter revision loop (max 1 revision)
- A single exit point through security_out β†’ END
"""
import os
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from src.state import TriageState
from src.agents.security_input import security_agent_input
from src.agents.classifier import classify_ticket
from src.agents.retriever import retrieve_kb
from src.agents.drafter import draft_response
from src.agents.critic import compliance_critic
from src.agents.security_output import security_agent_output
# ── Thresholds ───────────────────────────────────────────────
INJECTION_THRESHOLD = float(os.environ.get("INJECTION_SCORE_THRESHOLD", "0.80"))
MAX_DRAFT_ITERATIONS = int(os.environ.get("MAX_DRAFT_ITERATIONS", "1"))
# ── Router Functions ─────────────────────────────────────────
def route_after_security_in(state: dict) -> str:
"""
After the Security Input Agent, decide whether to proceed
with the pipeline or short-circuit on injection detection.
Returns:
"classifier" β€” normal flow, proceed to classification
"security_out" β€” injection detected, skip to exit
"""
injection_score = state.get("injection_score", 0.0)
if injection_score > INJECTION_THRESHOLD:
return "security_out"
return "classifier"
def route_after_critic(state: dict) -> str:
"""
After the Compliance Critic, decide the next step:
Returns:
"security_out" β€” safe to send OR escalation (no revision possible)
"drafter" β€” critic wants a revision and we haven't exceeded the cap
"""
safe = state.get("safe_to_send", False)
feedback = state.get("critic_feedback")
iteration = state.get("draft_iteration", 0)
# Safe to send β†’ exit
if safe:
return "security_out"
# Critic provided fixable feedback and we haven't hit the revision cap
if feedback and iteration <= MAX_DRAFT_ITERATIONS:
return "drafter"
# Not safe, no fixable feedback or cap exceeded β†’ exit as escalation
return "security_out"
# ── Graph Assembly ───────────────────────────────────────────
def build_graph() -> StateGraph:
"""
Assemble the full triage pipeline as a LangGraph StateGraph.
Pipeline flow:
security_in β†’ [injection check] β†’ classifier β†’ retriever
β†’ drafter β†’ critic β†’ [revision check] β†’ security_out β†’ END
Returns:
Compiled StateGraph ready for invocation.
"""
workflow = StateGraph(TriageState)
# ── Add nodes ────────────────────────────────────────────
workflow.add_node("security_in", security_agent_input)
workflow.add_node("classifier", classify_ticket)
workflow.add_node("retriever", retrieve_kb)
workflow.add_node("drafter", draft_response)
workflow.add_node("critic", compliance_critic)
workflow.add_node("security_out", security_agent_output)
# ── Entry point ──────────────────────────────────────────
workflow.set_entry_point("security_in")
# ── Edges ────────────────────────────────────────────────
# After security_in: conditional β€” normal flow or injection short-circuit
workflow.add_conditional_edges(
"security_in",
route_after_security_in,
{
"classifier": "classifier",
"security_out": "security_out",
},
)
# Sequential: classifier β†’ retriever β†’ drafter β†’ critic
workflow.add_edge("classifier", "retriever")
workflow.add_edge("retriever", "drafter")
workflow.add_edge("drafter", "critic")
# After critic: conditional β€” exit or revision loop
workflow.add_conditional_edges(
"critic",
route_after_critic,
{
"security_out": "security_out",
"drafter": "drafter",
},
)
# Terminal edge
workflow.add_edge("security_out", END)
return workflow
def compile_graph():
"""
Build and compile the graph with an in-memory checkpointer.
Returns:
A compiled LangGraph app ready for .invoke() or .stream()
"""
workflow = build_graph()
checkpointer = MemorySaver()
return workflow.compile(checkpointer=checkpointer)
# ── Module-level compiled graph ──────────────────────────────
# Import this to use the pipeline:
# from src.graph import triage_app
# result = triage_app.invoke(initial_state, config={"configurable": {"thread_id": "..."}})
triage_app = compile_graph()