Skip to content

feat(demos): ai-gateway demo with tiered token budgets and span-derived dashboards - #23

Merged
Ladas merged 1 commit into
praxis-proxy:mainfrom
Ladas:feat/ai-gateway-demo
Sep 10, 2026
Merged

feat(demos): ai-gateway demo with tiered token budgets and span-derived dashboards#23
Ladas merged 1 commit into
praxis-proxy:mainfrom
Ladas:feat/ai-gateway-demo

Conversation

@Ladas

@Ladas Ladas commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Adds demos/ai-gateway: the Praxis experimental gateway on its own KIND
cluster, in front of a real local model (Ollama), enforcing per-tier token
budgets, with Grafana dashboards built from Prometheus counters and Tempo span
metrics. Also enables the token-rate-limit-filter cargo feature on the
experimental server — this image exists to expose feature-gated upstream work,
and previously enabled only one of the four experimental AI filters.

What it demonstrates

Token budgets that a real workload actually reaches. Five rules in
configs/token-budget.yaml, evaluated in order, first match wins:

Rule Match Budget
premium X-Tier: premium 20,000 tokens/min
agent-daily ×3 X-Model: one of qwen3.8:27b, qwen3-coder:30b, deepseek-r1:32b 10,000,000 tokens/day
free catch-all 5,000 tokens/min

scripts/rate-limit-demo.sh sends 100 requests per tier and reproduces
53 admitted / 47 denied on free against 100 / 0 on premium — same load, same
model, same moment. Denials are decided at admission, before the upstream call,
so a rejected request costs no GPU time.

A daily budget is the same filter with a longer window; that is all a "total
budget" is here. window accepts ms/s/m/h, so a day is "24h" and a
week "168h""7d" is rejected and praxis keeps the previous config.

Tiering without client cooperation. model_to_header promotes the request
body's model into X-Model, and rules match on it, so Codex, opencode and
Claude Code land on the right budget without sending a custom header — none of
them let you.

Reservations sized from measurement. A bare curl costs ~406 tokens; one
trivial Codex turn costs ~9,400, because system prompts and tool schemas
dominate. At reserved_tokens: 1500 that recorded 7,971 tokens of overage over
four turns; at 10,000 a turn settles as
estimated 10000 / actual 9435 / refunded 565 / overage 0.

Dashboards

Four dashboards, 31 panels, every one carrying an i tooltip explaining
what it plots, which telemetry plane it comes from, and what a wrong value looks
like.

Dashboard Panels
Praxis AI Gateway Overview 8
Praxis Token Budget & Rate Limiting 13
Praxis Filter Latency (from spans) 6
Praxis OTel Traces 4

They are built around a distinction that is easy to get wrong: Prometheus
counters cover 100% of requests and are sampling-independent; span metrics come
only from sampled traces.
Prometheus therefore owns counts, rates and budget
state, while span metrics own latency distributions and the per-filter
breakdown. The Weighted Filter Cost panel takes its quantile from spans and its
rate from praxis_http_requests_total for exactly this reason — multiplying two
sampled series would under-report by the sampling factor while looking
plausible. A Sampling Ratio Cross-Check panel divides one plane by the other; a
drop means spans are being lost rather than sampled.

Traps the demo is built to avoid

  • token_rate_limit must be declared before token_count. Response hooks
    run in reverse declared order, and it reconciles against the token.total
    metadata token_count writes. Declared after, it charges every request the
    flat estimate and the metrics look reasonable but are wrong — the signature is
    actual == estimated with zero refunds.
  • Ollama stays on its default loopback binding. A KIND pod reaches it via
    host.docker.internal, which the runtime proxies from the host side.
    OLLAMA_HOST=0.0.0.0 is unnecessary and publishes the model server to the
    local network.
  • The burst demo uses a small model deliberately. The limiter decides at
    admission, so the model is irrelevant to what is shown, but 100 requests must
    land inside the 1m window. qwen3.8:27b takes 74-78s under concurrency, so
    the budget would age out faster than it was consumed.

