Files
tatlock/AGENTS.md
T
jpmschweitzer fd459e9ffb docs: update documentation for v0.2.0 tools release
README.md:
- Add Tatlock agent capabilities and tool descriptions
- Add requirements section (Ollama, SearXNG setup)
- Add configuration examples for external services
- Add tool usage examples and philosophy
- Add troubleshooting for Ollama and SearXNG
- Update test statistics

AGENTS.md:
- Refactor for LLM development focus
- Add PydanticAI tool registration pattern
- Add tool implementation guidelines
- Remove project status, focus on development instructions

IMPLEMENTATION_ROADMAP.md:
- Mark Phase 1 as "MOSTLY COMPLETE"
- Update detailed completion status
- Update current state summary
2025-12-07 00:13:41 +01:00

18 KiB

LLM Agent Instructions

This document contains instructions and documentation references for AI assistants working with this codebase.

📖 Important: Before working on this project, read PHILOSOPHY.md to understand the system vision, architectural patterns, and design goals. All development should work towards realizing those patterns.

Project Overview

This project implements an OpenAI-compatible API with FastAPI, featuring a hybrid architecture that provides both the OpenAI Responses API and Chat Completions compatibility layer.

Architecture Pattern

The Orchestrator infrastructure layer with hybrid API architecture:

Client (Open WebUI)
      ↓
Chat Completions (/v1/chat/completions) → Wrapper
      ↓
Responses API (/v1/responses) → Primary
      ↓
Agent Interface (lorem-tester, Tatlock)
      ↓
Mock Agents (lorem-tester) / Future: PydanticAI Agents (Tatlock, Steward, etc.)

Architectural Layers:

  1. The Orchestrator (Current Implementation)

    • FastAPI application providing the infrastructure
    • HTTP/SSE endpoints, streaming coordination
    • Conversation history and context management
    • OpenAI-compatible API surface
  2. Future: The Household (Phases 1-4)

    • Steward: First-tier LLM for request analysis (PydanticAI agent)
    • Tatlock: Second-tier LLM with butler personality (PydanticAI agent)
    • Expert Agents: Domain specialists (Librarian, Developer, Handyman, etc.)

Key Architectural Decisions:

  1. Single Source of Truth: All response generation happens in the Responses API

    • Structured output with reasoning, function_call, and message items
    • Real-time stop sequence and max tokens enforcement
    • Conversation history tracking
    • Context window management
  2. Chat Completions Wrapper: Provides compatibility without duplicating logic

    • Calls Responses API internally
    • Automatically enables reasoning generation
    • Converts reasoning items to <think> tags for Open WebUI
    • Maintains OpenAI-compatible format
  3. Agent Interface: Clean abstraction for multiple models

    • lorem-tester: Full-featured mock agent with realistic behavior
      • Reasoning summaries (adjustable effort levels)
      • Random tool/function calls
      • Error triggers for testing
      • Temperature variation
    • Tatlock: Advertised model name (currently mock, future: PydanticAI Butler agent)
  4. Hybrid Conversation History:

    • Client MUST send full context in input array (OpenAI compatible)
    • Server optionally tracks via metadata.conversation_id
    • Auto-generates deterministic IDs from first message
    • Supports future vector memory integration (Qdrant)

Why This Architecture?

  • Open WebUI Compatibility: Native Responses API support not yet in stable release
  • Future-Proof: Easy migration when Open WebUI adds native support
  • Testability: Full-featured mock agent (lorem-tester) for integration testing
  • Clean Separation: Responses API as stable core, wrappers can change

Components

  • FastAPI: Web framework for the API layer
  • SSE-Starlette: Server-Sent Events for streaming responses
  • Pydantic: Request/response validation with field validators
  • Agent Interface: Abstract base class for model implementations
  • Conversation History: Server-side tracking with configurable max turns
  • Context Window: Token counting and management
  • PydanticAI: Integrated with Tatlock agent (Ollama backend)
  • Agent Tools: Permanent tools module (src/agents/tools.py)
    • Calculator: Safe mathematical expression evaluation
    • Date/Time toolkit: Current time, relative dates, time differences
    • Web Search: SearXNG integration for privacy-preserving search

