feat(spring-ai): bridge Spring AI ToolCallback to ADK BaseTool#1300
feat(spring-ai): bridge Spring AI ToolCallback to ADK BaseTool#1300mekkiamiri wants to merge 2 commits into
Conversation
Introduces SpringAiToolCallbackBackedAdkTool — an adapter that wraps any Spring AI ToolCallback (FunctionToolCallback, MCP SyncMcpToolCallback / AsyncMcpToolCallback, @Tool-annotated method callbacks, etc.) as an ADK BaseTool so they can be attached to an LlmAgent. The bridge is the reverse direction of the existing ToolConverter (ADK -> Spring AI). Together they make ADK and Spring AI tools fully interoperable. Schema is extracted from ToolCallback.getToolDefinition().inputSchema() (JSON Schema string) via Schema.fromJson(). If the JSON Schema cannot be parsed into ADK's structured Schema type, the bridge falls back to the parametersJsonSchema(Object) escape hatch — no hard failure on malformed schemas, just a logged warning. Invocation path: - ADK runtime calls runAsync(Map<String, Object>, ToolContext) - Args are serialized to JSON via ObjectMapper.writeValueAsString - Dispatched to ToolCallback.call(String) — Spring AI's sync invocation - Response JSON is parsed back to Map<String, Object> - Non-object responses (primitive / array / raw string) are wrapped under a "result" key for structural consistency Also adds a static wrapAll(List<? extends ToolCallback>) helper so consumers of McpToolCallbackProvider.getToolCallbacks() can fan out to BaseTool[] in one line. Tests (7): name/description/schema extraction, JSON object result, scalar-wrapping, empty response, null args, wrapAll fan-out, and graceful fallback on malformed schema.
Adds a new section to contrib/spring-ai/README.md describing SpringAiToolCallbackBackedAdkTool — the reverse-direction tool bridge that exposes any Spring AI ToolCallback (MCP, @Tool-annotated, FunctionToolCallback) as an ADK BaseTool for use with LlmAgent. Includes usage examples for the MCP-via-Spring-AI case and the single-tool case, plus a note on coexistence with ADK's native McpToolset.
9e8b683 to
763ce23
Compare
|
Hi @mekkiamiri (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()) { 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 buildDeclaration is currently static and has no ObjectMapper; pass the instance mapper in (it's already a field). Current: Suggestion (entirely within SpringAiToolCallbackBackedAdkTool): (2) Additional tests, not only mocks Nothing exercises a real SyncMcpToolCallback/AsyncMcpToolCallback or an MCP-shaped result (content arrays, isError, the JSON the MCP callback actually emits) (3) Exception handling This needs to be documented in the README as well (4) Autoconfigure can be updated to add automatic discovery of ToolCallback beans |
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
ToolCallbackimplementations:SyncMcpToolCallback/AsyncMcpToolCallbackproduced byspring-ai-starter-mcp-clientfromspring.ai.mcp.client.*properties,@Tool-annotated method callbacks, programmatically declaredFunctionToolCallbacks, and so on. Today, none of these can be used by an ADKLlmAgentwithout manual conversion. The existingToolConverterincontrib/spring-aionly converts ADKBaseTool→ Spring AIToolCallback(one direction). Spring Boot users who want their ADK agent to use MCP tools have to either reimplement MCP wiring via ADK's nativeMcpToolset.createMcpToolset(...)Java API, or write their own ToolCallback → BaseTool adapter.Solution:
Add
SpringAiToolCallbackBackedAdkTool— an adapter that wraps any Spring AIToolCallbackas an ADKBaseTool. It is the reverse direction of the existingToolConverter. Together they make ADK and Spring AI tool ecosystems fully interoperable.How it works:
ToolCallback.getToolDefinition()provides the tool name, description, and JSON Schema.SchemaviaSchema.fromJson(...). If parsing fails (e.g. malformed schema), the bridge falls back toparametersJsonSchema(Object)with a logged warning — no hard failure.Map<String, Object>arguments are serialized to JSON, dispatched toToolCallback.call(String), and the JSON response is parsed back toMap<String, Object>."result"key for structural consistency.wrapAll(List<? extends ToolCallback>)helper fans a list of callbacks (e.g. fromMcpToolCallbackProvider.getToolCallbacks()) into aList<BaseTool>in one call.Usage: