Full Application Example¶
Complete FastAPI app with WebSocket streaming, Docker, uploads, and more.
Source Code¶
examples/full_app/
This is a comprehensive example demonstrating all pydantic-deep features in a production-like setup.
Features¶
| Feature | Description |
|---|---|
| DockerSandbox | Isolated file operations and code execution |
| WebSocket Streaming | Real-time events and text deltas |
| Human-in-the-Loop | Approval UI for execute commands |
| File Uploads | PDF/CSV processing |
| Skills | Data analysis skill |
| Subagents | Joke generator for entertainment |
| Multi-User | SessionManager for isolated sessions |
| Custom Tools | Mock GitHub integration |
Project Structure¶
Text Only
examples/full_app/
├── app.py # FastAPI application
├── github_tools.py # Custom GitHub tools (mock)
├── skills/
│ └── data-analysis/
│ └── SKILL.md # Data analysis skill
├── static/
│ ├── index.html # Web UI
│ └── styles.css # Styling
└── workspace/ # Persistent storage
Running the Example¶
Bash
# Navigate to the example directory
cd examples/full_app
# Install dependencies (if needed)
uv pip install fastapi uvicorn websockets python-multipart
# Start Docker (required for sandbox)
docker pull python:3.12-slim
# Run the application
uvicorn app:app --reload --port 8080
# Open in browser
open http://localhost:8080
Architecture Overview¶
Text Only
┌─────────────────────────────────────────────────────────────┐
│ Web Browser │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Chat UI │ │ File List │ │ TODO List │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ FastAPI Backend │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ WebSocket /ws/chat │ │
│ │ - Streaming responses │ │
│ │ - Tool call events │ │
│ │ - Approval requests │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ REST Endpoints │ │
│ │ - POST /upload (file uploads) │ │
│ │ - GET /files (list files) │ │
│ │ - GET /todos (todo list) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ pydantic-deep Agent │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ TodoToolset │ │ FilesysToolst│ │ SubAgentTlst │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ SkillsToolst │ │ GitHub Tools │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ SessionManager │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ User Session 1 → DockerSandbox (Container A) │ │
│ │ User Session 2 → DockerSandbox (Container B) │ │
│ │ User Session 3 → DockerSandbox (Container C) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Key Implementation Details¶
Agent Creation¶
Python
def create_agent() -> Agent[DeepAgentDeps, str]:
"""Create the shared agent (stateless - can be used by all sessions)."""
github_toolset = create_github_toolset(id="github")
return create_deep_agent(
model="openai:gpt-4.1",
instructions=MAIN_INSTRUCTIONS,
backend=None, # Backend comes from deps at runtime
# Toolsets
include_todo=True,
include_filesystem=True,
include_subagents=True,
include_skills=True,
include_execute=True,
toolsets=[github_toolset],
# Subagents
subagents=SUBAGENT_CONFIGS,
include_general_purpose_subagent=False,
# Skills
skill_directories=[{"path": str(SKILLS_DIR), "recursive": True}],
# Human-in-the-loop
interrupt_on={"execute": True},
)
Session Management¶
Python
@asynccontextmanager
async def lifespan(app: FastAPI):
global agent, session_manager
# Create shared agent
agent = create_agent()
# Create session manager for Docker containers
session_manager = SessionManager(
default_runtime=None,
default_idle_timeout=3600,
workspace_root="./workspaces", # Persistent storage for user files
)
session_manager.start_cleanup_loop(interval=300)
yield
# Cleanup
await session_manager.shutdown()
With workspace_root, each session gets persistent storage at ./workspaces/{session_id}/workspace/, so user files survive container restarts and app reboots.
WebSocket Streaming¶
Python
@app.websocket("/ws/chat")
async def websocket_chat(websocket: WebSocket):
await websocket.accept()
async with agent.iter(message, deps=session.deps) as run:
async for node in run:
if Agent.is_model_request_node(node):
# Stream text deltas
async with node.stream(run.ctx) as request_stream:
async for event in request_stream:
if isinstance(event, PartDeltaEvent):
await websocket.send_json({
"type": "text_delta",
"content": event.delta.content_delta,
})
elif Agent.is_call_tools_node(node):
# Stream tool events
async with node.stream(run.ctx) as handle_stream:
async for event in handle_stream:
if isinstance(event, FunctionToolCallEvent):
await websocket.send_json({
"type": "tool_start",
"tool_name": event.part.tool_name,
})
Human-in-the-Loop¶
Python
if isinstance(result.output, DeferredToolRequests):
# Send approval request to frontend
await websocket.send_json({
"type": "approval_required",
"requests": [
{
"tool_call_id": call.tool_call_id,
"tool_name": call.tool_name,
"args": call.args,
}
for call in result.output.approvals
],
})
# Handle approval response
approval_response = message_data.get("approval")
if approval_response:
approvals = {
tool_id: ToolApproved() if approved else ToolDenied()
for tool_id, approved in approval_response.items()
}
await agent.run(
None,
deps=session.deps,
message_history=session.message_history,
deferred_tool_results=DeferredToolResults(approvals=approvals),
)
File Uploads¶
Python
@app.post("/upload")
async def upload_file(
file: UploadFile = File(...),
session_id: str = Query(""),
):
session = await get_or_create_session(session_id)
content = await file.read()
# Upload to session's Docker container
path = session.deps.upload_file(file.filename, content)
return {"path": path, "size": len(content)}
WebSocket Protocol¶
Client → Server¶
JSON
// Send message
{"session_id": "abc123", "message": "Create a script"}
// Send approval
{"approval": {"tool_call_id_1": true, "tool_call_id_2": false}}
Server → Client¶
JSON
// Text streaming
{"type": "text_delta", "content": "I'll create"}
// Tool call
{"type": "tool_start", "tool_name": "write_file", "args": {...}}
// Tool result
{"type": "tool_output", "tool_name": "write_file", "output": "Created..."}
// Approval needed
{"type": "approval_required", "requests": [...]}
// TODO update
{"type": "todos_update", "todos": [...]}
// Complete
{"type": "done"}
REST Endpoints¶
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Serve web UI |
/ws/chat |
WebSocket | Chat with streaming |
/upload |
POST | Upload file |
/files |
GET | List files |
/files/content/{path} |
GET | Get file content |
/files/binary/{path} |
GET | Get binary file |
/todos |
GET | Get TODO list |
/reset |
POST | Reset session |
/session/new |
POST | Create new session |
/sessions |
GET | List sessions |
/health |
GET | Health check |
Custom GitHub Tools¶
The example includes mock GitHub tools:
Python
def create_github_toolset(id: str) -> FunctionToolset:
toolset = FunctionToolset()
@toolset.tool
async def github_list_repos(ctx: RunContext[DeepAgentDeps]) -> str:
"""List popular repositories."""
return json.dumps(MOCK_REPOS)
@toolset.tool
async def github_list_issues(
ctx: RunContext[DeepAgentDeps],
repo: str,
) -> str:
"""List issues for a repository."""
return json.dumps(MOCK_ISSUES)
return toolset
Skills¶
The data-analysis skill (skills/data-analysis/SKILL.md):
Markdown
---
name: data-analysis
version: "1.0"
description: Comprehensive data analysis and visualization
triggers:
- analyze data
- create chart
- data visualization
---
# Data Analysis Skill
You are a data analysis expert. When analyzing data:
1. Load and explore the data structure
2. Calculate summary statistics
3. Identify patterns and outliers
4. Create visualizations
5. Provide actionable insights
Deployment Considerations¶
Docker Requirements¶
- Docker must be running
- Pull required images:
docker pull python:3.12-slim - Consider resource limits for containers
Scaling¶
- Agent is stateless (shared across sessions)
- Each session gets isolated Docker container
- SessionManager handles container lifecycle
- Consider container pool for faster startup
Security¶
- Containers provide isolation
- Human-in-the-loop for execute
- Session cleanup on idle timeout
- CORS configured for local development
Next Steps¶
- Docker Sandbox - Sandbox details
- Docker Runtimes - Pre-configured environments
- Human-in-the-Loop - Approval workflows
- Streaming - Streaming concepts