Problem
Sometimes a model replies with nothing: no text, no tool call.
RetryWithFeedbackPolicy then adds a message asking the model to try again, and sends the conversation a second time. But the empty reply is still in the conversation, so it is sent too.
Vertex AI and Mistral reject any request containing an empty assistant message. They answer 400, so the retry never reaches the model and can never work.
Where
In DefaultToolLoop.execute, the reply is added to the conversation before the code checks if it is empty:
state.conversationHistory.add(transformedResponse) // empty reply added here
...
is EmptyResponseAction.FeedbackToModel -> {
state.conversationHistory.add(UserMessage(action.message))
continue // empty reply still there
}
Tested against the real APIs
| Provider |
Result |
| Gemini (Vertex AI) |
400 . must include at least one parts field |
| Mistral |
400 - Assistant message must have either content or tool_calls, but not none |
| Gemini (API key), OpenAI, Anthropic |
accepted |
Gemini only fails through Vertex AI, not through the API key endpoint.
The 400 is retried with backoff. Measured on Mistral: 32s to fail with max-attempts=3. The default is 10 attempts, so about 20 minutes.
Reproduce
Make the first model reply empty, let the second call reach the real API, with RetryWithFeedbackPolicy(maxRetries = 1).
Note for mocks: AssistantMessage(" ") reproduces it on Vertex AI but not on Mistral, which accepts the space. The reply must have no text and no tool calls to fail on both.
Possible fix
Remove the empty reply before sending the retry:
state.conversationHistory.removeAt(state.conversationHistory.lastIndex)
state.conversationHistory.add(UserMessage(action.message))
Only on the retry path. When the loop stops on an empty reply instead of retrying, that reply is the answer and the caller should still get it.
Problem
Sometimes a model replies with nothing: no text, no tool call.
RetryWithFeedbackPolicythen adds a message asking the model to try again, and sends the conversation a second time. But the empty reply is still in the conversation, so it is sent too.Vertex AI and Mistral reject any request containing an empty assistant message. They answer
400, so the retry never reaches the model and can never work.Where
In
DefaultToolLoop.execute, the reply is added to the conversation before the code checks if it is empty:Tested against the real APIs
400 . must include at least one parts field400 - Assistant message must have either content or tool_calls, but not noneGemini only fails through Vertex AI, not through the API key endpoint.
The
400is retried with backoff. Measured on Mistral: 32s to fail withmax-attempts=3. The default is 10 attempts, so about 20 minutes.Reproduce
Make the first model reply empty, let the second call reach the real API, with
RetryWithFeedbackPolicy(maxRetries = 1).Note for mocks:
AssistantMessage(" ")reproduces it on Vertex AI but not on Mistral, which accepts the space. The reply must have no text and no tool calls to fail on both.Possible fix
Remove the empty reply before sending the retry:
state.conversationHistory.removeAt(state.conversationHistory.lastIndex) state.conversationHistory.add(UserMessage(action.message))Only on the retry path. When the loop stops on an empty reply instead of retrying, that reply is the answer and the caller should still get it.