ChromeBuiltInLlm rewrites schema-valid null tool args to {} before execution
- Dominant language
- TypeScript
- Stars
- 1.4k
- Forks
- 205
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 92
Description
## Describe the bug
`ChromeBuiltInLlm` can generate a `responseConstraint` that explicitly permits `null` for a tool's top-level `args`, but `parseToolChoice()` silently rewrites that valid model output to `{}` before creating the ADK `functionCall`.
That means the value constrained and returned by Chrome is not the value the Runner executes.
The relevant parser logic is currently:
```ts
const args = isRecord(parsed['args']) ? parsed['args'] : {};
```
For a declaration whose argument schema is nullable, schema conversion can produce:
```json
{"type":["object","null"],"properties":{"mode":{"type":"string"}}}
```
Chrome is therefore allowed to return:
```json
{"kind":"tool","name":"sensitive_action","args":null}
```
but ADK emits and executes the equivalent of:
```text
sensitive_action({})
```
## Reproduction
Verified against `google/adk-js` commit `77b08030479611c75dd559a68d18185b609f6e28` with the real Chrome Prompt API / on-device model, not only a mock.
Environment used for the real-browser reproduction:
- Chrome `153.0.8010.36`
- Windows x64
- `LanguageModel.availability() === "available"`
Minimal tool shape:
```ts
new FunctionTool({
name: 'sensitive_action',
description: 'Test action',
parameters: {
type: 'OBJECT',
nullable: true,
properties: {mode: {type: 'STRING'}},
},
execute: (args) => {
console.log(args);
return {ok: true};
},
});
```
Observed real Chrome output:
```json
{"kind":"tool","name":"sensitive_action","args":null}
```
Observed argument received by `FunctionTool.execute()`:
```json
{}
```
The full ADK loop completes normally: the function call is emitted, the tool runs, and the function response is added to the conversation.
I also reproduced this in a focused local regression test around `LlmAgent -> Runner -> FunctionTool`; the existing Chrome model tests plus the boundary tests pass while demonstrating the rewrite.
## Expected behavior
The constrained output and the dispatched tool call should have the same argument semantics.
Possible fixes seem to be either:
1. Do not allow `null` at the top-level `args` position when building the synthetic tool-call constraint, because ADK function calls require object arguments; or
2. Preserve/validate the constrained value and reject an incompatible tool call rather than silently converting a non-object value to `{}`.
I would prefer explicit validation over coercion, because `{}` can have different application semantics from `null`.
## Additional note
This is a correctness/schema-fidelity report, not a claim that Chrome's constrained decoding is being bypassed. In a negative control, Chrome correctly enforced a declared numeric argument type and refused to emit the requested wrong-typed value.
If this direction looks right, I am happy to prepare a PR with the parser/constraint fix and regression coverage.
Contributor guide
Assessment
This issue has not been assessed yet.