Skip to content

Conversation

cho-thinkfree-com
Copy link
Contributor

@cho-thinkfree-com cho-thinkfree-com commented Sep 30, 2025

Summary

  • Use DefaultToolExecutionExceptionProcessor(false) so that tool exceptions are not thrown but are converted into tool responses containing the exception message. This prevents exceptions from bubbling up through the streaming pipeline.
  • If a tool exception were thrown outward, it would be hard to catch and turn into a user-friendly message at the right layer. So we treat the exception message as the tool’s output instead.
  • However, Vertex AI expects tool responses to be valid JSON. If the exception message is plain text (i.e., not JSON), Vertex will throw another error. Therefore, when forwarding exception messages as tool responses, they must be wrapped/normalized into JSON (e.g., { "result": "" }).
  • ensure Vertex AI tool call responses fall back to the raw JSON payload when JsonProcessingException is raised during tool result conversion
  • Fix Gemini Tool Calling for texts returned from MethodToolCallback #3040
Signed-off-by: cho-thinkfree-com <cho@thinkfree.com>
@cho-thinkfree-com cho-thinkfree-com force-pushed the fix/gh-4230-vertexai-tool-call-fallback branch from 960654a to 3ad2acc Compare September 30, 2025 15:19
@cho-thinkfree-com cho-thinkfree-com changed the title GH-4230: Handle Vertex AI tool call JsonProcessingException fallback Handle Vertex AI tool call JsonProcessingException fallback Sep 30, 2025
@cho-thinkfree-com
Copy link
Contributor Author

You can also address this by introducing a VertexAwareToolCallingManager that normalizes tool responses before they reach Vertex. The idea is simple:

  • If the tool returns a valid JSON object, pass it through unchanged.
  • If it returns non-JSON text (e.g., an exception message), wrap it as {"result":""}.

This guarantees Vertex always receives a top-level JSON object.

private static final class VertexAwareToolCallingManager implements ToolCallingManager { private final DefaultToolCallingManager peerToolCallingManager; public VertexAwareToolCallingManager(DefaultToolCallingManager peerToolCallingManager) { this.peerToolCallingManager = peerToolCallingManager; } @Override public ToolExecutionResult executeToolCalls(@NonNull Prompt prompt, @NonNull ChatResponse chatResponse) { ToolExecutionResult res = this.peerToolCallingManager.executeToolCalls(prompt, chatResponse); ObjectMapper om = safeObjectMapper(); var newHistory = res.conversationHistory().stream().map(msg -> { if (msg instanceof ToolResponseMessage trm) { var patched = trm.getResponses().stream().map(r -> { String data = r.responseData(); // Normalizes to object JSON: returns {"result": data} when wrapping is required; otherwise returns data as is. String normalized = normalizeToObjectJson(om, data); return new ToolResponseMessage.ToolResponse(r.id(), r.name(), normalized); }).toList(); return new ToolResponseMessage(patched); } return msg; }).toList(); return new DefaultToolExecutionResult(newHistory, res.returnDirect()); } //... // ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 participants