Skip to content

Allow empty and null ToolContext in tool method invocation#6557

Open
kuntal1461 wants to merge 2 commits into
spring-projects:mainfrom
kuntal1461:fix/tool-context-empty-and-null-values
Open

Allow empty and null ToolContext in tool method invocation#6557
kuntal1461 wants to merge 2 commits into
spring-projects:mainfrom
kuntal1461:fix/tool-context-empty-and-null-values

Conversation

@kuntal1461

Copy link
Copy Markdown
Contributor

Problem

Fixes #6545

Two related but independent bugs prevent callers from expressing an optional or absent tool context:

Bug 1 — MethodToolCallback (spring-ai-model)

validateToolContextSupport() treated an empty ToolContext the same as a missing one, and threw IllegalArgumentException in both cases. This forced callers to inject fake placeholder entries (e.g. Map.of("foo", "bar")) just to satisfy the guard, even when no real context was needed:

// Workaround required before this fix:
Map<String, Object> toolContext = new HashMap<>();
if (orderNumber != null) {
    toolContext.put("orderNumber", orderNumber);
} else {
    toolContext.put("foo", "bar"); // fake entry to prevent exception
}

Bug 2 — DefaultChatClient (spring-ai-client-chat)

ChatClientRequestSpec.toolContext() rejected maps that contained null values via Assert.noNullElements(), making it impossible to represent optional context entries as null.

Changes

spring-ai-modelMethodToolCallback

  • Remove validateToolContextSupport(). Empty context is valid; the tool method decides which keys it requires.
  • In buildMethodArguments(), convert a null ToolContext to new ToolContext(Map.of()) so tool methods that declare the parameter never receive null, preventing NPE without throwing.
  • Remove now-unused imports ClassUtils and CollectionUtils.

spring-ai-client-chatDefaultChatClient

  • Remove the Assert.noNullElements() guard on context map values only.
  • The following guards are preserved unchanged:
    • null map reference → still rejected
    • null keys → still rejected
    • blank/empty string keys → still rejected

Behaviour matrix

Scenario Before After
Method declares ToolContext + null context throws tool receives ToolContext(Map.of())
Method declares ToolContext + empty context throws tool receives the empty context
Method declares ToolContext + non-empty context works works (unchanged)
Method does not declare ToolContext + any context silently ignored silently ignored (unchanged)
toolContext(map) with null value throws accepted
toolContext(null) map reference throws throws (unchanged)
toolContext(map) with null key throws throws (unchanged)

Tests

New: MethodToolCallbackToolContextTests — 13 tests covering:

  • null context (implicit via call(input)) → tool receives empty ToolContext
  • explicit null context → tool receives empty ToolContext
  • empty HashMap and Map.of() are accepted
  • single and multiple context entries pass through unchanged (backward compatibility)
  • null value in context map is forwarded to the tool
  • key case is preserved
  • method without ToolContext param: null / empty / non-empty context are all silently ignored (original behaviour preserved)
  • exception path with empty and null context wraps correctly in ToolExecutionException

Updated: DefaultChatClientTests — 2 tests renamed from *ThenThrow to *ThenSucceed to reflect the new allowed behaviour for null map values.

Test results

Module Tests Failures
spring-ai-model 829 0
spring-ai-client-chat 569 0

When a tool method declares a `ToolContext` parameter, callers were
previously required to supply a non-empty context map or the call
failed immediately with `IllegalArgumentException` from
`MethodToolCallback.validateToolContextSupport()`. This forced
workarounds such as injecting fake placeholder entries
(e.g. `Map.of("foo","bar")`) even when the real context was empty
or optional.

Separately, `ChatClientRequestSpec.toolContext()` rejected any map
containing `null` values, making it impossible to represent optional
context entries (e.g. an `orderNumber` that may not always be
present) without pre-filtering them in application code.

Changes in `spring-ai-model` (`MethodToolCallback`):
- Remove `validateToolContextSupport()`, which incorrectly treated
  an empty `ToolContext` the same as a missing one. Empty context is
  valid; the tool method itself decides which keys it requires.
- In `buildMethodArguments()`, convert a `null` `ToolContext` to
  `new ToolContext(Map.of())` so tool methods that declare the
  parameter never receive `null`, preventing `NullPointerException`
  without throwing at the framework level.
- Remove now-unused imports `ClassUtils` and `CollectionUtils`.

Changes in `spring-ai-client-chat` (`DefaultChatClient`):
- Remove the `Assert.noNullElements()` guard on context map values.
  Callers can now pass `null` values to represent optional entries.
  Guards for a null map reference, null keys, and blank keys are
  preserved unchanged.

Closes spring-projects#6545

Signed-off-by: kuntal1461 <kuntal.1461@gmail.com>
The existing guard that rejects null values in the toolContext map was
correct and intentional — null values are ambiguous, break getOrDefault
semantics, and can cause serialization issues in MCP contexts. However,
the previous error message did not explain what to do instead:

  Before: "context values cannot contain null elements"
  After:  "context values cannot contain null elements; to represent an
           optional entry, omit the key rather than setting it to null"

The improved message guides callers toward the correct pattern: omit
the key entirely when a value is absent rather than setting it to null.
This is already possible now that an empty ToolContext is accepted by
MethodToolCallback (see companion fix in the previous commit).

Also adds two tests to DefaultChatClientTests covering edge cases that
were not previously exercised: a map with multiple values where one is
null (mixed case), and an empty map (which succeeds).

Closes spring-projects#6545

Signed-off-by: kuntal1461 <kuntal.1461@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support empty ToolContext, or null values

2 participants