A FastAPI service that takes a natural-language request, autonomously plans
what document it needs to produce, drafts it section by section with an LLM,
self-checks its own draft, and returns a polished .docx.
pip install -r requirements.txt
# Free tier LLM: https://console.groq.com -> create an API key
export GROQ_API_KEY="your_key_here"
uvicorn main:app --reload --port 8000If GROQ_API_KEY is not set, the app runs in mock mode automatically
(LLMClient.mock_mode) — every endpoint still works end-to-end with
deterministic canned content, so the pipeline and doc generation can be
demoed/tested without any credits or network access.
POST /agent
{"request": "Create meeting minutes for our weekly engineering standup covering sprint progress, blockers, and action items"}Returns the agent's plan, the generated section content, its self-check
notes, and a download_url for the .docx.
GET /agent/download/{filename} — fetch the generated document.
GET /health — liveness + whether it's running in mock mode.
Three explicit LLM stages, each independently retryable and inspectable — deliberately not one giant "do everything" prompt:
- Plan (
agent.py: DocumentAgent.plan) — the LLM reads the request and decides what kind of document fits (proposal, meeting minutes, project plan, SOP, etc.) and returns a JSON task list of sections. If the request is ambiguous or has conflicting asks, the planner is instructed to make a reasonable assumption rather than stall, and to add an explicit "Assumptions & Risks" section — this is what makes the agent handle the underspecified test case autonomously instead of asking the user for clarification. - Execute (
DocumentAgent.execute) — one LLM call per planned section, writing concrete content (with plausible mock data where real data wasn't given). - Reflect / self-check (
DocumentAgent.reflect_and_revise) — this is the mandatory engineering improvement. The agent re-reads its own full draft against the original request, using a separate "editor" prompt, and returns{"needs_revision": bool, "notes": "..."}. If revision is flagged, it runs one targeted rewrite pass on the weak section. This is the same reason a human writer reads back their own draft before sending it — it catches thin or off-topic sections that a single-pass generation tends to produce, at the cost of one extra LLM call.
main.py (FastAPI) orchestrates: validate → plan → execute → reflect →
render .docx (doc_generator.py, via python-docx) → return JSON +
download link.
Why reflection over the other options: conversation memory and RAG
don't fit a stateless one-shot document generator; tool calling and
multi-step planning are already implicit in the plan/execute split; retry
logic is included anyway as basic resilience (see llm_client.py) but
doesn't improve output quality, only availability. Reflection is the one
lever that visibly improves the actual document for the harder, ambiguous
test case.
llm_client.py retries transient failures (timeouts / 429 / 5xx) with
exponential backoff, and every stage in agent.py has a fallback so one bad
LLM response (unparsable JSON, a dropped section) degrades gracefully
instead of crashing the whole request.
Standard:
{"request": "Create meeting minutes for our weekly engineering standup covering sprint progress, blockers, and action items"}Complex / ambiguous / conflicting:
{"request": "We need a proposal for a client but I am not fully sure of the budget or timeline yet, they mentioned wanting AI features and also wanting it cheap and delivered fast, figure out something reasonable"}This one has missing info (budget/timeline) and a direct conflict ("cheap" vs "fast" vs "AI features"). The planner is expected to still produce a full plan, make an assumption about scope, and surface the tension explicitly in an "Assumptions & Risks" section rather than fail or ask a follow-up question.