PydanticAI + Ollama streaming with tool calls has known issues: - Issue #1292: Streaming stops after tool call due to empty TextPart - Issue #2256: Empty text part causes run to end prematurely This change uses run() for the actual tool execution while still yielding the response in chunks to maintain the streaming UX. The orchestration loop can emit <think> updates between await calls. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tatlock - Your Homelab Butler
📖 For the complete system vision and architectural philosophy, see PHILOSOPHY.md
A privacy-first, offline-capable personal assistant system that coordinates specialized AI agents to help with research, development, home automation, and daily organization.
Current Status
- ✅ Production-ready testing API with OpenAI Responses API format
- ✅ Open WebUI integration with reasoning bubbles (
<think>tags) - ✅ Conversation history with auto-generated IDs and context management
- ✅ Tatlock PydanticAI Agent - Real LLM integration with Ollama + permanent tools
- ✅ Permanent Tools - Calculator, date/time toolkit, web search (SearXNG)
- ✅ Comprehensive testing - 131 tests, 81.78% coverage
Features
API Endpoints
-
Responses API (
/v1/responses) - OpenAI Responses API format with structured output- Reasoning items for displaying thinking process
- Function call items for tool execution
- Message items for assistant responses
- Streaming and non-streaming support
-
Chat Completions (
/v1/chat/completions) - OpenAI Chat Completions compatibility- Automatic reasoning conversion to
<think>tags for Open WebUI - Full OpenAI API compatibility
- Streaming support
- Automatic reasoning conversion to
-
Models (
/v1/models) - List available models
Advanced Capabilities
- Conversation History: Auto-generated IDs, configurable max turns (default: 20)
- Context Management: Token counting, automatic trimming, usage statistics
- Parameter Validation: Temperature (0.0-2.0), reasoning effort levels, max tokens, stop sequences
- Real-time Enforcement: Stop sequence detection and max token limits during streaming
Available Models
-
lorem-tester: Full-featured mock agent with realistic behavior
- Configurable reasoning effort levels
- Random tool/function calls
- Error triggers for testing (rate_limit, context_overflow)
-
Tatlock: Real PydanticAI agent with butler personality
- LLM Backend: Ollama (mistral-nemo:latest)
- Personality: Witty British butler, research-oriented
- Permanent Tools:
- Calculator: Safe mathematical expression evaluation (arithmetic, algebra, trigonometry, logarithms)
- Date/Time Toolkit: Current time, relative dates ("1 week ago"), time differences
- Web Search: Privacy-preserving search via SearXNG
- Capabilities: Streaming, reasoning, tool calling
- Phase: Phase 1 - Basic Integration (full household coordination coming in future phases)
Requirements
- Python 3.12+ (Python 3.12.11 recommended)
- Ollama (for Tatlock agent): Running locally or network-accessible
- Download: https://ollama.ai/
- Model:
ollama pull mistral-nemo:latest
- SearXNG (for web search tool): Optional but recommended
- Docker:
docker run -d -p 8087:8080 searxng/searxng - Or use public instance (less private)
- Docker:
Quick Start
Installation
# Clone the repository
git clone https://git.schweitz.net/jpmschweitzer/tatlock.git
cd tatlock
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
Run the Server
uvicorn src.main:app --reload
API available at http://localhost:8000
Usage Examples
Responses API
Generate a response with reasoning:
curl http://localhost:8000/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "lorem-tester",
"input": [
{"role": "user", "content": "Explain quantum computing"}
],
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"max_output_tokens": 500,
"stream": false
}'
Response Structure:
{
"id": "resp_abc123",
"object": "response",
"created_at": 1733529600,
"model": "lorem-tester",
"status": "completed",
"output": [
{
"type": "reasoning",
"summary": ["Analyzing the request...", "Considering quantum mechanics..."]
},
{
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": "Quantum computing uses..."}]
}
],
"usage": {
"input_tokens": 10,
"output_tokens": 50,
"reasoning_tokens": 20,
"total_tokens": 80
}
}
Chat Completions (OpenAI-compatible)
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "lorem-tester",
"messages": [
{"role": "user", "content": "Hello!"}
],
"temperature": 0.7,
"stream": true
}'
List Models
curl http://localhost:8000/v1/models
Conversation History
Optionally track conversations using metadata:
curl http://localhost:8000/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "lorem-tester",
"input": [{"role": "user", "content": "Hello"}],
"metadata": {"conversation_id": "conv_abc123"}
}'
Note: Client must send full conversation history in input array (OpenAI compatible). Server optionally tracks via metadata.conversation_id for future features.
Using Tatlock with Tools
Tatlock automatically uses his permanent tools when appropriate:
# Mathematical calculation
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Tatlock",
"messages": [{"role": "user", "content": "What is sqrt(144) + 25?"}]
}'
# Date/time queries
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Tatlock",
"messages": [{"role": "user", "content": "What was the date 2 weeks ago?"}]
}'
# Web search for current information
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Tatlock",
"messages": [{"role": "user", "content": "Search for recent Python 3.12 features"}]
}'
Tatlock's Tool Usage Philosophy:
- Uses calculator for ALL mathematics (even simple arithmetic)
- Uses date/time tools instead of guessing dates
- Searches for current/volatile information to verify facts
- Maintains a researcher's mindset with tool-assisted verification
Open WebUI Integration
Connection
If running Open WebUI in Docker and API on host:
# Use Docker bridge gateway IP
http://172.17.0.1:8000/v1/chat/completions
Reasoning Display
The Chat Completions endpoint automatically:
- Enables reasoning generation
- Converts reasoning to
<think>tags - Streams thinking before the response
Open WebUI displays this as thought bubbles separate from the main response.
Testing Error Handling
Use special triggers in user messages:
"trigger_rate_limit"- Simulates rate limit error"trigger_context_overflow"- Simulates context length error
API Documentation
Interactive documentation available at:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
Testing
# Run all tests
pytest
# Run with coverage
pytest --cov=src --cov-report=term-missing
# Current: 131 tests, 81.78% coverage
Test Categories:
- Unit tests: Agent tools, streaming, schemas
- Integration tests: Full API stack with real Ollama calls
- End-to-end tests: Chat completions, responses API
Deployment
Production Server
# Multiple workers for production
uvicorn src.main:app --host 0.0.0.0 --port 8000 --workers 4
Recommendations
- Use reverse proxy (nginx/caddy) for HTTPS
- Enable rate limiting
- Set up monitoring and logging
- Configure resource limits
- Use process manager (systemd/supervisor)
Configuration
Create a .env file for custom configuration:
# API Configuration
API_HOST=0.0.0.0
API_PORT=8000
# Ollama Configuration
OLLAMA_HOST=http://localhost:11434
OLLAMA_DEFAULT_MODEL=mistral-nemo:latest
OLLAMA_TIMEOUT=120
# SearXNG Configuration (for web search tool)
SEARXNG_HOST=http://localhost:8087
SEARXNG_TIMEOUT=30
# Logging
LOG_LEVEL=INFO
# CORS (default: allow all)
CORS_ORIGINS=["*"]
See .env.example for full configuration options.
Troubleshooting
Streaming not working
- Verify SSE-Starlette is installed
- Check client supports Server-Sent Events
- Test with:
pytest tests/responses/ -k streaming
Open WebUI can't connect
- Use Docker bridge gateway IP:
172.17.0.1:8000 - Check firewall settings
- Verify server is running on
0.0.0.0
Reasoning not showing
- Ensure using Chat Completions endpoint (auto-enables reasoning)
- Or manually enable in Responses API:
"reasoning": {"effort": "medium", "summary": "auto"} - Check Open WebUI version supports
<think>tags
Tatlock agent errors
- Verify Ollama is running:
curl http://localhost:11434/api/tags - Check model is downloaded:
ollama list - Review environment variables:
OLLAMA_HOST,OLLAMA_DEFAULT_MODEL - Check logs:
tail -f logs/server.log
Web search not working
- Verify SearXNG is running:
curl http://localhost:8087/ - Check
SEARXNG_HOSTenvironment variable - SearXNG is optional - Tatlock will note if search is unavailable
Project Structure
tatlock/
├── src/
│ ├── agents/ # Agent interface and implementations
│ │ ├── base.py # AgentInterface abstract class
│ │ ├── lorem_tester.py # Mock agent for testing
│ │ ├── tatlock.py # Real PydanticAI butler agent
│ │ ├── tools.py # Permanent tools (calculator, date/time, search)
│ │ └── registry.py # Model registry
│ ├── responses/ # Responses API (primary endpoint)
│ ├── chat/ # Chat Completions wrapper
│ ├── models/ # Models listing
│ ├── core/ # Shared utilities and config
│ └── main.py # Application entry point
├── tests/ # Comprehensive test suite (131 tests)
├── AGENTS.md # LLM agent development guidelines
├── PHILOSOPHY.md # System vision and architecture
├── IMPLEMENTATION_ROADMAP.md # Development phases
├── CHANGELOG.md # Version history
└── README.md # This file
Development
For LLM agent development guidelines and architectural decisions, see AGENTS.md.
Contributing
- Fork the repository
- Create a feature branch
- Make changes with tests
- Ensure tests pass:
pytest - Submit pull request
Documentation
- System Philosophy: PHILOSOPHY.md - Vision, goals, and architectural patterns
- User Guide: This file - Installation, usage, and examples
- Developer Guidelines: AGENTS.md - LLM agent development patterns
- Version History: CHANGELOG.md - Changes and releases
External References
- OpenAI Responses API: https://platform.openai.com/docs/api-reference/responses
- FastAPI: https://fastapi.tiangolo.com/
- PydanticAI: https://ai.pydantic.dev/
License
[Add your license here]
Version
Current version: 0.2.5 - Phase 2: The Steward (Two-Tier Architecture)
Note: This is a production-ready testing API with mock responses. The architecture is designed for easy integration with real LLM backends (PydanticAI, Ollama, OpenAI, etc.).