Gourab RoyHome

SOFTWARE PROJECT · 2026

A chat agent that classifies, decomposes, and streams its own reasoning

I built a two-part chat application: a FastAPI and LangGraph backend that plans how to answer a question, and a React and TypeScript frontend that renders the answer as it streams in. Questions that mix several intents are split into sub-queries and routed through parallel graph branches instead of one long call.

Backend
FastAPI + LangGraph
Frontend
React + TypeScript + Vite
Model
Anthropic Claude
Transport
NDJSON streaming

THE QUESTION

One question can hide several intents

A user question can be a quick factual lookup, a request for structured analysis, or a mix of both stitched into one sentence. I wanted the backend to recognize that split automatically, answer each part with the node built for it, and still return one coherent response, rather than forcing every request through a single generic answering path.

I also wanted the UI to stay responsive during a multi-step answer, so the client shows progress as the graph works instead of a blank screen until the final text arrives.

METHOD

Classify, decompose, then route

  1. 01

    Classify and decompose

    A query analyzer node classifies the question as insights or analytical, and breaks compound questions into up to three sub-queries.

  2. 02

    Route through the graph

    Single-purpose questions go to one LangGraph node. Mixed questions are routed through parallel branches so each sub-query reaches the node built to answer it.

  3. 03

    Optional web search

    When a request sets web_search=true, a search step runs before the answering node, giving the model outside context for that turn.

  4. 04

    Synthesize

    A summary node assembles the branch outputs into one final response before it is streamed back to the client.

STREAMING AND MEMORY

NDJSON events and compressed history

The chat endpoint returns application/x-ndjson: one JSON object per line, typed as progress, text, error, or final_response. The React frontend consumes this stream incrementally, so partial answer text and status updates appear as the graph produces them rather than all at once.

Conversation history is saved to disk per session and reloaded by conversation ID, which is what makes a session resumable. Once a conversation passes 20 messages, older turns are compressed into a summary and the 10 most recent messages are kept in full.

SCOPE

What this project is, and is not

This is not a retrieval-augmented system

There is no vector database, embedding index, or document chunk retrieval in the current backend. The only path to outside information is the optional web search step, which is scoped per request rather than always on.

REPRODUCE

Running both halves locally

Start the backendpip install -r requirements.txt python run.py

The backend listens on port 8000 by default. Point the frontend's API base URL there and allow its origin under URL_ALLOWED_ORIGINS.