Documentation References

Core Framework Documentation

FastAPI

Uvicorn

  • Official Documentation: https://www.uvicorn.org/
  • Version: 0.38.0 (Oct 2025)
  • Key Topics:
    • ASGI server configuration
    • Deployment settings
    • Logging and monitoring
    • SSL/TLS configuration

AI/LLM Integration

PydanticAI

Pydantic

  • Official Documentation: https://docs.pydantic.dev/latest/
  • Version: 2.11+ (Required for PydanticAI, currently using >=2.11,<2.13)
  • Key Topics:
    • Data validation and serialization
    • Field types and validators
    • Model configuration
    • JSON schema generation

HTTP and Streaming

HTTPX

  • Official Documentation: https://www.python-httpx.org/
  • Version: 0.28.1
  • Key Topics:
    • Async HTTP client for Ollama communication
    • Streaming responses
    • Timeout configuration
    • Connection pooling

SSE-Starlette

Ollama Integration

Ollama API

  • Official Documentation: https://github.com/ollama/ollama/blob/main/docs/api.md
  • Status: Async client implemented in src/ollama/client.py, ready for future integration
  • Key Topics (for future implementation):
    • REST API endpoints
    • Streaming responses
    • Model management
    • Generate and chat endpoints
    • Model configuration
  • Current Model Target: mistral-nemo:latest

OpenAI API Compatibility

OpenAI API Reference

  • Official Documentation: https://platform.openai.com/docs/api-reference

  • Key API Endpoints:

    • /v1/responses - Responses API (PRIMARY) with structured output
    • /v1/chat/completions - OpenAI Chat Completions compatibility wrapper
    • /v1/models - List available models
  • Key Features for Development:

    • Responses API Format: Structured output with reasoning, function_call, and message items
    • Parameter Validation: Temperature, reasoning effort levels, max tokens, stop sequences
    • Conversation History: Hybrid client/server approach with auto-generated IDs
    • Context Management: Token counting and window trimming
    • Streaming: Real-time SSE streaming with stop sequence and max token enforcement
    • Error Handling: Custom exception types (RateLimitError, ContextLengthError)
    • Tool Calling: PydanticAI tool integration with permanent tools
    • Testing: Comprehensive test suite with mocks and real Ollama integration

FastAPI Best Practices

This project follows best practices from github.com/zhanymkanov/fastapi-best-practices

Project Structure

Domain-Based Organization: Code is organized by domain/feature rather than by file type:

src/
├── agents/                # Agent interface and implementations
│   ├── base.py            # Abstract AgentInterface
│   ├── lorem_tester.py    # Full-featured mock agent
│   ├── tatlock.py         # Placeholder for real agent
│   └── registry.py        # ModelRegistry for agent management
├── responses/             # Responses API domain (PRIMARY)
│   ├── router.py          # POST /v1/responses endpoint
│   ├── schemas.py         # Request/response models with validators
│   ├── service.py         # Response generation logic
│   ├── streaming.py       # SSE streaming coordinator
│   ├── history.py         # Conversation history management
│   └── context.py         # Context window and token management
├── chat/                  # Chat Completions domain (WRAPPER)
│   ├── router.py          # POST /v1/chat/completions endpoint
│   ├── schemas.py         # Chat request/response models
│   ├── service.py         # Wraps Responses API, converts to <think> tags
│   ├── constants.py       # Chat constants (roles, finish reasons)
│   └── __init__.py
├── models/                # Models listing domain
│   ├── router.py          # GET /v1/models endpoint
│   ├── schemas.py         # Model schemas
│   ├── service.py         # Accesses ModelRegistry
│   └── __init__.py
├── core/                  # Shared utilities
│   ├── config.py          # Global configuration (BaseSettings)
│   ├── models.py          # Custom base Pydantic models
│   ├── exceptions.py      # Custom exceptions (RateLimitError, etc.)
│   ├── dependencies.py    # Shared dependencies
│   └── router.py          # Core routes (health, root)
├── ollama/                # Ollama client layer (not yet integrated)
│   ├── client.py          # Async Ollama HTTP client
│   └── schemas.py         # Ollama API models
└── main.py                # Application factory & configuration

