The Narrator
Thread and file storage components for conversational AI - the storage foundation for the Slide ecosystem.
Overview
The Narrator provides robust, production-ready storage solutions for conversational AI applications, serving as the storage layer for Tyler and other Slide components. It includes:
- ThreadStore: Persistent storage for conversation threads with support for both in-memory and SQL backends
- FileStore: Secure file storage with automatic processing for various file types
- Models: Pydantic models for threads, messages, and attachments
- CLI Tools: Command-line interface for database management and setup
Features
ThreadStore
- Multiple Backends: In-memory (development), SQLite (local), PostgreSQL (production)
- Async/Await Support: Built for modern Python async applications
- Message Filtering: Automatic handling of system vs. user messages
- Platform Integration: Support for external platform references (Slack, Discord, etc.)
- Connection Pooling: Production-ready database connection management
FileStore
- Secure Storage: Automatic file validation and type checking
- Multiple Formats: Support for documents, images, audio, and more
- Content Processing: Automatic text extraction from PDFs, image analysis
- Storage Limits: Configurable file size and total storage limits
- Sharded Storage: Efficient file organization to prevent directory bloat
Installation
# Using uv (recommended)
uv add slide-narrator
# Using pip (fallback)
pip install slide-narrator
Setup
Docker Setup (Recommended for Development)
For local development with PostgreSQL, Narrator includes built-in Docker commands:
# One-command setup: starts PostgreSQL and initializes tables
uv run narrator docker-setup
# This will:
# 1. Start a PostgreSQL container
# 2. Wait for it to be ready
# 3. Initialize the database tables
# 4. Show you the connection string
# The database will be available at:
# postgresql+asyncpg://narrator:narrator_dev@localhost:5432/narrator
To manage the Docker container:
# Stop container (preserves data)
uv run narrator docker-stop
# Stop and remove all data
uv run narrator docker-stop --remove-volumes
# Start container again
uv run narrator docker-start
# Check database status
uv run narrator status
For custom configurations, the Docker commands respect environment variables:
# Use a different port
uv run narrator docker-setup --port 5433
# Or set environment variables (matching docker-compose.yml)
export NARRATOR_DB_NAME=mydb
export NARRATOR_DB_USER=myuser
export NARRATOR_DB_PASSWORD=mypassword
export NARRATOR_DB_PORT=5433
# Then run docker-setup
uv run narrator docker-setup
# This will create:
# postgresql+asyncpg://myuser:mypassword@localhost:5433/mydb
Database Setup
For production use with PostgreSQL or SQLite persistence, you'll need to initialize the database tables:
# Initialize database tables (PostgreSQL)
uv run narrator init --database-url "postgresql+asyncpg://user:password@localhost/dbname"
# Initialize database tables (SQLite)
uv run narrator init --database-url "sqlite+aiosqlite:///path/to/your/database.db"
# Check database status
uv run narrator status --database-url "postgresql+asyncpg://user:password@localhost/dbname"
You can also use environment variables instead of passing the database URL:
# Set environment variable
export NARRATOR_DATABASE_URL="postgresql+asyncpg://user:password@localhost/dbname"
# Then run without --database-url flag
uv run narrator init
uv run narrator status
Environment Variables
Configure the narrator using environment variables:
# Database settings
NARRATOR_DATABASE_URL="postgresql+asyncpg://user:password@localhost/dbname"
NARRATOR_DB_POOL_SIZE=5 # Connection pool size
NARRATOR_DB_MAX_OVERFLOW=10 # Max additional connections
NARRATOR_DB_POOL_TIMEOUT=30 # Connection timeout (seconds)
NARRATOR_DB_POOL_RECYCLE=300 # Connection recycle time (seconds)
NARRATOR_DB_ECHO=false # Enable SQL logging
# File storage settings
NARRATOR_FILE_STORAGE_PATH=/path/to/files # Storage directory
NARRATOR_MAX_FILE_SIZE=52428800 # 50MB max file size
NARRATOR_MAX_STORAGE_SIZE=5368709120 # 5GB max total storage
NARRATOR_ALLOWED_MIME_TYPES=image/jpeg,application/pdf # Allowed file types
# Logging
NARRATOR_LOG_LEVEL=INFO # Log level
Quick Start
Basic Thread Storage
import asyncio
from narrator import ThreadStore, Thread, Message
async def main():
# Create an in-memory store for development
store = await ThreadStore.create()
# Create a thread
thread = Thread(title="My Conversation")
# Add messages
thread.add_message(Message(role="user", content="Hello!"))
thread.add_message(Message(role="assistant", content="Hi there!"))
# Save the thread
await store.save(thread)
# Retrieve the thread
retrieved = await store.get(thread.id)
print(f"Thread: {retrieved.title}")
print(f"Messages: {len(retrieved.messages)}")
asyncio.run(main())
File Storage
import asyncio
from narrator import FileStore
async def main():
# Create a file store
store = await FileStore.create()
# Save a file
content = b"Hello, world!"
metadata = await store.save(content, "hello.txt", "text/plain")
print(f"File ID: {metadata['id']}")
print(f"Storage path: {metadata['storage_path']}")
# Retrieve the file
retrieved_content = await store.get(metadata['id'])
print(f"Content: {retrieved_content.decode()}")
asyncio.run(main())
Database Storage
import asyncio
from narrator import ThreadStore
async def main():
# Use SQLite for persistent storage
store = await ThreadStore.create("sqlite+aiosqlite:///conversations.db")
# Use PostgreSQL for production
# store = await ThreadStore.create("postgresql+asyncpg://user:pass@localhost/dbname")
# The API is the same regardless of backend
thread = Thread(title="Persistent Conversation")
await store.save(thread)
asyncio.run(main())
Configuration
Database Configuration
The Narrator supports multiple database backends:
Memory storage (Default)
from narrator import ThreadStore
# Use factory pattern for immediate connection validation
store = await ThreadStore.create() # Uses memory backend
# Thread operations are immediate
thread = Thread()
await store.save(thread)
Key characteristics:
- Fastest possible performance (direct dictionary access)
- No persistence (data is lost when program exits)
- No setup required (works out of the box)
- Perfect for scripts and one-off conversations
- Great for testing and development
PostgreSQL storage
from narrator import ThreadStore
# Use factory pattern for immediate connection validation
db_url = "postgresql+asyncpg://user:pass@localhost/dbname"
try:
store = await ThreadStore.create(db_url)
print("Connected to database successfully")
except Exception as e:
print(f"Database connection failed: {e}")
# Handle connection failure appropriately
# Must save threads and changes to persist
thread = Thread()
await store.save(thread) # Required
thread.add_message(message)
await store.save(thread) # Save changes
# Always use thread.id with database storage
result = await store.get(thread.id)
Key characteristics:
- Async operations for non-blocking I/O
- Persistent storage (data survives program restarts)
- Cross-session support (can access threads from different processes)
- Production-ready
- Automatic schema management through SQLAlchemy
- Connection validation at startup with factory pattern
SQLite storage
from narrator import ThreadStore
# Use factory pattern for immediate connection validation
db_url = "sqlite+aiosqlite:///path/to/db.sqlite"
store = await ThreadStore.create(db_url)
# Or use in-memory SQLite database
store = await ThreadStore.create("sqlite+aiosqlite://") # In-memory SQLite
File Storage Configuration
from narrator import FileStore
# Create a FileStore instance with factory pattern
file_store = await FileStore.create(
base_path="/path/to/files", # Optional custom path
max_file_size=100 * 1024 * 1024, # 100MB (optional)
max_storage_size=10 * 1024 * 1024 * 1024 # 10GB (optional)
)
# Or use default settings from environment variables
file_store = await FileStore.create()
Advanced Usage
Using ThreadStore and FileStore Together
import asyncio
from narrator import ThreadStore, FileStore, Thread, Message
async def main():
# Create stores
thread_store = await ThreadStore.create("sqlite+aiosqlite:///main.db")
file_store = await FileStore.create("/path/to/files")
# Create a thread with file attachment
thread = Thread(title="Document Discussion")
# Create a message with an attachment
message = Message(role="user", content="Here's a document")
# Add file content
pdf_content = b"..." # Your PDF content
message.add_attachment(pdf_content, filename="document.pdf")
thread.add_message(message)
# Save thread (attachments are processed automatically)
await thread_store.save(thread)
print(f"Thread saved with ID: {thread.id}")
asyncio.run(main())
Message Attachments
Messages can include file attachments that are automatically processed:
import asyncio
from narrator import Thread, Message, Attachment, FileStore
async def main():
file_store = await FileStore.create()
# Create a message with an attachment
message = Message(role="user", content="Here's a document")
# Add file content
pdf_content = b"..." # Your PDF content
attachment = Attachment(filename="document.pdf", content=pdf_content)
message.add_attachment(attachment)
# Process and store the attachment
await attachment.process_and_store(file_store)
# The attachment now has extracted text and metadata
print(f"Status: {attachment.status}")
print(f"File ID: {attachment.file_id}")
if attachment.attributes:
print(f"Extracted text: {attachment.attributes.get('text', 'N/A')[:100]}...")
asyncio.run(main())
Platform Integration
Threads can be linked to external platforms:
import asyncio
from narrator import Thread, ThreadStore
async def main():
store = await ThreadStore.create()
# Create a thread linked to Slack
thread = Thread(
title="Support Ticket #123",
platforms={
"slack": {
"channel": "C1234567",
"thread_ts": "1234567890.123"
}
}
)
await store.save(thread)
# Find threads by platform
slack_threads = await store.find_by_platform("slack", {"channel": "C1234567"})
print(f"Found {len(slack_threads)} Slack threads in channel")
asyncio.run(main())
Database CLI
The Narrator includes a CLI tool for database management:
# Initialize database tables
uv run narrator init --database-url "postgresql+asyncpg://user:pass@localhost/dbname"
# Initialize using environment variable
export NARRATOR_DATABASE_URL="postgresql+asyncpg://user:pass@localhost/dbname"
uv run narrator init
# Check database status
uv run narrator status --database-url "postgresql+asyncpg://user:pass@localhost/dbname"
# Check status using environment variable
uv run narrator status
Available commands:
uv run narrator init- Initialize database tablesuv run narrator status- Check database connection and basic statistics
Key Design Principles
- Factory Pattern: Use
await ThreadStore.create()andawait FileStore.create()for proper initialization and connection validation - Backend Agnostic: Same API whether using in-memory, SQLite, or PostgreSQL storage
- Production Ready: Built-in connection pooling, error handling, and health checks
- Tyler Integration: Seamlessly integrates with Tyler agents for conversation persistence
- Platform Support: Native support for external platforms like Slack, Discord, and custom integrations
API Reference
ThreadStore
Methods
await ThreadStore.create(database_url=None): Factory method to create and initialize a storeawait store.save(thread): Save a thread to storageawait store.get(thread_id): Retrieve a thread by IDawait store.delete(thread_id): Delete a threadawait store.list(limit=100, offset=0): List threads with paginationawait store.find_by_attributes(attributes): Find threads by custom attributesawait store.find_by_platform(platform_name, properties): Find threads by platformawait store.list_recent(limit=None): List recent threads
FileStore
Methods
await FileStore.create(base_path=None, ...): Factory method to create and validate a storeawait store.save(content, filename, mime_type=None): Save file contentawait store.get(file_id, storage_path=None): Retrieve file contentawait store.delete(file_id, storage_path=None): Delete a fileawait store.get_storage_size(): Get total storage sizeawait store.check_health(): Check storage health
Models
Thread
id: Unique thread identifiertitle: Thread titlemessages: List of messagescreated_at: Creation timestampupdated_at: Last update timestampattributes: Custom attributes dictionaryplatforms: Platform-specific metadata
Message
id: Unique message identifierrole: Message role (user, assistant, system, tool)content: Message contentattachments: List of file attachmentstimestamp: Message timestampmetrics: Performance metrics
Attachment
filename: Original filenamemime_type: File MIME typefile_id: Storage file IDstorage_path: Path in storagestatus: Processing status (pending, stored, failed)attributes: Processed content and metadata
Development
Running Tests
To run the test suite locally:
# Install development dependencies
uv sync --extra dev
# Run tests with coverage
uv run pytest tests/ --cov=narrator --cov-report=term-missing --cov-branch --cov-report=term --no-cov-on-fail -v
# Run tests without coverage (faster)
uv run pytest tests/ -v
Test Requirements
The test suite requires:
- Python 3.13+
- pytest with async support
- Test coverage reporting
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Run the test suite to ensure everything works
- Submit a pull request
License
MIT License - see LICENSE file for details.
Support
For issues and questions:
- GitHub Issues: Repository Issues
- Documentation: API Reference
Metadata
Release files for slide-narrator 6.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| slide_narrator-6.4.0.tar.gz | 35.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| slide_narrator-6.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 76.7 kB
Release files / slide_narrator-6.4.0.tar.gz
| Download URL | slide_narrator-6.4.0.tar.gz |
|---|---|
| Size | 35.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f382bbce2553c579efc2a8534c82b05c544bc108ee8b78a6be4fe92033b7ca8d
|
|
BLAKE2b-256 checksum How to use checksums |
d2c4245baae2e0e7c868459430cf50397653415b2d83953ad769feadf142dff6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
Hatch/1.17.0 {"ci":true,"cpu":"x86_64","distro":{"id":"noble","libc":{"lib":"glibc","version":"2.39"},"name":"Ubuntu","version":"24.04"},"implementation":{"name":"CPython","version":"3.13.13"},"installer":{"name":"hatch","version":"1.17.0"},"openssl_version":"OpenSSL 3.0.13 30 Jan 2024","python":"3.13.13","system":{"name":"Linux","release":"6.17.0-1015-azure"}} HTTPX2/2.3.0
|
Release files / slide_narrator-6.4.0-py3-none-any.whl
| Download URL | slide_narrator-6.4.0-py3-none-any.whl |
|---|---|
| Size | 41.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b6c97c9f5bf9bbd176a1ff6390a0f8d04468948c75b27aa6b8dfa56babf34a09
|
|
BLAKE2b-256 checksum How to use checksums |
46c2d480082d5a9524caee80353ba680de461d4f7b09d90e86e0fac81a1ca0fe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
Hatch/1.17.0 {"ci":true,"cpu":"x86_64","distro":{"id":"noble","libc":{"lib":"glibc","version":"2.39"},"name":"Ubuntu","version":"24.04"},"implementation":{"name":"CPython","version":"3.13.13"},"installer":{"name":"hatch","version":"1.17.0"},"openssl_version":"OpenSSL 3.0.13 30 Jan 2024","python":"3.13.13","system":{"name":"Linux","release":"6.17.0-1015-azure"}} HTTPX2/2.3.0
|