feat(azure): add azure_to_openai translation filter - #910
Conversation
afccb81 to
590bb7d
Compare
praxis-bot
left a comment
There was a problem hiding this comment.
Review: feat(azure): add azure_to_openai translation filter
Well-structured filter following established {provider}/to_openai module patterns. Clean separation of request, response, SSE, and error normalization. Good reuse of the shared SseFrameParser for streaming. Unit test coverage (34 tests) and integration tests (5 tests) are thorough.
Findings
| # | Severity | File | Description |
|---|---|---|---|
| 1 | Medium | apis/src/azure/wire.rs |
normalize_error_response does not handle entirely-missing type field |
| 2 | Medium | apis/src/azure/to_openai/mod.rs |
rebuild_sse_frames silently drops event_type from SSE frames |
praxis-bot
left a comment
There was a problem hiding this comment.
PR Review
Azure-to-OpenAI translation filter — re-review focusing on new findings only (2 prior comments already posted).
1 new finding below.
280f2f5 to
726b1ec
Compare
|
@noalimoy do not force push - it's hard to determine which commits addressed reviews, PTAL https://github.com/praxis-proxy/ai/blob/main/CONTRIBUTING.md#pr-review-process |
leseb
left a comment
There was a problem hiding this comment.
A few finding from AI which look legit:
[P1] api-version is lost. Both the Azure filter and subsequent path_rewrite overwrite ctx.rewritten_path from the original URI. The documented ordering drops the required query parameter; reversing it drops the deployment path. Existing api-version values are also duplicated instead of replaced. The integration test never inspects the forwarded URI. Code, test, Azure requirement.
[P1] Asynchronous moderation evidence is deleted after content was streamed. The response transformation strips content_filter_results and content_filter_offsets. Azure asynchronous filtering emits text before moderation annotations; those offsets identify which emitted range must be redacted. Preserve/translate these annotations or reject unsupported asynchronous mode. Code, Azure streaming semantics.
Additionally, could we add an integration test using the official OpenAI Python SDK? It should cover non-streaming, streaming, and errors through the proxy, with a strict local fake Azure backend asserting the rewritten path, api-version, and request body. No live Azure endpoint is needed. Since client compatibility is the core contract of this filter, I consider this required before merging.
726b1ec to
6b192bb
Compare
|
@noalimoy please read https://github.com/praxis-proxy/ai/blob/main/CONTRIBUTING.md#pr-review-process on how to resolve praxis bot discussions. Thanks |
leseb
left a comment
There was a problem hiding this comment.
please resolve all the conversations when ready
@leseb sorry, that last force-push was a mistake. review fixes are in 6b192bb; :) |
|
@noalimoy lint fails |
fixed |
Head branch was pushed to by a user without write access
Normalize Azure OpenAI requests and responses into standard Chat Completions form: inject api-version, strip model from request body, remove content-filter fields from JSON and SSE responses, and fill type: null in error envelopes. SSE streaming reuses the shared SseFrameParser for correct cross-chunk buffering, CRLF handling, and buffer-overflow protection. Signed-off-by: noalimoy <nlimoy@redhat.com>
…K coverage - Compose with rewritten_path and replace api-version instead of duplicating it - Drop async-filter annotation chunks; keep finish_reason - Drop unstripped SSE on parse error - Add OpenAI Python SDK tests for path, query, body, streaming, and errors Signed-off-by: noalimoy <nlimoy@redhat.com>
Signed-off-by: noalimoy <nlimoy@redhat.com>
Signed-off-by: noalimoy <nlimoy@redhat.com>
be596b4 to
bb6843c
Compare
Summary
Add an
azure_to_openaiHttpFilter underapis/src/azure/that lets standard Chat Completions clients talk to Azure OpenAI deployments through Praxis. Azure's wire format is nearly identical to Chat Completions — the filter handles the small but operationally critical differences:api-versionquery parameter, strips themodelfield (Azure uses the deployment name in the URL path), removesAccept-Encodingto prevent double-compressed bodies.prompt_filter_resultsand per-choicecontent_filter_results/content_filter_offsets.SseFrameParserrather than hand-rolling line splitting — the parser already handles cross-chunk buffering, bare\rand split\r\nline endings,data:without space after colon, multi-linedata:joining, and buffer-overflow protection. The filter callsparse_chunk, strips Azure fields from each frame's data payload via the samestrip_azure_fieldsused for non-streaming JSON, and re-emits clean SSE.type: nullfrom the HTTP status code and stripsinnererror, normalizing Azure errors to the standard Chat Completions error envelope.SSE handled inline, not as a separate filter
Unlike
anthropic_stream_events(which is a standalone filter doing full format translation between OpenAI and Anthropic event schemas), Azure SSE is already Chat Completions format — the only work is stripping the samecontent_filter_resultsfields thatstrip_azure_fieldsalready removes from non-streaming JSON. A separate filter would add config surface and registration overhead for no new logic, so the SSE path lives alongside the JSON and error paths inon_response_body.Follows the same
{provider}/to_openaimodule structure established byanthropic/to_openai. This is the first provider filter for Epic #114 (Schema translation); the remaining providers (Bedrock, Vertex AI, Cohere) follow separately.Related issue
Part of #114
Closes #886
Validation
praxis-ai-apis(config parsing, request/response stripping, SSE frame rebuild, partial-line buffering via SseFrameParser, multi-byte UTF-8 boundary, error normalization)azure_translation.rs(request forwarding with api-version, content-filter stripping, SSE event stripping, error normalization withtype: null+innererror, pass-through of already-valid errors)make lint— clippy (default + all-features), nightly fmt, rustdoc all passChecklist
Signed-off-bytrailer.Breaking changes
None. New filter only; no existing behavior or API is changed.