Skip to content
smitdighePublic

About

Autonomous research agent that iteratively searches, reflects, and synthesizes a cited report from a single topic.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

███████╗  █████╗  ████████╗ ██╗  ██╗  ██████╗  ███╗   ███╗
██╔════╝ ██╔══██╗ ╚══██╔══╝ ██║  ██║ ██╔═══██╗ ████╗ ████║
█████╗   ███████║    ██║    ███████║ ██║   ██║ ██╔████╔██║
██╔══╝   ██╔══██║    ██║    ██╔══██║ ██║   ██║ ██║╚██╔╝██║
██║      ██║  ██║    ██║    ██║  ██║ ╚██████╔╝ ██║ ╚═╝ ██║
╚═╝      ╚═╝  ╚═╝    ╚═╝    ╚═╝  ╚═╝  ╚═════╝  ╚═╝     ╚═╝

It doesn't stop at the first search result.

🌐 Live Demo: https://fathom-dev.vercel.app

Fathom is an autonomous research agent built on LangGraph. Give it one topic — it plans sub-questions, searches the web, extracts and reads sources, reflects on whether coverage is sufficient, loops back to research more if it isn't, cross-references claims for contradictions, and writes a cited report. No human in the loop after you hit submit.


🔍 How It Works

topic → Planner (decompose into sub-questions)
      → Search (Tavily, per sub-question)
      → Fetch (httpx + trafilatura, extract page content)
      → Reflect (sufficiency check — LLM verdict)
           ├─ needs_more_research → back to Planner (capped at max_iterations)
           └─ sufficient          → Cross-reference
      → Cross-reference (dedupe claims, flag contradictions, attach sources)
      → Writer (synthesize report, sections cite real source URLs)
      → final report (streamed live via SSE)

Every node's output is a validated Pydantic model — no prompt-hacked JSON. The graph is a real state machine (LangGraph StateGraph), not a linear script, and the reflect→planner loop is hard-capped (1–3 iterations) so it can't spiral into unbounded cost.


✨ Features

🔁

Self-Correcting Loop
Reflects on its own findings and re-plans follow-up sub-questions when coverage is thin

📡

Live Agent Trace
SSE-streamed graph execution — watch each node fire in real time, not just a spinner

🧩

Structured Everywhere
Pydantic v2 schema at every node boundary — planner/reflect/writer can't return malformed output

⚖️

Contradiction Detection
Cross-reference node dedupes claims across sources and flags where they disagree

🔗

Source-Grounded Citations
Report sections cite real, fetched source URLs — never model-invented references

📏

Depth-Themed UI
Live "fathom line" gauge tracks iteration depth as the agent sounds for a sufficient answer

🛠️ Tech Stack

Layer Technology Purpose
⚛️ Frontend React + Vite + TypeScript Core UI framework and bundler
🎨 Styling Tailwind CSS Depth-gauge, live graph trace, reflect log UI
🗄️ Backend FastAPI (async) + Uvicorn SSE-streamed API driving the graph
🕸️ Orchestration LangGraph Planner → Search → Fetch → Reflect → Cross-reference → Writer state machine
🤖 LLM Cerebras (gpt-oss-120b) Structured tool-calling at every node
🔎 Search Tavily API Per-sub-question web search
📄 Extraction httpx + trafilatura Fetch + clean page content from source URLs
✅ Validation Pydantic v2 Schema enforcement at every node boundary
☁️ Hosting Render/Railway/Fly.io (API) + Vercel (Web) Deployment

📁 Project Structure

Fathom/
├── backend/
│   ├── app/
│   │   ├── api/
│   │   │   └── routes.py           # POST /research (SSE)
│   │   ├── graph/
│   │   │   ├── state.py            # GraphState + create_initial_state
│   │   │   ├── edges.py            # reflect → planner | cross_reference
│   │   │   ├── build.py            # compiled StateGraph singleton
│   │   │   └── nodes/
│   │   │       ├── planner.py
│   │   │       ├── search.py
│   │   │       ├── fetch.py
│   │   │       ├── reflect.py
│   │   │       ├── cross_reference.py
│   │   │       └── writer.py
│   │   ├── schemas/
│   │   │   ├── planner.py          # PlannerOutput
│   │   │   ├── reflect.py          # ReflectVerdict
│   │   │   ├── source.py           # Source, Claim, Contradiction
│   │   │   └── report.py           # WriterOutput, FinalReport
│   │   ├── services/
│   │   │   ├── llm.py              # Cerebras client, tool-calling helper
│   │   │   ├── search.py           # Tavily wrapper
│   │   │   └── extract.py          # httpx + trafilatura fetch
│   │   ├── streaming/
│   │   │   └── sse.py              # astream_events → SSE event translation
│   │   ├── config.py
│   │   └── main.py
│   ├── tests/
│   ├── .env.example
│   └── pyproject.toml
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   ├── LandingView.tsx
│   │   │   ├── TraceView.tsx
│   │   │   ├── DepthGauge.tsx
│   │   │   ├── NodeGraph.tsx
│   │   │   ├── VerdictPanel.tsx
│   │   │   ├── ReportView.tsx
│   │   │   └── ErrorState.tsx
│   │   ├── useResearch.ts          # SSE consumption hook
│   │   ├── types.ts                # shared SSE payload types
│   │   └── App.tsx
│   ├── .env.example
│   └── vite.config.ts
└── README.md

⚙️ Getting Started

Prerequisites

1. Clone the Repository

git clone https://github.com/YOUR_USERNAME/fathom.git
cd fathom

2. Setup Backend

cd backend
python -m venv venv
venv\Scripts\activate      # Windows
pip install -e .

Create a .env file inside backend/ (use .env.example as a reference):

CEREBRAS_API_KEY=your_cerebras_api_key
TAVILY_API_KEY=your_tavily_api_key
DATABASE_URL=
MAX_ITERATIONS=3
ENVIRONMENT=development

Then start the backend:

uvicorn app.main:app --reload

API running at http://localhost:8000 — Swagger docs at http://localhost:8000/docs

3. Setup Frontend

cd frontend
npm install

Create a .env file inside frontend/:

VITE_API_URL=http://localhost:8000
npm run dev

Frontend running at http://localhost:5173 ⚠️ Both servers need to be running simultaneously.


🔌 API

POST /research

// request
{ "topic": "string", "max_iterations": 3 }

// response: text/event-stream (SSE)
event: node_start       data: {"node": "planner"}
event: node_end         data: {"node": "planner", "output": {...}}
event: reflect_verdict  data: {"verdict": "sufficient" | "needs_more_research", "reasoning": "string", "gaps": [], "follow_up_questions": []}
event: report_ready     data: {"final_report": "<JSON-encoded FinalReport>"}
event: error            data: {"message": "string"}

GET /health — liveness check. Interactive docs at /docs when running locally.


⚠️ Known Limitations

  • Single topic in, single report out — no multi-turn chat, no follow-up questions after the report
  • No persistence — reports aren't saved between runs (v2: Postgres for run history)
  • No auth
  • Free-tier LLM rate limits (per provider) can interrupt a run mid-loop on longer topics — no automatic provider fallback yet
  • Fetch step silently skips sources that block scraping (403s) rather than retrying with alternate methods

🔮 Future Improvements

  • Persistence: Postgres-backed run history / past reports dashboard
  • Multi-provider fallback: automatic failover across LLM providers on rate limit
  • Redis caching: avoid re-fetching the same URL across reflect loops
  • PDF export: download the final report as a formatted document

About

Autonomous research agent that iteratively searches, reflects, and synthesizes a cited report from a single topic.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages