A stateful conversational AI agent built with LangGraph, Gemini 1.5 Flash, ChromaDB, and sentence-transformers. The agent qualifies leads from social media DMs through intelligent conversation, RAG-powered product Q&A, and progressive data collection.
Total cost: $0 — all components are free or open-source.
| Component | Technology |
|---|---|
| LLM | Google Gemini 1.5 Flash (free tier via Google AI Studio) |
| Agent Framework | LangGraph (LangChain) |
| Embeddings | sentence-transformers (all-MiniLM-L6-v2, runs on CPU) |
| Vector Store | ChromaDB (local persistent storage) |
| State Persistence | LangGraph SqliteSaver |
| Runtime | Python 3.11 |
# 1. Clone the repository
git clone https://github.com/your-username/autostream-agent.git
cd autostream-agent
# 2. Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
# 3. Install dependencies
pip install -r requirements.txt
# 4. Set up your API key
cp .env.example .env
# Open .env and paste your Google AI Studio API key
# Get a free key at: https://aistudio.google.com
# 5. Run the agent
python main.pyautostream-agent/
├── agent/
│ ├── graph.py # LangGraph StateGraph definition + conditional edges
│ ├── nodes.py # All 7 node functions (input, intent, greeting, rag, responder, qualifier, tool exec)
│ ├── state.py # GraphState TypedDict — 9 typed fields
│ ├── intent.py # Gemini-backed intent classifier with JSON parsing
│ └── tools.py # mock_lead_capture() + leads.json persistence
│
├── rag/
│ ├── indexer.py # One-time embedding + ChromaDB ingestion
│ ├── retriever.py # Query embedding + cosine similarity search
│ └── knowledge_base.json # AutoStream product knowledge (13 chunks)
│
├── data/ # Auto-created at runtime
│ ├── checkpoints.db # LangGraph SQLite checkpointer
│ ├── chroma_store/ # ChromaDB persistent embeddings
│ └── leads.json # Captured lead records
│
├── main.py # Entry point — terminal REPL loop
├── webhook.py # FastAPI server for WhatsApp webhook integration
├── config.py # API keys, model names, thresholds, paths
├── requirements.txt
├── Procfile # Railway/Render deployment
├── .env.example
└── README.md
Why LangGraph over AutoGen?
LangGraph models the agent as an explicit directed state machine where each node handles one responsibility — intent classification, RAG retrieval, lead qualification, or tool execution. Edges between nodes are conditional, meaning the graph routes conversations differently based on classified intent and collected state. This makes control flow fully deterministic and auditable. AutoGen is designed for multi-agent role-play scenarios, which adds unnecessary complexity for a single-agent conversational pipeline. LangGraph's typed StateGraph with TypedDict enforces structure at every node boundary, preventing the kind of silent state corruption that happens with free-form dictionaries.
How is state managed?
The agent uses a GraphState TypedDict with 9 fields: messages (full conversation history), intent, rag_context, lead_name, lead_email, lead_platform, lead_captured, missing_fields, and turn_count. Every node reads from and writes to this single object. In-session, a sliding window of the last 10 messages is sent to the LLM to manage context length. Cross-session, LangGraph's SqliteSaver checkpointer persists the full GraphState to a local SQLite file (checkpoints.db), keyed by thread_id. Resuming a conversation with the same thread_id restores all prior state — including partially collected lead fields — without any external database.
- Greeting — User says "Hi" →
intent_classifierreturnsGREETING→ agent introduces AutoStream. - Product Inquiry — User asks about pricing/features →
intent_classifierreturnsINQUIRY→rag_noderetrieves relevant chunks from ChromaDB →responder_nodegenerates a grounded answer. - Intent Shift — User says "I want to try the Pro plan for my YouTube channel" →
intent_classifierreturnsHIGH_INTENT_LEAD→lead_qualifier_nodeactivates. - Lead Qualification — Agent progressively collects name → email → platform (one field per turn). Gemini extraction sub-prompts parse structured values from free-form replies.
- Tool Execution — All 3 fields collected →
missing_fields == []→tool_exec_nodefiresmock_lead_capture(name, email, platform)→ lead saved toleads.json. - Post-Capture —
lead_captured = Trueprevents re-triggering. Agent continues answering questions in inquiry mode.
- Hard gate on tool execution:
mock_lead_capture()only fires whenmissing_fields == []. The tool cannot be triggered prematurely by any code path. - Confidence threshold: Intent classifications below 0.6 confidence default to
INQUIRY, preventing false high-intent routing. - Sliding window: Last 10 messages sent to LLM to stay within free-tier rate limits while retaining full context.
- Cross-session memory:
SqliteSaverpersists state perthread_id, enabling multi-session conversations. - Post-capture lockout: Once
lead_captured == True, subsequentHIGH_INTENT_LEADsignals are downgraded toINQUIRY.
To deploy this agent on WhatsApp, the architecture uses the Meta WhatsApp Business Cloud API with a FastAPI webhook server (webhook.py).
How it works:
The Meta Cloud API sends incoming WhatsApp messages as HTTP POST requests to a registered webhook URL. The FastAPI server receives these payloads, extracts the sender's phone number and message text, and uses the phone number as the LangGraph thread_id. This means each WhatsApp user gets their own persistent conversation state via SqliteSaver — the same checkpointing that powers the terminal REPL.
The server invokes the compiled LangGraph agent with the user's message, retrieves the AI reply from the final state, and sends it back to the user via the Meta Send Message API (POST https://graph.facebook.com/v18.0/{phone_number_id}/messages).
Implementation steps:
- Register a Meta Developer App and enable the WhatsApp Business product.
- Configure the webhook URL pointing to the FastAPI server (hosted on Railway or Render free tier).
- Implement
GET /webhookfor Meta's verification challenge (VERIFY_TOKENhandshake). - Implement
POST /webhookto parse incoming messages, invoke the LangGraph agent, and POST the reply back. - Use the sender's phone number as
thread_idfor per-user session persistence. - Handle delivery status webhooks (
delivered,read) for engagement analytics.
The webhook.py file in this repository implements all of the above and is deployment-ready.