Skip to content

feat(spring-ai): bridge Spring AI ToolCallback to ADK BaseTool - #1300

Open
mekkiamiri wants to merge 1 commit into
google:mainfrom
mekkiamiri:feat-spring-ai-bridges
Open

mekkiamiri wants to merge 1 commit into
google:mainfrom
mekkiamiri:feat-spring-ai-bridges

Conversation

@mekkiamiri

Copy link
Copy Markdown

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

2. Or, if no issue exists, describe the change:

If applicable, please follow the issue templates to provide as much detail as
possible.

Problem:
Spring AI provides a rich ecosystem of ToolCallback implementations: SyncMcpToolCallback / AsyncMcpToolCallback produced by spring-ai-starter-mcp-client from spring.ai.mcp.client.* properties, @Tool-annotated method callbacks, programmatically declared FunctionToolCallbacks, and so on. Today, none of these can be used by an ADK LlmAgent without manual conversion. The existing ToolConverter in
contrib/spring-ai only converts ADK BaseTool → Spring AI ToolCallback (one direction). Spring Boot users who want their ADK agent to use MCP tools have to either reimplement MCP wiring via ADK's native McpToolset.createMcpToolset(...) Java API, or write their own ToolCallback → BaseTool adapter.

Solution:
Add SpringAiToolCallbackBackedAdkTool — an adapter that wraps any Spring AI ToolCallback as an ADK BaseTool. It is the reverse direction of the existing ToolConverter. Together they make ADK and Spring AI tool ecosystems fully interoperable.

How it works:

  • ToolCallback.getToolDefinition() provides the tool name, description, and JSON Schema.
  • The JSON Schema is converted to ADK's Schema via Schema.fromJson(...). If parsing fails (e.g. malformed schema), the bridge falls back to parametersJsonSchema(Object) with a logged warning — no hard failure.
  • At invocation time, ADK's Map<String, Object> arguments are serialized to JSON, dispatched to ToolCallback.call(String), and the JSON response is parsed back to Map<String, Object>.
  • Non-object responses (primitives, arrays, raw strings) are wrapped under a "result" key for structural consistency.
  • A static wrapAll(List<? extends ToolCallback>) helper fans a list of callbacks (e.g. from McpToolCallbackProvider.getToolCallbacks()) into a List<BaseTool> in one call.

Usage:

@Configuration
class AgentConfig {

  @Bean
  public LlmAgent rootAgent(SpringAI springAI, List<ToolCallback> mcpToolCallbacks) {
    return LlmAgent.builder()
        .name("root_agent")
        .model(springAI)
        .tools(SpringAiToolCallbackBackedAdkTool.wrapAll(mcpToolCallbacks))
        .instruction("Use the available tools to answer the user.")
        .build();
  }
}

The List<ToolCallback> is auto-injected by spring-ai-starter-mcp-client's McpToolCallbackAutoConfiguration. The bridge converts every callback into a BaseTool consumable by the agent. Full documentation is in contrib/spring-ai/README.md under "Tool / MCP Bridge — Spring AI ToolCallback as ADK BaseTool".

This PR is fully independent of #653 (the Spring Boot starter PR). It only touches contrib/spring-ai/. Either can be merged first; no ordering dependency.

ADK's native MCP client (com.google.adk.tools.mcp.*) is not removed and not changed — it remains the right choice for CLI / non-Spring-Boot usage. The bridge is additive.

### Testing Plan

_Please describe the tests that you ran to verify your changes. This is required
for all PRs that are not small documentation or typo fixes._

**Unit Tests:**

- [X] I have added or updated unit tests for my change.
- [X] All unit tests pass locally.

_Please include a summary of passed java test results._
7 unit tests in SpringAiToolCallbackBackedAdkToolTest cover:

