fix(validate): reject conflicting history selectors - #999
Conversation
23f99b5 to
dc2af51
Compare
| .get_metadata("openai_responses_format.stream") | ||
| .is_some_and(|value| value == "true"); | ||
| reject_invalid( | ||
| "Mutually exclusive parameters. Ensure you are only providing one of: 'previous_response_id' or 'conversation'.", |
There was a problem hiding this comment.
we should return something similar to openai i guess:
{
"message": "Mutually exclusive parameters: ''. Ensure you are only providing one of: 'pre..._id' or 'conversation'.",
"type": "invalid_request_error",
"param": null,
"code": "mutually_exclusive_parameters"
}
i don't mind about the message but the code is incorrect here
There was a problem hiding this comment.
I can fix this, but I think we have the same (pre existing problem) on many error responses,
when developing this all of the errors messages I looked at used responses_error_body (or responses_error_sse_payload), this takes a single code param and then uses it for both code and type in the error response.
ai/apis/src/openai/responses/error.rs
Lines 16 to 24 in d5145ff
There was a problem hiding this comment.
Let's fix it here and have another PR for the rest of the code, how does that sound?
|
@derekhiggins also please add a test in test_openai_responses_vllm.py to validate the error shape with the client, thanks (streaming and buffered mode). |
Reject Responses API requests that provide both previous_response_id and conversation instead of silently prioritizing one history source. Return an OpenAI-compatible invalid-request error in JSON or SSE as appropriate, and validate both forms through the official OpenAI client. Fixes: praxis-proxy#840 Signed-off-by: Derek Higgins <derekh@redhat.com>
dc2af51 to
a335a94
Compare
praxis-bot
left a comment
There was a problem hiding this comment.
Clean, well-layered change. Moving the mutual-exclusion check into the validate filter is the right call -- it catches the conflict before generating IDs, enriching context, or touching the store. The null-awareness in reject_conflicting_history_selectors correctly handles previous_response_id: null (OpenAI SDK sends this for absent fields). Test coverage is thorough: unit tests for both buffered and streaming rejection, integration test with a real conversation + nonexistent response ID to prove the rejection fires before any store lookup, and SDK-level tests that verify the exact error shape the OpenAI client parses.
One documentation gap noted below.
| @@ -100,9 +100,6 @@ impl RehydrateFilter { | |||
| /// Parse body, resolve rehydration source (`previous_response_id` or | |||
There was a problem hiding this comment.
[Medium] The precedence doc comment is removed, but the rehydrate method still resolves previous_response_id before conversation (lines 115-117). Since the upstream validate filter now rejects conflicts, this code path is unreachable in the standard pipeline -- but it remains as an implicit defense-in-depth fallback.
Add a brief doc note so future maintainers know this ordering exists intentionally and that the validate filter is the expected enforcement point:
/// Parse body, resolve rehydration source (`previous_response_id` or
/// `conversation`), and populate [`ResponsesState`] with the full
/// conversation history.
///
/// The upstream `openai_responses_validate` filter rejects requests
/// that supply both selectors; the resolution order here is a
/// silent fallback only.
Reject Responses API requests that provide both previous_response_id and conversation instead of silently prioritizing one history source. Return the OpenAI-compatible invalid-request JSON error for both streaming and non-streaming requests.
Fixes: #840