Alan Gaudet
AI-01

Supply Chain Agents

LangGraph agents answer supply chain questions on a local model and wait for a person to approve a reorder.

Archived

Self-hosted Langfuse traces list: two LangGraph runs, each followed by output_guard.regex and output_guard.llm_judge spans.
Traces from a local run in self-hosted Langfuse.

Description

Planners ask what demand looks like for a SKU, whether stock has fallen below its reorder point, or how risky a supplier is. A router sends each question to one specialist agent (demand, inventory or supplier risk), which calls tools over seeded synthetic data; the supplier agent also searches a small document set and cites its sources. When the inventory agent recommends a reorder, the graph pauses until a person approves or rejects it. I built it as a portfolio piece about the platform work around agents: tracing, output guardrails, tests that run without a GPU, and infrastructure as code.

Block diagram

  1. Streamlit chat
  2. FastAPI
  3. Router
  4. Agent + tools
  5. Synthesis
  6. Output guard

Decisions

Guard the output after synthesis

Prompt-level rules leaked on the small local models; in benchmark runs the default model repeated its own system prompt when asked. An output_guard node now runs after synthesis. Regex rules for email addresses, phone numbers and prompt fragments block the answer and swap in a canned refusal, and an LLM judge then flags suspected fabrication or missing citations as warnings only. The judge fails open on timeouts, connection errors and malformed JSON, so a judge outage lets answers through unannotated, and every answer that passes the regex tier costs one more model call.

A refusal clears citations and tool calls too

Replacing only the answer text left the retrieved snippet that caused the refusal in the citations list, so the blocked content still reached the API response. The guard node now empties citations and tool calls on a hard refusal, and the /query handler empties them again whenever refused is true. That meant dropping the append reducer on those two state fields, because appending an empty list changes nothing. Any future design where several agents feed one answer has to merge those lists by hand.

Pause only when there is something to approve

The inventory agent calls LangGraph's interrupt() only when its tools say a reorder is needed; plain stock questions run straight through. Graph state is checkpointed in Redis, and pending approvals are stored there with an expiry and taken with an atomic get-and-delete, so only one API replica can resume a given approval. The checkpointer needs Redis Stack for its search module. Without Redis the graph compiles with no checkpointer and the pause cannot work.

Errata

  • No labelled evaluation set was built, so guardrail false-positive and false-negative rates were never measured. CI runs only the mock-model unit tests; the real-model integration tests are run by hand against a local Ollama.
  • The Azure Bicep templates fell behind the Compose stack: they have no MinIO store for Langfuse and run plain Redis, which the LangGraph Redis checkpointer cannot use. CI compiles them, and no deployment from them is recorded.

Revision history

Version Changes
M12 Output guard node: regex hard block, LLM judge warnings, refusals scrub citations and tool calls.
M11 Hardening pass: Redis-backed approvals, FAISS docstore persistence fix, Langfuse v3 Compose fixes, real-model integration tests.
M10 Azure Bicep templates for Container Apps and ACR, with a deployment guide.
M6 FastAPI layer with SSE streaming and the approval endpoints.