Production-ready pipeline to create ontology from documents.
on:to is a document processing pipeline that extracts structured ontology from text documents. It uses:
- Apache Pulsar - Message queue for async processing
Everything runs in Docker - no Python setup required:
# 1. Create .env with your API key
echo "OPENAI_API_KEY=your-key" > .env
# 2. Start all services (Pulsar, Consumer, API)
docker compose up -d
# 3. Send a document
curl -X POST http://localhost:8000/documents \
-H "Content-Type: application/json" \
-d '{"tenant_id": "my-tenant", "content": "John works at Acme Corp."}'The docker-compose includes:
pulsar- Message queueconsumer- Background worker (logs messages)api- HTTP API server
For development with hot-reload:
# 1. Start infrastructure only (Pulsar, Qdrant, PostgreSQL)
docker compose -f docker-compose.dev.yml up -d pulsar qdrant postgres
# 2. Install Python deps
pip install -r requirements.txt
# 3. Create .env with your API key
echo "OPENAI_API_KEY=your-key" > .env
# 4. Run consumer and API
python -m app.queue.consumer &
python -m app.main- Python 3.10+
- Docker & Docker Compose
- OpenAI API key (or compatible LLM)
pip install -r requirements.txt| Variable | Required | Default | Description |
|---|---|---|---|
PULSAR_URL |
No | pulsar://localhost:6650 |
Pulsar broker URL |
PULSAR_ADMIN |
No | http://localhost:8080 |
Pulsar admin URL |
TOPIC_PREFIX |
No | persistent://public/default |
Topic prefix for tenants |
HOST |
No | 0.0.0.0 |
API server host |
PORT |
No | 8000 |
API server port |
# Start all services
docker compose up -d
# View logs
docker compose logs -f
# Stop
docker compose down# Start infrastructure only
docker compose -f docker-compose.dev.yml up -d pulsar qdrant postgres
# Set your API key
export OPENAI_API_KEY="sk-..."
# Start the consumer (worker that processes messages)
python -m app.queue.consumer &
# Start the API server
python -m app.mainSend documents to the pipeline via the API:
curl -X POST http://localhost:8000/documents \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "my-tenant",
"content": "John works at Acme Corp as a software engineer."
}'Response:
{
"job_id": "abc-123",
"status": "accepted",
"tenant_id": "my-tenant"
}# Health check
curl http://localhost:8000/health
# Readiness check
curl http://localhost:8000/ready| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Liveness probe |
GET |
/ready |
Readiness probe (Pulsar check) |
POST |
/documents |
Submit document for processing |
Submit a document for ontology extraction.
Request:
{
"tenant_id": "tenant-abc",
"content": "Your text content here..."
}Response:
{
"job_id": "job-xyz",
"status": "accepted",
"tenant_id": "tenant-abc"
}Error Responses:
400- Invalid request body422- Validation error503- Pulsar not ready
- Producer sends documents to Pulsar topic per tenant
- Consumer reads messages and logs them to console
When running in Docker (via docker-compose.dev.yml), services use host.docker.internal to connect to Pulsar instead of container names. This is because the Pulsar container may not be reachable by hostname from other containers in certain Docker configurations. The extra_hosts directive maps host.docker.internal to the host gateway, allowing containers to access Pulsar on localhost:6650 and localhost:8080.
pytest tests/unit/ -v
ruff check app/Each scenario runs on a fresh Docker environment (clean after every scenario, start before every scenario).
Prerequisites: pip install behave
# 1. Run tests - environment.py automatically handles cleanup/start
behave tests/bdd/features/ | tee /dev/null
# 2. Verify pass: check exit code (should be 0) and output shows "X scenarios passed, 0 failed"
echo $?app/
├── api/
│ └── routes.py # FastAPI routes
├── jobs/
│ └── store.py # Job status tracking
├── queue/
│ ├── producer.py # Pulsar producer
│ └── consumer.py # Pulsar consumer (logs messages)
├── schemas/
│ └── document.py # Request schemas
├── config.py # Configuration
├── logger.py # Logging
└── main.py # App entrypoint
tests/
├── unit/ # Unit tests
└── bdd/
└── features/ # BDD test scenarios
- Retry logic on send failure
- Dynamic topic discovery
- Configurable Pulsar admin URL
- Custom topic prefix support
- Graceful shutdown
- Health checks
- Input validation