Bound Upstream Dispatch
Selects an upstream endpoint straight from the logical binding, with no second router
Versions marked “overview” do not contain this page. Selecting one opens that version’s documentation overview.
Category: Setup-dependent integration
Task: Selects an upstream endpoint straight from the logical binding, with no second router
Prerequisites: The external service, credentials, or certificates referenced by this configuration.
This configuration comes from the selected release. The example has not been run here; external services are not bundled.
Download the source file.
# Bound Upstream Dispatch
#
# Selects an upstream endpoint straight from the logical binding, with no
# second router. A binding router publishes one logical cluster; a
# load_balancer with `cluster_source: bound_upstream` then resolves that
# frozen binding and picks an endpoint. This drives one pipeline with two
# ownership modes: an OpenAI-tagged request is dispatched directly by a
# branch, while every other request runs gateway-owned processing and an
# iterative_request_router that also dispatches from the same binding.
#
# Requests under /openai/ bind the openai cluster and take the direct
# branch, so gateway filters and the IRR are skipped. Every other request
# binds the chat cluster, gains an X-Gateway-Processed marker, and is
# dispatched by the IRR step's bound-consuming load balancer.
#
# Usage:
# cargo run -p praxis-proxy --features iterative-request-router -- -c examples/configs/traffic-management/bound-upstream-dispatch.yaml
# curl -i http://localhost:8080/openai/v1/responses # direct dispatch
# curl -i http://localhost:8080/v1/chat/completions # gateway + IRR dispatch
listeners:
- name: default
address: "127.0.0.1:8080"
filter_chains:
- main
filter_chains:
- name: main
filters:
# 1. The router binds one logical cluster from a trusted routing fact
# (here the request path). It selects no endpoint; the binding
# carries only the logical cluster name and its catalog metadata.
- filter: router
routes:
- path_prefix: "/openai/"
cluster: openai_backend
- path_prefix: "/"
cluster: chat_backend
# 2. Direct provider path: gated on the bound provider, this branch
# dispatches straight from the binding via a bound-consuming load
# balancer and rejoins `terminal`, so the gateway processing and
# IRR below never run for a provider-owned request. The headers
# filter intentionally has no mutations; it is the branch host.
#
# The IRR's bounded StreamBuffer is a pipeline-wide capability, so
# direct requests are still fully pre-read and share its body limit
# before routing decides which path runs.
- filter: headers
conditions:
- when:
bound_upstream:
application_provider: openai
branch_chains:
- name: direct-openai
rejoin: terminal
chains:
- name: direct-openai-dispatch
filters:
- filter: load_balancer
cluster_source: bound_upstream
clusters:
- name: openai_backend
http:
application_protocol: openai_responses
application_provider: openai
endpoints:
- "127.0.0.1:3001"
# 3. Gateway-owned processing. The direct branch above rejoins
# `terminal`, so a provider-owned request never reaches here;
# only the non-provider path is marked.
- filter: headers
response_set:
- name: "X-Gateway-Processed"
value: "true"
# 4. The iterative_request_router owns the non-provider exchange
# lifecycle. Its step selects an endpoint from the same frozen
# binding through a bound-consuming load balancer: no second
# router, no top-level load balancer. Provider-owned requests
# already terminated at the direct branch, which is also why the
# IRR carries no bound_upstream condition of its own. It could
# not: the IRR pre-reads the request body, and validation rejects
# a bound condition on a pre-read body hook.
- filter: iterative_request_router
initial_step: inference
max_state_bytes: 65536
steps:
- name: inference
filters:
- filter: load_balancer
cluster_source: bound_upstream
clusters:
- name: chat_backend
http:
application_protocol: openai_chat_completions
application_provider: vllm
endpoints:
- "127.0.0.1:3002"
on_result:
- default: true
done: true
insecure_options:
allow_private_endpoints: true # example proxies to local backends