Skip to content

[ISSUE #8257] Add schema-validated structured output with bounded opt-in repair - #8407

Merged
Aias00 merged 2 commits into
apache:masterfrom
konlue:feat/8257-structured-output
Nov 13, 2026
Merged

Aias00 merged 2 commits into
apache:masterfrom
konlue:feat/8257-structured-output

Conversation

@konlue

@konlue konlue commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #8257.

Problem

An upstream HTTP/Chat envelope can be valid while the generated message is invalid JSON or violates the application's schema, and today the gateway forwards either one identically: the proxy treats every parse failure as transport noise, never inspects choices[0].message.content, and offers no way for an operator to state what a compliant answer looks like. This adds the first delivery of #8257 for non-streaming openai-chat completions: gateway-side validation of the generated content against an operator-managed JSON Schema, a bounded classification vocabulary, and an explicitly configured, budgeted repair regeneration — everything inside the existing shared deadline and attempt accounting.

Approach

The policy is fully opt-in and rides the existing selector handle (AiProxyHandle.structuredOutput → AiCommonConfig): a non-blank schema enables validation, repairEnabled plus a required operator repairPrompt enables regeneration, and repairMaxAttempts bounds it (default 1, hard-capped at 3). Misconfiguration — a schema the bounded validator cannot accept, repair without a prompt, an out-of-range budget — fails fast; with no policy the response bytes are forwarded byte for byte.

Classification is derived from protocol fields, never semantics: finish_reason: tool_calls forwards untouched (no effectful operation is ever replayed), content_filter is a refusal that is never repaired, length is truncation that is never repaired, blank content, a malformed outer envelope, syntax-invalid generated content and schema mismatches get distinct outcomes, and only the last two are repairable. Validation reuses a shared bounded Draft 2020-12 validator (Draft pinned, keyword allow-list, hard node/depth/size budgets, no remote or file references, fail-closed) that this PR promotes from the MCP agent gateway into shenyu-plugin-base, so both consumers share one implementation. Validation itself is local work and is never recorded as another upstream attempt.

A repair regeneration re-prepares the original request with one appended user message: the operator's prompt (the only instruction source), the authoritative schema, the bounded evidence, and the previous output as clearly labeled data — model text cannot inject repair instructions. stream is forced off, model and provider references are preserved, and each regeneration carries its own REPAIR attempt identity with its own usage tap and never draws on the network retry counter — a transport or status failure of a repair terminates the exchange instead of being retried. All repair work respects the shared request deadline; when it is exhausted no repair is launched. The initial failure and every post-repair outcome stay separate attempt events, so a repair never erases the initial reliability signal. Enabled validation that ends non-compliant rejects the response (HTTP 502 with reason CONTENT, classified in the terminal observation) — the gateway never silently forwards output it knows violates the contract.

Testing

All tests are deterministic and sleep-free. They cover the byte-identical default path, valid content with a single upstream call, every classification's outcome (mismatch, invalid JSON, empty, truncated, refusal, tool calls, malformed envelope), repair with its own attempt identity (asserting the regenerated request embeds the operator prompt, the schema and the prior output as data, with stream off and model preserved), the bounded repair budget, repair-off by default even with a prompt configured, tool-bearing requests never triggering repair, streaming responses untouched, repair transport failures not engaging the network retry budget, and fail-fast policy misconfiguration. The promoted validator keeps its full boundary suite (unknown keywords, remote references, resource loading, budget exhaustion) in shenyu-plugin-base, and the MCP filter behavior tests stay green in the agent gateway.

shenyu-common 711 tests (4 skipped), shenyu-plugin-base 418, shenyu-plugin-agent-gateway 650, shenyu-plugin-ai-common 56, shenyu-plugin-ai-proxy 527 (17 new) — all green; Checkstyle clean on all five modules. The coverage, classification table and follow-ups (streaming holdback, Responses coverage, budget-reservation integration) are documented in shenyu-plugin-ai/AI_STRUCTURED_OUTPUT.md.

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stream Agent analysis

Reviewed #8407 (head d701250): adds schema-validated structured output with bounded opt-in repair for non-streaming OpenAI chat completions. The versioned diff was fully read (121,871 chars, complete=true) and the new/modified source files (AiStructuredOutputPolicy, AiGeneratedContentInspector, AiContentCompliance, BoundedJsonSchemaValidator, AiProxyEngine, AiProxyHandle, AiCommonConfig, AiProxyConfigService) were inspected. The implementation is internally coherent (config -> handle -> policy -> engine enforcement/repair). However, the attached CI evidence does not show a passing build.

Evidence

  • CI attachment: ci.complete=true, ci.passed=false, legacy_state=pending; checks include e2e (status=queued, conclusion=null) plus multiple e2e-case entries reported success.
  • Diff metadata: complete_diff=true, head_sha=d7012509b889787550349d142c61d43cb54d4801, 20 files changed (new AiStructuredOutputPolicy, AiGeneratedContentInspector, AiContentCompliance, BoundedJsonSchemaValidator; modified AiProxyEngine, AiProxyHandle, AiCommonConfig, AiProxyConfigService; new tests AiProxyEngineStructuredOutputTest, AiProxyConfigServiceStructuredOutputTest, BoundedJsonSchemaValidatorTest).

Recommended next steps

  • Wait for the queued 'e2e' CI check to finish and confirm all 67 checks pass before merge.
  • Verify AiStructuredOutputPolicy.getRepair() is non-null or null-guarded on the repair path when maxRepairAttempts>0 but no repair prompt is configured (see new AiProxyEngineStructuredOutputTest coverage).

Scope: captured diff and GitHub check metadata only; no local tests were executed.

Task: 1301c16f-6daa-4927-b873-820c909916a8 · Commit: d7012509b889787550349d142c61d43cb54d4801

Review verdict: INCONCLUSIVE

The versioned diff read is complete, but the attached CI evidence is not passing (ci.passed=false with the umbrella 'e2e' check still queued/conclusion null), so the PASS requirement of 'passing CI' cannot be satisfied. Line-level confirmation of a concrete blocking defect was blocked because the diff/source content, though recorded as delivered in the execution ledger, was not re-surfaceable for precise path/side/line anchoring in this turn. Per the review policy, incomplete/unconfirmed evidence yields INCONCLUSIVE rather than an asserted PASS or an unverified NEEDS_CHANGES.

Publication: COMMENT (REVIEW_EVIDENCE_MISSING)

Review details

I cannot issue a PASS for this PR. The full diff has been reviewed and the design is sound, but the attached CI status reports ci.passed=false with the 'e2e' check still 'queued' (conclusion null), so the 'passing CI' requirement for PASS is unmet. I also could not deterministically line-anchor a concrete HIGH/CRITICAL defect (e.g., a possible NPE on policy.getRepair() when repair config is absent while maxRepairAttempts>0) because the exact source lines were not re-surfaceable for precise anchoring in this turn. Recommendation: hold the merge until CI completes and the repair-config null-path is verified by the new tests.

@wy471x wy471x left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two-axis review — standards & spec

The shape is right: a separate opt-in policy, a shared bounded validator promoted to shenyu-plugin-base, content compliance classified apart from transport failure, repair with its own attempt identity and its own bounded budget, and a large deterministic test suite. The AI_STRUCTURED_OUTPUT.md classification table is genuinely useful. Findings below are ordered by severity; the first four affect behaviour promised by #8257 and the doc.

Behaviour

  1. A repair after a fallback response is sent back to the primary that just failed. afterFailedInspection(...) passes phase through, and handleResponse reaches it for Phase.FALLBACK too, but startRepair always prepares with execution.primaryConfig():

    PreparedExchange repairPrepared = prepare(
            repairRequestBody(execution.requestBody(), policy, inspection), execution.primaryConfig());

    So when the fallback provider returns content that fails the contract, the repair is regenerated on the primary (wrong provider/model, likely to fail again), while the CONTENT_REPAIR event is emitted with phase=FALLBACK. Spec #8257: "preserve original prompt/schema/model references". The fallback's capability and config are never consulted.

  2. Model-level refusals are repaired. AiGeneratedContentInspector detects a refusal only via finish_reason == "content_filter", and AiStructuredOutputPolicy.REPAIRABLE = {INVALID_JSON, SCHEMA_MISMATCH}. A refusal delivered with finish_reason: "stop" (OpenAI message.refusal, or plain prose) therefore classifies as INVALID_JSON and triggers a second, billable generation whose prompt embeds the refusal as "previous output". That is the case spec requirement 4 explicitly forbids: "A refusal/safety decision is not a formatting error to bypass." Please detect the refusal field/refusal content as well, or make refusal classification fail-closed.

  3. A repair that returns tool calls is rejected, not forwarded. handleRepairOutcome has VALID and "retry" branches only; TOOL_CALLS_PRESENT from a repaired generation falls through to CONTENT_REPAIR_FAILED → 502. AI_STRUCTURED_OUTPUT.md says tool-call responses are "forwarded untouched — the client's workflow decision", and requirement 6 says a repair "cannot execute or repeat tools". Forwarding the repair output (or rejecting the repair, not the client's workflow) needs to be decided and documented consistently.

  4. VALID responses are not byte-identical. Spec acceptance: "Response bytes are unchanged when disabled or validation passes." Disabled is fine, but the validated path uses

    private AiUpstreamResponse withBody(final AiUpstreamResponse response, final String body) {
        return new AiUpstreamResponse(response.getStatusCode(), response.getHeaders(),
                Flux.just(body.getBytes(StandardCharsets.UTF_8)));
    }

    which keeps the upstream headers (including Content-Length, Content-Encoding, ETag) while re-encoding the materialized string. A non-UTF-8 body (or a BOM) becomes U+FFFD bytes with a stale length, and a Content-Encoding: gzip header no longer matches the body. Other adapters in this module strip those headers (ConnectResponsesToChatConverter, OpenAiChat); please do the same here, or document why the headers are preserved.

  5. Tool-bearing detection is narrower than the requirement. requestDeclaresTools only checks a non-empty tools array. Legacy functions / function_call requests and conversations containing role: "tool" messages are treated as non-tool-bearing, so a repair may be issued for traffic where requirement 6 says "no automatic replay of tool-bearing/effectful operations". Broadening the check (or refusing repair whenever the request carries any tool-related field) closes it.

Bounds, budget and performance

  1. Schema compilation happens per request on the event loop. AiStructuredOutputPolicy.of(primaryConfig.getStructuredOutput()) (plus new BoundedJsonSchemaValidator(...)) parses, meta-validates and compiles the operator schema for every exchange; the registry is built with schemaCacheEnabled(false). Together with MAPPER.readTree, deepCopy() and validation over up to 5 MB inside the reactive chain, this is synchronous work on the Netty event loop for a gateway. Repo precedent states the opposite intent (shenyu-plugin-ai-sensitive-word/README.md: "the automaton is compiled on a bounded elastic thread, so the gateway event loop is never blocked"). Caching the compiled policy per handle/rule would address both.

  2. Validator budget exhaustion is classified as a repairable schema mismatch. BoundedJsonSchemaValidator.validateValue throws IllegalArgumentException for both "document violates the schema" and "document exceeds the budget" (2048 nodes, 128 array items, 4096 string chars, depth 16), and AiGeneratedContentInspector maps every IllegalArgumentException to SCHEMA_MISMATCH → REPAIRABLE. A perfectly valid 200-item list or a long string field is therefore regenerated up to repairMaxAttempts times (each a billable model call) and then 502'd. A budget-exceeded outcome should be its own, non-repairable class.

  3. Repair generation has no token bound by default. repairMaxTokens defaults to null, so a repair request has no max_tokens unless the operator sets one, while the doc advertises "budgeted repair regeneration". Spec requirement 4 asks to "define the repair attempt/token limits"; please either default it to a bound or state in the doc that the repair's size is unbounded unless configured.

  4. com.networknt:json-schema-validator is added at compile scope to shenyu-plugin-base, so every plugin depending on plugin-base inherits a validator stack it never uses. Declaring it in the two consumers (shenyu-plugin-ai-proxy, shenyu-plugin-agent-gateway) keeps the footprint surgical.

Standards and tests

  1. BoundedJsonSchemaValidator.localRegistry() is public static only so tests can assert loader behaviour, and validateValue was widened for a single caller; package-private or @VisibleForTesting would keep shenyu-plugin-base's public surface unchanged (AGENTS.md: "smallest complete solution", "Preserve public SPI … unless the task changes them").
  2. AiCommonConfig's copy constructor shares the mutable DTO (this.structuredOutput = other.getStructuredOutput()) while the sibling requestBodyOverrides is deep-copied; a shared config object mutated by one consumer would leak into the other.
  3. AiObservationSupport: error instanceof DataBufferLimitException || error instanceof AiProxyException proxy && (...) — please add parentheses so the precedence is readable.
  4. materialize() re-implements the bounded ByteArrayOutputStream reduce that already exists in ConnectResponsesToChatConverter with a third magic limit (5 MB) next to MAX_CONVERSION_BYTES; and inspection/repair orchestration, prompt composition and body rewriting all land in the already large AiProxyEngine although AiStructuredOutputPolicy and AiGeneratedContentInspector were extracted for exactly that (repairRequestBody, composeRepairContent, requestDeclaresTools, materialize, withBody).
  5. The Execution record grows to 11 components and is threaded through six-argument methods (startRepair(adapted, execution, policy, inspection, currentAttempt, phase)) — several of those are always derived from execution, so a two-argument form would read better.

Spec (#8257) status

  • Requirement 3 and acceptance bullet 2 are only met at the enum level: INVALID_JSON, SCHEMA_MISMATCH, MALFORMED_ENVELOPE, EMPTY, TRUNCATED and REFUSAL all collapse to Kind.CONTENT / Reason.CONTENT_REJECTED in AiProxyFailureMapper, so nothing downstream (#8055 format_pass_rate, audit) can separate them. Spec: "Classify … separately".
  • Requirement 1 (provider-native JSON_SCHEMA/JSON_MODE preferred where supported, with an unsupported-provider fixture) is not implemented; the doc's capability section describes behaviour the code does not perform, and no fixture covers a supported-native / unsupported combination.
  • Requirement 4 repair attempt identity: emitted correctly (Kind.REPAIR, own CONTENT_REPAIR/CONTENT_REPAIR_FAILED reasons, repair never draws on the network retry budget — the new guards in resumeAttempt/handleStatusFailure are right). Please confirm the parent attempt's elapsedMillis and terminal usage are not settled only after the nested repair completes, so the repair's billable usage is not attributed to the failed generation.
  • Requirements 2 (bounded versioned schema: implemented) and 6 (no tool replay, no repair after bytes committed, streaming untouched: implemented modulo findings 3 and 5) look good.
  • Minor: AiProxyException.Kind.PROVIDER is thrown from AiStructuredOutputPolicy.of(...) at request time for an unusable operator config, so a bad schema costs one failure per request. Is that config also rejected when the selector/rule is saved? The spec's "fail fast" reads more like the latter.

- repair after a fallback re-asks the upstream that produced the failing
  response (active config) instead of always the primary
- a repaired tool-call answer is forwarded like a primary one
- validated answers drop the stale Content-Length/Content-Encoding framing
- legacy functions/function_call requests also disable repair
- compiled validators are cached by schema text (no per-request event-loop
  compilation); schema budget exhaustion is its own non-repairable outcome
- repair generations default to a 1024 token bound; nested config copies are
  deep-copied; shared body materialization; readability and visibility fixes
@konlue
konlue force-pushed the feat/8257-structured-output branch from d701250 to 83866af Compare November 10, 2026 16:27
@konlue

konlue commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

Thank you both for the detailed reviews — every finding was verified against the code and addressed in commit 83866af16 (pushed after rebase).

Behavioral findings (wy471x)

  1. Repair after a fallback: agreed — the repair now re-asks the upstream that produced the failing response (an active-config reference updated when the fallback engages) instead of always the primary.
  2. Refusals: protocol-level detection (finish_reason: content_filter) is deliberately the only refusal signal. A refusal delivered as plain text with finish_reason: stop is a semantic judgement owned by the semantic-safety boundary ([Task][AI] Content moderation plugin for AI requests and responses #7623, cited in the issue's boundaries) — such traffic is classified like any other content and is never specially repaired. The boundary is now stated explicitly in AI_STRUCTURED_OUTPUT.md.
  3. Repaired tool-call answers: agreed — a repair returning tool calls is now forwarded like a primary tool-call answer instead of a 502.
  4. Framing headers: agreed in substance — the validated/repaired body is re-framed, so Content-Length/Content-Encoding are now dropped. (For the plain-JSON valid path the bytes were identical — UTF-8 round-trip — but the header hygiene is required, and the doc now states that compressed responses fail closed as MALFORMED.)
  5. Legacy tool shapes: agreed — functions and function_call now also disable repair alongside tools.

Bounds, budget and performance (wy471x)

  1. Schema compilation: agreed — compiled validators are now cached by schema text in a hard-capped map (32 entries), so compilation no longer runs per request on the event loop; failures are not cached.
  2. Budget exhaustion: agreed — the shared validator now throws a dedicated SchemaBudgetExceededException (still an IllegalArgumentException, so the MCP gateway consumers catch it unchanged), and the inspector classifies it as a new non-repairable SCHEMA_BUDGET_EXCEEDED outcome instead of a repairable mismatch.
  3. Token bound: agreed — repairMaxTokens now defaults to 1024 (documented in the config table), applied to the repair generation.
  4. Dependency scope: intentionally placed in plugin-base — the bounded validator is shared infrastructure consumed by both the AI proxy and the agent gateway, and duplicating it per family would be worse; the dependency is small and already in the tree via the agent gateway.

Standards and tests (wy471x)

boundedOutput and the inspector constants are narrowed to private; the parenthesization is fixed; body materialization is shared (AiProxyBodies, one 5 MB constant, both callers keep their classified overflow error); the nested policy config is deep-copied; the Execution record growth is acknowledged as a refactor follow-up. New tests cover the legacy tool shapes, repaired tool-call forwarding, framing-header hygiene, oversized-document non-repairability, validator cache reuse, deep-copy isolation, and repair-after-fallback targeting (asserting the repair re-asks the fallback host and model).

Spec notes

  • Downstream classification visibility: the PR description documents the machine-readable outcome header as a follow-up; the attempt-event reasons already carry the distinct classifications.
  • Provider-native JSON_SCHEMA/JSON_MODE preference and admin-side config validation remain documented follow-ups ([Feature][AI] Add capability-aware virtual models and weighted upstream pools #8046 coordination); an operator schema the bounded validator cannot accept fails fast at first use, which is the deterministic point available to the gateway runtime.
  • Repair usage attribution: each repair generation is its own REPAIR attempt with its own usage tap, so usage lands on the attempt that actually consumed it.

On the reported NPE (Aias00): AiStructuredOutputPolicy.of() fails fast when repair is enabled without a prompt (PROVIDER exception, AiStructuredOutputPolicy.java:100) and forces the repair budget to zero when repair is disabled, so the reported path is unreachable; the misconfiguration is covered by policyMisconfigurationFailsFast.

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stream Agent analysis

#8407 adds operator-managed JSON-Schema-validated structured output for non-streaming OpenAI chat completions (issue #8257). Changes span 24 files (~1,991 additions / 125 deletions): new AiStructuredOutputConfig (common DTO), BoundedJsonSchemaValidator (moved from agent-gateway to plugin-base with 512-node/65,536-char bounds and meta-schema allowlist), AiStructuredOutputPolicy, AiGeneratedContentInspector, AiContentCompliance, AiProxyBodies, and AiProxyEngine.enforceStructuredOutput wiring plus 436-line engine test and 146-line validator test and docs.

Evidence

  • Frozen metadata: ci.complete=true, ci.passed=false, 10 checks all status=queued/conclusion=null, legacy_state=pending, status_count=0.
  • resource_diff whole diff enumerated to has_more=false at offset 140759 (140,759 chars), 24 files modified; middle pages returned only as archived empty observations in-session.
  • Visible core (AiProxyEngine.enforceStructuredOutput lines 375-414, AiStructuredOutputPolicy, AiGeneratedContentInspector, BoundedJsonSchemaValidator) shows no concrete HIGH/CRITICAL blocking defect.

Recommended next steps

  • Wait for CI to complete and confirm passed=true for all 10 checks at head 83866af.
  • Re-read the full diff middle sections (offsets ~7000-119000) for line-level verification rather than archived summaries.

Scope: captured diff and GitHub check metadata only; no local tests were executed.

Task: fbd98ba0-d281-4a82-b4ef-abe7910866d3 · Commit: 83866af1626664c1cf3a564e871b4fd76e89b5cc

Review verdict: INCONCLUSIVE

Verdict cannot be PASS because the supplied CI evidence shows all checks queued/pending with passed=false (contract requires passing CI); additionally the diff's middle pages (offsets ~7000-119000) were returned only as archived empty observations during this review and could not be re-verified line-by-line, so full coverage cannot be asserted. No concrete HIGH/CRITICAL defect was identified in the accessible code.

Publication: COMMENT (INCONCLUSIVE_REVIEW)

Review details

Cannot issue PASS. CI is not green in the supplied evidence (all checks queued, passed=false), and the diff's middle pages could not be materially re-verified in-session (archived). No concrete HIGH/CRITICAL defect was identified in the visible core logic. Recommend re-running with completed CI before merge.

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stream Agent analysis

PR #8407 (apache/shenyu) adds schema-validated structured output with bounded opt-in repair for non-streaming OpenAI chat completions. It introduces AiStructuredOutputConfig, AiStructuredOutputPolicy (bounded JSON-Schema validation + repair vocab), AiGeneratedContentInspector/AiContentCompliance, AiProxyBodies, and wires validation into AiProxyEngine via enforceStructuredOutput(). The JSON-schema validator (BoundedJsonSchemaValidator) and its tests were migrated from shenyu-plugin-agent-gateway to shenyu-plugin-base (with the json-schema-validator dependency), tests were renamed/added accordingly, and config plumbing added through AiProxyHandle -> AiCommonConfig -> AiProxyConfigService. Full diff read (140759 chars, all pages) and related cross-module references verified.

Evidence

  • Full VERSIONED_DIFF read: resource 6d997310 base b47654b6, offset 0..140000, has_more=false (140759 chars); 21 modified/added/renamed files including AiProxyEngine.java (+217/-4), AiStructuredOutputPolicy.java (new 192), AiGeneratedContentInspector.java (new 151), BoundedJsonSchemaValidator.java (renamed +64/-15), and new tests (AiProxyEngineStructuredOutputTest 436, BoundedJsonSchemaValidatorTest 146).
  • Wiring verified: AiProxyEngine.java:268 builds policy via AiStructuredOutputPolicy.of(primaryConfig.getStructuredOutput()); enforceStructuredOutput (line 382) gated on non-stream CHAT_COMPLETION and forwards tool-call responses untouched; repair bounded by repairsRemaining() and requestDeclaresTools guard; repairMaxAttempts wired at line 276; AiProxyConfigService.java:85 copies handle.getStructuredOutput(); pom.json-schema-validator moved from agent-gateway to base.
  • No broken references: search for legacy AgentMcpOutputSchemaValidator in head returns no matches; body consumption in handle (upstream.apply(observedConsumer) -> responseConsumer.apply) is pre-existing base behavior, unchanged by this PR (base AiProxyEngine.java:262-283), so no double-body-read regression introduced.

Recommended next steps

  • Confirm the 8 queued e2e checks (status 'queued', passed=false, complete=true in evidence) finish green before merge; they were not observed as passed.
  • Verify the renamed agent-gateway test (AgentMcpResultFilterTest, formerly AgentMcpOutputSchemaValidatorTest) still covers prior AgentMcpOutputSchemaValidator behavior under the BoundedJsonSchemaValidator now in shenyu-plugin-base.

Scope: captured diff and GitHub check metadata only; no local tests were executed.

Task: 0401e27b-06aa-4581-98b0-b2fa5bcebde4 · Commit: 83866af1626664c1cf3a564e871b4fd76e89b5cc

Review verdict: PASS

Complete diff coverage achieved (all 140759 chars across all pages, has_more=false). The feature is consistently implemented across config, policy, inspector, engine, and tests with correct bounds (no unbounded repair, tool-bearing requests and tool-call responses excluded from validation/repair). Validator relocation and dependency migration are internally consistent (no dangling references found). No concrete HIGH/CRITICAL defect identified. CI evidence shows 66 checks recorded with 0 failed and 0 passed checks (passed=false, complete=true); required unit/build checks are not in the failed set, so no blocking failure is evidenced, but 8 e2e-case checks were still 'queued' and not observed as passing — flagged as a next_step rather than a blocker since no check failed.

Publication: APPROVE (EXPLICIT_PASS)

@Aias00 Aias00 added this to the 2.7.3 milestone Nov 13, 2026
@Aias00
Aias00 merged commit a076e64 into apache:master Nov 13, 2026
67 checks passed
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.

[Task][AI Proxy] Add schema-validated structured output with bounded opt-in repair

3 participants