Supply Chain Agents
LangGraph agents answer supply chain questions on a local model and wait for a person to approve a reorder.
Archived
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
- Streamlit chat
- FastAPI
- Router
- Agent + tools
- Synthesis
- 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. |