Repository navigation
Conversation
|
Hi @caps-xia, Thank you for your contribution and for taking the time to submit this pull request. Our team is currently reviewing your changes, and we will reach out if we need any further information. |
dosadczuk
left a comment
There was a problem hiding this comment.
Thanks for the clear issue and repro. The warning itself looks good. I have two small requests on MessageConverter and one on the test.
| } | ||
|
|
||
| if (hasTools) { | ||
| if (modelDefaultOptions != null && !(modelDefaultOptions instanceof ToolCallingChatOptions)) { |
There was a problem hiding this comment.
Nit: the !(modelDefaultOptions instanceof ToolCallingChatOptions) check is always true here, because line 447 already returns in that case, so if (modelDefaultOptions != null) is enough. The comment at 458-464 repeats the log message. Could you drop it and shorten the message? For example:
logger.warn(
"Model options {} are not ToolCallingChatOptions, so tools go out in generic options"
+ " that providers such as OpenAI reject with a ClassCastException. A wrapping"
+ " ChatModel should delegate getOptions() to the wrapped model.",
modelDefaultOptions.getClass().getName());There was a problem hiding this comment.
Done — simplified to the null check, dropped the duplicated comment and shortened the message.
| // Prompt.getOptions() to their own type (e.g. OpenAiChatModel.createRequest) will then | ||
| // fail with a bare ClassCastException whose cause is hard to spot. Warn so the cause is | ||
| // one grep away; dummy models that ignore options keep working. | ||
| logger.warn( |
There was a problem hiding this comment.
Optional: this logs on every model call, including for test models that ignore options. Logging once per MessageConverter would cut the noise.
There was a problem hiding this comment.
Done — the warning now logs once per MessageConverter.
| * returns null) makes the bridge build provider-neutral ToolCallingChatOptions, which | ||
| * OpenAiChatModel.createRequest(...) cannot cast — ClassCastException on the first call. | ||
| */ | ||
| class WrappedModelOptionsNullProbeTest { |
There was a problem hiding this comment.
This test passes with the MessageConverter change reverted. The mock throws the ClassCastException itself, and nothing checks the warning or the prompt options. Lines 29, 51 and 100 also describe a null default and a fail-fast IllegalStateException, which this PR doesn't do. Could you remove the file? If you want coverage, add a MessageConverterTest case next to testToolOptionsFallBackToGenericWhenNoProviderDefaults. It would pass ChatOptions.builder().build() with testTool() and assert the options are ToolCallingChatOptions with one callback.
There was a problem hiding this comment.
Good catch — the old probe tested the mock, not the fix. Rewritten as MessageConverterProviderNeutralOptionsTest: it drives the converter directly, asserts the tool callbacks land on the options, and (via a logback ListAppender) that exactly one warning is logged and not repeated on a second call; a second test locks the no-warning path for real ToolCallingChatOptions defaults.
…ions ChatModel.getOptions() has an interface default returning an empty DefaultChatOptions, so delegating/wrapping ChatModels that do not override it yield non-ToolCallingChatOptions defaults. With ADK tools attached, buildChatOptions then constructs provider-neutral ToolCallingChatOptions, and provider models that cast Prompt.getOptions() to their own type (e.g. OpenAiChatModel .createRequest) fail with a bare ClassCastException several frames away from the cause. Log an actionable WARN in that case (dummy/test models that ignore options keep working); reproduction and analysis in the linked issue.
c68d8c0 to
9aee154
Compare
|
Thanks @dosadczuk for the review! All three points addressed in 9aee154 (single commit): the condition is simplified to the null check with a shortened one-off warning (logged once per MessageConverter), and the old probe test — which indeed tested the mock rather than the fix — is replaced by MessageConverterProviderNeutralOptionsTest, asserting the warning itself (exactly once, via a logback appender) plus the no-warning path for tool-calling defaults. Full module suite green locally (309 tests). |
Link to Issue or Description of Change
Fixes: #1731
Problem:
ChatModel.getOptions()has an interface default returning an emptyDefaultChatOptions, so delegating/wrapping ChatModels that do not override it yield non-ToolCallingChatOptionsdefaults. With ADK tools attached,buildChatOptions(...)then constructs provider-neutralToolCallingChatOptions, and provider models that castPrompt.getOptions()to their own type (e.g.OpenAiChatModel.createRequest(...)) fail with a bareClassCastExceptionseveral frames away from the cause.Solution:
Warn-only in
buildChatOptions(...): when tool callbacks would land on provider-neutral options, log an actionable message naming the actual cause ("if the model is a wrapper, make getOptions() delegate to the underlying provider model's options, e.g. OpenAiChatOptions") right before the provider would fail. Behavior is unchanged — dummy/test models that ignore options keep working (the existing integration tests cover that path); the diagnosis goes from "unexplainable CCE" to one grep.Testing Plan
Unit Tests:
WrappedModelOptionsNullProbeTest: a wrapper withoutgetOptions()override + an ADK tool reproduces the exact failure chain (defaultDefaultChatOptions, notToolCallingChatOptions, then the cast failure), asserting the provider-sideClassCastExceptionstill surfaces as before (warn-only, no behavior change).Manual End-to-End (E2E) Tests:
Reproduced in production (failover wrapper, non-streaming path died on first call) and via the probe on current main — the WARN now fires immediately before the provider failure.
Checklist
Additional context
A fail-fast variant was considered and rejected: the upstream dummy-model integration tests legitimately rely on the same interface default, so throwing would break valid usages. Warn-only keeps every current behavior intact while making the failure diagnosable. If maintainers prefer a different remediation (e.g. docs-only, or constructing provider-typed options another way), happy to adjust.