Responses To Chat Completions Reasoning
Translates finite and streaming vLLM reasoning into Responses reasoning output items, including storage and replay on continuation
Category: Setup-dependent integration
Task: Translates finite and streaming vLLM reasoning into Responses reasoning output items, including storage and replay on continuation
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.
Companion resources from the same snapshot:
# Responses API to Chat Completions with reasoning translation
# Requires `--no-default-features --features store-sqlite` because SQLite is opt-in.
#
# Translates finite and streaming vLLM reasoning into Responses reasoning
# output items, including storage and replay on continuation.
#
# A single iterative_request_router step contains openai_stream_events before
# the translator so streamed terminal resources reach the response store.
# See the base example for the request/response ordering and matched limits.
#
# Same pipeline as responses-to-chat-completions.yaml, but targets a vLLM
# backend that returns raw reasoning in choices[].message.reasoning. The reasoning
# dialect promotes that raw reasoning into a Responses reasoning output item.
#
# Reasoning dialect contract (see the openai_responses_to_chat_completions filter):
# - dialect: vllm reads message.reasoning (the current vLLM field) and
# falls back to the deprecated message.reasoning_content alias
# for older backends, emitting it as a reasoning item whose content is
# reasoning_text. Raw reasoning is never placed in the item summary, which
# is reserved for safe summaries.
# - vLLM has no safe-summary capability, so a client that requests
# reasoning.summary (or the deprecated reasoning.generate_summary) is
# rejected with a 400 before the request is forwarded.
# - max_reasoning_bytes bounds the raw reasoning preserved per
# response; oversized reasoning fails closed rather than truncating.
# - On a continuation, raw reasoning is replayed in the following
# assistant turn's reasoning field, preserving its ordinary content.
# - Reasoning-only output is replayed as its own assistant message before
# the next non-assistant item or at the end of input.
# - Reasoning input requires an enabled dialect and non-empty raw reasoning_text
# content. Encrypted, summary-only, and malformed items are rejected with 400.
#
# Streaming requests use the same dialect and limits. delta.reasoning (or the
# deprecated delta.reasoning_content) becomes response.reasoning_text.delta,
# followed by reasoning text/content/item completion events and a terminal
# response containing the same raw reasoning item. Stored streaming output is
# replayable on continuation. Malformed or oversized reasoning fails the stream
# with response.failed; raw reasoning is never truncated or exposed as a summary.
listeners:
- name: responses-chat-reasoning-gateway
address: "127.0.0.1:8080"
filter_chains: [responses-chat-reasoning-translation]
filter_chains:
- name: responses-chat-reasoning-translation
filters:
# Publishes the typed operation consumed by openai_responses_request.
- filter: ai_operation
- filter: openai_responses_request
initialize_state: false
on_invalid: reject
headers:
format: x-praxis-ai-format
model: x-praxis-ai-model
stream: x-praxis-ai-stream
- filter: openai_responses_request
on_invalid: reject
headers:
format: ~
model: ~
stream: ~
mode: ~
- filter: state_owner
mode: single_tenant
tenant_id: default
- filter: openai_responses_store
backend: sqlite
database_url: "sqlite://responses.db?mode=rwc"
responses_table: openai_responses
conversations_table: openai_conversations
- filter: openai_responses_rehydrate
- filter: iterative_request_router
initial_step: inference
max_iterations: 1
# The OpenResponses suite drives concurrent requests through a CPU
# backend, and production model streams may also exceed the IRR's 30s
# default. This single step inherits an 11-minute total deadline for
# time-to-first-byte plus response streaming; the accumulator below
# independently caps its portion of the stream at 10 minutes.
timeout_ms: 660000
steps:
- name: inference
filters:
- filter: openai_stream_events
# Accumulates every Responses SSE event the converter emits below
# so the store can persist streamed turns. Always composes the
# current IRR execution into one logical Responses stream, so it
# must run inside this step. Its limits must never be tighter than
# the converter's: if this filter aborted a stream the client had
# already received, that turn would silently skip persistence.
#
# - max_events sits at or above the converter's max_stream_events,
# which bounds the converter's TOTAL emitted events (terminal
# included), so an accepted stream never exceeds this event budget.
# - max_buffer_bytes must hold the largest single frame the converter
# can emit. The converter refuses to emit any frame larger than its
# max_emitted_sse_frame_bytes, so this buffer is set strictly above
# that ceiling (leaving SSE-framing headroom) so no frame the client
# receives is ever rejected here and dropped from the store.
# - timeout_secs is no shorter than the converter's stream_timeout_secs.
max_buffer_bytes: 67108864
max_events: 200000
timeout_secs: 600
- filter: openai_responses_to_chat_completions
# The converter is the authority on stream bounds and fails closed
# when it exceeds them. Keep every limit at or below
# openai_stream_events' above so any stream the client receives is
# always within the accumulator's capacity and is never silently
# dropped from the store.
#
# The size ceilings form a chain, smallest to largest:
# max_rewritten_body_bytes <= max_emitted_sse_frame_bytes
# < accumulator max_buffer_bytes
# - max_rewritten_body_bytes bounds the response resource a
# terminal/lifecycle frame carries.
# - max_emitted_sse_frame_bytes bounds the COMPLETE encoded frame
# (resource plus SSE framing). Kept at or above
# max_rewritten_body_bytes so legitimate frames are not rejected,
# and strictly below the accumulator's max_buffer_bytes so every
# emitted frame fits the buffer it is reassembled in. A frame that
# would exceed this ceiling fails closed with a minimal
# response.failed instead of being emitted and later dropped
# downstream.
reasoning:
dialect: vllm
max_reasoning_bytes: 65536
max_rewritten_body_bytes: 33554432
max_emitted_sse_frame_bytes: 50331648
max_stream_events: 100000
stream_timeout_secs: 300
- filter: path_rewrite
replace:
pattern: "^/v1/responses/?$"
replacement: "/v1/chat/completions"
conditions:
- when:
path_prefix: "/v1/responses"
methods: [POST]
- filter: router
routes:
- path_prefix: "/"
cluster: "chat-completions-backend"
- filter: load_balancer
clusters:
- name: "chat-completions-backend"
endpoints:
- "127.0.0.1:3001"
on_result:
- default: true
done: true
insecure_options:
allow_private_endpoints: true # example proxies to local backends