fix(server): enable OTel tracing export - #751
Conversation
Signed-off-by: Sébastien Han <seb@redhat.com>
…s-proxy#743) Signed-off-by: Sébastien Han <seb@redhat.com>
…axis-proxy#740) Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
…raxis-proxy#750) Signed-off-by: Sébastien Han <seb@redhat.com>
…xis-proxy#749) Signed-off-by: Sébastien Han <seb@redhat.com>
…axis-proxy#722) Signed-off-by: Sébastien Han <seb@redhat.com>
…SSE parser (praxis-proxy#739) Signed-off-by: Sébastien Han <seb@redhat.com>
…d Content-Type (praxis-proxy#745) Signed-off-by: Sébastien Han <seb@redhat.com>
To migrate to this repository we simply based on the core repository, and this patch removes, modifies and updates everything to match the new AI filters and capabilities crates we want here. This method allowed us to keep all git history and attribution. Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
Signed-off-by: Shane Utt <shaneutt@linux.com>
…xis-proxy#228) Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: dependabot[bot] <support@github.com>
…s-proxy#223) Signed-off-by: dependabot[bot] <support@github.com>
…axis-proxy#225) Signed-off-by: Dimitri Saridakis <dimitri.saridakis@gmail.com> Signed-off-by: dimakis <dimitri.saridakis@gmail.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Alex Snaps <alex@wcgw.dev>
Signed-off-by: Sébastien Han <seb@redhat.com>
…axis-proxy#237) Signed-off-by: Sébastien Han <seb@redhat.com>
…xy#236) Signed-off-by: Sébastien Han <seb@redhat.com>
…praxis-proxy#241) Signed-off-by: Sébastien Han <seb@redhat.com>
…xis-proxy#253) Signed-off-by: Alex Snaps <alex@wcgw.dev>
* implement and integrate is_promotable_value Signed-off-by: Harrison Stropkay <hstropka@redhat.com> * add oversized_model_not_promoted_to_header_or_results_or_metadata to model_rewrite/tests.rs Signed-off-by: Harrison Stropkay <hstropka@redhat.com> --------- Signed-off-by: Harrison Stropkay <hstropka@redhat.com> Co-authored-by: Francisco Javier Arceo <farceo@redhat.com>
…search (praxis-proxy#493) * feat(openai): add file_search_callout filter for hosted vector store search Add the `openai_file_search_callout` filter for executing OpenAI Responses API `file_search_call` output items against any vector store API compatible backend. The filter runs inside `iterative_request_router` so a model response can trigger vector store search and another inference within the same client request. vLLM backends that emit `function_call(name=file_search)` instead of native `file_search_call` are automatically translated before execution. Key capabilities: - Bounded fan-out (64 specs, 8-way concurrency) - Cross-store ranked aggregation with max_num_results - Citation tracking via ResponsesState.citation_files - SSRF-safe URL validation at startup - Fail-open (ignore) and fail-closed (reject) error policies - Partial failure tracking with incomplete status Authentication is delegated to upstream header injection or cluster-level request_headers_to_add rather than per-filter config. Closes praxis-proxy#29. Signed-off-by: Sébastien Han <seb@redhat.com> * fix(openai): preserve completed searches at deadline Record synthetic deadline failures only for specs at or after the scheduler cursor. Completed concurrency chunks have already produced and merged their results, so marking them failed could incorrectly reject the request or downgrade the call to incomplete. Also mark top-of-loop deadline handling as recorded to avoid duplicating failures after result sorting, and cover the scheduler boundary with a focused regression test. Signed-off-by: Sébastien Han <seb@redhat.com> * fix(file_search): inject shared SubRequestClient instead of private connector Replace the hardcoded SubRequestConnector::new(4, None) in file_search_callout with the shared server-level SubRequestClient, following the same pattern as file_resolve, compact, and web_search. Add from_config_with_client to FileSearchCalloutFilter and wire closure-based registration in register.rs so the filter inherits runtime.subrequest_pool_size and subrequest_max_connections from the server config. Signed-off-by: Sébastien Han <seb@redhat.com> * fix(file_search): add forward_headers and align failure vocabulary - Add `forward_headers` config field to forward request headers (e.g. Authorization) from the incoming request to the vector store callout, using the shared `ApiClient` header-forwarding mechanism already used by file_resolve. - Replace custom `on_error: ignore|reject` with `callout_failure_mode: open|closed` using the shared `FailureMode` enum from `config_validation`, matching the vocabulary used by `openai_responses_compact`. - Clarify `max_state_bytes` documentation: the filter and the enclosing iterative router may use different values because the smaller limit wins at runtime. - Update example config, integration test config, generated filter docs, and all unit tests. Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
…raxis-proxy#688) extract_arguments used Value::to_string() which re-serializes JSON string values, wrapping them in an extra layer of quotes and escapes. Extract a shared normalize_arguments helper for the string-vs-non-string dispatch used by both the approval and execution paths. Closes praxis-proxy#558 Signed-off-by: Sébastien Han <seb@redhat.com>
* chore(deps): update Praxis crates to v0.5.2 Bump praxis-core, praxis-filter, praxis-protocol, praxis-tls, and praxis from v0.5.1 to v0.5.2. Since v0.5.2 is not yet published on crates.io, a temporary [patch.crates-io] section pins the crates to the v0.5.2 git tag. The new HttpFilterContext.metrics_route field (added in v0.5.2) is initialized in both test context constructors (apis, filters). Closes praxis-proxy#681 Signed-off-by: Sébastien Han <seb@redhat.com> * fix(ci): use inline git deps instead of patch section Replace the [patch.crates-io] section with inline git tag references in workspace dependencies. The patch approach conflicted with the test-praxis-main CI job (which appends its own patch section) and was rejected by cargo-deny (unknown-git = "deny"). Add https://github.com/praxis-proxy/praxis to deny.toml allow-git until v0.5.2 is published on crates.io. Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
…criminator bodies (praxis-proxy#594) * fix(classifier): treat POST /v1/responses as authoritative for no-discriminator bodies A valid Responses create body that omits the discriminator fields the body heuristics key on (input, prompt object, previous_response_id, conversation) — e.g. `{"model":"gpt-5"}` — classified as unknown_json, which could be rejected or misrouted despite being a legitimate Responses create request. Treat the POST /v1/responses endpoint as authoritative: when body classification yields UnknownJson, promote it to Responses while keeping the extracted routing facts. Bodies with positive signals for another format (ChatCompletions / AnthropicMessages) and genuine parse failures (NonJson / InvalidJson) are left untouched so real errors still reject. Fixes praxis-proxy#413 Signed-off-by: Charlie Doern <cdoern@redhat.com> * test(classifier): update integration tests for authoritative create endpoint The unknown_json routing/reject integration tests posted a no-discriminator body to POST /v1/responses, which now classifies as Responses. Retarget them to a non-create path so they still exercise unknown_json handling, and add a test asserting a no-discriminator create body routes to the responses cluster. Signed-off-by: Charlie Doern <cdoern@redhat.com> * docs(classifier): document authoritative create endpoint and add reject-path test Address PR review feedback: - Convert inline comments in test bodies to assertion messages, per the project convention of putting explanations in the assertion's message argument so they print on failure. - Add `post_v1_responses_create_without_discriminator_not_rejected`, covering a valid no-discriminator create body under `on_invalid: reject` and closing the gap between the `continue`-mode and invalid-JSON tests. - Document the authoritative `POST /v1/responses` classification in the `ResponsesFormatFilter` doc comment, regenerate the filter doc, and describe the behavior in the format-routing example config. Signed-off-by: Charlie Doern <cdoern@redhat.com> * fix(responses): format merged classifier imports Signed-off-by: Charlie Doern <cdoern@redhat.com> --------- Signed-off-by: Charlie Doern <cdoern@redhat.com>
* fix(ci): stabilize flaky vLLM integration job Lower CPU memory reservation from 70% to 50% — the runner's ~15.6 GiB total minus other processes (cargo build, OGX, docker) often left less than the 10.93 GiB that 0.7 demanded, causing WorkerProc to crash on startup. The Qwen3-0.6B model needs ~1.2 GiB so 0.5 (~7.8 GiB) is plenty. Also add a container liveness check in the readiness loop so a crashed vLLM fails immediately instead of curling a dead container for 15 minutes. Signed-off-by: Sébastien Han <seb@redhat.com> * fix(ci): print full vLLM logs on container crash Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
…raxis-proxy#717) Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.4 to 4.37.6. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@f205ea1...5595cca) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…axis-proxy#718) Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.4 to 4.37.6. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@f205ea1...5595cca) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix(server): resolve external-filter discovery for optional dependencies
collect_root_deps previously matched packages by the literal name
"praxis-proxy" to find the AI server crate, colliding with the core
alias praxis = { package = "praxis-proxy" }. Anchor on cargo_metadata's
resolved root instead. Move the discovery logic into a new
praxis-ai-build-support crate so it's unit-testable, add an e2e test,
and fix container build + CI test-dependency issues found in review.
Closes praxis-proxy#478
Signed-off-by: mkoushni <mkoushni@redhat.com>
* fix(container): copy real build_support source instead of stubbing it
server/build.rs is a build-dependency consumer of praxis-ai-build-support:
cargo compiles build.rs (and its dependencies) up front, before any of the
crate's own source is available. Stubbing build_support/src/lib.rs to
`//! stub` therefore compiled a crate that exported none of the functions
build.rs calls (ActiveFeatures, discover_external_filter_crate_names,
etc.), breaking build.rs's own compilation and failing the entire
container build.
Copy the real build_support source alongside build.rs in the cache-build
stage instead, and drop it from both the stub-generation step and the
later cache-tricks re-copy, since it never needs the stub-then-replace
cycle used for the project's other crates.
Verified with a full `make container` build.
Signed-off-by: mkoushni <mkoushni@redhat.com>
* style(build_support): move test module to end of file, add section separators
Address praxis-bot review feedback on praxis-proxy#592: #[cfg(test)] mod tests must be
the last item in the file per convention, but it was declared right after
the imports. Move it after resolve_or_panic.
Also add the missing Public Types / Public Functions / Private Helpers
separator comments the file was otherwise missing, matching the file
ordering convention documented in CONTRIBUTING.md.
Signed-off-by: mkoushni <mkoushni@redhat.com>
* style: remove doc comment on test fn, add missing test-utilities separator
Address further praxis-bot review feedback on praxis-proxy#592:
- server/tests/external_filter_discovery_e2e.rs: drop the doc comment on
the #[test] fn; convention is that the function name is the
documentation, and the eprintln! already explains the skip-when-missing-
sibling-checkout behavior.
- server/build_support/src/tests.rs: add the missing `// Test Utilities`
separator before the fixture-building helpers, per the test-file
ordering convention in CONTRIBUTING.md.
Signed-off-by: mkoushni <mkoushni@redhat.com>
* fix(build_support): panic when root package has no matching resolve node
Addresses praxis-bot review: collect_root_deps previously fell back to
an empty vec via unwrap_or_default() if resolve.root had no matching
node in resolve.nodes, silently mirroring the old no-discovery
failure mode instead of panicking with an actionable message.
Signed-off-by: mkoushni <mkoushni@redhat.com>
---------
Signed-off-by: mkoushni <mkoushni@redhat.com>
…proxy#687) * feat(store): add validate_get_response_query_params Add a validation-only function that rejects unsupported (stream=true, include, starting_after, include_obfuscation), invalid, and unknown query parameters on GET /v1/responses/{id}. Keys and values are percent-decoded before validation; stream=false is accepted. Ref: praxis-proxy#555 Signed-off-by: Sébastien Han <seb@redhat.com> * feat(store): validate query params on GET /v1/responses/{id} Wire validate_get_response_query_params into handle_get_response before store initialization. Invalid, unsupported, or unknown query parameters now return 400 instead of being silently ignored. Closes: praxis-proxy#555 Signed-off-by: Sébastien Han <seb@redhat.com> * fix(store): strict percent-decoding, inline retrieval, new unit tests Use strict decode_utf8() with borrowed Cow values instead of decode_utf8_lossy/into_owned. Pass decoded values into validation so percent-encoded stream=false (e.g. stream=%66alse) is accepted. Inline retrieve_and_respond back into handle_get_response. Fix include integration test to use encoded key with valid value (include%5B%5D=reasoning.encrypted_content). Add unit tests for: percent-encoded stream=false accepted, invalid UTF-8 key rejected, invalid UTF-8 value rejected. Ref: praxis-proxy#555 Signed-off-by: Sébastien Han <seb@redhat.com> * style(store): fix nightly rustfmt attribute formatting Expand #[expect] attribute to multi-line format to match CI's nightly rustfmt output. Ref: praxis-proxy#555 Signed-off-by: Sébastien Han <seb@redhat.com> * test(store): add sync test for known params and validator match arms Addresses praxis-bot review feedback: ensures every entry in GET_RESPONSE_KNOWN_PARAMS is handled by validate_get_response_param without producing an "Unknown" error, preventing silent drift between the constant and the match arms. Signed-off-by: Sébastien Han <seb@redhat.com> * docs(store): document GET query parameter validation in architecture docs Operators reading the architecture docs will now learn that GET /v1/responses/{id} validates query parameters and rejects unsupported ones with a 400 response. Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
…raxis-proxy#686) * fix(openai_responses): place web-search sources inside action object Move sources from the top-level output item into action.sources and encode each source as {"type":"url","url":"..."} to match the OpenAI Responses API web_search_call schema. Signed-off-by: Sébastien Han <seb@redhat.com> * fix(openai_responses): gate action.sources on include field Only emit action.sources in web_search_call output items when the request includes "web_search_call.action.sources", matching the include-controlled response contract. Signed-off-by: Sébastien Han <seb@redhat.com> * style(openai_responses): move INCLUDE_ACTION_SOURCES to constants section Group the include-gating constant with the other module constants at the top of the file, matching the existing separator convention. Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
praxis-proxy#690) The openai_mcp_tool_resolve filter applied max_body_bytes only when buffering the original client body via StreamBuffer, but after expanding MCP tool definitions into function tools the rewritten body could exceed the limit unchecked. Add a post-serialization size guard that returns HTTP 413 before committing the expanded body. Signed-off-by: Sébastien Han <seb@redhat.com>
…-proxy#689) * fix: reject invalid query params in conversation item listing Make parse_item_list_params fallible. Reject malformed limit values, invalid order enums, unknown parameters, duplicates, and key-only params with 400 invalid_request_error instead of silently defaulting. Move param parsing before the conversation store lookup so malformed requests consistently return 400 rather than sometimes 404. Remove effective_limit clamping (now validated at parse time) and decode_query_component (replaced by strict decoding). Closes praxis-proxy#547 Signed-off-by: Sébastien Han <seb@redhat.com> * test: update and add unit tests for strict query validation Update existing tests to assert errors instead of silent defaults. Remove effective_limit and decode_query_component tests (dead code). Add tests for: limit=0, limit above max, duplicates, unknown params, key-only params, empty after, invalid UTF-8, empty components, and encoded duplicate keys. Signed-off-by: Sébastien Han <seb@redhat.com> * test: add integration tests for query validation error responses Verify handler-level 400 responses for invalid limit, invalid order, unknown params, and duplicate params. Also verify limit=0 returns an empty 200 page with has_more=false. Signed-off-by: Sébastien Han <seb@redhat.com> * test: add validation precedence regression test Verify that an invalid query on a nonexistent conversation returns 400, not 404, confirming that query validation runs before the store lookup. Signed-off-by: Sébastien Han <seb@redhat.com> * test: add boundary test for limit at MAX_PAGE_LIMIT Addresses praxis-bot review comment on PR praxis-proxy#689. Signed-off-by: Sébastien Han <seb@redhat.com> * style: fix rustfmt formatting in boundary test Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
Increase all timeout values in the vLLM Responses SDK integration tests to 300 seconds to reduce flaky failures from slow model inference: - read_timeout_ms: 120000 → 300000 - timeout_ms: 120000 → 300000 - step_timeout_ms: 120000 → 300000 - SDK client timeout: 180 → 300 (all three client fixtures) Signed-off-by: Sébastien Han <seb@redhat.com>
…raxis-proxy#704) feat(openai): resolve connector_id through configured MCP connectors (praxis-proxy#316) Add connector_id resolution to the openai_mcp_tool_resolve filter, allowing requests to reference named MCP connectors instead of embedding raw server_url values. Configured connectors map short IDs to validated server URLs, keeping client payloads free of infrastructure details. Key changes: - ConnectorConfig with validation (max 64 connectors, 128-byte ID limit) - EntryResolution enum (PassThrough vs Resolved) preventing connector_id leak - resolve_connector_ids() with request-side ID length cap - URL redaction in client-facing connector errors - Zero-tool tool_choice rejection for resolved labels - Integration tests and example config Signed-off-by: Sébastien Han <seb@redhat.com>
* feat(openai): support finite chat continuations
Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
* fix(openai): fail closed for unresolved stream history
Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
* fix(test): add assertion messages and Responses live recording coverage
Add diagnostic messages to all bare assert! calls introduced in the
chat continuation feature to comply with repository conventions.
Extend live recording tests with a two-turn Responses scenario that
exercises the ${PREVIOUS_RESPONSE_ID} placeholder binding during
record_live, verifying the first response ID reaches the second
request.
Signed-off-by: Sébastien Han <seb@redhat.com>
---------
Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
Co-authored-by: Sébastien Han <seb@redhat.com>
* Limit MCP dynamic values and add boundary tests Signed-off-by: Sumanth Kamenani <skamenan@redhat.com> * fix(mcp): bound method and name filter results Signed-off-by: Sumanth Kamenani <skamenan@redhat.com> --------- Signed-off-by: Sumanth Kamenani <skamenan@redhat.com> Co-authored-by: Sébastien Han <seb@redhat.com>
…xy#711) * fix(responses): disable parallel tools on first inference Signed-off-by: Sumanth Kamenani <skamenan@redhat.com> * test(inference): add fixture coverage for agentic parallel_tool_calls Add a controlled synthetic scenario and recording that proves the agentic loop injects `parallel_tool_calls: false` into the upstream request when the client omits it. This closes the inference fixture coverage gap for the dirty-marker implementation. - Add `agentic-loop-fixture.yaml` example config (replay-safe subset of the agentic loop pipeline without external callout filters) - Add `agentic_loop` and `iterative_request_router` to the replay filter allowlist (they make no external callouts) - Add scenario, recording, and coverage.yaml entry for `responses.agentic.parallel_tool_calls` - Update the snapshot test in `coverage.rs` for the new scope, feature, scenario, and recording counts - Regenerate example and inference READMEs Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sumanth Kamenani <skamenan@redhat.com> Signed-off-by: Sébastien Han <seb@redhat.com> Co-authored-by: Sébastien Han <seb@redhat.com>
* feat(filters): add OpenTelemetry routing spans Add feature-gated routing.select spans for successful intelligent_route decisions while leaving request lifecycle, propagation, sampling, and export ownership in Praxis core. Keep the default build unchanged and avoid OpenTelemetry SDK dependencies in the AI filters. Record only validated, bounded routing attributes and document the ownership and privacy boundaries. Signed-off-by: Brent Salisbury <bsalisbu@redhat.com> * test(filters): align telemetry test conventions Document the private routing-selection fields used to project bounded OpenTelemetry attributes. Move the candidate fixture below the tests, use the standard test-utilities separator, and add diagnostic messages to every assertion. Signed-off-by: Brent Salisbury <bsalisbu@redhat.com> --------- Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
…-proxy#625) * fix(conversations): align create and update request contracts Align the locally owned Conversation create and update request bodies with confirmed OpenAI behavior. Create requests accept an absent body and nullable optional fields, while update requests require a non-null metadata object. Generate the matching OpenAPI schemas from the shared runtime contracts, retain the 20-item limit, cover absent, null, empty, valid, and invalid body shapes, and document the verified live behavior where it contradicts the pinned upstream specification. Closes praxis-proxy#566 Signed-off-by: Sébastien Han <seb@redhat.com> * docs(conformance): remove unnecessary probe cleanup detail Signed-off-by: Sébastien Han <seb@redhat.com> * test(conversations): expect 400 for update without metadata Signed-off-by: Sébastien Han <seb@redhat.com> * fix(conversations): return OpenAI error codes on update validation Map update-conversation parse and validation errors to the OpenAI error contract with code (missing_required_parameter, invalid_type) and param fields. Add inner anyOf assertions for the two-layer nullable metadata schema and an array-type rejection test. Signed-off-by: Sébastien Han <seb@redhat.com> * fix(conversations): narrow error code/param scope in update responses - Remove hardcoded "but got null" from classify_update_error message since the error applies to all non-object types, not just null - Restore invalid_input_response to not include code/param fields, keeping those only for update-specific errors via invalid_input_response_with - Use invalid_type code only for actual type violations from validate_metadata, not for constraint violations (key/value length, key count) Signed-off-by: Sébastien Han <seb@redhat.com> * fix(conversations): address praxis-bot review findings - Replace string-matching heuristic (msg.contains("must be")) with MetadataError enum that structurally distinguishes type errors from constraint violations - Add maxItems assertion to OpenAPI schema test ensuring the 20-item bound is preserved in the generated document - Handle empty update body with missing_required_parameter error code matching the {} case, as documented in conformance README Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
…proxy#725) v0.5.2 is now published on crates.io, so remove the temporary git tag workaround introduced in praxis-proxy#701. Signed-off-by: Sébastien Han <seb@redhat.com>
…raxis-proxy#733) * fix(a2a): defer route commit until EOS to prevent prefix-poisoning GuardResult::Redact was returning FilterAction::Continue while recording status=redacted (guardrails fix). Separately, the A2A non-streaming capture path committed task-route ownership as soon as the JSON balance scanner reported a structurally complete value, regardless of end_of_stream. A backend could deliver a valid JSON task response with end_of_stream=false, have the route stored, then append trailing garbage to produce an overall-invalid response — poisoning task ownership while the complete body was not valid JSON. When the scanner reports is_complete but end_of_stream is false, parse_to_tentative() now stores the parsed value in filter metadata under a2a.response.tentative_json without committing to the route store. Subsequent chunks are inspected: - non-whitespace bytes → tentative discarded (trailing content proves the response is not valid JSON) - end_of_stream with no further non-whitespace → commit_tentative_capture() promotes the held value into the store If end_of_stream arrives together with is_complete, try_capture_from_buffer() is called directly as before, preserving the common fast path. Update tests: - json_response_split_across_chunks_defers_capture_until_eos - many_single_byte_chunks_capture_route_at_eos - split_json_response_with_context_stores_context_route - assert_capture_scratch_cleared includes a2a.response.tentative_json Add regression tests: - complete_json_prefix_then_garbage_does_not_capture_route - complete_json_followed_by_whitespace_at_eos_captures_route Fixes: fnd_sig-feat-custom-ai-agentic-class_e1322f3e62 Signed-off-by: mkoushni <mkoushni@redhat.com> * test(a2a): add tentative-survives-whitespace-chunk regression test Covers the keep-waiting branch of the tentative guard: a whitespace-only chunk with end_of_stream=false must leave the tentative JSON intact without committing the route; only the subsequent EOS callback should commit. Reported by praxis-bot review of fix/a2a-defer-route-commit-until-eos. Signed-off-by: mkoushni <mkoushni@redhat.com> * docs(a2a): correct try_capture_from_buffer doc comment The previous comment claimed the function was called after a tentative parse was promoted by a whitespace-only EOS callback; that path actually calls commit_tentative_capture. Update to accurately describe the two real call sites: the fast path (is_complete && end_of_stream simultaneously) and the fallback where EOS arrives with an incomplete buffer. Reported by praxis-bot review of fix/a2a-defer-route-commit-until-eos. Signed-off-by: mkoushni <mkoushni@redhat.com> * fix(a2a): resolve clippy too-many-lines and needless-pass-by-ref-mut Extract the tentative-guard logic from handle_non_streaming_capture into a dedicated handle_pending_tentative helper. This reduces handle_non_streaming_capture to under the 30-line clippy limit while improving readability. Drop the unused &mut on ctx in commit_tentative_capture — the function only reads filter_metadata, so &HttpFilterContext<'_> is sufficient. Remove two inline comments from json_response_split_across_chunks_defers_ capture_until_eos that violated the project convention (no inline comments in test bodies) and pushed the function over the 30-line limit. Signed-off-by: mkoushni <mkoushni@redhat.com> * fix(a2a): clear capture state on serde_json serialization failure in parse_to_tentative If serde_json::to_string fails inside parse_to_tentative, the tentative_json key is not set but buffer_hex, balance state, and capture_enabled all remain. Subsequent chunks re-enter the main flow and when EOS arrives is_complete || end_of_stream fires, committing the route via try_capture_from_buffer and bypassing the tentative guard entirely. In practice this is effectively infallible for a Value that was just deserialized, but add a defense-in-depth else branch that calls clear_capture_metadata so the tentative guard cannot be bypassed under any failure mode. Reported by praxis-bot review of fix/a2a-defer-route-commit-until-eos. Signed-off-by: mkoushni <mkoushni@redhat.com> * fix(a2a): extract assert_tentative_pending helper to fix too-many-lines lint json_response_split_across_chunks_defers_capture_until_eos was 32 non-blank lines (limit 30). Extract the paired tentative-state assertions into a shared assert_tentative_pending helper, bringing the test well under the limit. Also restore the #[expect(clippy::too_many_lines)] attribute to its correct position on assert_capture_scratch_cleared after it was accidentally displaced during the helper insertion. Signed-off-by: mkoushni <mkoushni@redhat.com> * fix(a2a): use RFC 8259 whitespace predicate and remove test inline comments Use a JSON-specific whitespace predicate (`b' ' | b'\t' | b'\n' | b'\r'`) instead of `is_ascii_whitespace()` which also matches vertical tab (\x0B) and form feed (\x0C). RFC 8259 defines only four insignificant-whitespace code points; using the broader ASCII predicate could allow non-JSON trailing bytes to be silently treated as whitespace, bypassing the tentative-guard discard branch. Move the Clawpatch finding ID for `complete_json_prefix_then_garbage_ does_not_capture_route` into the first assertion message where it is visible as traceability. Remove all other inline comments from new and modified test function bodies per project conventions. Signed-off-by: mkoushni <mkoushni@redhat.com> --------- Signed-off-by: mkoushni <mkoushni@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
…lout (praxis-proxy#737) feat(file_search): add full-flow-agentic example with IRR file search callout Add a full-flow-agentic.yaml example config that wraps the inference step in an iterative_request_router, enabling server-side file search execution through vector store callouts. On IRR continuation iterations the synthetic request lacks client credentials; callout_request_headers now returns the original headers (via Cow to avoid cloning on iteration 0) with Connection-nominated hop-by-hop headers filtered out. Signed-off-by: Sébastien Han <seb@redhat.com>
…-proxy#748) * refactor(filters): rename agentic_loop filter to openai_agentic_loop Align the agentic loop filter name with the existing OpenAI Responses API filter naming convention (openai_mcp_dispatch, openai_web_search, openai_tool_parse, etc.). Updated across registration, filter impl, config validation, YAML examples, unit/integration tests, docs, and fixture replay. Signed-off-by: Sébastien Han <seb@redhat.com> * fix: rename agentic_loop in vllm SDK integration test Missed reference in test_openai_responses_vllm.py caught during PR review. Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
…y#739) * ci(store): add PostgreSQL CI coverage for response store Add CI workflows that exercise the response store with a real PostgreSQL backend, covering both unit tests and end-to-end vLLM integration tests. New postgres.yaml workflow runs the 27 ignored store unit tests (with a service container providing DATABASE_URL) and the 2 ignored Rust integration tests (which spawn their own container). The vLLM integration workflow gains a vllm-responses-postgres job that reruns the full Python SDK test suite with STORE_BACKEND=postgres, exercising persistence, rehydration, and conversations through the PostgreSQL path. Also fixes two pre-existing bugs in the ignored Postgres test infrastructure: table name suffixes containing uppercase ThreadId characters caused schema validation failures (PostgreSQL folds unquoted identifiers to lowercase), and the Rust integration test omitted ssl_mode: disable, causing VerifyFull handshake failures against the plaintext test container. Signed-off-by: Sébastien Han <seb@redhat.com> * fix(ci): detect store backend from DATABASE_URL prefix Drop the STORE_BACKEND env var and infer the backend type from the DATABASE_URL scheme instead, addressing review feedback. Signed-off-by: Sébastien Han <seb@redhat.com> --------- Signed-off-by: Sébastien Han <seb@redhat.com>
…t Router (praxis-proxy#730) * Add conformance test: inference fallback with protocol translation First conformance test for the AI gateway. Validates that an iterative_request_router composes correctly with responses_to_chat_completions protocol translation and credential_injection across a failover boundary. Two tests exercise the example config: - fallback_on_primary_503: primary returns 503, fallback receives the translated Chat Completions request with isolated credentials, client gets a Responses API resource. - primary_succeeds_no_fallback: primary returns 200, fallback receives no requests. Each IRR step re-runs openai_responses_format and openai_responses_validate because the IRR resets per-step metadata for credential isolation while preserving extensions. Assisted by Opus 4.6 Signed-off-by: usize <mofoster@redhat.com> * Document step boundary rules in config comments Move the metadata/extension persistence explanation into the example config header comments, where someone building a pipeline encounters it. Add a one-liner to ai-inference.md pointing to the example. Document real-world adaptation (model rewrite, TLS/SNI, env_var credentials, Host header) in the "Adapting for real providers" section. Assisted by Opus 4.6 Signed-off-by: usize <mofoster@redhat.com> * Address review feedback and sync with upstream - Regenerate examples/README.md for new inference/ category - Remove inline section comments from test bodies - Add assertion messages to all bare assert_eq! calls - Add both_backends_fail_returns_last_error test case - Update Cargo.lock after rebase (git tags -> crates.io) Assisted by Opus 4.6 Signed-off-by: usize <mofoster@redhat.com> --------- Signed-off-by: usize <mofoster@redhat.com>
Store the TracingGuard return value from init_tracing() to keep the Tokio runtime alive for the BatchSpanProcessor's async gRPC export. Without this, the guard drops immediately and traces are silently lost. Also enable the praxis-core 'otel' feature in workspace dependencies so the OTLP exporter and tracing subscriber are compiled in when building with --features opentelemetry. Signed-off-by: Ladislav Smola <lsmola@redhat.com>
praxis-bot
left a comment
There was a problem hiding this comment.
PR Review
Summary: The _tracing_guard fix in main.rs is correct and necessary. However, enabling praxis-core/otel unconditionally at the workspace level pulls OTel dependencies into every build, even when the opentelemetry feature is not requested.
Overall: One issue to address before merging.
| Severity | Count |
|---|---|
| Critical | 0 |
| Large | 1 |
| Medium | 0 |
|
|
||
| # Praxis core dependencies | ||
| praxis-core = { version = "0.5.2", package = "praxis-proxy-core" } | ||
| praxis-core = { version = "0.5.2", package = "praxis-proxy-core", features = ["otel"] } |
There was a problem hiding this comment.
[Large] This unconditionally enables praxis-core/otel for all 7 workspace crates that depend on praxis-core, pulling in the OTLP exporter and its transitive dependencies (gRPC/tonic, opentelemetry SDK, etc.) even when building without --features opentelemetry.
Revert this line to the featureless form:
praxis-core = { version = "0.5.2", package = "praxis-proxy-core" }Then propagate the feature conditionally in server/Cargo.toml:
[features]
opentelemetry = ["praxis-ai-filters/opentelemetry", "praxis-core/otel"]This keeps OTel opt-in while fixing the original bug.
Problem
Two bugs prevent trace export when building with
--features opentelemetry:init_tracing()return value (TracingGuard) was immediately dropped inmain.rs, killing the Tokio runtime that theBatchSpanProcessorneeds for gRPC exportopentelemetryfeature flag only enabled the AIrouting.selectspan creation but did NOT enablepraxis-core/otel— the OTLP exporter was never compiled inFix
TracingGuardinlet _tracing_guardso it lives for the process lifetimefeatures = ["otel"]to thepraxis-coreworkspace dependencyTesting
Verified on KIND with OTel Collector + Tempo: 11 spans per AI request (root + 8 filter + upstream_exchange + response_body phase).