google / google/adk-js

ChromeBuiltInLlm rewrites schema-valid null tool args to {} before execution

Open
#900 4 comments 0 reactions 1 assignee Claimed by @Varun-S10 View on GitHub
bug request clarification
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.