1. declaration_isBuiltFromToolDefinition — verifies name / description / schema are extracted from ToolDefinition
2. runAsync_serializesArgs_andParsesJsonObjectResponse — happy-path JSON object response
3. runAsync_wrapsScalarResponse_underResultKey — non-object responses are wrapped
4. runAsync_emptyResponse_yieldsEmptyMap — empty / blank responses produce an empty map
5. runAsync_nullArgs_sendsEmptyObject — null args serialize to {} instead of null
6. wrapAll_convertsEveryCallback — static helper fans out correctly
7. invalidSchema_fallsBackToRawJsonSchema_withoutThrowing — graceful fallback on malformed JSON Schema

Test results:

[INFO] --- surefire:3.5.2:test (default-test) @ google-adk-spring-ai ---
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

**Manual End-to-End (E2E) Tests:**

Tested end-to-end with the existing Spring AI SpringAIAutoConfiguration providing the SpringAI (BaseLlm) bean and a mocked ToolCallback representing an MCP tool. The agent invokes the bridge, which dispatches to the callback and returns parsed JSON to the agent flow. Real-world MCP integration follows the same path with spring-ai-starter-mcp-client providing the ToolCallback beans from spring.ai.mcp.client.* properties.

Setup to reproduce locally:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat.options.model: gpt-4o-mini
    mcp:
      client:
        sse:
          connections:
            filesystem:
              url: http://localhost:3000

Then wire the agent as in the Usage snippet above and start the Spring Boot app — LlmAgent.tools(...) will receive every MCP tool exposed by the configured MCP server, transparently usable by the LLM.


### Checklist

- [X] I have read the [CONTRIBUTING.md](./CONTRIBUTING.md) document.
- [X] My pull request contains a single commit.
- [X] I have performed a self-review of my own code.
- [X] I have commented my code, particularly in hard-to-understand areas.
- [X] I have added tests that prove my fix is effective or that my feature works.
- [X] New and existing unit tests pass locally with my changes.
- [X] I have manually tested my changes end-to-end.
- [X] Any dependent changes have been merged and published in downstream modules.

### Additional context

