Bug description
When tool response is anything else, but:
String
, that doesn't contain a number (i.e. not"22"
); more about this later- class/record
- array/collection of class/record
then when VertexAiGeminiChatModel
tries to encode tool response to protobuf, it throws an exception similar to this:
java.lang.RuntimeException: com.google.protobuf.InvalidProtocolBufferException: Expect a map object but found: 22
at org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel.jsonToStruct(VertexAiGeminiChatModel.java:373) ~[spring-ai-vertex-ai-gemini-1.0.1.jar:1.0.1]
at org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel.lambda$messageToGeminiParts$1(VertexAiGeminiChatModel.java:304) ~[spring-ai-vertex-ai-gemini-1.0.1.jar:1.0.1]
...
(Where 22
is an actual tool return value of int
type)
This issue is probably related to #2849 except it is about any type (not just array) that is not an object, and about calling a tool (without mcp).
In my tests returning numeric String
, String[]
or List<String>
from a tool doesn't work too despite the fact it is presumably handled by the code.
It looks like when a tool returns a String that is numeric-like (e.g. "123"
), some code converts it to a JSON number
before calling jsonToStruct
. But jsonToStruct
currently can handle only JSON string
, array
(only of object
types) and object
, but not JSON number
, boolean
or null
. And not array
of any type other than object
.
I don't know protobuf very well, but it looks like the code JsonFormat.parser().ignoringUnknownFields().merge(elementJson, elementBuilder);
accepts only JSON of object
type.
Environment
JDK: 21 Spring AI: 1.0.1
Steps to reproduce
public class WeatherService {
@Tool(description = "Get the weather in location")
public String weatherByLocation(@ToolParam(description= "City or state name") String location) {
return "22";
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public ApplicationRunner run(ChatModel chatModel) {
return args -> {
String response = ChatClient.create(chatModel)
.prompt("What's the weather like in Boston?")
.tools(new WeatherService())
.call()
.content();
System.out.println("Response: " + response);
};
}
}
This is basicaly an example from the docs.
Expected behavior
Any return type of the tool, that can be converted to a correct JSON, should be supported.
Note: The same code works with Ollama.
Workaround
Wrap the tool response type into a class/record, such as public record MyResponse(String temperature) {}
.
Comment From: xak2000
Found a code from LangChain4J responsible for the same task. They use a workaround with enveloping the result into a JSON object if exception is thrown. They also use a second try with an additional quoting of the result (I'm not sure why).
Anyway, it is obviously a workaround and it's better to implement it properly.