A production-grade multi-agent AI system that researches, writes, directs, and evaluates video scripts in real time — built with LangGraph, FastAPI, and SSE streaming.
CineAgent takes a simple request like "write a 30-second TikTok about energy drinks" and runs it through a full multi-agent pipeline:
User Request
↓
🔍 Research Agent → searches the web for real 2026 facts (Tavily)
↓
✍️ Script Agent → writes a structured, formatted screenplay
↓
🎨 Creative Director → reviews tone, hook strength, platform fit
↓
🏷️ Title Agent → generates 3 viral-ready titles
↓
📊 Eval Engine → scores trajectory, tool accuracy, output quality
All of this streams live to the browser — token by token, agent by agent.
| Layer | Technology | Purpose |
|---|---|---|
| Backend | FastAPI + Uvicorn | Async API server |
| Agent Framework | LangGraph | Multi-agent state graph |
| LLM | LangChain + OpenAI (gpt-4o-mini) | Language model calls |
| Web Search | Tavily API | Real-time internet research |
| Short-Term Memory | LangChain message compression | Context window management |
| Long-Term Memory | ChromaDB + OpenAI Embeddings | Persistent vector memory |
| Streaming | SSE (Server-Sent Events) + sse-starlette | Real-time token streaming |
| Frontend | Vanilla HTML/CSS/JS | AG-UI protocol client |
| Evals | Custom eval engine (LLM-as-judge) | Trajectory + quality scoring |
cineagent/
├── backend/
│ ├── main.py # FastAPI server + SSE streaming endpoint
│ ├── agents/
│ │ └── all_agents.py # 4 specialized agents + supervisor
│ ├── graph/
│ │ ├── state.py # LangGraph TypedDict state
│ │ └── workflow.py # Graph nodes, edges, conditional routing
│ ├── tools/
│ │ └── all_tools.py # search_web + format_script tools
│ ├── memory/
│ │ └── memory_manager.py # Short-term compression + ChromaDB
│ └── evals/
│ └── eval_engine.py # Trajectory + tool accuracy + LLM-as-judge
├── frontend/
│ └── index.html # AG-UI SSE streaming frontend
├── data/
│ ├── chroma_db/ # Persistent vector memory (auto-created)
│ └── evals/ # Benchmark results (auto-created)
├── requirements.txt
├── .env.example
└── README.md
git clone https://github.com/yourname/cineagent.git
cd cineagentpython -m venv venv
source venv/bin/activate # Mac/Linux
# venv\Scripts\activate # Windowspip install -r requirements.txtcp .env.example .envOpen .env and fill in your keys:
OPENAI_API_KEY=sk-... # platform.openai.com/api-keys
OPENAI_MODEL=gpt-4o-mini
TAVILY_API_KEY=tvly-... # tavily.com (free tier: 1000 searches/month)
CHROMA_PERSIST_DIR=./data/chroma_db
MAX_CONTEXT_TOKENS=8000mkdir -p data/chroma_db data/evals data/checkpointsuvicorn backend.main:app --reload --port 8000You should see:
INFO: Uvicorn running on http://0.0.0.0:8000
INFO: Application startup complete.
Open a second terminal and run:
python -m http.server 3000 --directory frontendVisit http://localhost:3000 in your browser.
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Health check |
GET |
/docs |
Auto-generated Swagger UI |
POST |
/generate |
Full pipeline — returns complete result |
GET |
/generate/stream |
SSE streaming — real-time token output |
GET |
/memory/search?query=... |
Search long-term memory |
POST |
/memory/save |
Save content to long-term memory |
GET |
/graph/info |
View graph structure |
curl -X POST http://localhost:8000/generate \
-H "Content-Type: application/json" \
-d '{"request": "write a 30 second TikTok about coffee", "session_id": "test001"}'curl -N "http://localhost:8000/generate/stream?request=write+a+script+about+AI"Run these from the Python shell to verify each component works:
pythonfrom backend.tools.all_tools import search_web, format_script
# Real web search
result = search_web.invoke({"query": "AI trends 2026"})
print(result[:200])
# Script formatting
result = format_script.invoke({
"raw_script": "Sleep is essential. It helps your brain recharge.",
"platform": "TikTok",
"duration_seconds": 30
})
print(result)from backend.memory.memory_manager import long_term_memory, compress_messages
from langchain_core.messages import HumanMessage, AIMessage
# Long-term memory
long_term_memory.save("User prefers funny scripts", {"type": "preference"})
results = long_term_memory.search("funny video content", k=3)
for r in results:
print(f"Similarity: {r['similarity']} | {r['content'][:80]}")from backend.graph.workflow import cineagent_graph
from backend.graph.state import create_initial_state
initial_state = create_initial_state("write a 30 second TikTok about meditation")
config = {"configurable": {"thread_id": "test_001"}}
final_state = cineagent_graph.invoke(initial_state, config=config)
print("Script:", final_state["final_script"][:200])
print("Titles:", final_state["titles"])from backend.evals.eval_engine import (
evaluate_trajectory,
evaluate_tool_accuracy,
evaluate_output_quality
)
traj = evaluate_trajectory(final_state)
tools = evaluate_tool_accuracy(final_state)
quality = evaluate_output_quality(final_state)
print(f"Trajectory: {traj['score']:.0%}")
print(f"Tool Accuracy: {tools['accuracy']:.0%}")
print(f"Quality Score: {quality['scores']['overall']}/10")
print(f"Feedback: {quality['feedback']}")START → script_agent → should_use_tools?
↓ YES ↓ NO
tool_runner creative_director
↓ ↓
script_agent title_agent
↓
END
LLM decides which tools to call → your code executes them → results sent back to LLM → loop until done.
Short-term → LangChain messages (in-context, auto-compressed at 75% capacity)
Long-term → ChromaDB vectors (persisted to disk, semantic search)
Trajectory Eval → did agent follow correct steps?
Tool Accuracy → were tool arguments valid?
Output Quality → LLM-as-judge scores 4 dimensions (0-10)
run_started → agent_start → token (×N) → tool_call → tool_result → run_finished
| Service | Where to get | Cost |
|---|---|---|
| OpenAI | platform.openai.com | ~$0.01-0.05 per script |
| Tavily | tavily.com | Free (1000 searches/month) |
- Never commit your
.envfile — it contains secret API keys - The
data/directory is excluded from Git (contains user data) - Always run commands from the
cineagent/root directory - Use
python -m uvicornnotpython backend/main.pydirectly
| File | Concept |
|---|---|
graph/state.py |
LangGraph TypedDict state, operator.add merging |
graph/workflow.py |
Nodes, edges, conditional routing, astream_events |
tools/all_tools.py |
@tool decorator, docstring-as-description pattern |
memory/memory_manager.py |
Token counting, compression, ChromaDB embeddings |
evals/eval_engine.py |
Trajectory eval, LLM-as-judge, benchmarking |
backend/main.py |
FastAPI, SSE, AG-UI event protocol |
frontend/index.html |
EventSource API, real-time UI updates |
Browser (EventSource)
↕ SSE / HTTP
FastAPI (main.py)
↕
LangGraph Workflow
↕
┌────────────────────────────────┐
│ script_agent │ ←→ search_web (Tavily)
│ tool_runner │ ←→ format_script
│ creative_director │
│ title_agent │
└────────────────────────────────┘
↕ ↕
Short-term Memory Long-term Memory
(compression) (ChromaDB vectors)
↕
Eval Engine
(trajectory + tool accuracy + LLM-as-judge)
Jibran Nakhwa — Built as a portfolio project demonstrating production-grade agentic AI engineering skills.
Covers: LangGraph, FastAPI, Tool Calling, Multi-Agent Orchestration, Memory Management, Evals, SSE Streaming, AG-UI Protocol
Built step by step, concept by concept. Every line understood.