# PydanticAI Multi-Agent Application Overview

A production-ready template for building multi-agent applications with [PydanticAI](https://github.com/pydantic/pydantic-ai).

## Table of Contents

- [Project Summary](#1-project-summary)
- [Quick Reference](#2-quick-reference)
- [Agents](#3-agents)
- [Tools](#4-tools)
- [Toolsets](#5-toolsets)
- [Dependencies](#6-dependencies)
- [Output Models](#7-output-models)
- [API Endpoints](#8-api-endpoints)
- [Architecture](#9-architecture)
- [Testing](#10-testing)
- [Project Files](#11-project-files-reference)

---

## 1. Project Summary

This template provides a complete multi-agent system with:

- **Router-based delegation architecture** - A lightweight router agent analyzes requests and delegates to specialized agents
- **6 specialized agents** - Research, Analyst, Code, Writer, Support, plus the Router
- **45 composable tools** - Organized into search, data, external, and common categories
- **Type-safe dependency injection** - Dataclass-based DI with protocol abstractions
- **Structured outputs** - Pydantic models for validated agent responses
- **FastAPI REST API** - With streaming support and conversation management
- **CLI interface** - Interactive chat and single-query modes

---

## 2. Quick Reference

### Environment Variables

| Variable | Description | Required | Default |
|----------|-------------|----------|---------|
| `OPENAI_API_KEY` | OpenAI API key for GPT models | Yes | - |
| `ANTHROPIC_API_KEY` | Anthropic API key for Claude models | Yes | - |
| `DATABASE_URL` | PostgreSQL connection string | Yes | - |
| `REDIS_URL` | Redis connection string | Yes | - |
| `SEARCH_API_KEY` | Web search API key | No | - |
| `DEBUG` | Enable debug mode | No | `false` |
| `LOG_LEVEL` | Logging level | No | `INFO` |

### CLI Commands

```bash
# Start the API server
my-app serve                    # Default: 0.0.0.0:8000
my-app serve --port 3000        # Custom port
my-app serve --reload           # Hot reload for development

# Interactive chat mode
my-app chat                     # Uses router agent
my-app chat --agent research    # Use specific agent
my-app chat --agent analyst     # Available: router, research, analyst, code, writer, support

# Single query
my-app query "What is Python?"              # Uses router agent
my-app query "Analyze this data" --agent analyst
```

### Development Commands

```bash
# Install dependencies
uv sync                         # Recommended
pip install -e ".[dev]"         # Alternative

# Run tests
pytest                          # All tests
pytest --cov=my_app             # With coverage
pytest tests/agents/ -v         # Specific directory

# Linting and type checking
ruff check src tests
ruff format src tests
mypy src
```

---

## 3. Agents

### 3.1 Router Agent

| Property | Value |
|----------|-------|
| **File** | `src/my_app/agents/router.py` |
| **Model** | `openai:gpt-4o-mini` |
| **Dependencies** | `SearchDeps` |
| **Output Type** | `str` |

**Purpose**: Lightweight routing agent that analyzes user requests and delegates to the most appropriate specialist agent.

**Delegation Tools**:

| Tool | Target Agent | Description |
|------|--------------|-------------|
| `delegate_to_research(query)` | Research | Information gathering questions |
| `delegate_to_analyst(query)` | Analyst | Data analysis requests |
| `delegate_to_code(query)` | Code | Programming questions |
| `delegate_to_writer(task, context)` | Writer | Content creation tasks |
| `delegate_to_support(query)` | Support | Customer service inquiries |

---

### 3.2 Research Agent

| Property | Value |
|----------|-------|
| **File** | `src/my_app/agents/research.py` |
| **Model** | `openai:gpt-4o` |
| **Dependencies** | `SearchDeps` |
| **Output Type** | `ResearchResult` |
| **Toolset** | `research_toolset` |

**Purpose**: Information gathering and synthesis. Searches web and knowledge base, verifies information from multiple sources, and provides cited answers.

**Available Tools**: `web_search_web`, `web_fetch_webpage`, `kb_search_knowledge_base`, `kb_get_document`, `kb_find_similar_documents`, plus all common tools.

---

### 3.3 Analyst Agent

| Property | Value |
|----------|-------|
| **File** | `src/my_app/agents/analyst.py` |
| **Model** | `anthropic:claude-sonnet-4-5` |
| **Dependencies** | `BaseDeps` |
| **Output Type** | `AnalysisResult` |
| **Toolset** | `analyst_toolset` |

**Purpose**: Data analysis and insights. Queries databases, performs statistical analysis, creates visualizations, and provides actionable recommendations.

**Available Tools**: `db_query_users`, `db_query_orders`, `db_get_user_statistics`, `stats_calculate_statistics`, `stats_calculate_percentiles`, `stats_detect_outliers`, `stats_analyze_trend`, `viz_create_ascii_bar_chart`, `viz_create_ascii_line_chart`, `viz_create_histogram`, `export_to_csv`, `export_to_json`, `export_to_markdown_table`, `export_format_as_summary`, plus all common tools.

---

### 3.4 Code Agent

| Property | Value |
|----------|-------|
| **File** | `src/my_app/agents/code.py` |
| **Model** | `anthropic:claude-sonnet-4-5` |
| **Dependencies** | `BaseDeps` |
| **Output Type** | `CodeResponse` |
| **Toolsets** | `github_toolset`, `common_toolset` |

**Purpose**: Programming assistance. Answers coding questions, writes clean code, debugs issues, and reviews code with suggestions.

**Supported Languages**: Python, JavaScript/TypeScript, Go, Rust, SQL, Shell, HTML/CSS

**Available Tools**: `github_search_issues`, `github_get_issue_details`, `github_list_pull_requests`, `github_get_repository_info`, plus all common tools.

---

### 3.5 Writer Agent

| Property | Value |
|----------|-------|
| **File** | `src/my_app/agents/writer.py` |
| **Model** | `openai:gpt-4o` |
| **Dependencies** | `BaseDeps` |
| **Output Type** | `str` |
| **Toolset** | `common_toolset` |

**Purpose**: Content creation. Creates blog posts, emails, documentation, social media content, reports, and marketing copy.

**Available Tools**: All common tools (datetime and formatting utilities).

---

### 3.6 Support Agent

| Property | Value |
|----------|-------|
| **File** | `src/my_app/agents/support.py` |
| **Model** | `openai:gpt-4o` |
| **Dependencies** | `AuthDeps` |
| **Output Type** | `SupportResponse` |
| **Toolset** | `support_toolset` |

**Purpose**: Customer support. Answers customer questions, searches knowledge base, looks up account details, and escalates when necessary.

**Available Tools**: `kb_search_knowledge_base`, `kb_get_document`, `kb_find_similar_documents`, `db_query_users`, `db_query_orders`, `db_get_user_statistics`, `slack_send_message`, `slack_get_channel_history`, `slack_search_messages`, `slack_list_channels`, `email_send_email`, `email_create_draft`, `email_format_email_template`, plus all common tools.

---

## 4. Tools

### 4.1 Search Tools

#### Web Search (`src/my_app/tools/search/web.py`)

Prefix: `web_` | Dependencies: `SearchDeps`

| Tool | Parameters | Description |
|------|------------|-------------|
| `search_web` | `query: str`, `num_results: int = 5` | Search the web for information (1-10 results) |
| `fetch_webpage` | `url: str` | Fetch and extract content from a webpage (max 4000 chars) |

#### Vector Search (`src/my_app/tools/search/vector.py`)

Prefix: `kb_` | Dependencies: `SearchDeps`

| Tool | Parameters | Description |
|------|------------|-------------|
| `search_knowledge_base` | `query: str`, `top_k: int = 5`, `category: str \| None` | Semantic search in internal knowledge base (1-20 results) |
| `get_document` | `doc_id: str` | Retrieve a specific document by ID |
| `find_similar_documents` | `doc_id: str`, `top_k: int = 5` | Find documents similar to a given document |

#### Database Search (`src/my_app/tools/search/database.py`)

Prefix: `db_` | Dependencies: `BaseDeps`

| Tool | Parameters | Description |
|------|------------|-------------|
| `query_users` | `email: str \| None`, `name_contains: str \| None`, `is_active: bool \| None`, `limit: int = 10` | Query user records with filters |
| `query_orders` | `user_id: str \| None`, `status: str \| None`, `limit: int = 10` | Query orders (status: pending, completed, cancelled) |
| `get_user_statistics` | `user_id: str` | Get user stats: total orders, total spent, avg order value |

---

### 4.2 Data Tools

#### Analysis (`src/my_app/tools/data/analysis.py`)

Prefix: `stats_` | Dependencies: `None`

| Tool | Parameters | Description |
|------|------------|-------------|
| `calculate_statistics` | `data: list[float]` | Calculate count, sum, mean, median, std dev, min, max, range |
| `calculate_percentiles` | `data: list[float]`, `percentiles: list[int] \| None` | Calculate percentiles (default: 25, 50, 75, 90, 95) |
| `detect_outliers` | `data: list[float]`, `method: str = "iqr"` | Detect outliers using IQR or Z-score method |
| `analyze_trend` | `values: list[float]`, `labels: list[str] \| None` | Analyze trends with linear regression |

#### Visualization (`src/my_app/tools/data/visualization.py`)

Prefix: `viz_` | Dependencies: `BaseDeps`

| Tool | Parameters | Description |
|------|------------|-------------|
| `create_ascii_bar_chart` | `labels: list[str]`, `values: list[float]`, `max_width: int = 40` | Create ASCII horizontal bar chart |
| `create_ascii_line_chart` | `values: list[float]`, `height: int = 10`, `width: int = 50` | Create ASCII line chart for time series |
| `create_histogram` | `data: list[float]`, `bins: int = 10`, `max_width: int = 40` | Create ASCII histogram |

#### Export (`src/my_app/tools/data/export.py`)

Prefix: `export_` | Dependencies: `None`

| Tool | Parameters | Description |
|------|------------|-------------|
| `export_to_csv` | `data: list[dict]`, `columns: list[str] \| None` | Export data to CSV format |
| `export_to_json` | `data: list[dict] \| dict`, `pretty: bool = True` | Export data to JSON format |
| `export_to_markdown_table` | `data: list[dict]`, `columns: list[str] \| None` | Export as Markdown table |
| `format_as_summary` | `title: str`, `sections: dict[str, str]` | Format as readable summary document |

---

### 4.3 External Tools

#### GitHub (`src/my_app/tools/external/github.py`)

Prefix: `github_` | Dependencies: `BaseDeps`

| Tool | Parameters | Description |
|------|------------|-------------|
| `search_issues` | `repo: str`, `query: str`, `state: str = "open"`, `limit: int = 10` | Search issues in a repository |
| `get_issue_details` | `repo: str`, `issue_number: int` | Get detailed information about an issue |
| `list_pull_requests` | `repo: str`, `state: str = "open"`, `limit: int = 10` | List pull requests |
| `get_repository_info` | `repo: str` | Get repository info (stars, forks, language, etc.) |

#### Slack (`src/my_app/tools/external/slack.py`)

Prefix: `slack_` | Dependencies: `BaseDeps`

| Tool | Parameters | Description |
|------|------------|-------------|
| `send_message` | `channel: str`, `message: str` | Send message to a Slack channel |
| `get_channel_history` | `channel: str`, `limit: int = 10` | Get recent messages from channel |
| `search_messages` | `query: str`, `limit: int = 10` | Search messages across Slack |
| `list_channels` | `limit: int = 20` | List available channels |

#### Email (`src/my_app/tools/external/email.py`)

Prefix: `email_` | Dependencies: `BaseDeps`

| Tool | Parameters | Description |
|------|------------|-------------|
| `send_email` | `to: str`, `subject: str`, `body: str`, `cc: str \| None` | Send an email |
| `create_draft` | `to: str`, `subject: str`, `body: str` | Create email draft for review |
| `format_email_template` | `template_name: str`, `variables: dict[str, str]` | Format using template (welcome, reminder, notification) |

---

### 4.4 Common Tools

#### DateTime (`src/my_app/tools/common/datetime_tools.py`)

Dependencies: `None`

| Tool | Parameters | Description |
|------|------------|-------------|
| `get_current_time` | `timezone: str = "UTC"` | Get current date and time |
| `parse_date` | `date_string: str`, `format_string: str = "%Y-%m-%d"` | Parse date string to standard format |
| `calculate_date_difference` | `date1: str`, `date2: str` | Calculate difference between two dates |
| `add_time_to_date` | `date_string: str`, `days: int = 0`, `weeks: int = 0`, `months: int = 0` | Add time to a date |
| `format_date` | `date_string: str`, `output_format: str = "long"` | Format date (long, short, iso, relative) |

#### Formatting (`src/my_app/tools/common/formatting.py`)

Dependencies: `None`

| Tool | Parameters | Description |
|------|------------|-------------|
| `format_as_bullet_list` | `items: list[str]`, `style: str = "dash"` | Format as bullet list (dash, asterisk, number) |
| `truncate_text` | `text: str`, `max_length: int = 200`, `suffix: str = "..."` | Truncate text to max length |
| `extract_keywords` | `text: str`, `max_keywords: int = 10` | Extract keywords from text |
| `format_number` | `value: float`, `style: str = "standard"`, `decimals: int = 2` | Format number (standard, currency, percentage, compact) |
| `clean_text` | `text: str`, `remove_extra_whitespace: bool`, `remove_urls: bool`, `lowercase: bool` | Clean and normalize text |

---

## 5. Toolsets

Toolsets are compositions of individual tools, often with prefixes for namespacing.

| Toolset | Location | Composition | Used By |
|---------|----------|-------------|---------|
| `research_toolset` | `toolsets/research.py` | `web_*` + `kb_*` + common | Research Agent |
| `analyst_toolset` | `toolsets/analyst.py` | `db_*` + `stats_*` + `viz_*` + `export_*` + common | Analyst Agent |
| `support_toolset` | `toolsets/support.py` | `kb_*` + `db_*` + `slack_*` + `email_*` + common | Support Agent |
| `search_toolset` | `tools/search/__init__.py` | `web_*` + `kb_*` + `db_*` | General use |
| `data_toolset` | `tools/data/__init__.py` | `stats_*` + `viz_*` + `export_*` | General use |
| `external_toolset` | `tools/external/__init__.py` | `github_*` + `slack_*` + `email_*` | General use |
| `common_toolset` | `tools/common/__init__.py` | datetime + formatting | Multiple agents |

### Toolset Composition Example

```python
from pydantic_ai.toolsets import CombinedToolset

research_toolset = CombinedToolset([
    web_search_toolset.prefixed("web"),    # web_search_web, web_fetch_webpage
    vector_search_toolset.prefixed("kb"),  # kb_search_knowledge_base, etc.
    common_toolset,                        # No prefix
])
```

---

## 6. Dependencies

### 6.1 BaseDeps

Base dependencies available to all agents.

**File**: `src/my_app/dependencies/base.py`

| Field | Type | Description |
|-------|------|-------------|
| `http_client` | `HttpClient` | HTTP client for external API calls |
| `db` | `DatabasePool` | Database connection pool |
| `cache` | `CacheClient` | Redis cache client |
| `user_id` | `str \| None` | Current user identifier |

**Methods**:
- `with_user(user_id: str) -> BaseDeps` - Create copy with specific user context

---

### 6.2 SearchDeps

Dependencies for search-related agents. Extends `BaseDeps`.

**File**: `src/my_app/dependencies/search.py`

| Field | Type | Description |
|-------|------|-------------|
| `vector_store` | `VectorStore` | Vector database for semantic search |
| `search_api_key` | `str` | Web search API key |
| *(inherited)* | | All fields from `BaseDeps` |

**Methods**:
- `to_base_deps() -> BaseDeps` - Convert to BaseDeps, dropping search fields
- `to_auth_deps(user_roles, permissions) -> AuthDeps` - Convert to AuthDeps
- `from_base(base, vector_store, search_api_key) -> SearchDeps` - Create from BaseDeps

---

### 6.3 AuthDeps

Dependencies for authentication-aware agents. Extends `BaseDeps`.

**File**: `src/my_app/dependencies/auth.py`

| Field | Type | Description |
|-------|------|-------------|
| `user_roles` | `list[str]` | User's assigned roles |
| `permissions` | `list[str]` | User's permissions |
| *(inherited)* | | All fields from `BaseDeps` |

**Methods**:
- `has_role(role: str) -> bool` - Check if user has a specific role
- `has_permission(permission: str) -> bool` - Check if user has permission
- `is_admin() -> bool` - Check if user has admin role

---

### Protocol Definitions

```python
class DatabasePool(Protocol):
    async def fetch_one(self, query: str, *args) -> dict | None: ...
    async def fetch_all(self, query: str, *args) -> list[dict]: ...
    async def execute(self, query: str, *args) -> None: ...

class CacheClient(Protocol):
    async def get(self, key: str) -> str | None: ...
    async def set(self, key: str, value: str, ttl: int | None = None) -> None: ...
    async def delete(self, key: str) -> None: ...

class HttpClient(Protocol):
    async def get(self, url: str, **kwargs) -> Any: ...
    async def post(self, url: str, **kwargs) -> Any: ...

class VectorStore(Protocol):
    async def search(self, query: str, top_k: int, filters: dict | None) -> list[dict]: ...
    async def get(self, doc_id: str) -> dict | None: ...
    async def upsert(self, doc_id: str, content: str, metadata: dict) -> None: ...
```

---

## 7. Output Models

### 7.1 ResearchResult

**File**: `src/my_app/models/analysis.py`

| Field | Type | Description |
|-------|------|-------------|
| `answer` | `str` | The researched answer |
| `sources` | `list[str]` | URLs or references used |
| `confidence` | `float` | Confidence score (0.0-1.0), default 0.8 |
| `follow_up_questions` | `list[str]` | Suggested follow-up questions |

**Methods**:
- `format_with_sources() -> str` - Format answer with source citations

---

### 7.2 AnalysisResult

**File**: `src/my_app/models/analysis.py`

| Field | Type | Description |
|-------|------|-------------|
| `summary` | `str` | Brief summary of the analysis |
| `confidence` | `float` | Confidence score (0.0-1.0) |
| `data_points` | `list[DataPoint]` | Analysis data points |
| `recommendations` | `list[str]` | Action recommendations |
| `methodology` | `str \| None` | How the analysis was performed |

**DataPoint Model**:
| Field | Type | Description |
|-------|------|-------------|
| `label` | `str` | Data point label |
| `value` | `float` | Numeric value |
| `metadata` | `dict[str, str]` | Additional metadata |

---

### 7.3 CodeResponse

**File**: `src/my_app/agents/code.py`

| Field | Type | Description |
|-------|------|-------------|
| `explanation` | `str` | Explanation of the solution or answer |
| `code` | `str \| None` | Code snippet if applicable |
| `language` | `str \| None` | Programming language of the code |
| `suggestions` | `list[str]` | Additional suggestions or improvements |

---

### 7.4 SupportResponse

**File**: `src/my_app/agents/support.py`

| Field | Type | Description |
|-------|------|-------------|
| `answer` | `str` | The response to the customer |
| `category` | `str` | Issue category (billing, technical, general, etc.) |
| `sentiment` | `str` | Customer sentiment (positive, neutral, frustrated) |
| `escalate` | `bool` | Whether to escalate to human support |
| `escalation_reason` | `str \| None` | Reason for escalation if applicable |
| `follow_up_actions` | `list[str]` | Suggested follow-up actions |

---

### 7.5 Search Models

**File**: `src/my_app/models/search.py`

**SearchQuery**:
| Field | Type | Description |
|-------|------|-------------|
| `query` | `str` | Search query string (min 1 char) |
| `filters` | `dict[str, str]` | Search filters |
| `top_k` | `int` | Results to return (1-100, default 10) |

**SearchResult**:
| Field | Type | Description |
|-------|------|-------------|
| `id` | `str` | Result identifier |
| `title` | `str` | Result title |
| `content` | `str` | Result content |
| `score` | `float` | Relevance score (0.0-1.0) |
| `url` | `str \| None` | Source URL |
| `metadata` | `dict[str, str]` | Additional metadata |

---

## 8. API Endpoints

### Chat Endpoints

**Router**: `/api/v1/chat`

| Method | Path | Request Body | Response | Description |
|--------|------|--------------|----------|-------------|
| POST | `/` | `ChatRequest` | `ChatResponse` | Send message, get response |
| POST | `/stream` | Form: `prompt`, `conversation_id` | SSE Stream | Streaming response |
| DELETE | `/{conversation_id}` | - | `{"status": "cleared"}` | Clear conversation |

**ChatRequest**:
```json
{
  "prompt": "string",
  "conversation_id": "string | null"
}
```

**ChatResponse**:
```json
{
  "response": "string",
  "conversation_id": "string"
}
```

---

### Agent Endpoints

**Router**: `/api/v1/agents`

| Method | Path | Request Body | Response | Description |
|--------|------|--------------|----------|-------------|
| GET | `/` | - | Agent list | List available agents |
| POST | `/research` | `ResearchRequest` | `ResearchResult` | Run research agent |
| POST | `/analyze` | `AnalysisRequest` | `AnalysisResult` | Run analyst agent |
| POST | `/code` | `CodeRequest` | `CodeResponse` | Run code agent |
| POST | `/write` | `WriterRequest` | `{"content": "..."}` | Run writer agent |

**Request Bodies**:
```json
// ResearchRequest, AnalysisRequest, CodeRequest
{ "query": "string" }

// WriterRequest
{ "task": "string", "context": "string | null" }
```

---

### Health Endpoints

| Method | Path | Response | Description |
|--------|------|----------|-------------|
| GET | `/health` | `{"status": "healthy"}` | Basic health check |
| GET | `/ready` | `{"status": "ready"}` | Readiness check (dependencies) |

---

## 9. Architecture

### Request Flow

```
┌──────────────────────────────────────────────────────────────────┐
│                         User Request                              │
└──────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌──────────────────────────────────────────────────────────────────┐
│                    API Layer (FastAPI)                           │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │
│  │ /chat/*     │  │ /agents/*   │  │ /health     │              │
│  └─────────────┘  └─────────────┘  └─────────────┘              │
└──────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌──────────────────────────────────────────────────────────────────┐
│                      Router Agent                                 │
│                   (openai:gpt-4o-mini)                           │
│                                                                   │
│  Analyzes request → Selects specialist → Delegates               │
└──────────────────────────────────────────────────────────────────┘
                                │
            ┌───────────────────┼───────────────────┐
            ▼                   ▼                   ▼
┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
│ Research Agent  │  │  Analyst Agent  │  │   Code Agent    │
│  (gpt-4o)       │  │ (claude-sonnet) │  │ (claude-sonnet) │
│                 │  │                 │  │                 │
│ SearchDeps      │  │ BaseDeps        │  │ BaseDeps        │
│ ResearchResult  │  │ AnalysisResult  │  │ CodeResponse    │
└─────────────────┘  └─────────────────┘  └─────────────────┘
            │                   │                   │
            ▼                   ▼                   ▼
┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
│ research_toolset│  │ analyst_toolset │  │ github_toolset  │
│ • web_*         │  │ • db_*          │  │ • github_*      │
│ • kb_*          │  │ • stats_*       │  │ common_toolset  │
│ • common        │  │ • viz_*         │  │                 │
│                 │  │ • export_*      │  │                 │
└─────────────────┘  └─────────────────┘  └─────────────────┘

┌─────────────────┐  ┌─────────────────┐
│  Writer Agent   │  │  Support Agent  │
│    (gpt-4o)     │  │    (gpt-4o)     │
│                 │  │                 │
│ BaseDeps        │  │ AuthDeps        │
│ str             │  │ SupportResponse │
└─────────────────┘  └─────────────────┘
            │                   │
            ▼                   ▼
┌─────────────────┐  ┌─────────────────┐
│ common_toolset  │  │ support_toolset │
│ • datetime      │  │ • kb_*          │
│ • formatting    │  │ • db_*          │
│                 │  │ • slack_*       │
│                 │  │ • email_*       │
└─────────────────┘  └─────────────────┘
```

### Dependency Hierarchy

```
BaseDeps
├── http_client: HttpClient
├── db: DatabasePool
├── cache: CacheClient
└── user_id: str | None

SearchDeps (extends BaseDeps)
├── vector_store: VectorStore
└── search_api_key: str

AuthDeps (extends BaseDeps)
├── user_roles: list[str]
└── permissions: list[str]
```

### Conversion Methods

```
SearchDeps ──── to_base_deps() ────► BaseDeps
    │
    └──────── to_auth_deps() ──────► AuthDeps

BaseDeps ───── SearchDeps.from_base() ──► SearchDeps
```

---

## 10. Testing

### Test Configuration

**File**: `pyproject.toml`

```toml
[tool.pytest.ini_options]
asyncio_mode = "auto"    # Async tests work without decorators
testpaths = ["tests"]
```

### Test Fixtures

**File**: `tests/conftest.py`

| Fixture | Type | Description |
|---------|------|-------------|
| `base_deps` | `BaseDeps` | Mocked base dependencies |
| `search_deps` | `SearchDeps` | Mocked search dependencies |
| `auth_deps` | `AuthDeps` | Mocked auth dependencies |

### Using TestModel

```python
from pydantic_ai.models.test import TestModel

async def test_research_agent(search_deps):
    test_model = TestModel()

    with research_agent.override(model=test_model):
        result = await research_agent.run("query", deps=search_deps)
        assert isinstance(result.output, ResearchResult)
```

### Running Tests

```bash
# All tests
pytest

# With coverage
pytest --cov=my_app

# Specific test file
pytest tests/agents/test_research.py -v

# Specific test function
pytest tests/agents/test_router.py::test_router_delegates -v
```

### Test Structure

```
tests/
├── conftest.py          # Shared fixtures
├── agents/
│   ├── test_analyst.py
│   ├── test_research.py
│   └── test_router.py
├── api/
│   └── test_health.py
└── tools/
    ├── test_analysis.py
    └── test_formatting.py
```

---

## 11. Project Files Reference

### Source Code Structure

```
src/my_app/
├── __init__.py
├── config.py                    # Configuration settings
│
├── agents/                      # Agent definitions
│   ├── __init__.py             # Exports all agents
│   ├── router.py               # Router agent + delegation tools
│   ├── research.py             # Research agent
│   ├── analyst.py              # Analyst agent
│   ├── code.py                 # Code agent + CodeResponse model
│   ├── writer.py               # Writer agent
│   └── support.py              # Support agent + SupportResponse model
│
├── tools/                       # Tool implementations
│   ├── __init__.py             # Exports all toolsets
│   ├── search/
│   │   ├── __init__.py         # Combined search_toolset
│   │   ├── web.py              # Web search tools
│   │   ├── vector.py           # Vector/semantic search tools
│   │   └── database.py         # Database query tools
│   ├── data/
│   │   ├── __init__.py         # Combined data_toolset
│   │   ├── analysis.py         # Statistical analysis tools
│   │   ├── visualization.py    # ASCII chart tools
│   │   └── export.py           # Data export tools
│   ├── external/
│   │   ├── __init__.py         # Combined external_toolset
│   │   ├── github.py           # GitHub API tools
│   │   ├── slack.py            # Slack API tools
│   │   └── email.py            # Email tools
│   └── common/
│       ├── __init__.py         # Combined common_toolset
│       ├── datetime_tools.py   # Date/time utilities
│       └── formatting.py       # Text formatting utilities
│
├── toolsets/                    # Agent-specific tool compositions
│   ├── __init__.py
│   ├── research.py             # research_toolset
│   ├── analyst.py              # analyst_toolset
│   └── support.py              # support_toolset
│
├── models/                      # Pydantic models
│   ├── __init__.py             # Exports all models
│   ├── analysis.py             # ResearchResult, AnalysisResult, DataPoint
│   ├── search.py               # SearchQuery, SearchResult, SearchResponse
│   ├── common.py               # Pagination, TimeRange
│   └── user.py                 # User, UserProfile
│
├── dependencies/                # Dependency injection
│   ├── __init__.py             # Exports deps + mock factories
│   ├── base.py                 # BaseDeps + protocols
│   ├── search.py               # SearchDeps
│   ├── auth.py                 # AuthDeps
│   └── mocks.py                # Mock implementations for testing
│
├── services/                    # Business logic
│   ├── __init__.py
│   ├── conversation.py         # Conversation management
│   ├── message_store.py        # Message persistence
│   └── usage_tracker.py        # Usage/cost tracking
│
├── api/                         # FastAPI application
│   ├── __init__.py
│   ├── app.py                  # FastAPI app setup
│   ├── middleware/
│   │   ├── __init__.py
│   │   ├── auth.py             # Authentication middleware
│   │   └── rate_limit.py       # Rate limiting
│   └── routes/
│       ├── __init__.py
│       ├── chat.py             # /chat endpoints
│       ├── agents.py           # /agents endpoints
│       └── health.py           # /health, /ready endpoints
│
└── cli/                         # Command-line interface
    ├── __init__.py
    └── main.py                  # CLI entry point
```

### Configuration Files

| File | Purpose |
|------|---------|
| `pyproject.toml` | Project config, dependencies, tool settings |
| `.env.example` | Environment variable template |
| `.gitignore` | Git ignore patterns |
| `CLAUDE.md` | Claude Code instructions |
| `README.md` | Project documentation |

---

## Summary Statistics

| Category | Count |
|----------|-------|
| **Agents** | 6 |
| **Tools** | 45 |
| **Toolsets** | 7 |
| **Output Models** | 4 (+2 support models) |
| **Dependency Classes** | 3 |
| **API Endpoints** | 9 |
| **CLI Commands** | 3 |
| **Environment Variables** | 7 |
