Skip to content

feat(ai-proxy): add defaults field for fallback model options#12895

Open
sihyeonn wants to merge 2 commits intoapache:masterfrom
sihyeonn:feat/sh-defaults
Open

feat(ai-proxy): add defaults field for fallback model options#12895
sihyeonn wants to merge 2 commits intoapache:masterfrom
sihyeonn:feat/sh-defaults

Conversation

@sihyeonn
Copy link
Copy Markdown
Contributor

@sihyeonn sihyeonn commented Jan 13, 2026

Summary

Add a defaults field to the ai-proxy and ai-proxy-multi plugins for fallback model parameters that apply only when the client request omits them.

This is complementary to the existing options field:

  • options: always overrides request values (enforcement)
  • defaults: applied only when the client does not set the field (fallback)

Priority order: options > client request > defaults

Closes #13149

Changes

  • Add defaults field to schema for ai-proxy and ai-proxy-multi
  • Apply defaults in base.lua before stream detection
  • Include defaults.model in llm_model variable fallback
  • Add documentation (en/zh) for ai-proxy.md and ai-proxy-multi.md
  • Add test cases covering precedence rules

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jan 13, 2026
Copy link
Copy Markdown
Contributor

@Baoyuantop Baoyuantop left a comment

Choose a reason for hiding this comment

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

  1. Supplement Documentation - Update ai-proxy.md and ai-proxy.md, adding documentation for the defaults field
  2. Supplement ai-proxy Plugin Tests - Add test cases to ai-proxy.openai-compatible.t or relevant test files
  3. Link to Related Issues or Supplement Requirement Context - Specify the source of use cases for this feature

@Baoyuantop Baoyuantop added the wait for update wait for the author's response in this issue/PR label Jan 26, 2026
@sihyeonn
Copy link
Copy Markdown
Contributor Author

Thanks for the review! I've addressed your feedback:

  1. Added documentation for the defaults field in both ai-proxy.md and ai-proxy-multi.md (en/zh)
  2. Added test cases to ai-proxy.openai-compatible.t
  3. This feature addresses a common use case where admins want to set fallback values (like max_tokens, temperature) without overriding user preferences. Unlike options which always overrides, defaults only applies when users don't specify values in their requests.

@Baoyuantop
Copy link
Copy Markdown
Contributor

Hi @sihyeonn, since this PR is not associated with any issue and is a new feature, we suggest that we first communicate the detailed solution for this feature in an issue, and then push the code forward after the community agrees.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for a defaults configuration block to the AI proxy plugins so operators can provide fallback model parameters that apply only when the client request omits them (while still allowing options to always override).

Changes:

  • Introduces defaults in ai-proxy / ai-proxy-multi plugin schemas and documents the new field (EN/ZH).
  • Passes defaults through the proxy base into the OpenAI-compatible driver layer.
  • Adds test coverage for default-vs-user-vs-options precedence.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
apisix/plugins/ai-drivers/openai-base.lua Applies model_defaults into the outgoing request body before applying model_options.
apisix/plugins/ai-proxy/base.lua Plumbs defaults into extra_opts for drivers.
apisix/plugins/ai-proxy/schema.lua Adds defaults field to single and multi instance schemas.
t/plugin/ai-proxy.openai-compatible.t Adds tests validating precedence of defaults vs request vs options.
t/plugin/ai-proxy-multi.openai-compatible.t Adds equivalent precedence tests for ai-proxy-multi.
docs/en/latest/plugins/ai-proxy.md Documents new defaults field for ai-proxy.
docs/en/latest/plugins/ai-proxy-multi.md Documents new instances.defaults field for ai-proxy-multi.
docs/zh/latest/plugins/ai-proxy.md Chinese documentation update for defaults.
docs/zh/latest/plugins/ai-proxy-multi.md Chinese documentation update for instances.defaults.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +403 to +406
test-type: defaults
--- response_body
{"max_tokens":512,"model":"server-model","temperature":0.7}

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

