Lab 02 — LangGraph Banking Agent

Goal. Build a stateful agent that, given a customer message, routes between: (a) RAG answer over policies (Lab 01), (b) a tool call to a mock core-banking API for live data (balance/transactions), or (c) a refusal for out-of-scope/unsafe requests — with a checkpoint for durability and a human-in-the-loop approval node on any money-moving action. This is JD4's first "THE MUST": agentic AI architectures for banking. Read K03 first.

Stack. LangGraph (graph, state, checkpointer, interrupts) · LangChain (AzureChatOpenAI) · a mock core_banking tool (SQLite/in-memory) · the Lab 01 retriever as a tool. Local fallback. Ollama via ChatOpenAI(base_url=...); MemorySaver/SQLite checkpointer instead of Cosmos.


Run it (offline, no Azure)

pip install -r requirements.txt        # only pytest; the engine is pure stdlib
python run.py                          # all 4 paths + approval + resume + audit trail
pytest -q                              # 8 tests = the lab's success criteria

We build a ~120-line LangGraph clone from scratch (agentgraph.py) so you can see exactly what state, conditional edges, the checkpointer, and interrupts do — then assemble the banking agent on top. No LLM or Azure needed: the intent router and policy responder are deterministic stand-ins with the production LangGraph/AzureChatOpenAI calls shown in the docstrings.

Code tour

FileWhat it teaches (K03 section)
agentgraph.pyThe engine: shared state, conditional edges + cycles, a checkpointer (durable/auditable/replayable), and interrupts (pause→resume) — the four things a naive loop lacks (§4-5)
bank.pyMock core-banking system of record + least-privilege tools (ownership enforced in the tool, not the prompt) (§12)
agent.pyThe graph: route → rag / balance / transfer(+human approval) / refuse, with an immutable audit trail
run.pyDemo of all four paths, human approval, declined transfer, durable resume, and a blocked cross-account transfer
test_agent.pySuccess criteria: tool-not-LLM money facts, human-in-the-loop, least-privilege, resume-after-crash, audit completeness

Steps

  1. State. Define AgentState (TypedDict): messages, user_id, intent, retrieved, pending_action, approved.
  2. Tools. Implement and register: search_policies(query) (Lab 01 RAG), get_balance(account_id), list_transactions(account_id, days), initiate_transfer(from, to, amount) (the high-risk one). Each tool enforces that account_id belongs to user_id (least-privilege — K03 §12).
  3. Nodes. route (LLM classifies intent → structured output), rag (answer from policies), agent (LLM with tools, ReAct loop), human_review (interrupt before any initiate_transfer), execute, refuse.
  4. Edges. START → route; conditional route → {rag | agent | refuse}; in agent, conditional agent → tools → agent (cycle) until done; for transfers, route through human_review (interrupt) → execute.
  5. Checkpointer. Compile with a checkpointer and run with a thread_id. Kill the process mid-conversation, restart, resume the same thread_id — state survives.
  6. Human-in-the-loop. When a transfer is requested, the graph interrupts before execute; you (the "agent/approver") inspect pending_action and resume with approved=True/False. Log the approver.
  7. Guardrails. Never let the LLM state a balance — it must come from get_balance. Add a step/iteration cap. Treat retrieved doc text as untrusted (prompt-injection — K06 §7).
  8. Audit. Persist every message, thought, tool call+args+result, and the approval decision.

Measurable result

Demonstrate all four paths: a policy question (→ RAG + citation), a "what's my balance?" (→ tool call, real value, never hallucinated), a transfer (→ pauses for human approval, then executes/declines), and an out-of-scope/unsafe ask (→ refusal). Show a resume-after-crash using the checkpointer and a full audit trail for one transfer.

Stretch

  • Add a supervisor/multi-agent split (a "retrieval agent" + a "transaction agent") and discuss the trade-off (K03 §10).
  • Expose core_banking as an MCP server and have the agent consume it (K03 §9).
  • Persist checkpoints to Cosmos (K07 §6).

Talking point

"A banking agent's hard parts aren't the LLM — they're durable state, controlled autonomy, and audit. LangGraph gives me an explicit graph with a checkpointer (resume after crash, auditable path) and interrupts (pause for human approval on money-moving steps), while tools enforce the user's permissions so an injection can't move funds."

Resume bullet

"Designed a LangGraph banking agent with stateful routing (RAG vs core-banking tool calls vs refusal), durable checkpointing for crash-resume, human-in-the-loop approval gating on fund transfers, least-privilege tools, and full audit logging — live figures sourced from systems of record, never the LLM."