Caveats

  • token_rate_limit is experimental, behind a cargo feature; its parent
    proposal is not accepted (ai#796) and the config surface may change.
  • It does not authenticate — a header-matched rule trusts whatever reached
    it, so a real deployment needs an auth filter to set the tier header and strip
    client copies (grid#101).
  • allow_public_admin and allow_private_endpoints are demo-only: the first
    exposes /api/log-level and /api/kv to the cluster, the second disables SSRF
    hardening so the gateway can reach the host. Both are called out in the README.
  • The memory backend is per-process, and a sliding window retains one entry per
    request for the length of the window; backend.kind: valkey moves that state
    out of process.

Test plan

  • praxis-forge up creates ai-gw-local; all five stacks deploy
  • KIND pod reaches host Ollama via host.docker.internal
  • qwen3.8:27b answers through the gateway
  • Tiered rate limit reproduces 53/47 free and 100/0 premium across repeated runs
  • Codex through the gateway lands on agent-daily, confirmed by the admitted counter
  • All 31 panels render with data; queries verified against the live cluster
  • cargo check --locked, make lint, shellcheck, taplo, markdownlint clean

Depends on praxis-proxy/forge#16 (extraPortMappings), merged.

pr-size-check counts 2,147 added lines against a 750 limit because its
exemption list covers examples/, tests/, benchmarks/ and docs/ but not
demos/. Needs skip/pr-conventions, or demos/** added to that list.

page@6aa31177458395a3dcd779d1c07cc299.webm

@Ladas
Ladas force-pushed the feat/ai-gateway-demo branch 15 times, most recently from 73e8cb0 to 307588c Compare September 8, 2026 12:05
@nerdalert nerdalert added the skip/pr-conventions Skip conventions checks for PRs label Sep 8, 2026
Adds demos/ai-gateway: the Praxis experimental gateway on its own KIND
cluster, in front of a real local model (Ollama), enforcing per-tier token
budgets, with dashboards built from both Prometheus counters and Tempo span
metrics. Ports are offset from otel-benchmark so both demos run at once.

Enables the token-rate-limit-filter cargo feature on the experimental
server. This image exists to expose feature-gated upstream work and
previously enabled only one of the four experimental AI filters.

The dashboards are built around a distinction that is easy to get wrong.
Prometheus counters are incremented on every request and are unaffected by
trace sampling; span metrics are derived only from sampled traces. So
Prometheus owns counts, rates and budget state, while span metrics own
latency distributions and the per-filter breakdown. The weighted-cost panel
takes its quantile from span metrics and its request rate from
praxis_http_requests_total -- multiplying two sampled series would
under-report by exactly the sampling factor while looking plausible. A
cross-check panel divides one plane by the other, which should approximate
the configured sampling rate; a drop means spans are being lost rather than
sampled.

Documents four things that are easy to get wrong and produce
plausible-but-wrong results:

  - token_rate_limit must be declared before token_count. Response hooks run
    in reverse declared order, and it reconciles against the token.total
    metadata token_count writes. Declared after, it charges every request at
    its flat estimate and the metrics look reasonable.
  - Ollama stays on its default loopback binding. Containers reach it via
    host.docker.internal, which the container runtime proxies from the host
    side, so OLLAMA_HOST=0.0.0.0 needlessly exposes the model server.
  - The rate-limit demo uses a small model deliberately. The limiter decides
    at admission, before the upstream call, but the burst must land inside
    the sliding window. A 27B model takes 74-78s per request under
    concurrency, so the budget would age out faster than it was consumed and
    the limit would never be reached.
  - reserved_tokens is a flat estimate well above real usage, so the tier
    throttles at roughly a third of its nominal budget. That is the current
    upstream milestone's design, pending configurable estimation (ai#121).

All output in the README was captured from real runs.

Signed-off-by: Ladislav Smola <lsmola@redhat.com>
@Ladas
Ladas force-pushed the feat/ai-gateway-demo branch from 307588c to aef9cc7 Compare September 9, 2026 07:29
@Ladas
Ladas marked this pull request as ready for review September 9, 2026 08:37
@Ladas

Ladas commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

merging this demo, I have followup with podman and tweked quickstart that need image built with this rate limitng feature enabled

@Ladas
Ladas merged commit ef3156f into praxis-proxy:main Sep 10, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip/pr-conventions Skip conventions checks for PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants