A demonstration of LangGraph's Generative UI capabilities with a DeepAgent that generates CSV and PDF reports. This example shows how to build an AI agent that not only processes data but also renders rich, interactive UI components dynamically.
π Based on: LangChain Generative UI React Documentation
- π CSV Report Generation with interactive table preview
- π PDF Report Generation with inline preview
- π¨ Professional UI with inline styles for consistent rendering
- π Research Subagent pattern - separates data fetching from UI generation
- β‘ GenUI Middleware for automatic UI component rendering
- ποΈ DeepAgent Architecture - supervisor agent coordinating specialized subagents
When you ask: "Generate a CSV report on user analytics"
The agent will:
- Delegate to the research subagent to fetch data
- Generate a CSV report with the data
- Automatically render an interactive table in the UI with download button
The same flow works for PDF reports with inline preview!
This example demonstrates several advanced patterns:
- GenUI Middleware Pattern: Automatically intercepts tool calls and pushes UI messages without cluttering your agent logic
- Subagent Delegation: Main agent delegates data fetching to a specialized subagent, maintaining clean separation of concerns
- Tool Result Integration: UI components read tool execution results via
useStreamContext()metadata, not props - Professional UI Components: Self-contained React components with inline styles for consistent rendering across different frontends
- Base64 Data Transfer: Handles binary data (PDFs) and text data (CSVs) seamlessly through base64 encoding
This pattern can be extended to any tool that generates visual outputs: charts, diagrams, forms, 3D visualizations, etc.
Main Agent (deepagent.py)
βββ Tools: generate_csv_report, generate_pdf_report
βββ Middleware: GenUIMiddleware
βββ Subagents:
βββ Research Subagent (subagents.py)
βββ Tools: get_sales_data, get_user_analytics
Key Pattern: The main agent delegates data fetching to a research subagent, then uses the data to generate reports. The GenUI middleware automatically detects report generation tools and pushes UI messages to render React components in the frontend.
- Python 3.11+
- Node.js 18+ (for UI dependencies)
- LangGraph CLI or LangGraph Studio
- An Anthropic or OpenAI API key
# Using uv (recommended)
uv sync
# Or using pip
pip install -e .yarn install
# or
npm installCreate a .env file with your API keys:
ANTHROPIC_API_KEY=your_key_here
# or
OPENAI_API_KEY=your_key_herelanggraph devThen connect with a frontend like Deep Agents UI or build your own using @langchain/langgraph-sdk.
Try these prompts to see the GenUI in action:
- "Generate a CSV report on user analytics"
- "Create a PDF report for sales data"
- "Show me user analytics for this month in a CSV"
- "Generate a sales report in PDF format"
This example requires a frontend to visualize the GenUI components. You have a few options:
- Deep Agents UI - Pre-built chat interface
- Custom Frontend - Build your own using
@langchain/langgraph-sdkanduseStream()hook
For custom frontends, see the LangChain GenUI React docs.
deepagent-gen-ui/
βββ deepagent.py # Main agent with UI generation tools
βββ subagents.py # Research subagent for data fetching
βββ ui_middleware.py # Middleware for GenUI integration
βββ ui.tsx # React components for CSV/PDF preview
βββ langgraph.json # LangGraph configuration
βββ package.json # JS dependencies (React, Tailwind)
βββ pyproject.toml # Python dependencies
βββ README.md # This file
User asks: "Generate a CSV on user analytics"
The main agent calls the research-specialist subagent to fetch data
The subagent uses get_user_analytics() tool to retrieve mock data
The main agent receives the data and calls generate_csv_report(data=...)
GenUIMiddleware detects the tool call and pushes a UI message with empty props
The CSVPreview component:
- Reads tool result from
useStreamContext() - Parses the base64 CSV data
- Displays an interactive table with download button
- Create the tool in
deepagent.py:
@tool
def generate_excel_report(data: dict, report_title: str = "Report") -> dict:
# Your implementation
return {"data": excel_base64, "filename": filename}- Add to main agent tools:
graph = create_deep_agent(
tools=[generate_csv_report, generate_pdf_report, generate_excel_report],
# ...
)- Update middleware mapping:
genui_middleware = GenUIMiddleware(
tool_to_genui_map={
"generate_csv_report": {"component_name": "csv_preview"},
"generate_pdf_report": {"component_name": "pdf_preview"},
"generate_excel_report": {"component_name": "excel_preview"},
}
)- Create React component in
ui.tsx:
const ExcelPreview = () => {
const context = useStreamContext();
// Your component implementation
};
export default {
csv_preview: CSVPreview,
pdf_preview: PDFPreview,
excel_preview: ExcelPreview,
};The UI components use Tailwind CSS. Customize by modifying classes in ui.tsx:
// Example: Change button color
className="bg-blue-600 hover:bg-blue-700" // Current
className="bg-purple-600 hover:bg-purple-700" // CustomWhen you look at the middleware code, you'll see:
push_ui_message(
component_name,
{}, # <-- Empty props! Why?
metadata={
"tool_call_id": tool_call["id"],
"message_id": last_message.id
},
message=last_message
)Why empty props? Because after_model() runs after the LLM decides to call a tool, but before the tool executes. The tool result doesn't exist yet!
1. User: "Generate CSV report"
β
2. LLM: Decides to call generate_csv_report(data=...)
β
3. Middleware (after_model):
- Detects the tool call
- Pushes UI message: csv_preview with {} empty props
- Includes metadata linking tool_call_id
β
4. Frontend:
- Renders CSVPreview component immediately
- Shows "pending" state (spinner)
β
5. Tool Executes:
- generate_csv_report() returns {"data": "...", "filename": "..."}
β
6. Frontend Updates Automatically:
- useStreamContext() receives tool result via metadata link
- CSVPreview re-renders with actual data
- Shows interactive table with download button
- Immediate Feedback: UI renders instantly when tool is called, showing loading state
- Separation of Concerns: Middleware doesn't need to know about tool results
- Automatic Updates: LangGraph framework handles linking tool results to UI via
tool_call_id - Stateless Middleware: No need to track execution or store results
- Flexible Data Flow: React components parse tool results however they want
The metadata passed to push_ui_message tells LangGraph:
"When the tool call with this ID completes, send its result to this UI component"
The framework handles all the wiring automatically! The React component just reads from useStreamContext() and gets the result when it's ready.
Components don't use props - they read tool results from stream context:
const context = useStreamContext();
const meta = context?.meta as any;
const result = meta?.result; // Tool execution result (arrives after tool runs)
const status = meta?.status; // pending/completed/errorThe component lifecycle:
- Initial render:
status: "pending",result: nullβ Show spinner - Tool completes:
status: "completed",result: {...}β Parse and display data - Tool fails:
status: "error"β Show error message
Tools return base64-encoded data with metadata:
return {
"data": base64_encoded_content,
"filename": "report.csv",
"rows": 5,
"columns": ["date", "users", "signups"]
}- Check
langgraph.jsonhas the UI mapping:
{
"ui": {
"agent": "./ui.tsx"
}
}-
Verify middleware is attached to the main agent (not subagent)
-
Check browser console for errors in React components
The middleware pushes empty props intentionally. Components read data from useStreamContext().meta.result, not from props.
Make sure your frontend (LangGraph Studio or custom UI) processes Tailwind CSS. The components use standard Tailwind classes that should work automatically.
deepagents>=0.2.6- DeepAgent frameworklangchain-openai>=1.0.2- LLM integrationlanggraph-cli[inmem]>=0.4.7- LangGraph CLIreportlab>=4.4.4- PDF generation
react@^18.3.1- UI framework@langchain/langgraph-sdk@^0.1.0- LangGraph React hookstailwindcss@^4.0.0- Styling
This is an example project demonstrating GenUI patterns. Feel free to fork and adapt for your use cases!
MIT