These assertions compare the raw JSON string exactly, but the response is produced via cjson.safe.encode on a Lua table with string keys, where key order is not guaranteed. This can make the test flaky across Lua/cjson builds. Prefer asserting via response_body_like/response_body eval (regexes) or decode the JSON and compare fields rather than matching the exact serialized key order.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already switched to response_body_like with regex in the previous commit to handle non-deterministic key ordering.

Comment on lines +414 to +417
test-type: defaults
--- response_body
{"max_tokens":100,"model":"server-model","temperature":0.5}

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

These assertions compare the raw JSON string exactly, but the response is produced via cjson.safe.encode on a Lua table with string keys, where key order is not guaranteed. This can make the test flaky across Lua/cjson builds. Prefer asserting via response_body_like/response_body eval (regexes) or decode the JSON and compare fields rather than matching the exact serialized key order.

Copilot uses AI. Check for mistakes.
Comment thread t/plugin/ai-proxy.openai-compatible.t Outdated
Comment on lines +424 to +427
--- more_headers
test-type: defaults
--- response_body
{"max_tokens":100,"model":"server-model","temperature":0.7}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

These assertions compare the raw JSON string exactly, but the response is produced via cjson.safe.encode on a Lua table with string keys, where key order is not guaranteed. This can make the test flaky across Lua/cjson builds. Prefer asserting via response_body_like/response_body eval (regexes) or decode the JSON and compare fields rather than matching the exact serialized key order.

Copilot uses AI. Check for mistakes.
Comment on lines +367 to +370
test-type: defaults
--- response_body
{"max_tokens":512,"model":"server-model","temperature":0.7}

Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

These assertions compare the raw JSON string exactly, but the response is produced via cjson.safe.encode on a Lua table with string keys, where key order is not guaranteed. This can make the test flaky across Lua/cjson builds. Prefer asserting via response_body_like/response_body eval (regexes) or decode the JSON and compare fields rather than matching the exact serialized key order.

Copilot uses AI. Check for mistakes.
Comment on lines +377 to +380
--- more_headers
test-type: defaults
--- response_body
{"max_tokens":100,"model":"server-model","temperature":0.5}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

These assertions compare the raw JSON string exactly, but the response is produced via cjson.safe.encode on a Lua table with string keys, where key order is not guaranteed. This can make the test flaky across Lua/cjson builds. Prefer asserting via response_body_like/response_body eval (regexes) or decode the JSON and compare fields rather than matching the exact serialized key order.

Copilot uses AI. Check for mistakes.
Comment on lines +387 to +391
{ "messages": [ { "role": "user", "content": "hello" } ], "model": "user-model", "max_tokens": 100 }
--- more_headers
test-type: defaults
--- response_body
{"max_tokens":100,"model":"server-model","temperature":0.7}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

These assertions compare the raw JSON string exactly, but the response is produced via cjson.safe.encode on a Lua table with string keys, where key order is not guaranteed. This can make the test flaky across Lua/cjson builds. Prefer asserting via response_body_like/response_body eval (regexes) or decode the JSON and compare fields rather than matching the exact serialized key order.

Copilot uses AI. Check for mistakes.
Comment on lines +318 to +325
-- defaults: apply only when not set in request
if extra_opts.model_defaults then
for opt, val in pairs(extra_opts.model_defaults) do
if request_table[opt] == nil then
request_table[opt] = val
end
end
end
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Applying model_defaults here mutates request_table after apisix/plugins/ai-proxy/base.lua has already derived ctx.var.request_type and stream_options from the original request body. If defaults (or options) sets fields that affect control flow (e.g., stream), APISIX can end up sending a streamed request while still treating it as non-streaming for logging/metrics and without setting stream_options.include_usage. Consider applying defaults/options earlier (before the request_body.stream branch in before_proxy) or re-evaluating stream-related ctx vars after merging defaults/options.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved defaults application to before the stream detection block so ctx vars are set correctly. This was addressed in the previous commit.