Key Architectural Principles:

  • Single Source of Truth: Responses API handles all generation logic
  • Wrapper Pattern: Chat Completions wraps Responses API without duplicating code
  • Agent Abstraction: AgentInterface defines contract for all models
  • Domain Separation: Each domain has its own router, schemas, service
  • Service Layer: Business logic in services, not routers
  • Type Safety: Pydantic models for ALL request/response validation
  • Async First: All I/O operations use async/await

Async/Await Best Practices

Critical Understanding: FastAPI handles sync and async routes differently:

  • Async routes (async def): Called directly in event loop

    • Use ONLY for non-blocking operations
    • Perfect for await httpx.get(), database queries, file I/O
    • NEVER use blocking calls like time.sleep() - this blocks entire server
  • Sync routes (def): Run in thread pool

    • Use for CPU-intensive work or blocking SDKs
    • Blocking I/O won't freeze the event loop
    • Example: time.sleep(10) is safe here

Example:

@router.get("/terrible")
async def terrible():
    time.sleep(10)  # ❌ BLOCKS ENTIRE SERVER

@router.get("/good")
def good():
    time.sleep(10)  # ✅ Runs in thread pool

@router.get("/perfect")
async def perfect():
    await asyncio.sleep(10)  # ✅ Non-blocking async

For CPU-intensive tasks: Use separate worker processes (not threads) due to Python's GIL.

Pydantic Configuration

Custom Base Model: All schemas inherit from CustomBaseModel for consistent behavior:

# src/core/models.py
class CustomBaseModel(BaseModel):
    model_config = ConfigDict(
        json_encoders={datetime: datetime_to_iso_str},
        populate_by_name=True,
        use_enum_values=True,
        validate_assignment=True,
    )

    def serializable_dict(self, **kwargs):
        """Return dict with only JSON-serializable fields."""
        return jsonable_encoder(self.model_dump(**kwargs))

Benefits:

  • Consistent datetime serialization across all responses
  • Alias support for field name flexibility
  • Easy JSON encoding for logging/debugging

Decoupled Settings: Split configuration by domain instead of one monolithic file:

# src/core/config.py - Global settings
class Config(BaseSettings):
    DATABASE_URL: PostgresDsn
    ENVIRONMENT: Environment

# src/chat/config.py - Chat-specific settings
class ChatConfig(BaseSettings):
    MAX_TOKENS: int
    DEFAULT_TEMPERATURE: float

Dependency Injection Patterns

Validation with Dependencies: Use dependencies for complex validations:

async def valid_post_id(post_id: UUID4) -> dict:
    """Validate post exists in database."""
    post = await service.get_by_id(post_id)
    if not post:
        raise PostNotFound()
    return post

@router.get("/posts/{post_id}")
async def get_post(post: dict = Depends(valid_post_id)):
    return post  # Already validated!

Chaining Dependencies: Build reusable validation layers:

async def valid_owned_post(
    post: dict = Depends(valid_post_id),
    token_data: dict = Depends(parse_jwt_data),
) -> dict:
    if post["creator_id"] != token_data["user_id"]:
        raise UserNotOwner()
    return post

Dependency Caching: Dependencies are cached within request scope - FastAPI only executes each dependency once per request, even if used multiple times.

Application Factory Pattern

Main.py uses factory pattern for testability and configuration:

def create_application() -> FastAPI:
    """Create and configure FastAPI app."""
    app = FastAPI(title=config.APP_NAME)

    # Add middleware
    app.add_middleware(CORSMiddleware, ...)

    # Register exception handlers
    register_exception_handlers(app)

    # Include routers
    app.include_router(chat_router, prefix="/v1")

    return app

app = create_application()

Development Guidelines

Git Workflow

IMPORTANT: Do NOT handle git commits or pushes automatically. Wait for explicit user instruction before:

  • Running git add
  • Running git commit
  • Running git push
  • Creating or pushing tags

The user will manage git operations themselves unless they specifically request assistance.

Server Logs and Debugging

