🔴 Required Information
Describe the Bug:
MessageConverter in contrib/spring-ai never converts ADK functionResponse parts into Spring AI ToolResponseMessage.
Before Spring AI 2.0.0, this was masked because ChatModel implementations could execute tools internally, so ADK did not need to send tool results back through the model. Spring AI 2.0.0 removed internal tool execution at the ChatModel level, so ADK now owns the tool loop and the tool result it appends is silently discarded by the converter.
The result is a request containing an assistant message with tool_calls but no matching role:"tool" message. The model never receives the tool output. No exception is thrown, and nothing in the logs indicates a problem; the agent may simply answer as if the tool returned nothing.
Each change is survivable on its own. Together, they make contrib/spring-ai non-functional for tool-using agents.
Steps to Reproduce:
- Depend on
com.google.adk:google-adk-spring-ai:1.7.1 and org.springframework.ai:spring-ai-bom:2.0.0, with spring-ai-starter-model-openai.
- Build an
LlmAgent whose model is SpringAI wrapping an OpenAiChatModel, with exactly one FunctionTool.
- Point the OpenAI client at any OpenAI-compatible endpoint and ask the agent a question that requires the tool.
- Capture the outbound HTTP request bodies.
- Observe the second request: it contains the assistant message with
tool_calls, but no role:"tool" message carrying the result.
Expected Behavior:
After ADK executes the tool and appends Content(role=user, functionResponse=...), the follow-up request should contain a corresponding tool message with role:"tool" and the matching tool_call_id, allowing the model to consume the tool result.
This is already how the corresponding tool-result handling works in Claude and chat.ChatCompletionsRequest in core/.
Observed Behavior:
The functionResponse part is skipped during conversion and never reaches the wire.
The follow-up request therefore contains tool_calls with no matching tool message. Depending on the OpenAI-compatible endpoint, the request may be rejected or the model may re-request the same call. In either case, the agent cannot reliably consume the tool output.
Environment Details:
- ADK Library Version:
com.google.adk:google-adk:1.7.1, com.google.adk:google-adk-spring-ai:1.7.1
- Spring AI:
org.springframework.ai:spring-ai-bom:2.0.0 (GA), with spring-ai-starter-model-openai
- Java: OpenJDK 21 (Temurin 21.0.3)
- OS: macOS and Linux
- TS Version: N/A, Java
Model Information:
An OpenAI-compatible gateway (LiteLLM) fronting open-weight chat models.
The defect is in message conversion, so it should be model-independent. It affects any ChatModel reached through SpringAI.
🟡 Optional Information
Regression:
No. This conversion path never worked.
MessageConverter has no ToolResponseMessage support in any released version. The missing conversion became a functional blocker with Spring AI 2.0.0 because Spring AI removed the internal tool execution that previously meant ADK did not have to send tool results back through the ChatModel.
Therefore, the trigger is the Spring AI 2.0.0 change rather than a regression in ADK's tool loop.
Logs:
The decisive evidence is the outbound request body rather than a stack trace, because the failure is silent.
Shape of the second request:
messages: [
{ role: "system", ... },
{ role: "user", ... },
{ role: "assistant", tool_calls: [
{ id: "call_...", function: { name: "...", arguments: "..." } }
] }
]
Expected but absent:
{ role: "tool", tool_call_id: "call_...", content: "<result>" }
Additional Context:
Spring AI 2.0.0 removed internal tool execution.
The Spring AI 2.0.0 upgrade notes state that internalToolExecutionEnabled was removed and that per-model internal tool execution was removed from all ChatModel implementations, with no replacement at the ChatModel level.
This is also reflected in the artifacts: executeToolCalls appears in ToolCallingAdvisor (a ChatClient advisor) and ToolCallingManager, while OpenAiChatModel calls only resolveToolDefinitions.
ADK's SpringAI integration uses ChatModel, not ChatClient, so it does not receive tool execution from Spring AI at that layer.
The conversion gap in google-adk-spring-ai:1.7.1:
MessageConverter.handleUserContent detects functionResponse, but skips it:
99: invokevirtual Part.functionResponse()
102: invokevirtual Optional.isPresent()
105: ifeq 111
108: goto 354 <- skips to the next part; the tool result is discarded
...
394: aload_3 <- "toolResponseMessages", which nothing ever adds to
395: invokeinterface List.addAll
There are zero references to org.springframework.ai.chat.messages.ToolResponseMessage in the artifact, while handleAssistantContent does emit AssistantMessage$ToolCall.
The published 1.7.0 javadoc also states that media attachments and tool responses are currently not supported.
This appears scoped to contrib/, not to ADK core.
| Connector |
Location |
Tool-result handling |
Gemini |
core/ |
Native google-genai types; no translation required |
Claude |
core/ |
functionResponse → ToolResultBlockParam |
chat.ChatCompletionsRequest |
core/ |
functionResponse → toolCallId, role:"tool" |
springai.MessageConverter |
contrib/ |
No ToolResponseMessage conversion |
ADK's own tool loop is healthy. flows/llmflows/Functions references FunctionResponse 17 times. The missing behavior is in the Spring AI bridge.
PR #733:
PR #733 appears to be the only previous fix attempt. It is open, mergeable: dirty, and has been stale since 2026-04-07.
Its proposed mechanism, internalToolExecutionEnabled(false), no longer compiles against Spring AI 2.0.0, so it cannot be revived as-is.
What we are asking for:
Either of the following would address the issue:
-
Fix handleUserContent to convert functionResponse parts into ToolResponseMessage.ToolResponse, keyed by the originating call ID, and add them to the toolResponseMessages list that is already assembled but never populated.
Claude and chat.ChatCompletionsRequest provide working references for the expected tool-result handling.
-
If contrib/spring-ai is not intended to be maintained, make that limitation explicit in the module documentation.
The current javadoc describes tool responses as unsupported, but on Spring AI 2.0.0 and later this means tool-using agents can silently proceed without receiving their tool results. A note pointing users toward com.google.adk.models.chat.ChatCompletionsHttpClient would help prevent others from losing time debugging this behavior.
We spent several days isolating this because each component behaves correctly in isolation and the primary symptom is a silently incorrect model response.
We have since migrated to core's ChatCompletionsHttpClient behind a small BaseLlm subclass, and we now assert the presence of the role:"tool" follow-up message in CI so this failure cannot pass silently.
Related: #1295 (fixed in 1.7.0) and #1426.
Minimal Reproduction Code:
The essential wiring is a SpringAI model plus a single function tool:
OpenAiChatModel chatModel = /* configured against any OpenAI-compatible endpoint */;
LlmAgent agent = LlmAgent.builder()
.name("repro-agent")
.model(new SpringAI(chatModel))
.instruction("Use the tool to answer.")
.tools(FunctionTool.create(MyTools.class, "lookup"))
.build();
// Ask something that requires "lookup", then inspect the outbound request bodies.
// The second request has assistant.tool_calls but no role:"tool" message.
How often has this issue occurred?:
🔴 Required Information
Describe the Bug:
MessageConverterincontrib/spring-ainever converts ADKfunctionResponseparts into Spring AIToolResponseMessage.Before Spring AI 2.0.0, this was masked because
ChatModelimplementations could execute tools internally, so ADK did not need to send tool results back through the model. Spring AI 2.0.0 removed internal tool execution at theChatModellevel, so ADK now owns the tool loop and the tool result it appends is silently discarded by the converter.The result is a request containing an assistant message with
tool_callsbut no matchingrole:"tool"message. The model never receives the tool output. No exception is thrown, and nothing in the logs indicates a problem; the agent may simply answer as if the tool returned nothing.Each change is survivable on its own. Together, they make
contrib/spring-ainon-functional for tool-using agents.Steps to Reproduce:
com.google.adk:google-adk-spring-ai:1.7.1andorg.springframework.ai:spring-ai-bom:2.0.0, withspring-ai-starter-model-openai.LlmAgentwhose model isSpringAIwrapping anOpenAiChatModel, with exactly oneFunctionTool.tool_calls, but norole:"tool"message carrying the result.Expected Behavior:
After ADK executes the tool and appends
Content(role=user, functionResponse=...), the follow-up request should contain a corresponding tool message withrole:"tool"and the matchingtool_call_id, allowing the model to consume the tool result.This is already how the corresponding tool-result handling works in
Claudeandchat.ChatCompletionsRequestincore/.Observed Behavior:
The
functionResponsepart is skipped during conversion and never reaches the wire.The follow-up request therefore contains
tool_callswith no matching tool message. Depending on the OpenAI-compatible endpoint, the request may be rejected or the model may re-request the same call. In either case, the agent cannot reliably consume the tool output.Environment Details:
com.google.adk:google-adk:1.7.1,com.google.adk:google-adk-spring-ai:1.7.1org.springframework.ai:spring-ai-bom:2.0.0(GA), withspring-ai-starter-model-openaiModel Information:
An OpenAI-compatible gateway (LiteLLM) fronting open-weight chat models.
The defect is in message conversion, so it should be model-independent. It affects any
ChatModelreached throughSpringAI.🟡 Optional Information
Regression:
No. This conversion path never worked.
MessageConverterhas noToolResponseMessagesupport in any released version. The missing conversion became a functional blocker with Spring AI 2.0.0 because Spring AI removed the internal tool execution that previously meant ADK did not have to send tool results back through theChatModel.Therefore, the trigger is the Spring AI 2.0.0 change rather than a regression in ADK's tool loop.
Logs:
The decisive evidence is the outbound request body rather than a stack trace, because the failure is silent.
Shape of the second request:
Expected but absent:
Additional Context:
Spring AI 2.0.0 removed internal tool execution.
The Spring AI 2.0.0 upgrade notes state that
internalToolExecutionEnabledwas removed and that per-model internal tool execution was removed from allChatModelimplementations, with no replacement at theChatModellevel.This is also reflected in the artifacts:
executeToolCallsappears inToolCallingAdvisor(aChatClientadvisor) andToolCallingManager, whileOpenAiChatModelcalls onlyresolveToolDefinitions.ADK's
SpringAIintegration usesChatModel, notChatClient, so it does not receive tool execution from Spring AI at that layer.The conversion gap in
google-adk-spring-ai:1.7.1:MessageConverter.handleUserContentdetectsfunctionResponse, but skips it:There are zero references to
org.springframework.ai.chat.messages.ToolResponseMessagein the artifact, whilehandleAssistantContentdoes emitAssistantMessage$ToolCall.The published 1.7.0 javadoc also states that media attachments and tool responses are currently not supported.
This appears scoped to
contrib/, not to ADK core.Geminicore/google-genaitypes; no translation requiredClaudecore/functionResponse→ToolResultBlockParamchat.ChatCompletionsRequestcore/functionResponse→toolCallId,role:"tool"springai.MessageConvertercontrib/ToolResponseMessageconversionADK's own tool loop is healthy.
flows/llmflows/FunctionsreferencesFunctionResponse17 times. The missing behavior is in the Spring AI bridge.PR #733:
PR #733 appears to be the only previous fix attempt. It is open,
mergeable: dirty, and has been stale since 2026-04-07.Its proposed mechanism,
internalToolExecutionEnabled(false), no longer compiles against Spring AI 2.0.0, so it cannot be revived as-is.What we are asking for:
Either of the following would address the issue:
Fix
handleUserContentto convertfunctionResponseparts intoToolResponseMessage.ToolResponse, keyed by the originating call ID, and add them to thetoolResponseMessageslist that is already assembled but never populated.Claudeandchat.ChatCompletionsRequestprovide working references for the expected tool-result handling.If
contrib/spring-aiis not intended to be maintained, make that limitation explicit in the module documentation.The current javadoc describes tool responses as unsupported, but on Spring AI 2.0.0 and later this means tool-using agents can silently proceed without receiving their tool results. A note pointing users toward
com.google.adk.models.chat.ChatCompletionsHttpClientwould help prevent others from losing time debugging this behavior.We spent several days isolating this because each component behaves correctly in isolation and the primary symptom is a silently incorrect model response.
We have since migrated to
core'sChatCompletionsHttpClientbehind a smallBaseLlmsubclass, and we now assert the presence of therole:"tool"follow-up message in CI so this failure cannot pass silently.Related: #1295 (fixed in 1.7.0) and #1426.
Minimal Reproduction Code:
The essential wiring is a
SpringAImodel plus a single function tool:How often has this issue occurred?: