Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🧩 Supervisor Multi-Agent Content Team

Python LangGraph Tests License

A LangGraph-based multi-agent system where a supervisor orchestrates four specialist AI agents β€” Researcher, Writer, Editor, and SEO β€” to autonomously produce a complete, SEO-ready article from a single topic.

Built with a 4-way LLM provider fallback chain (Gemini β†’ Groq β†’ OpenRouter β†’ Cerebras) so the system keeps running even when a free-tier API gets rate-limited.


πŸ“– Overview

You give the system one topic. Five AI roles cooperate to turn it into a finished article:

  • Researcher gathers current facts from the web (Tavily search)
  • Writer turns the research into a full draft
  • Editor tightens the draft and can request exactly one rewrite
  • SEO produces a title, meta description, keywords, and slug
  • Supervisor sits above all of them β€” it writes no content, it only decides who works next, based on what's been completed so far

The supervisor never hands off to two agents at once and never lets the loop run forever β€” a deterministic safety net in code enforces valid transitions even if the LLM's routing decision is wrong.


✨ Features

  • 🧠 Adaptive orchestration β€” not a fixed pipeline; the supervisor routes based on live state
  • πŸ” Bounded revision loop β€” editor gets exactly one rewrite chance, enforced in code (not just prompted)
  • 🌐 4-provider automatic fallback β€” Gemini β†’ Groq β†’ OpenRouter β†’ Cerebras; a rate-limit or 404 on one provider silently switches to the next, mid-run
  • πŸ–₯️ Two interfaces β€” a CLI for scripting, a Streamlit UI for interactive testing with a live handoff log
  • βœ… 20 automated tests, all running without any API key (pure logic, no live LLM calls)
  • πŸ“„ Structured output everywhere β€” supervisor routing, editor verdicts, and SEO metadata all use Pydantic schemas, never free-text parsing
  • πŸ”’ Defensive design β€” every LLM decision is double-checked by deterministic code rules

πŸ—οΈ System Overview

Five roles, one shared state. The supervisor sits in the center β€” every specialist reports back to it after finishing its job.

graph TD
    START([START]) --> SUP[Supervisor]
    SUP --> RES[Researcher]
    SUP --> WRI[Writer]
    SUP --> EDI[Editor]
    SUP --> SEO[SEO]
    RES --> SUP
    WRI --> SUP
    EDI --> SUP
    SEO --> SUP
    SUP --> END([END])

    style SUP fill:#7c3aed,color:#fff
    style RES fill:#2563eb,color:#fff
    style WRI fill:#2563eb,color:#fff
    style EDI fill:#2563eb,color:#fff
    style SEO fill:#2563eb,color:#fff
    style START fill:#16a34a,color:#fff
    style END fill:#16a34a,color:#fff
Loading

All five nodes read from and write to the same shared state β€” a single object passed through the whole run, carrying the topic, research, draft, edited draft, and SEO package.


πŸ—οΈ Architecture

Read left to right. Execution enters at START, goes to the supervisor, which picks exactly one specialist to run next. Every specialist hands control back to the supervisor. Only the supervisor can route to FINISH.

graph LR
    START([START]) --> SUP{Supervisor}
    SUP -- researcher --> RES[Researcher + Tavily]
    SUP -- writer --> WRI[Writer]
    SUP -- editor --> EDI[Editor]
    SUP -- seo --> SEO[SEO]
    SUP -- FINISH --> END([END])

    style SUP fill:#7c3aed,color:#fff
    style RES fill:#2563eb,color:#fff
    style WRI fill:#2563eb,color:#fff
    style EDI fill:#2563eb,color:#fff
    style SEO fill:#2563eb,color:#fff
    style START fill:#16a34a,color:#fff
    style END fill:#16a34a,color:#fff
Loading

LLM Provider Fallback Chain

graph LR
    A[Gemini] --> B[Groq]
    B --> C[OpenRouter]
    C --> D[Cerebras]

    style A fill:#2563eb,color:#fff
    style B fill:#2563eb,color:#fff
    style C fill:#2563eb,color:#fff
    style D fill:#2563eb,color:#fff
Loading

If one provider hits a rate limit or a deprecated-model error, the next one in the chain is tried automatically β€” including inside structured-output calls (supervisor routing, editor verdict, SEO package).


πŸ”„ Agent Workflow

graph TD
    A[Topic] --> B[Researcher: gathers facts]
    B --> C[Writer: drafts article]
    C --> D[Editor: reviews draft]
    D -- APPROVED --> F[SEO: builds metadata]
    D -- REVISION, max once --> C
    F --> G[Done: article + SEO package]

    style A fill:#16a34a,color:#fff
    style B fill:#2563eb,color:#fff
    style C fill:#2563eb,color:#fff
    style D fill:#2563eb,color:#fff
    style F fill:#2563eb,color:#fff
    style G fill:#16a34a,color:#fff
Loading

Key detail: when the writer revises a draft, the previous edited_draft and editor_verdict are cleared from state, so the editor is forced to review the new draft rather than being skipped. This was a real infinite-loop bug found during testing (see Known Issues Fixed below).

πŸ“ Project Structure

supervisor-agent-team/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ config.py            # 4-way LLM provider fallback (Gemini/Groq/OpenRouter/Cerebras)
β”‚   β”œβ”€β”€ state.py              # Shared state schema (TypedDict)
β”‚   β”œβ”€β”€ prompts.py            # System prompts for all agents
β”‚   β”œβ”€β”€ supervisor.py         # Routing brain + deterministic safety net
β”‚   β”œβ”€β”€ graph.py               # LangGraph wiring (nodes + edges)
β”‚   β”œβ”€β”€ main.py                 # CLI entry point
β”‚   └── agents/
β”‚       β”œβ”€β”€ researcher.py     # Tavily web search + synthesis
β”‚       β”œβ”€β”€ writer.py          # Draft + revision (clears stale edit state)
β”‚       β”œβ”€β”€ editor.py          # Tightens draft, verdict, revision cap
β”‚       └── seo.py              # Structured SEO metadata + slug cleaning
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ test_state.py
β”‚   β”œβ”€β”€ test_supervisor_routing.py
β”‚   β”œβ”€β”€ test_editor_revision_cap.py
β”‚   β”œβ”€β”€ test_seo_slug.py
β”‚   β”œβ”€β”€ test_writer_revision_reset.py   # regression test for the infinite-loop bug
β”‚   └── test_no_infinite_loop.py         # full state-machine simulation, worst case
β”œβ”€β”€ streamlit_app.py           # UI - provider switch, live log, tabs, download
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ .env.example
└── README.md

βš™οΈ Setup

# 1. Create a virtual environment
python -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate

# 2. Install dependencies
pip install -r requirements.txt

# 3. Configure API keys
cp .env.example .env

Then open .env and fill in your keys:

Key Where to get it Required?
GOOGLE_API_KEY https://aistudio.google.com/apikey (free) Yes (primary provider)
GROQ_API_KEY https://console.groq.com/keys (free) Recommended (fallback)
OPENROUTER_API_KEY https://openrouter.ai/keys (free) Recommended (fallback)
CEREBRAS_API_KEY https://cloud.cerebras.ai (free) Recommended (fallback)
TAVILY_API_KEY https://app.tavily.com (free) Yes (researcher agent)

You only strictly need one LLM provider key to run the system, but having 2+ configured means the fallback chain can actually do its job.


πŸš€ Usage

Run the tests first (no API key needed)

PYTHONPATH=. python -m pytest tests/ -v

Expected: 20 passed.

Option A β€” CLI

PYTHONPATH=. python -m src.main "How RAG improves LLM accuracy"

Option B β€” Streamlit UI (recommended)

streamlit run streamlit_app.py

Opens at http://localhost:8501. From there:

  1. Check the sidebar for API key status (checkmark / cross per provider)
  2. Pick a provider (or leave on Gemini β€” fallback handles the rest)
  3. Enter a topic β†’ Generate Article
  4. Watch the live handoff log as each agent works
  5. Review results in three tabs: Article, SEO Package, Full Log
  6. Download the finished piece as Markdown

If a provider hits a rate limit mid-run, the fallback chain switches automatically β€” you'll see [fallback] Switching to ... in the log.


πŸ“„ Sample Output

A real run's output is saved under output/. Example: output/ai-agents-2026-...md β€” a full researched, edited, SEO-tagged article with inline citations, produced end-to-end by the agent team with no manual editing.


πŸ§ͺ Testing β€” What's Covered and Why

All tests run without any API key β€” they test pure logic (state transitions, revision caps, slug cleaning) rather than calling a live LLM. This is deliberate: if tests depended on live APIs, a rate limit would make the test suite itself unreliable β€” exactly the problem this project's fallback chain exists to solve.

Test file What it checks Why it matters
test_state.py initial_state() shape is correct; state isn't accidentally shared across runs Foundation β€” everything else depends on state being correct
test_supervisor_routing.py deterministic_route() picks the right agent for every state combination The most critical logic β€” wrong routing means an infinite loop or a crash
test_editor_revision_cap.py Editor can only request one revision; a second REVISION verdict is force-approved Prevents the writer-editor loop from running forever
test_seo_slug.py Messy LLM slug output is cleaned into a safe, URL-friendly string Used directly as a filename downstream β€” edge cases matter
test_writer_revision_reset.py Writer clears stale edited_draft/editor_verdict after a revision Direct regression test for the infinite-loop bug found in production
test_no_infinite_loop.py Full state-machine simulation β€” even if the editor always says REVISION, the run terminates in a bounded number of steps Covers the worst-case scenario end-to-end
PYTHONPATH=. python -m pytest tests/ -v

πŸ› Known Issues Fixed

Documented here deliberately β€” this is the debugging trail, and it's useful context for anyone reading the code (or asking about it in an interview).

  1. Infinite writer-supervisor loop. After the writer revised a draft, the old edited_draft and editor_verdict stayed in state. The supervisor's if not edited_draft: route to editor check never re-triggered, so the editor stopped being called and the graph looped forever. Fix: the writer now clears those fields whenever it produces a revision.
  2. Structured-output calls bypassed the fallback chain. with_structured_output() returned a raw LangChain object, so the retry/fallback logic in .invoke() never applied to it β€” and the supervisor, editor, and SEO agents all use structured output. Fix: with_structured_output() now returns a wrapper that goes through the same fallback chain.
  3. UnicodeEncodeError on Windows. Saving articles with special characters (smart quotes, non-breaking hyphens) crashed on Windows' default cp1252 encoding. Fix: file writes now explicitly specify encoding="utf-8".
  4. Model deprecation (404 errors). Both Groq (llama-3.3-70b-versatile) and Cerebras (llama-3.3-70b) deprecated their free-tier Llama models. Fix: defaults updated to currently-supported free-tier models (openai/gpt-oss-120b on both), and model-not-found errors now also trigger the fallback chain, not just rate limits.
  5. Runtime-cached provider selection. The active provider was read once at import time, so switching providers from the Streamlit sidebar had no effect until restart. Fix: the provider is now read fresh from the environment on every LLM call.

πŸ—ΊοΈ Roadmap

  • Retry with exponential backoff respecting the Retry-After header before falling back to the next provider
  • Streaming token-by-token output in the Streamlit UI instead of waiting for each full agent response
  • Persist run history (topic, provider used, duration) for comparison across runs
  • Swap the content pipeline for a code-review pipeline (same supervisor pattern, different specialists) as a second demo of the architecture's transferability
  • Add a "confidence" field to the editor's verdict so borderline REVISION calls can be surfaced to a human instead of auto-approved
  • Dockerfile for one-command setup

πŸ” Safety & Guardrails

  • Deterministic routing overrides the LLM. The supervisor's LLM call is used for its reasoning/logging, but the actual next-node decision comes from deterministic_route() β€” pure code, no model dependency. A confused or misbehaving LLM cannot send the graph into an invalid state.
  • Hard revision cap. Regardless of what the editor's LLM says, revision_count is capped in code β€” a second REVISION verdict is force-converted to APPROVED.
  • recursion_limit=25 is set on every graph invocation as a last-resort circuit breaker, in case a future bug reintroduces a loop.
  • No silent failures. If all four LLM providers fail, LLMWithFallback raises a RuntimeError with the full chain listed β€” errors are never swallowed.
  • .env is gitignored. API keys are never committed; .env.example ships with placeholders only.

πŸ› οΈ Troubleshooting

Error Cause Fix
ModuleNotFoundError: No module named 'src' Missing PYTHONPATH Prefix commands with PYTHONPATH=. (or $env:PYTHONPATH="." on PowerShell)
429 / rate limit Free-tier quota hit on the active provider Should auto-fallback; if all providers are exhausted, wait a minute or switch manually in the sidebar
RESOURCE_EXHAUSTED with limit: 0 (Gemini) Your API key/project has no free-tier quota granted at all (not a temporary rate limit) Check project eligibility in Google AI Studio; try a fresh project/key
model_not_found / 404 Provider deprecated the configured model Update GROQ_MODEL / CEREBRAS_MODEL / OPENROUTER_MODEL in .env; check the provider's current model list
ValidationError on structured output A weaker model failed to follow the Pydantic schema Try a stronger model for that provider
Tavily search returns nothing Missing/invalid TAVILY_API_KEY Verify the key in .env
UnicodeEncodeError on save (Windows) Already fixed β€” file writes use encoding="utf-8" explicitly Pull the latest main.py / streamlit_app.py
Streamlit sidebar provider switch has no effect Old cached config.py (pre-fix) reads env var once at import Update to the current config.py β€” it reads the provider fresh on every call
RuntimeError: Event loop is closed after clicking Stop Symptom of a long-running/looping script, not the root cause Fix the underlying loop (see Known Issues #1); this is a Streamlit internal cleanup artifact

🎯 Why This Project

"Multi-agent orchestration" is one of the most commonly discussed AI engineering patterns right now. This project builds one from scratch with a working supervisor, a genuine adaptive routing decision, a bounded revision loop, and a production-grade fallback layer across four LLM providers β€” not just a single-provider happy-path demo.

The supervisor pattern shown here is directly transferable: swap the four content specialists for a code-review team, a customer-support triage system, or a research pipeline, and the orchestration logic barely changes.

About

Supervisor-orchestrated multi-agent content pipeline (LangGraph) with 4-way LLM fallback (Gemini/Groq/OpenRouter/Cerebras), bounded revision loop, and a Streamlit UI.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages