Proactive LLM cost intelligence powered by Google TimesFM.
156
chiefj/forecast-workerProactive LLM cost intelligence powered by Google TimesFM.
Pairs withchiefj/instrumentation-sdk-apito deliver live forecast graphs in Grafana.
The Forecast Worker is a Temporal-based cron worker that solves proactive cost and latency tracking using Google TimesFM (Time Series Forecasting Model).
Unlike EWMA (Exponentially Weighted Moving Average) — which is reactive and only alerts after a budget spike or SLO breach — the Forecast Worker is proactive. It:
┌─────────────────────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION STACK │
│ │
│ ┌──────────────────────────────┐ ┌──────────────────────────────────┐ │
│ │ chiefj/forecast-worker │ │ chiefj/instrumentation-sdk-api │ │
│ │ │ │ │ │
│ │ ┌──────────────────────┐ │ │ ┌────────────────────────────┐ │ │
│ │ │ Temporal Cron Worker │ │ │ │ FastAPI (port 8000) │ │ │
│ │ │ Runs every 5 minutes │ │ │ │ POST /v1/metrics/forecast │ │ │
│ │ └──────────┬───────────┘ │ │ │ POST /v1/metrics/record │ │ │
│ │ │ │ │ │ GET /v1/metrics/health │ │ │
│ │ ┌──────────▼───────────┐ │ │ └────────────┬───────────────┘ │ │
│ │ │ TimesFM Inference │ │ │ │ │ │
│ │ │ (google/timesfm- │ │ │ ┌────────────▼───────────────┐ │ │
│ │ │ 2.5-200m-pytorch) │ │ │ │ OTel Prometheus Adapter │ │ │
│ │ └──────────┬───────────┘ │ │ │ Observable Gauges (p/s) │ │ │
│ │ │ │ │ └────────────┬───────────────┘ │ │
│ │ ┌──────────▼───────────┐ │ │ │ │ │
│ │ │ Quantile Output │ │ │ ┌────────────▼───────────────┐ │ │
│ │ │ mean / p10 / p90 ├────┼────► │ Prometheus Exporter :9464 │ │ │
│ │ └──────────────────────┘ │ │ └────────────┬───────────────┘ │ │
│ │ │ │ │ │ │
│ │ Your app also calls SDK │ │ ┌────────────▼───────────────┐ │ │
│ │ directly for span metrics │ │ │ Prometheus :9090 │ │ │
│ └──────────────────────────────┘ │ └────────────┬───────────────┘ │ │
│ │ │ │ │
│ ┌───────────────────────────────────┼───────────────▼───────────────┐ │ │
│ │ Grafana :3000 │ Dashboards auto-provisioned │ │ │
│ │ ┌────────────────────────────────┼──────────────────────────────┐│ │ │
│ │ │ LLM Cost Forecast Dashboard │ Mean/p10/p90 time-series ││ │ │
│ │ │ LLM Cost Dashboard │ Actual vs Forecast overlay ││ │ │
│ │ │ LLM Security Dashboard │ PII / Injection events ││ │ │
│ │ └────────────────────────────────┴──────────────────────────────┘│ │ │
│ └───────────────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
| Step | Component | What Happens |
|---|---|---|
| 1 | Your App | Calls POST /v1/metrics/record with span data (tokens, cost, latency) |
| 2 | instrumentation-sdk-api | Records OTel counters/histograms → exported to Prometheus port 9464 |
| 3 | forecast-worker (cron, every 5m) | Queries ClickHouse for last 168h of cost data |
| 4 | forecast-worker | Runs TimesFM to produce mean, p10, p90 for next 24h |
| 5 | forecast-worker | Calls POST /v1/metrics/forecast on the SDK API with results |
| 6 | instrumentation-sdk-api | Sets observable Prometheus gauges: llm_forecast_cost_mean_usd_micro, etc. |
| 7 | Prometheus | Scrapes port 9464 every 5s |
| 8 | Grafana | Renders forecast band charts from Prometheus data |
8000, 3000, 9090, 4317 available on your hostdocker login -u chiefj
# Enter your token when prompted
docker pull chiefj/instrumentation-sdk-api:latest
docker pull chiefj/forecast-worker:latest
Start the main observability stack (FastAPI + Prometheus + Grafana + Tempo in one container):
docker run -d \
--name observability-stack \
-p 8000:8000 \
-p 3000:3000 \
-p 9090:9090 \
-p 4317:4317 \
chiefj/instrumentation-sdk-api:latest
Wait ~10 seconds for all services to start, then verify:
curl http://localhost:8000/v1/metrics/health
# → {"initialized": true, "message": "# HELP ..."}
The worker needs access to your ClickHouse instance and the SDK API:
docker run -d \
--name forecast-worker \
-e CLICKHOUSE_HOST=your-clickhouse-host \
-e CLICKHOUSE_PORT=8123 \
-e CLICKHOUSE_DATABASE=llm_observability \
-e CLICKHOUSE_USER=default \
-e CLICKHOUSE_PASSWORD=your-password \
-e INSTRUMENTATION_SDK_URL=http://observability-stack:8000 \
-e TEMPORAL_HOST=your-temporal-host:7233 \
--link observability-stack \
chiefj/forecast-worker:latest
Tip: For local development without ClickHouse, you can push forecast results directly to the SDK API (see Step 5 below).
Navigate to http://localhost:3000 (default credentials: admin / admin).
Go to Dashboards → LLM Observability folder. You'll see:
| Dashboard | What It Shows |
|---|---|
| LLM Cost Forecast Dashboard | Mean/p10/p90 forecast band, uncertainty width, actual vs forecast overlay |
| LLM Cost Dashboard | Cumulative cost, cost by model/service, forecast panel |
| LLM Security & Safety Dashboard | PII detections, injection attempts, violation trends |
| LLM Latency & TTFT Dashboard | p50/p95/p99 latency, time-to-first-token by model |
| LLM Guardrails Dashboard | Invariant breach tracking, human review SLO |
If you're integrating your own forecasting model or want to push forecasts programmatically without the Temporal worker, you can call the SDK directly:
curl -X POST http://localhost:8000/v1/metrics/forecast \
-H "Content-Type: application/json" \
-d '{
"mean": 9200,
"p10": 3800,
"p90": 17500,
"model": "gpt-4o",
"provider": "openai",
"service_name": "my-chat-app"
}'
| Field | Type | Description |
|---|---|---|
mean | int | Expected cost in micro-USD (e.g. 9200 = $0.0092) |
p10 | int | Optimistic lower bound (10th percentile) |
p90 | int | Pessimistic upper bound (90th percentile) |
model | str | LLM model name (e.g. "gpt-4o") |
provider | str | LLM provider (e.g. "openai", "anthropic") |
service_name | str | Your service identifier |
import httpx
SDK_URL = "http://localhost:8000"
# Record a span (actual usage)
httpx.post(f"{SDK_URL}/v1/metrics/record", json={
"model": "gpt-4o",
"provider": "openai",
"service_name": "my-chat-app",
"prompt_tokens": 1500,
"completion_tokens": 400,
"latency_ms_total": 1800,
"latency_ms_ttft": 320,
"finish_reason": "stop",
"status": "success",
"cost_usd_micro": 7200,
"pii_detected": False,
"injection_attempt": False,
})
# Push a forecast (from your own model or from forecast-worker output)
httpx.post(f"{SDK_URL}/v1/metrics/forecast", json={
"mean": 9200,
"p10": 3800,
"p90": 17500,
"model": "gpt-4o",
"provider": "openai",
"service_name": "my-chat-app",
})
After sending at least one forecast, open Grafana and go to:
Dashboards → LLM Observability → LLM Cost Forecast Dashboard
You'll see:
Mean (blue, solid), p10 (green, dashed), p90 (orange, dashed). This is the confidence interval band produced by TimesFM.Metric names in Prometheus (for custom PromQL queries):
llm_forecast_cost_mean_usd_micro{model="gpt-4o", service_name="my-chat-app"} llm_forecast_cost_p10_usd_micro{model="gpt-4o", service_name="my-chat-app"} llm_forecast_cost_p90_usd_micro{model="gpt-4o", service_name="my-chat-app"}Divide by
1000000to convert from micro-USD to USD.
[Cron: 5 * * * *]
│
▼
1. fetch_cost_series (Activity F-FM-01)
│ Queries ClickHouse cost_by_dimension for last 168 hours
▼
2. ForecastService.build_dense_series (Domain Layer)
│ Zero-pads missing hours, validates min_history_hours >= 48
▼
3. TimesFM Inference (Adapter Layer)
│ google/timesfm-2.5-200m-pytorch
│ context_len=168, patch_len=32, horizon=24
▼
4. Quantile Projection
├── forecast_mean → Expected cost trend
├── forecast_p10 → Optimistic lower bound
└── forecast_p90 → Worst-case for budget breach alerting
│
▼
5. POST /v1/metrics/forecast → instrumentation-sdk-api
│
▼
6. OTel Observable Gauge → Prometheus :9464 → Grafana
google/timesfm-2.5-200m-pytorch (registered in platform Model Registry)168 hours (1 week of history)3224 hours aheadtorch --index-url https://download.pytorch.org/whl/cpu)~/.cache/huggingface as a Docker volume to avoid re-downloading on restart:docker run -d \
--name forecast-worker \
-v ~/.cache/huggingface:/root/.cache/huggingface \
... \
chiefj/forecast-worker:latest
Migrations live in database/migrations/ and use immutable versioned SQL files:
| File | Purpose |
|---|---|
0001_init.sql | Creates forecasts table with forecast_mean, forecast_p10, forecast_p90, forecast_time, per service/model (unique constraint) |
0001_init.rollback.sql | Reverts the schema |
schema.lock | Current schema hash lock |
packages/python/forecast-worker/
├── build/
│ └── Dockerfile # CPU-optimized PyTorch container
├── contracts/
│ └── workflows/
│ └── forecast_workflow.yaml
├── database/
│ ├── migrations/
│ │ ├── 0001_init.rollback.sql
│ │ └── 0001_init.sql
│ └── schema.lock
├── outcome/ # Generated forecast graphs (PNG/SVG)
├── scripts/
│ └── test.sh
├── src/
│ ├── features/forecast/
│ │ └── service.py # Dense series + business validation
│ ├── infra/adapters/clickhouse/
│ │ └── clickhouse_adapter.py
│ ├── shared/
│ │ ├── contracts/validator.py
│ │ ├── errors/base.py
│ │ └── ports/clickhouse_port.py
│ └── worker/
│ └── index.py # Entry point
├── tests/unit/
├── pyproject.toml
├── worker-registry.yaml
└── README.md
python3 -m venv --without-pip .venv
.venv/bin/python3 get-pip.py
.venv/bin/pip install -e ".[dev]"
./scripts/test.sh
Or with coverage:
.venv/bin/pytest --cov=src --cov-report=term-missing
Minimum coverage threshold: 80% (currently 94% package-wide).
| Variable | Required | Default | Description |
|---|---|---|---|
CLICKHOUSE_HOST | ✅ | — | ClickHouse hostname |
CLICKHOUSE_PORT | ❌ | 8123 | ClickHouse HTTP port |
CLICKHOUSE_DATABASE | ❌ | llm_observability | Database name |
CLICKHOUSE_USER | ❌ | default | ClickHouse user |
CLICKHOUSE_PASSWORD | ✅ | — | ClickHouse password |
INSTRUMENTATION_SDK_URL | ✅ | — | Base URL of the SDK API (e.g. http://observability-stack:8000) |
TEMPORAL_HOST | ✅ | — | Temporal server address (e.g. temporal:7233) |
TEMPORAL_NAMESPACE | ❌ | default | Temporal namespace |
FORECAST_CRON_SCHEDULE | ❌ | 5 * * * * | Cron expression for forecast cadence |
MIN_HISTORY_HOURS | ❌ | 48 | Minimum hours of history required to run forecast |
| Image | Tag | Size | Description |
|---|---|---|---|
chiefj/instrumentation-sdk-api | latest | ~2.1 GB | FastAPI + Prometheus + Grafana + Tempo all-in-one observability stack |
chiefj/forecast-worker | latest | ~3.5 GB | TimesFM CPU inference worker + Temporal integration |
# Pull and run the full stack
docker pull chiefj/instrumentation-sdk-api:latest
docker pull chiefj/forecast-worker:latest
Content type
Image
Digest
sha256:c31b6d6c0…
Size
707.9 MB
Last updated
3 months ago
docker pull chiefj/forecast-worker