OpenArg uses Celery with Redis as broker for background processing. The system runs 7+ specialized worker types, each with its own queue.
graph TD
Broker[(Redis Broker)]
subgraph Workers ["Celery Workers"]
direction LR
Scraper[Scraper Queue\nconcurrency 2]
Embedding[Embedding Queue\nconcurrency 8]
Collector[Collector Queue\nconcurrency 4]
Analyst[Analyst Queue\nconcurrency 2]
Transparency[Transparency Queue\nconcurrency 2]
Ingest[Ingest Queue\nconcurrency 2]
S3[S3 Queue\nconcurrency 2]
end
Beat[Beat Scheduler] --> Broker
Broker --> Workers
# Queue routing
task_routes = {
"app.infrastructure.celery.tasks.scraper_tasks.*": {"queue": "scraper"},
"app.infrastructure.celery.tasks.embedding_tasks.*": {"queue": "embedding"},
"app.infrastructure.celery.tasks.collector_tasks.*": {"queue": "collector"},
"app.infrastructure.celery.tasks.analyst_tasks.*": {"queue": "analyst"},
}
# Worker settings
worker_prefetch_multiplier = 4
worker_max_tasks_per_child = 500Fetches dataset metadata from CKAN portals and indexes them.
Flow:
- Fetch total count from portal API.
- Paginate through results in batches of 100.
- For each dataset, extract metadata (title, description, organization, resources).
- Select best resource (prefer CSV > JSON > XLSX).
- Upsert dataset into PostgreSQL.
- Dispatch
index_dataset_embedding.delay(dataset_id)for each new/updated dataset.
Task Flow:
sequenceDiagram
participant W as Scraper Worker
participant P as CKAN Portal API
participant DB as PostgreSQL
participant E as Embedding Queue
W->>P: Fetch metadata (batch 100)
P-->>W: Metadata results
W->>W: Select best resource (CSV/JSON/XLSX)
W->>DB: Upsert dataset
W->>E: Dispatch index_dataset_embedding(id)
Supported portals:
datos_gob_ar— datos.gob.ar (national)caba— data.buenosaires.gob.ar (Buenos Aires city)
Generates vector embeddings for a dataset's metadata.
Flow:
- Load dataset from DB.
- Generate 3 text chunks:
- Chunk 1 (Main): Title + description + organization + tags + format + row_count
- Chunk 2 (Columns): Column names + contextual description
- Chunk 3 (Use-case): How to query, what data is available, relevance hints
- Delete existing chunks for this dataset.
- Batch embed all chunks via AWS Bedrock Cohere Embed Multilingual v3 (1024 dims).
- Insert chunks with embeddings into
dataset_chunkstable.
Re-generates embeddings for all datasets (or filtered by portal).
Dispatches index_dataset_embedding.delay() for each dataset.
Downloads a dataset file and caches it as a PostgreSQL table.
Flow:
- Load dataset metadata from DB.
- Check if already cached (status = "ready").
- Create
cached_datasetsentry with status "downloading". - Download file via HTTPX.
- Parse with pandas (supports CSV, JSON, XLSX).
- Sanitize table name:
cache_{lowercase_alphanumeric_title}. - Limit to 500k rows.
- Write to PostgreSQL via
pandas.to_sql(). - Update cached_datasets with status "ready", row_count, columns_json.
- Update parent dataset:
is_cached=True,row_count.
Task Flow:
sequenceDiagram
participant W as Collector Worker
participant DB as PostgreSQL
participant DS as Data Source (URL)
W->>DB: Load metadata
W->>DB: Update status to "downloading"
W->>DS: Download file (HTTPX)
DS-->>W: File data
W->>W: Parse with pandas
W->>DB: to_sql(cache_{title})
W->>DB: Update status to "ready" + row_count
Full multi-agent analysis pipeline.
Flow:
- Plan Phase — LLM (AWS Bedrock Claude Haiku 3.5) generates JSON execution plan:
{ "approach": "...", "datasets_needed": ["tipo1", "tipo2"], "analysis_steps": ["step1", "step2"] } - Search Phase — Generate embedding for the question, vector search top-5 datasets.
- Gather Phase — For each matched dataset:
- Ensure data is cached (dispatch collector if needed).
- Fetch sample rows (20 rows max) from cached tables.
- Analyze Phase — LLM generates final analysis using:
- Original question
- Dataset metadata
- Real sample data (when available)
- Execution plan context
Task Flow:
sequenceDiagram
participant W as Analyst Worker
participant L as LLM (Bedrock)
participant DB as PostgreSQL
participant C as Collector Queue
W->>L: Generate execution plan
L-->>W: Plan (JSON)
W->>DB: Vector search datasets
DB-->>W: Top matched datasets
alt Dataset not cached
W->>C: Dispatch collect_dataset(id)
end
W->>DB: Fetch sample rows
W->>L: Generate final analysis
L-->>W: Markdown analysis
Status progression: pending → planning → collecting → analyzing → completed (or error)
Each phase is logged as an AgentTask entry with input/output JSON, tokens used, and duration.
Configured in celery/app.py:
| Schedule | Task | Arguments |
|---|---|---|
| Daily 03:00 ART | scrape_catalog |
portal="datos_gob_ar" |
| Daily 03:30 ART | scrape_catalog |
portal="caba" |
# Individual workers
make workers.scraper # Scraper queue, concurrency 2
make workers.collector # Collector queue, concurrency 4
make workers.embedding # Embedding queue, concurrency 8
make workers.analyst # Analyst queue, concurrency 2
make workers.transparency # Transparency queue, concurrency 2
make workers.ingest # Ingest queue, concurrency 2
make workers.s3 # S3 queue, concurrency 2
# Scheduler
make beat # Celery Beat for scheduled tasks
# Monitoring
make flower # Flower UI on port 5556In production/docker, each worker runs as a separate container:
| Service | Queue | API Keys / Credentials Needed |
|---|---|---|
worker-scraper |
scraper | AWS credentials (Bedrock embeddings) |
worker-collector |
collector | — |
worker-embedding |
embedding | AWS credentials (Bedrock Cohere) |
worker-analyst |
analyst | AWS credentials (Bedrock Claude Haiku) |
worker-transparency |
transparency | — |
worker-ingest |
ingest | — |
worker-s3 |
s3 | AWS credentials (S3) |
beat |
— (scheduler) | — |
flower |
— (monitoring) | — |