Skip to content

feat(filter): add rehydrate filter for previous_response_id (#494)#604

Draft
leseb wants to merge 9 commits into
praxis-proxy:mainfrom
leseb:leseb/rehydrate-filter-494-previous-resp-id
Draft

feat(filter): add rehydrate filter for previous_response_id (#494)#604
leseb wants to merge 9 commits into
praxis-proxy:mainfrom
leseb:leseb/rehydrate-filter-494-previous-resp-id

Conversation

@leseb

@leseb leseb commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds the openai_responses_rehydrate filter that loads conversation context from previous_response_id by fetching stored responses and prepending their message history to the current request's input field
  • Extends ResponseStoreFilter to register its store backend in ctx.response_stores during both on_request and on_request_body, ensuring availability during StreamBuffer pre-read when body filters run before request filters
  • Includes 23 unit tests (with MockStore), 2 integration tests, example config (rehydrate.yaml), and updates to full-flow.yaml
  • Deduplicates shared constants (DEFAULT_STORE_NAME, DEFAULT_TENANT_ID, TENANT_METADATA_KEY) into the parent responses module

Test plan

  • cargo test -p praxis-proxy-filter -- rehydrate — 23 unit tests pass
  • cargo test -p praxis-tests-integration --test suite -- rehydrate — 2 integration tests pass (store+rehydrate end-to-end, passthrough)
  • make lint — clean
  • make build — clean
  • make doc — clean

Closes #494

@leseb leseb force-pushed the leseb/rehydrate-filter-494-previous-resp-id branch from 8bfa482 to 3974848 Compare June 16, 2026 06:59
@leseb leseb marked this pull request as ready for review June 16, 2026 08:30
@leseb leseb requested review from a team June 16, 2026 08:30
@leseb leseb marked this pull request as draft June 16, 2026 09:27
@leseb

leseb commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

converting to a draft as i just realized that the filter should not mutate the body but just pass a state - reworking this

@praxis-bot

Copy link
Copy Markdown
Collaborator

This PR has been in draft state for some time, so we're just checking in: If you're in need of any help to get this unblocked, please don't hesitate to reach out to @praxis-proxy/praxis-maintainers for support! 🖖

leseb and others added 8 commits June 22, 2026 08:56
Add ResponsesState, stored in RequestExtensions, to carry parsed
request data across the validate → rehydrate → tool_parse →
responses_proxy → stream_events → tool_dispatch pipeline.

Extracts input messages (string or array), tools, and tool_choice
from the request body; tracks tool calls, output items, usage, and
agentic loop iteration across inference rounds.

Signed-off-by: Sébastien Han <seb@redhat.com>
The openai_response_store filter now registers its initialized store
backend as 'default' in the ResponseStoreRegistry on ctx, making it
accessible to downstream filters (rehydrate, compact) that need to
read stored responses.

Signed-off-by: Sébastien Han <seb@redhat.com>
Implement the rehydrate filter that loads conversation context from
`previous_response_id` by fetching stored responses and prepending
their message history to the current request's `input` field.

The filter runs in on_request_body at end-of-stream with ReadWrite
body access. It reads from the ResponseStoreRegistry populated by
the upstream openai_response_store filter, validates the stored
response status is 'completed', assembles previous messages with
current input, and writes the enriched body back. Falls back to
reconstructing from input + output when messages is null/empty.

Signed-off-by: Sébastien Han <seb@redhat.com>
The response store filter now always initializes its backend during
on_request, regardless of format metadata. This is necessary because
format metadata is set by the classifier in on_request_body (a later
phase), and downstream filters like rehydrate need the store available
during on_request_body. The skip_persist guard still gates actual
persistence in later phases.

Adds the rehydrate example config, integration test, test-utils
ai-inference feature passthrough, and README entry.

Signed-off-by: Sébastien Han <seb@redhat.com>
With StreamBuffer mode, the Pingora handler runs pre_read_body
(calling all filters' on_request_body) BEFORE on_request. The
store filter only registered its backend in on_request, so the
rehydrate filter couldn't find it during pre-read. Add
on_request_body to ResponseStoreFilter that eagerly initializes
and registers at end-of-stream.

Also renames filter to openai_responses_rehydrate, fixes
user_message_item to take &str, and deduplicates shared constants
into the parent responses module.

Signed-off-by: Sébastien Han <seb@redhat.com>
…ing body

The rehydrate filter no longer rewrites the request body with assembled
conversation history. It validates the previous_response_id, confirms
the stored response status is completed, and promotes the ID to filter
metadata. Body construction is deferred to the downstream responses_proxy
filter which will consume ResponsesState.

Also fixes post-rebase build errors: defines store constants locally,
updates SqliteResponseStore::new call signature, and resolves clippy
lints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
…hydrate

After validating the previous response, the rehydrate filter now creates
ResponsesState from the parsed request body and prepends the stored
conversation history to the current input messages. Downstream filters
read from state.messages instead of the raw body.

Signed-off-by: Sébastien Han <seb@redhat.com>
- Gate store initialization on classifier metadata: only open the
  backend for Responses API traffic that needs persistence or
  rehydration, not for unrelated POSTs.
- Capture the original request input during request-body processing
  and persist it alongside the response, since a spec-conformant
  ResponseResource does not include the create-request input field.
- Scope response store registrations per pipeline/listener to prevent
  cross-listener data leakage when multiple listeners or hot-reloaded
  configs use different store backends.

Signed-off-by: Sébastien Han <seb@redhat.com>
@leseb leseb force-pushed the leseb/rehydrate-filter-494-previous-resp-id branch from 3974848 to 884fa92 Compare June 22, 2026 10:09
@leseb

leseb commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator Author

this now lives on top of #635

Fix three CI failures:
- Clippy: use map_or_else instead of map().unwrap_or_else() in
  ResponseCapture::from_response_json
- Rustdoc: remove intra-doc links to private ResponsesState from
  public RehydrateFilter docs
- Test: update postgres store test to expect both normalized input
  and output in persisted messages (matching sqlite test)

Signed-off-by: Sébastien Han <seb@redhat.com>
@leseb

leseb commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator Author

ok i extracted some portions into #646 so this one will be smaller to review

leseb added a commit to leseb/praxis that referenced this pull request Jun 22, 2026
The pipeline_persists_rehydrated_messages_when_response_omits_input
test depends on the openai_responses_rehydrate filter which lives in
the rehydrate PR (praxis-proxy#604). Move it there.

Signed-off-by: Sébastien Han <seb@redhat.com>
leseb added a commit to leseb/praxis that referenced this pull request Jun 22, 2026
The pipeline_persists_rehydrated_messages_when_response_omits_input
test depends on the openai_responses_rehydrate filter which lives in
the rehydrate PR (praxis-proxy#604). Move it there.

Signed-off-by: Sébastien Han <seb@redhat.com>
leseb added a commit to leseb/praxis that referenced this pull request Jun 22, 2026
The pipeline_persists_rehydrated_messages_when_response_omits_input
test depends on the openai_responses_rehydrate filter which lives in
the rehydrate PR (praxis-proxy#604). Move it there.

Signed-off-by: Sébastien Han <seb@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rehydrate filter: previous_response_id path + filter skeleton

3 participants