Development Mode Logging: When the server is started using ./wakeup.sh, logs are written to logs/server.log. This file is:

  • Cleared on each server startup (fresh logs every time)
  • Written in real-time as the server runs
  • Already gitignored (won't be committed)

Accessing Logs: You can read the log file at any time while the server is running:

# View current logs
cat logs/server.log

# Follow logs in real-time
tail -f logs/server.log

# Search logs
grep "ERROR" logs/server.log

This is useful for debugging issues, monitoring API calls, and understanding server behavior during development.

Code Structure Guidelines

  • Use async/await for ALL I/O operations (database, HTTP, file access)
  • Use sync (def) for blocking SDKs or CPU-intensive work
  • Implement proper error handling and logging
  • Follow dependency injection for validation and shared resources
  • Use Pydantic models for ALL request/response validation
  • Keep business logic in service modules, not routers
  • Domain-based project structure (not file-type based)

Security Considerations

  • Validate all inputs using Pydantic models
  • Use environment variables for sensitive configuration
  • Keep dependencies updated and CVE-checked
  • Minor version locking for supply chain protection
  • Consider rate limiting for production deployment
  • Plan for authentication/API keys when needed

Testing Approach

  • Write integration tests for API endpoints
  • Test streaming functionality with appropriate timeouts
  • Use pytest-asyncio for async test support
  • Validate OpenAI API compatibility in tests
  • Test both mock and real LLM integrations
  • Cover main application (CORS, exception handlers, lifespan)
  • Test wrapper layers (chat completions, etc.)
  • Include tool functionality tests

Configuration Management

  • Use .env files for local development
  • Document all environment variables in README
  • Provide sensible defaults where possible
  • Use BaseSettings from pydantic-settings
  • Support both local and container-based configuration

Common Patterns

Streaming Response Pattern

Example from src/chat/router.py:

from sse_starlette.sse import EventSourceResponse
from fastapi import FastAPI

async def event_generator():
    # Currently yields mock lorem ipsum chunks
    # Future: Stream from Ollama/PydanticAI
    yield {"data": chunk.model_dump_json()}
    yield {"data": "[DONE]"}

@app.post("/stream")
async def stream():
    return EventSourceResponse(event_generator())

PydanticAI Agent Pattern

When implementing agents with PydanticAI and Ollama:

from pydantic_ai import Agent

agent = Agent(
    'ollama:mistral-nemo',  # Target model
    # Configuration here
)

# Use the agent
result = await agent.run('Your prompt')

OpenAI-Compatible Response Format

Example schema from src/chat/schemas.py:

{
    "id": "chatcmpl-123",
    "object": "chat.completion.chunk",
    "created": 1234567890,
    "model": "mistral-nemo:latest",
    "choices": [{
        "index": 0,
        "delta": {"content": "response"},
        "finish_reason": None
    }]
}

PydanticAI Tool Registration Pattern

Tools are registered with PydanticAI agents using decorators. See src/agents/tatlock.py for examples:

from pydantic_ai import Agent, RunContext

# After creating the agent
@agent.tool
def tool_name(ctx: RunContext[None], param: str) -> str:
    """
    Tool description that the LLM sees.

    Args:
        param: Parameter description

    Returns:
        Result description
    """
    return result

Tool Implementation Guidelines:

  • Keep tools in src/agents/tools.py for reusability
  • Use clear, descriptive docstrings (LLM reads these)
  • Include parameter descriptions in docstrings
  • Handle errors gracefully and return error messages as strings
  • For async operations, declare the tool function as async def
  • Test tools independently before integration

Example Tool Module (src/agents/tools.py):

def calculate(expression: str) -> str:
    """Safe calculator implementation."""
    try:
        # Implementation
        return str(result)
    except Exception as e:
        return f"Error: {str(e)}"

async def search_web(query: str) -> str:
    """Web search via SearXNG."""
    async with httpx.AsyncClient() as client:
        # Implementation
        return formatted_results

Update Policy

This document should be updated when:

  • New development patterns are established
  • Package versions are upgraded
  • Major architectural changes occur
  • New best practices are identified

Last updated: 2025-12-06 (Tools integration)