Comment on lines 61 to 66
local extra_opts = {
name = ai_instance.name,
endpoint = core.table.try_read_attr(ai_instance, "override", "endpoint"),
model_options = ai_instance.options,
model_defaults = ai_instance.defaults,
conf = ai_instance.provider_conf or {},
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Now that ai_instance.defaults is passed through as extra_opts.model_defaults, fields like defaults.model can affect the effective model sent upstream, but before_proxy currently sets ctx.var.llm_model based only on options.model or request_body.model. This means logging/metrics may miss the actual model when it comes solely from defaults. Consider including ai_instance.defaults.model in the model selection logic (with the intended precedence) so ctx vars reflect the request actually sent.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added defaults.model as a fallback in the llm_model assignment. Also removed the now-unnecessary model_defaults passthrough in extra_opts since defaults are applied directly to request_body in before_proxy.

Copy link
Copy Markdown
Member

@moonming moonming left a comment

Choose a reason for hiding this comment

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

Hi @sihyeonn, thank you for the ai-proxy defaults field proposal!

The concept of having a defaults field for fallback model options is interesting. A few points to discuss:

  1. Naming clarity: defaults vs fallback — the name defaults implies these are baseline values that can be overridden, while fallback implies they're used when something is missing. Could you clarify the intended semantics? If these are values used when the request doesn't specify them, defaults is the right name.

  2. Interaction with options: How does defaults interact with the existing options field? We need clear documentation on the precedence: request values > options (override) > defaults (fallback).

  3. Schema documentation: Please ensure the schema includes clear descriptions for each field in defaults.

Looking forward to the design discussion! Thank you.

@sihyeonn
Copy link
Copy Markdown
Contributor Author

Addressed all review feedback. Thanks for the reviews!

@sihyeonn
Copy link
Copy Markdown
Contributor Author

@Baoyuantop Addressed all the review feedback — tests switched to regex matching, defaults applied before stream detection, and llm_model now includes defaults.model fallback. Ready for review.

@Baoyuantop
Copy link
Copy Markdown
Contributor

Hi @sihyeonn, before we push this PR further, I hope you can answer these design questions first #12895 (review)

@Baoyuantop
Copy link
Copy Markdown
Contributor

Hi @sihyeonn, please fix the code lint error and merge the master branch

Separate options and defaults behavior:
- options: always override user request values
- defaults: apply only when not set in user request

This allows more flexible configuration where administrators can
enforce certain values (via options) while providing sensible
defaults for optional parameters.

Priority order: options > client request > defaults

Closes apache#13149

Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
…erty

Add explicit `model` property to model_defaults_schema for consistency
with model_options_schema, and improve the description to clarify the
fallback semantics (only applied when not set in request).

Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
@sihyeonn
Copy link
Copy Markdown
Contributor Author

sihyeonn commented Apr 16, 2026

Hi @moonming and @Baoyuantop, apologies for the very late response — I've been on an extended vacation and kept missing the review notifications. Sorry for the repeated delays!

1. Naming: defaults vs fallback

I'd like to keep the name defaults. fallback tends to connote error-recovery semantics, whereas defaults is the established term for "values that apply when not explicitly provided" (e.g., function default parameters, HTTP default headers). Since that's exactly the intent here, I think defaults is the clearer choice — and as you noted in the review, if these are values used when the request doesn't specify them, defaults is the right name.

2. Priority order

I believe the correct precedence is options > client request > defaults, not request values > options as written in the review.

options is an enforcement/override mechanism — it always wins regardless of what the client sends (admin enforces). defaults is the opposite: it only kicks in when the client omits the field (admin suggests). So the full priority chain is:

Priority Source Behavior
1 (highest) options Always overrides — admin enforcement
2 client request What the client actually sent
3 (lowest) defaults Applied only when client omitted the field

3. Schema documentation

Good catch — I've added an explicit model property to the defaults schema (consistent with options) and improved the description to clarify the fallback semantics. See the latest commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

discuss enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files. user responded

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ai-proxy): add defaults field for fallback model parameters

4 participants