Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions demos/anthropic-messages/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Generated by envsubst from the .tmpl files at demo run time -- see README.md.
passthrough.yaml
transform.yaml
17 changes: 13 additions & 4 deletions demos/anthropic-messages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@ to different backends with optional format transformation.
|------|-------------|
| `goals.md` | One-page summary of what this demo covers |
| `demo.md` | Step-by-step walkthrough with curl commands |
| `passthrough.yaml` | Config: classify, validate JSON envelopes, route by model to Anthropic API or vLLM |
| `transform.yaml` | Config: transform Anthropic Messages to OpenAI Chat Completions |
| `passthrough.yaml.tmpl` | Config template: classify, validate JSON envelopes, route by model to Anthropic API or vLLM |
| `transform.yaml.tmpl` | Config template: transform Anthropic Messages to OpenAI Chat Completions |

Both `.tmpl` files are expanded via `envsubst` into `passthrough.yaml` /
`transform.yaml` (git-ignored, regenerated each run) — see Quick Start below.

## Quick Start

```bash
# Point at your vLLM endpoint (defaults to 10.0.0.99:8000 if unset)
export VLLM_ENDPOINT="${VLLM_ENDPOINT:-10.0.0.99:8000}"
envsubst < passthrough.yaml.tmpl > passthrough.yaml
envsubst < transform.yaml.tmpl > transform.yaml

# Passthrough (demos 1-3)
export ANTHROPIC_API_KEY=sk-ant-...
RUST_LOG=praxis_filter=debug cargo run -p praxis-proxy --release -- \
Expand All @@ -28,6 +36,7 @@ RUST_LOG=praxis_filter=debug cargo run -p praxis-proxy --release -- \
## Prerequisites

- **Praxis** built from source (`cargo build -p praxis-proxy --release`)
- `envsubst` (`brew install gettext` on macOS) to render the `.tmpl` configs
- `ANTHROPIC_API_KEY` env var set (for Anthropic API passthrough)
- vLLM endpoint at `10.0.0.99:8000` (update `passthrough.yaml` /
`transform.yaml` for your setup)
- A reachable vLLM endpoint — set `VLLM_ENDPOINT` (defaults to `10.0.0.99:8000`,
a placeholder unlikely to be reachable from your network)
6 changes: 6 additions & 0 deletions demos/anthropic-messages/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
## Setup

```
# Render configs (defaults to a placeholder vLLM endpoint -- set VLLM_ENDPOINT
# to point at a real one)
export VLLM_ENDPOINT="${VLLM_ENDPOINT:-10.0.0.99:8000}"
envsubst < passthrough.yaml.tmpl > passthrough.yaml
envsubst < transform.yaml.tmpl > transform.yaml

# Terminal 1 (Praxis logs)
RUST_LOG=praxis_filter=debug cargo run -p praxis-proxy --release -- -c passthrough.yaml 2>&1 \
| grep -E 'classified|validation|route matched|upstream selected|credential|transformed|streaming'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ filter_chains:
sni: "api.anthropic.com"
- name: vllm
endpoints:
- "10.0.0.99:8000"
- "${VLLM_ENDPOINT}"
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ filter_chains:
clusters:
- name: vllm
endpoints:
- "10.0.0.99:8000"
- "${VLLM_ENDPOINT}"
51 changes: 51 additions & 0 deletions demos/maas-ipp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,37 @@ Forge owns cluster lifecycle and infra stacks. `maas-controller` **owns EnvoyFil

This profile is for MaaS + Praxis integration work. It is **not** the Grid multi-cluster GLB demo, and it intentionally diverges from issue #2’s “CRDs-only / skip Authorino” simulation table.

## What this demo proves

MaaS (Models-as-a-Service on OpenShift AI) already ships a payload-processing
step — **IPP** — as an Envoy `ExternalProcessor` in front of every model call,
split into an **IPP-pre** hop (before Kuadrant auth: extract model identity,
resolve the MaaS auth identity, apply bounded request mutations) and an
**IPP-post** hop (after auth: protect the provider boundary, select the
provider, translate protocol, inject provider credentials). Today that
processor is a stock ODH container (`odh-ai-gateway-payload-processing`).

This demo swaps that one container for **Praxis**, with nothing else in the
stock MaaS datapath changed — same Gateway, same Kuadrant `AuthPolicy`, same
CRDs, same HTTPRoute. `MAAS_IPP_PROFILE` is the switch:

| `MAAS_IPP_PROFILE` | IPP-pre/IPP-post implementation | Platform manifests overlay |
|---|---|---|
| `llm-d` (stock) | `odh-ai-gateway-payload-processing` (`$IPP_IMAGE`) | `overlays/xks` |
| `praxis` (this lab's default) | Praxis (`$PRAXIS_EXTPROC_IMAGE`), implementing the same Pre-Auth/Post-Auth contract | `overlays/xks-praxis` |

That table is the actual point of the demo: it's evidence Praxis can sit in
MaaS's existing `ExternalProcessor` slot and preserve IPP's pre/post-auth
behavior, not a new, separate datapath that MaaS would have to adopt
wholesale. From a roadmap perspective, that's the difference between "replace
your gateway" and "replace one container" — the latter is a far smaller ask
of a platform team already running MaaS in production.

The `## Call models` walkthrough below exercises both hops end-to-end
(model-identity extraction pre-auth, provider selection and credential
injection post-auth) against two backend shapes MaaS supports: an in-cluster
`LLMInferenceService` sim and an `ExternalModel`.

## Pins

Version and namespace pins live in `forge.yaml` cluster `properties`. Stacks
Expand Down Expand Up @@ -107,6 +138,13 @@ echo "$API_KEY"

## Call models

> Every request below crosses the full stock datapath — `Client → Istio
> Gateway → IPP-pre → Kuadrant Auth → IPP-post → HTTPRoute → LLM sim` — with
> Praxis standing in for IPP-pre/IPP-post per `MAAS_IPP_PROFILE=praxis`. What
> to watch: the request succeeds identically to how it would against the
> stock `llm-d` IPP profile — that equivalence, not the model's answer, is
> the thing this demo is proving.

Gateway LB (MetalLB on the Kind docker network — reachable from the Kind host):

```bash
Expand All @@ -116,6 +154,14 @@ GW=$(kubectl --context kind-maas-ipp-local -n istio-system \

**Internal llm-d sim** (`LLMInferenceService` `sim-internal`):

> Praxis's IPP-pre hop reads the `/llm-internal/sim-internal/...` path and
> `model` field to resolve which MaaS-registered model this call targets
> *before* Kuadrant decides whether the bearer token is allowed to call it.
> Post-auth, IPP-post resolves `sim-internal` to its backing
> `LLMInferenceService` and forwards — the same provider-selection step the
> stock IPP container performs, just implemented by Praxis's filter chain
> instead.

```bash
curl -sk "https://${GW}/llm-internal/sim-internal/v1/chat/completions" \
-H "Authorization: Bearer ${API_KEY}" \
Expand All @@ -125,6 +171,11 @@ curl -sk "https://${GW}/llm-internal/sim-internal/v1/chat/completions" \

**External model** (`ExternalModel` `llm-katan-openai` — remote simulator must be reachable):

> Same pre/post-auth contract, but IPP-post now resolves to an `ExternalModel`
> instead of an in-cluster `LLMInferenceService` — the provider-boundary and
> credential-injection step that matters most for customers routing to
> models MaaS itself doesn't host.

```bash
curl -sk "https://${GW}/llm/llm-katan-openai/v1/chat/completions" \
-H "Authorization: Bearer ${API_KEY}" \
Expand Down