An agentic AI platform that weaves memory, tools, and autonomous reasoning into one intelligent system.
AgentWeave is a production-ready, full-stack agentic AI platform built on LangGraph and FastAPI. It exposes a streaming-capable REST API consumed by a Streamlit frontend, and runs a ReAct-style reasoning agent backed by Groq-hosted LLMs. A built-in short-term memory (STM) layer prevents unbounded token growth by maintaining a rolling summary of conversation history, keeping costs predictable across long sessions.
- Features
- Architecture
- Project Structure
- Tech Stack
- Prerequisites
- Installation
- Configuration
- Running the Application
- API Reference
- Short-Term Memory (STM)
- Development
- Changelog
- License
- ReAct Agent — LangGraph-powered reasoning loop that selects and executes tools before answering
- Streaming responses — Server-Sent Events (SSE) endpoint for real-time token-by-token output
- Short-term memory — Rolling summary mechanism that keeps the live context window bounded, preventing token runaway on long conversations
- Multi-session support — Each user session gets an isolated thread managed by LangGraph's in-memory checkpointer
- Session history — Retrieve full chat history for any session via a dedicated endpoint
- Built-in tools — Web search (DuckDuckGo) and arithmetic operations (add, multiply, divide)
- Streamlit UI — Chat interface with streaming, multi-session sidebar, and new/delete chat controls
- Pydantic v2 config — Environment variables loaded and validated at startup; no runtime surprises
- Pre-commit quality gates — Black formatting and Pylint linting enforced on every commit
┌──────────────────────────────────────────────────┐
│ Streamlit Frontend │
│ (frontend.py — streaming SSE consumer) │
└───────────────────────┬──────────────────────────┘
│ HTTP / SSE
▼
┌──────────────────────────────────────────────────┐
│ FastAPI Backend (app/) │
│ │
│ POST /api/v1/chat → full response │
│ POST /api/v1/chat/stream → SSE stream │
│ POST /api/v1/chat/history → session history │
│ │
│ api/helper.py → ai_layer/agent.py (chatbot) │
└───────────────────────┬──────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ LangGraph ReAct Agent │
│ │
│ START → reasoner ──(tools_condition)──► tools │
│ ▲ │ │
│ └───────────────────────────┘ │
│ │
│ reasoner node │
│ ├─ STM check (stm.py) │
│ │ ├─ summarize_and_trim() if threshold hit │
│ │ └─ get_llm_context() always │
│ └─ model_with_tools.invoke([sys_msg] + context) │
│ │
│ tools node (ToolNode) │
│ ├─ add / multiply / divide │
│ └─ DuckDuckGoSearchRun │
│ │
│ State (GraphState) │
│ ├─ messages (add_messages reducer) │
│ ├─ summary (rolling STM summary) │
│ ├─ summarize_at (next trigger count) │
│ └─ context_start (live window pointer) │
│ │
│ Checkpointer: InMemorySaver (per thread_id) │
└──────────────────────────────────────────────────┘
│
▼
Groq API (qwen/qwen3.6-27b)
AgentWeave/
├── frontend.py # Streamlit chat UI
├── pyproject.toml # Project metadata, dependencies, tool config
├── .pre-commit-config.yaml # Black + Pylint + uv hooks
├── .env # API keys (not committed)
├── requirements.txt # Generated by uv-export hook
│
└── app/
├── main.py # FastAPI app entry point
│
├── api/
│ ├── chat.py # Route handlers (/chat, /chat/stream, /chat/history)
│ ├── helper.py # chatbot invocation, SSE streaming, history retrieval
│ └── schemas.py # Pydantic request/response models
│
├── ai_layer/
│ ├── agent.py # LangGraph graph definition & compilation
│ ├── graph_states.py # GraphState TypedDict (messages + STM fields)
│ ├── nodes.py # reasoner node (STM-aware) + chat_with_model
│ ├── stm.py # Short-term memory helpers & constants
│ ├── prompts.py # System message factory
│ ├── model.py # ChatGroq initialisation
│ └── tools.py # add, multiply, divide, DuckDuckGoSearchRun
│
└── utils/
├── config.py # Secrets (pydantic-settings) + Config dataclass
└── version.py # __version__
| Layer | Library / Tool | Version |
|---|---|---|
| LLM provider | langchain-groq |
≥ 1.1.2 |
| Agent orchestration | langgraph |
≥ 1.2.0 |
| LLM abstractions | langchain + langchain-community |
≥ 1.3.0 |
| API framework | fastapi[standard] |
≥ 0.136.1 |
| Frontend | streamlit |
≥ 1.57.0 |
| Config / validation | pydantic + pydantic-settings |
≥ 2.13.4 |
| Web search tool | ddgs (DuckDuckGo) |
≥ 9.14.4 |
| Package manager | uv |
— |
| Code formatter | black |
≥ 26.3.1 |
| Linter | pylint |
≥ 4.0.5 |
| Python | — | ≥ 3.12 |
- Python 3.12 (see
.python-version) - uv package manager
- A Groq API key
- A Google Gemini API key (reserved for future use)
# 1. Clone the repository
git clone <repo-url>
cd AgentWeave
# 2. Install dependencies (uv creates and manages the venv automatically)
uv sync
# 3. Install pre-commit hooks
uv run pre-commit installCreate a .env file in the project root:
GROQ_API_KEY=gsk_...
GEMINI_API_KEY=AIza...The application model and other runtime settings are configured via dataclasses in app/utils/config.py:
| Setting | Default | Description |
|---|---|---|
model |
qwen/qwen3.6-27b |
Groq-hosted model identifier |
temperature |
0.7 |
Sampling temperature |
max_tokens |
1000 |
Max tokens per response |
Both processes must run simultaneously. Open two terminals.
Terminal 1 — FastAPI backend
uv run uvicorn app.main:app --reloadThe API will be available at http://localhost:8000.
Terminal 2 — Streamlit frontend
uv run streamlit run frontend.pyThe UI will open automatically at http://localhost:8501.
All endpoints are prefixed with /api/v1.
Send a message and receive the full response.
Request body
{
"message": "What is 12 multiplied by 7?",
"session_id": "550e8400-e29b-41d4-a716-446655440000"
}Response
{
"response": [
{ "role": "user", "content": "What is 12 multiplied by 7?" },
{ "role": "assistant", "content": "12 multiplied by 7 is 84." }
]
}Stream the assistant response as Server-Sent Events.
Request body — same as /chat
Response — text/event-stream
data: 12 multiplied
data: by 7 is 84.
data: [DONE]
Each data: line contains a token chunk. Newlines within a chunk are escaped as \n. The stream ends with data: [DONE].
Retrieve the full message history for an existing session.
Request body
{ "session_id": "550e8400-e29b-41d4-a716-446655440000" }Response
{
"response": [
{ "role": "user", "content": "..." },
{ "role": "assistant", "content": "..." }
]
}Long conversations accumulate tokens quickly. Without intervention, every call eventually exceeds the model's context limit and costs grow linearly with session length.
AgentWeave solves this with a rolling summary strategy implemented in app/ai_layer/stm.py.
Turn 1–9 All messages accumulate normally
LLM sees: [sys_msg] + [msg_0 … msg_N]
Turn 10 THRESHOLD hit → summarise_and_trim() runs
├─ Slice msg[0 : N-5] is summarised by the plain model
├─ context_start advances to N-5
└─ summarize_at advances to N + SUMMARIZE_STEP
Turn 16+ LLM sees: [sys_msg] + [summary_sys_msg] + [msg_{N-5} … msg_N+k]
The LLM always receives two system messages:
- The fixed role instruction (
You are a helpful assistant…) - A
SystemMessagecontaining the rolling summary — injected byget_llm_context()only when a summary exists
| Constant | Value | Meaning |
|---|---|---|
THRESHOLD |
10 |
Total messages before first summarisation |
KEEP_RECENT |
5 |
Messages kept in the live context window |
SUMMARIZE_STEP |
10 |
Messages until the next summarisation pass |
These are tunable. Lower THRESHOLD / KEEP_RECENT saves more tokens; higher values preserve more verbatim context.
The three STM fields are NotRequired in GraphState, so a brand-new session can be invoked without pre-populating them. Every helper falls back to safe defaults ("" / 0) when they are absent.
Pre-commit hooks run automatically on git commit. To run them manually against all files:
uv run pre-commit run --all-filesHooks configured:
| Hook | Tool | What it does |
|---|---|---|
black |
Black 26+ | Auto-formats Python to line length 120 |
pylint |
Pylint 4+ | Static analysis and style enforcement |
uv-lock |
uv | Keeps uv.lock in sync |
uv-export |
uv | Regenerates requirements.txt |
Note: Multi-line LLM prompt strings in
stm.pyare wrapped with# fmt: off/# fmt: onto prevent Black from reformatting them.
- Define the tool function with a
@tooldecorator inapp/ai_layer/tools.py - Add it to
tools_list - No other changes required —
ToolNodeandmodel.bind_tools()pick it up automatically
Update model in app/utils/config.py. Any model available on Groq that supports tool-calling will work without further changes.
See CHANGELOG.md for a full history of changes following Keep a Changelog and Semantic Versioning.
MIT © 2026 Muhammad Abdullah — see LICENSE for details.