Skip to content

fix(ai-gateway): rewrite JSON Schema oneOf as anyOf when routed to friendli#4002

Open
chrarnoldus wants to merge 6 commits into
mainfrom
feat/ai-gateway/friendli-rewrite-oneof-to-anyof
Open

fix(ai-gateway): rewrite JSON Schema oneOf as anyOf when routed to friendli#4002
chrarnoldus wants to merge 6 commits into
mainfrom
feat/ai-gateway/friendli-rewrite-oneof-to-anyof

Conversation

@chrarnoldus

@chrarnoldus chrarnoldus commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Friendli does not support the JSON Schema oneOf keyword. When a chat completions request is routed through Friendli (provider.order contains friendli), the upstream provider rejects schemas that use oneOf.
  • Adds apps/web/src/lib/ai-gateway/schema-rewrite.ts:
    • isFriendliChatCompletionsRequest — a type guard that narrows a GatewayRequest to a chat completions request whose provider.order includes friendli.
    • rewriteChatCompletionsOneOfAsAnyOf — recursively rewrites every oneOf keyword as anyOf across both tool function parameters and response_format.json_schema.schema, mutating the schemas in place. Cycle-safe via a visited set; merges into any pre-existing anyOf instead of overwriting it. Logs once per request, but only when at least one oneOf was actually rewritten.
  • Wires it into applyProviderSpecificLogic via isFriendliChatCompletionsRequest, placed after applyPreferredProvider so the effective provider.order is observed (GLM traffic that prefers Friendli is covered), scoped to chat_completions requests only.

Verification

  • Sent a chat completions request containing a tool with a oneOf parameters schema while provider.order included friendli, and confirmed the outgoing schema used anyOf with no oneOf.
  • Confirmed a comparable request without friendli in provider.order leaves oneOf schemas untouched.

Visual Changes

N/A

Reviewer Notes

  • The rewrite runs after applyPreferredProvider, so it sees the effective provider.order regardless of whether Friendli was caller-supplied or preferred by the gateway (e.g. GLM models). It is gated to chat_completions; /responses and /messages are unaffected.
  • When a schema already declares both anyOf and oneOf, the oneOf entries are appended to anyOf rather than overwriting it, to avoid silently dropping schemas.
  • oneOf (XOR) → anyOf (OR) is a deliberate, lossy semantic downgrade: Friendli rejects oneOf, so the most compatible fallback is to accept any of the sub-schemas.
  • The rewrite emits a single log (ai_gateway_chat_completions_one_of_rewritten, with model + count) per request, only when something actually changed.

…iendli

Friendli does not support the JSON Schema oneOf keyword. For chat
completions requests whose provider.order includes friendli, downgrade
every oneOf in tool parameters and response_format schemas to anyOf.

Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
@chrarnoldus chrarnoldus self-assigned this Jun 12, 2026
@kilo-code-bot

kilo-code-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental diff (one commit since last review) removes an unnecessary try/catch wrapper around the log(...) call in rewriteChatCompletionsOneOfAsAnyOf. The default logger (warnExceptInTestconsole.warn) cannot throw, so the guard was dead code; the refactor is correct and the clean-up is appropriate.

Files Reviewed (1 file, incremental)
  • apps/web/src/lib/ai-gateway/schema-rewrite.ts — removed try/catch around diagnostic log() call; no logic change, no risk

Previous review findings (unchanged files, carried forward):

  • apps/web/src/lib/ai-gateway/schema-rewrite.tsisFriendliChatCompletionsRequest type guard; BFS oneOfanyOf rewrite with visited-set cycle guard; correct
  • apps/web/src/lib/ai-gateway/schema-rewrite.test.ts — test coverage for logging behaviour and type guard
  • apps/web/src/lib/ai-gateway/providers/apply-provider-specific-logic.ts — uses isFriendliChatCompletionsRequest; organizationId param removed
  • apps/web/src/app/api/openrouter/[...path]/route.tsorganizationId arg removed to match updated signature
  • apps/web/src/lib/ai-gateway/schema-logging.ts — deleted (reverted)

Notable non-blocking observations (unchanged):

  • isRecord returns true for arrays, so array values are pushed onto the traversal stack and iterated via numeric Object.entries keys. Harmless but does slightly more work than necessary.
  • oneOf (XOR) → anyOf (OR) is a deliberate, lossy semantic downgrade acknowledged in the PR description.

Reviewed by claude-sonnet-4.6 · 399,263 tokens

Review guidance: REVIEW.md from base branch main

chrarnoldus and others added 5 commits June 12, 2026 18:14
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
…logging

Refactor the schema rewriting logic to use a dedicated type guard for
detecting Friendli chat completions requests. This improves readability
and type safety in the provider logic.

Additionally, update the `oneOf` to `anyOf` rewriting process to return
the count of rewritten schemas and include optional logging to track
when these transformations occur in production.

- Add `isFriendliChatCompletionsRequest` type guard
- Update `rewriteChatCompletionsOneOfAsAnyOf` to support optional logging
- Add unit tests for the new type guard and logging behavior

Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
Remove the try-catch block around the Friendli schema rewrite log
to simplify the code, as the logging utility is expected to be safe.

Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.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.

1 participant