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
| File | What it teaches (K03 section) |
|---|---|
| agentgraph.py | The 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.py | Mock core-banking system of record + least-privilege tools (ownership enforced in the tool, not the prompt) (§12) |
| agent.py | The graph: route → rag / balance / transfer(+human approval) / refuse, with an immutable audit trail |
| run.py | Demo of all four paths, human approval, declined transfer, durable resume, and a blocked cross-account transfer |
| test_agent.py | Success criteria: tool-not-LLM money facts, human-in-the-loop, least-privilege, resume-after-crash, audit completeness |
Steps
- State. Define
AgentState(TypedDict):messages,user_id,intent,retrieved,pending_action,approved. - 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 thataccount_idbelongs touser_id(least-privilege — K03 §12). - Nodes.
route(LLM classifies intent → structured output),rag(answer from policies),agent(LLM with tools, ReAct loop),human_review(interrupt before anyinitiate_transfer),execute,refuse. - Edges.
START → route; conditionalroute → {rag | agent | refuse}; inagent, conditionalagent → tools → agent(cycle) until done; for transfers, route throughhuman_review(interrupt) →execute. - Checkpointer. Compile with a checkpointer and run with a
thread_id. Kill the process mid-conversation, restart, resume the samethread_id— state survives. - Human-in-the-loop. When a transfer is requested, the graph interrupts before
execute; you (the "agent/approver") inspectpending_actionand resume withapproved=True/False. Log the approver. - 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). - 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_bankingas 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."