- The bridge lives in contrib/spring-ai/src/main/java/com/google/adk/models/springai/bridge/ to keep it visually grouped with future bridges if Spring AI's VectorStore / ChatMemoryRepository integrations are ever requested.
- This PR deliberately scopes to tools only. The VectorStore → BaseMemoryService and ChatMemoryRepository → BaseSessionService bridges are out of scope — for sessions/memory, ADK's native Firestore (contrib/firestore-session-service) is a better fit because it preserves the full Event semantics (function calls with structured args/results, state deltas, branch IDs for sub-agent transfers) that Spring AI's Message model
would lose in translation.
- No changes to ADK core. No changes to the starter (which is the subject of #653, independent).

@mekkiamiri
mekkiamiri force-pushed the feat-spring-ai-bridges branch from 9e8b683 to 763ce23 Compare June 24, 2026 09:15
@ddobrin
ddobrin self-requested a review June 24, 2026 16:54
@ddobrin

ddobrin commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Hi @mekkiamiri
a few notes on this PR:

(1) Schema Degradation:

buildDeclaration(...) prefers Schema.fromJson(inputSchema) and assigns it via builder.parameters(...):

builder.parameters(Schema.fromJson(inputSchema)); // populates FunctionDeclaration.parameters()

When these tools run on the SpringAI model, ToolConverter.convertToSpringAiTools must turn the FunctionDeclaration back into a Spring AI schema, and it branches on which field is set ToolConverter.java:

if (declaration.parameters().isPresent()) {
// LOSSY: convertSchemaToSpringAi() copies only type/description/properties/required
// → drops items, enum, format, anyOf/oneOf, additionalProperties, $defs/$ref, default
} else if (declaration.parametersJsonSchema().isPresent()) {
// FAITHFUL: writeValueAsString(parametersJsonSchema.get()) — serializes the schema verbatim
}

Because the bridge sets parameters(), every bridged tool takes the lossy branch. The PR's test schema ({city: string}) is too flat to reveal it; a real MCP tool with an array/items, enum, or $defs reaches the model with those stripped.

The parametersJsonSchema fallback has the inverse problem: it passes the raw String (builder.parametersJsonSchema(inputSchema)), so the faithful branch does writeValueAsString("{...}") → a double-encoded, quoted string.

Option
Set the schema via parametersJsonSchema(parsedNode) and do not set parameters(...). This routes through the faithful branch, preserves the schema verbatim, and removes the double-encoding fallback in one move.

buildDeclaration is currently static and has no ObjectMapper; pass the instance mapper in (it's already a field).

Current:
String inputSchema = def.inputSchema();
if (inputSchema != null && !inputSchema.isBlank()) {
try {
builder.parameters(Schema.fromJson(inputSchema)); // → lossy branch
} catch (Exception parseFailed) {
builder.parametersJsonSchema(inputSchema); // → raw String → double-encoded
}
}

Suggestion (entirely within SpringAiToolCallbackBackedAdkTool):
String inputSchema = def.inputSchema();
if (inputSchema != null && !inputSchema.isBlank()) {
try {
// Pass the JSON Schema through verbatim, parsed to a structure (not a raw String).
// Mirrors AbstractMcpTool.declaration(); ToolConverter then serializes it faithfully,
// preserving items/enum/format/$defs/$ref/additionalProperties.
builder.parametersJsonSchema(objectMapper.readValue(inputSchema, MAP_TYPE));
} catch (Exception schemaUnparseable) {
// Genuinely malformed JSON: there is no usable schema to send. Warn and leave it unset
// rather than emit a degraded or quoted-string schema that misleads the model.
logger.warn(
"Spring AI tool '{}' has an unparseable input schema; declaring it with no parameter"
+ " schema. Cause: {}",
def.name(), schemaUnparseable.getMessage());
}
}

(2) Additional tests, not only mocks
All tests mock a generic ToolCallback + DefaultToolDefinition; the described E2E uses "a mocked ToolCallback."

Nothing exercises a real SyncMcpToolCallback/AsyncMcpToolCallback or an MCP-shaped result (content arrays, isError, the JSON the MCP callback actually emits)

(3) Exception handling
In runAsync, wrap the call(...) dispatch so a thrown exception (and a recognizable error payload) yields ImmutableMap.of("error", ...), matching AbstractMcpTool.wrapCallResult.

This needs to be documented in the README as well

(4) Autoconfigure can be updated to add automatic discovery of ToolCallback beans

@ddobrin ddobrin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestions left in the PR comments

@mekkiamiri
mekkiamiri force-pushed the feat-spring-ai-bridges branch from 763ce23 to 85d439d Compare July 3, 2026 08:04
mekkiamiri added a commit to mekkiamiri/adk-java that referenced this pull request Jul 3, 2026
…autoconfig discovery

Addresses 3 of the 4 review comments on PR google#1300:

1. Schema degradation (fidelity) — buildDeclaration now sets
   FunctionDeclaration.parametersJsonSchema(Map) with the parsed JSON
   schema, not parameters(Schema.fromJson). Downstream ToolConverter
   takes the faithful branch, preserving items/enum/format/anyOf/oneOf/
   additionalProperties/$defs/$ref. Malformed schemas leave the field
   unset (logged warning) instead of emitting a double-encoded quoted
   string via the old parametersJsonSchema(String) fallback.

2. Exception handling — runAsync now catches exceptions thrown by
   ToolCallback.call(...) and returns Map.of("error", <message>),
   matching AbstractMcpTool.wrapCallResult(...). Falls back to the
   exception's simple class name when message is null. The agent flow
   observes a structured error result instead of an aborted Single.

3. Autoconfigure discovery — new SpringAiToolBridgeAutoConfiguration
   auto-discovers every ToolCallback bean and exposes them as
   @bean("springAiTools") List<BaseTool>. Users inject the list
   directly into their agent factory; no more manual wrapAll(...) call.
   Registered in META-INF/spring/AutoConfiguration.imports.

Tests updated: 9 tests total (was 7). New tests cover the faithful
schema path (rich schema with items/enum/additionalProperties preserved),
exception handling with and without a message, and the unparseable-
schema-leaves-fields-unset behaviour.

README documents error handling behaviour, autoconfig discovery, and
schema fidelity in three new subsections.

Out of scope for this commit: adding integration tests against a real
SyncMcpToolCallback / AsyncMcpToolCallback — those require an MCP
testcontainer setup and will be handled in a follow-up.
@mekkiamiri

Copy link
Copy Markdown
Author

@ddobrin I updated the pr. could you please check again ?

mekkiamiri added a commit to mekkiamiri/adk-java that referenced this pull request Jul 3, 2026
…autoconfig discovery

Addresses 3 of the 4 review comments on PR google#1300:

1. Schema degradation (fidelity) — buildDeclaration now sets
   FunctionDeclaration.parametersJsonSchema(Map) with the parsed JSON
   schema, not parameters(Schema.fromJson). Downstream ToolConverter
   takes the faithful branch, preserving items/enum/format/anyOf/oneOf/
   additionalProperties/$defs/$ref. Malformed schemas leave the field
   unset (logged warning) instead of emitting a double-encoded quoted
   string via the old parametersJsonSchema(String) fallback.

2. Exception handling — runAsync now catches exceptions thrown by
   ToolCallback.call(...) and returns Map.of("error", <message>),
   matching AbstractMcpTool.wrapCallResult(...). Falls back to the
   exception's simple class name when message is null. The agent flow
   observes a structured error result instead of an aborted Single.

3. Autoconfigure discovery — new SpringAiToolBridgeAutoConfiguration
   auto-discovers every ToolCallback bean and exposes them as
   @bean("springAiTools") List<BaseTool>. Users inject the list
   directly into their agent factory; no more manual wrapAll(...) call.
   Registered in META-INF/spring/AutoConfiguration.imports.

Tests updated: 9 tests total (was 7). New tests cover the faithful
schema path (rich schema with items/enum/additionalProperties preserved),
exception handling with and without a message, and the unparseable-
schema-leaves-fields-unset behaviour.

README documents error handling behaviour, autoconfig discovery, and
schema fidelity in three new subsections.

Out of scope for this commit: adding integration tests against a real
SyncMcpToolCallback / AsyncMcpToolCallback — those require an MCP
testcontainer setup and will be handled in a follow-up.
@mekkiamiri
mekkiamiri force-pushed the feat-spring-ai-bridges branch from f2c6377 to 3bd1882 Compare July 3, 2026 13:22
@hemasekhar-p hemasekhar-p self-assigned this Jul 22, 2026
@hemasekhar-p

Copy link
Copy Markdown
Contributor

Hi @mekkiamiri, thank you for your contribution! We appreciate you taking the time to submit this pull request.To move forward with the review process, could you please squash your commits into a single commit?

@hemasekhar-p hemasekhar-p added the waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale. label Jul 22, 2026
@mekkiamiri
mekkiamiri force-pushed the feat-spring-ai-bridges branch from 3bd1882 to 9b0790c Compare July 27, 2026 13:34
@mekkiamiri

Copy link
Copy Markdown
Author

@hemasekhar-p done
could you please check ?

@hemasekhar-p

Copy link
Copy Markdown
Contributor

@mekkiamiri, thank you for addressing the feedback and squashing your commits, Currently this PR is under review by our team, we will keep you posted if any additional information is required. thank you.

@hemasekhar-p hemasekhar-p added needs review and removed waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale. labels Jul 27, 2026
@mekkiamiri
mekkiamiri force-pushed the feat-spring-ai-bridges branch from 9b0790c to 499ab37 Compare August 6, 2026 08:42
@mekkiamiri
mekkiamiri force-pushed the feat-spring-ai-bridges branch from 499ab37 to 70ff226 Compare September 2, 2026 15:54

### How it works

`SpringAiToolCallbackBackedAdkTool` reads `ToolCallback.getToolDefinition()` to extract the tool name, description, and JSON Schema. The schema is converted to ADK's `Schema` type via `Schema.fromJson(...)`; if parsing fails the bridge falls back to the `parametersJsonSchema(Object)` escape hatch (no hard failure). At invocation time the bridge serializes the `Map<String, Object>` arguments to JSON, dispatches to `ToolCallback.call(String)`, and parses the JSON response back to `Map<String, Object>`. Non-object responses (primitives / arrays / arbitrary strings) are wrapped under a `"result"` key for structural consistency.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MEDIUM - Please rewrite this section for Spring AI 2.0. 2.0 removed the tool loop from every ChatModel (upgrade notes), so with .model(springAI) ADK runs the bridged tools. In 2.0, @Tool objects become callbacks through ToolCallbacks.from(...) or a MethodToolCallbackProvider, and MCP tools arrive through a ToolCallbackProvider. Also drop what no longer matches the code: Schema.fromJson, the lost items/enum (fixed in #1617), and the wrapCallResult comparison.

Comment on lines +374 to +385
public LlmAgent rootAgent(SpringAI springAI, List<ToolCallback> mcpToolCallbacks) {
return LlmAgent.builder()
.name("root_agent")
.model(springAI)
.tools(SpringAiToolCallbackBackedAdkTool.wrapAll(mcpToolCallbacks))
.instruction("Use the available tools to answer the user.")
.build();
}
}
```

That's it. The `List<ToolCallback>` is auto-injected by `spring-ai-starter-mcp-client`'s `McpToolCallbackAutoConfiguration`. The bridge converts every callback into a `BaseTool`. The agent uses them transparently.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

HIGH - Under Spring AI 2.0 this injects no MCP tools. The 2.0 MCP starter registers SyncMcpToolCallbackProvider/AsyncMcpToolCallbackProvider beans (source), not ToolCallbacks; verified with an ApplicationContextRunner (0 tools, no error). The provider lists tools lazily and drops its cache on McpToolsChangedEvent (source), so query it per request through a BaseToolset, like ADK Kotlin's SpringAiToolset.


### Coexistence with ADK's native MCP

ADK ships its own MCP client in `com.google.adk.tools.mcp.*` (CLI / non-Spring-Boot scenarios). The two paths can be mixed at the `.tools(...)` boundary — both produce `BaseTool` instances — but it is strongly recommended to **pick one** in any given application. The Spring AI MCP route is the natural choice for Spring Boot apps because everything is property-driven; ADK's native `McpToolset` remains the right choice for non-Spring usage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LOW - Worth stating the trade-off here. Through Spring AI 2.0, MCP tools lose outputSchema and structuredContent: McpToolUtils.createToolDefinition copies only name, description and input schema (source), and SyncMcpToolCallback returns only content (source). ADK's McpToolset keeps the output schema as responseJsonSchema (source).

Comment on lines +42 to +50
@AutoConfiguration
@ConditionalOnClass({ToolCallback.class, SpringAiToolCallbackBackedAdkTool.class})
public class SpringAiToolBridgeAutoConfiguration {

@Bean(name = "springAiTools")
@ConditionalOnBean(ToolCallback.class)
@ConditionalOnMissingBean(name = "springAiTools")
public List<BaseTool> springAiTools(List<ToolCallback> toolCallbacks) {
return SpringAiToolCallbackBackedAdkTool.wrapAll(toolCallbacks);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

HIGH - This doesn't match how Spring AI 2.0 exposes tools. 2.0 dropped bean-name resolution (upgrade notes), so tools arrive as ToolCallback or ToolCallbackProvider beans (@Tool objects, MCP). Its own ToolCallingAutoConfiguration reads both and leaves MCP providers out of its eager list (source). This class reads ToolCallback only, so MCP and @Tool tools never show up (verified). Either mirror that with a toolset, ordering (@AutoConfigureOrder(LOWEST_PRECEDENCE), as SpringAIAutoConfiguration does since #1502), the adk.spring-ai.auto-configuration.enabled guard and tests, or drop it.

Comment on lines +1 to +9
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LOW - The license header is truncated: the "Unless required by applicable law..." paragraph is missing here and in the other two new files (see java.header).

}
try {
String responseJson = toolCallback.call(requestJson);
return parseResponse(responseJson);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LOW - In Spring AI 2.0 the tool loop (ToolCallingAdvisor or ToolCallingManager) applies ToolMetadata.returnDirect(). ADK replaces that loop, so the flag is silently ignored. Map it to skipSummarization after a successful call, as Kotlin does (L74-L78).

Comment on lines +97 to +107
} catch (Exception callFailed) {
// Match AbstractMcpTool.wrapCallResult(...) — return a structured error map rather
// than propagate the raw exception through the ADK Single, so the agent flow can
// observe the failure as a tool result instead of aborting the invocation.
logger.warn(
"Spring AI tool '{}' invocation failed: {}", name(), callFailed.getMessage());
return ImmutableMap.of(
"error",
callFailed.getMessage() == null
? callFailed.getClass().getSimpleName()
: callFailed.getMessage());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MEDIUM - In 2.0, ADK runs these tools, so Spring AI's DefaultToolExecutionExceptionProcessor never sees their failures. Its contract: a ToolExecutionException with a runtime cause becomes a message for the model, and everything else is rethrown (source). McpError is a hard failure in 2.0 (source). The catch-all sends everything to the model and skips ADK's onToolErrorCallback: live, it ran 0 times here and once with propagation. Apply that contract here

Comment on lines +112 to +130
private Map<String, Object> parseResponse(String responseJson) {
if (responseJson == null || responseJson.isBlank()) {
return ImmutableMap.of();
}
try {
return objectMapper.readValue(responseJson, MAP_TYPE);
} catch (Exception notAnObject) {
Object decoded = tryDecodeAsJsonValue(responseJson);
return ImmutableMap.of("result", decoded);
}
}

private Object tryDecodeAsJsonValue(String responseJson) {
try {
return objectMapper.readTree(responseJson);
} catch (Exception notJson) {
return responseJson;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

HIGH - Decode what Spring AI 2.0 actually returns. Its DefaultToolCallResultConverter writes JSON: null for a null result and quoted strings (source). MCP callbacks return the content array. Today a null aborts the run with an NPE, readTree turns "404 Not Found" into 404, and arrays stay JsonNodes, which FirestoreSessionService persists as {nodeType=ARRAY, ...} (all verified). Decode strictly into plain values, with null as an empty map, like Kotlin (L72-L73, L117-L123). Spring AI's JsonHelper won't help: its mapper turns off FAIL_ON_TRAILING_TOKENS (source).

objectMapper.readerFor(Object.class).with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS).readValue(json)

Comment on lines +151 to +157
} catch (Exception schemaUnparseable) {
// Genuinely malformed JSON: leave parameters unset rather than emit a degraded schema.
logger.warn(
"Spring AI tool '{}' has an unparseable input schema; declaring it with no parameter"
+ " schema. Cause: {}",
def.name(),
schemaUnparseable.getMessage());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MEDIUM - Spring AI 2.0's DefaultToolDefinition always has an input schema (source), but a custom ToolDefinition may not. Then this leaves the declaration with no parameters, and on the SpringAI path ToolConverter throws IllegalArgumentException: inputType cannot be null (reproduced). Fall back to an empty object schema, as Kotlin does (L103-L115).

Comment on lines +160 to +170
private static ToolCallback mockCallback(String name, String description, String inputSchema) {
ToolCallback cb = mock(ToolCallback.class);
ToolDefinition def =
DefaultToolDefinition.builder()
.name(name)
.description(description)
.inputSchema(inputSchema)
.build();
when(cb.getToolDefinition()).thenReturn(def);
return cb;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MEDIUM - These mock ToolCallback, so none of the Spring AI 2.0 behavior above is exercised, and point (2) of the earlier review is still open. Use real 2.0 callbacks (FunctionToolCallback.builder, ToolCallbacks.from(new Tools()), ToolCallbackProvider.from(...)) and mirror ADK Kotlin's SpringAiToolTest: returnDirect, array, scalar and null results, blank schema, toolset, and the context key.

@damianmomotgoogle damianmomotgoogle added waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale. and removed needs review labels Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting on reporter Waiting for reaction by reporter. Failing that, maintainers will eventually closed it as stale.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants