AXIOM is a multi-agent system for math, physics, and chemistry. It runs on LangGraph: specialist agents work in parallel, a critic checks the output, and a deterministic solver handles common school problems without calling an LLM.
Single agents doing STEM problems fail in predictable ways: they run everything sequentially even when steps are independent, they don't check their own algebra, and one prompt trying to do symbolic math + code + research + plotting does all of it badly. AXIOM splits the work and audits the output before it reaches you.
- Parallel agent orchestration — Coordinator fans out to 7 specialist agents simultaneously using LangGraph
Send - Multi-layer critic — SymPy re-derivation +
pintunit checks + LLM audit (PASS/REVISE/FAIL) - Deterministic school solver — 40+ formula-based solvers for algebra, kinematics, chemistry (no LLM cost)
- Adaptive memory — Qdrant automatic with FAISS fallback; Redis or in-process LRU cache
- Built-in RAG — PDF ingest + retrieval with local embeddings (
fastembed, no API calls) - Streaming API — SSE progress events via FastAPI + Streamlit frontend
- Observability — Langfuse tracing, configurable hop limits, guardrail evaluations
query → coordinator → [math_physics, reasoning, code, research,
visualization, rag, chemistry] (parallel via Send)
→ critic (PASS / REVISE / FAIL)
→ aggregator → response
REVISE sends it back to the coordinator with suggestions attached. Bounded by MAX_AGENT_HOPS so it can't loop forever.
State (app/graph/state.py) uses reducer-annotated fields (dict_merge, list_merge) so parallel branches can write to shared state without clobbering each other.
Coordinator picks specialists from a fixed catalog and returns Send objects. Also handles a "staged code hop" — if code depends on a math/chemistry result that hasn't landed yet, it gets one extra hop to wait for it.
Critic is two layers:
- Programmatic — re-derives algebra with SymPy, cross-checks numeric vs symbolic answers, runs
pintunit checks, catches degree/radian mix-ups. - LLM audit — correctness, cross-agent contradictions, missing reasoning steps, hallucinated formulas. Payload capped at ~24K chars, RAG context truncated first.
Memory — Qdrant if reachable, FAISS if not, decided automatically. Response cache is Redis or in-process LRU, same fallback logic. Neither is required to run the app.
RAG — a separate Qdrant collection (mac_documents) from conversational memory, so document chunks and chat history never mix. The full pipeline lives in app/rag/:
- Ingest (
app/rag/ingest.py) —pypdfextracts text per page, a sentence/paragraph-aware chunker splits it (~800 chars, ~150 overlap),fastembed(bge-small-en-v1.5, local ONNX — no API calls) embeds it, and chunks are upserted into Qdrant. The collection dimension is derived from the embedder (not a hardcoded constant),doc_id/sourceget KEYWORD payload indexes so per-document filters stay fast, upserts are batched, and re-uploading adoc_idreplaces its old chunks instead of duplicating them. Chunks carrypage,chunk_index,source,title, andingested_atmetadata. - Retrieve (
app/rag/retriever.py) — the score floor (RAG_SCORE_THRESHOLD) is pushed server-side to Qdrant, requestingtop_k × 3candidates so the client-side trim still returns a fulltop_k(RAG_TOP_K); overlapping duplicate chunks are deduped before the LLM sees them. - Chat —
chat_with_documents(used by/chat-doc) skips the multi-agent graph entirely: one retrieval call + one Groq call, both async. The in-graphragagent (app/agents/document_agent.py) reuses the sameretrievewhen a query references an uploaded document.
Embeddings are shared with the conversational memory stores (store_qdrant, faiss_store).
app/
├── api/main.py # FastAPI: /solve, /solve/stream, /upload, /chat-doc
├── ui/streamlit_app.py # form input + SSE polling via st.fragment
├── graph/ # state + graph wiring
├── agents/ # coordinator, specialists, critic, aggregator
├── tools/ # sympy, sandboxed exec, tavily, plotting, chemistry, school solver
├── memory/ # qdrant/faiss store, response cache
├── rag/ # PDF ingest + retrieval
├── evals/ # guardrails + eval harness
└── observability/ # langfuse tracing, v2/v4 tolerant
Qdrant and Redis are optional — falls back to FAISS and in-process LRU if they're not up.
# optional, for persistent memory/cache
docker run -d -p 6333:6333 -v qdrant_data:/qdrant/storage qdrant/qdrant
docker run -d -p 6379:6379 -v redis_data:/data redis:7-alpine
uv sync
cp .env.example .env # fill in GROQ_API_KEY, OPENROUTER_API_KEY at minimum
uv run python run.py # backend, localhost:8000
uv run streamlit run app/ui/streamlit_app.py # frontend, localhost:8501
uv run python -m app.evals.eval_harness # eval harness, optional| Var | Purpose |
|---|---|
TAVILY_API_KEY |
web research agent |
WOLFRAM_ALPHA_APP_ID |
Wolfram tool |
GEMINI_API_KEY |
fallback when Groq is rate-limited |
AXIOM_API_KEY |
if set, gates /solve, /upload, /chat-doc behind X-API-Key |
RAG_SCORE_THRESHOLD |
cosine floor for retrieval, default 0.40 |
RAG_TOP_K |
max chunks passed to the LLM per query, default 5 |
MAX_AGENT_HOPS |
hop budget, default 8 |
See the Mermaid diagram in
assests/for a visual explanation of the agent graph.
| Endpoint | Description |
|---|---|
| POST /solve | Full graph run, synchronous. Simple algebra/school-level questions skip the graph entirely and go through a deterministic solver — these come back with verified: false, verdict: UNAUDITED since nothing was critiqued. |
| POST /solve/stream | Same thing, SSE progress events (agent_start, agent_done, done). |
| POST /upload | PDF ingestion for RAG. Form field doc_id (optional, defaults to filename); re-uploading the same doc_id replaces its chunks. |
| POST /chat-doc | Document Q&A, no multi-agent graph involved. Body {query, doc_id?}; returns grounded answer + ranked sources with page/score. |
| GET /health | Liveness + which memory/cache backends are active. |
The deterministic solver (app/tools/school_solver.py) handles common homework without LLM calls. Physics coverage now matches math and chemistry:
| Domain | Topics |
|---|---|
| Math | percentages, arithmetic, linear/quadratic equations, Pythagoras, area/perimeter/volume, trig values, mean, slope, distance, ratios |
| Physics | kinematics (SUVAT), forces (F=ma, weight, normal), resultant forces (vector addition), momentum (p=mv), elastic & inelastic collisions, energy (KE, PE, work, power), Ohm's law, density/pressure, waves, heat (Q=mcΔT), projectile motion, friction, centripetal force, inclined planes |
| Chemistry | molar mass, moles, molarity, dilution, pH, mass from moles |
Physics problems are solved with full step-by-step working (formula → substitution → result → verification where applicable), the same as math and chemistry.
- Code execution is subprocess isolation with a static blocklist (blocks
os,subprocess,eval,exec, network calls) — not a hardened sandbox. Don't run untrusted code through it. - Hop limit means gnarly multi-revision cases surface as unresolved rather than loop indefinitely. Tune
MAX_AGENT_HOPSif needed. - Critic quality is bottlenecked by whatever LLM is doing the audit.
- No token-level streaming — UI shows agent-level progress only.
Orchestration: LangGraph · Langfuse
LLM Providers: Groq · OpenRouter · Gemini (fallback)
Computation: SymPy · SciPy · NumPy · Matplotlib · Seaborn · Chempy · Pint
Vector Storage: Qdrant · FAISS · fastembed
Web Research: Tavily
Web Framework: FastAPI · Streamlit · SSE
Language: Python 3.11+
MIT — see LICENSE.
multi-agent system, LangGraph, AI agents, STEM problem solver, math solver, physics solver, chemistry solver, agent orchestration, LLM collaboration, FastAPI, Streamlit, RAG, vector search, Qdrant, FAISS, SymPy, Python, machine learning, artificial intelligence, agentic AI, parallel agents, critic agent, school homework solver