Ollama has recently introduced native support for JSON structured output, as described in https://ollama.com/blog/structured-outputs.
This PR introduces support for it, both for directly passing a JSON schema and when using the Spring AI output conversion APIs.
Example
Data:
record CountryInfo(
@JsonProperty(required = true) String name,
@JsonProperty(required = true) String capital,
@JsonProperty(required = true) List<String> languages
) {}
Using BeanOutputConverter
Example with ChatClient
:
@PostMapping("/chat/json")
CountryInfo chatJsonOutput(String country) {
var outputConverter = new BeanOutputConverter<>(CountryInfo.class);
var userPromptTemplate = """
Tell me about {country}.
""";
return chatClient.prompt()
.user(userSpec -> userSpec
.text(userPromptTemplate)
.param("country", country)
)
.options(OllamaOptions.builder()
.withFormat(outputConverter.getJsonSchemaMap())
.build())
.call()
.entity(outputConverter);
}
Example with ChatModel
:
@GetMapping("/chat/json")
CountryInfo chatJsonOutput(String country) {
var outputConverter = new BeanOutputConverter<>(CountryInfo.class);
var userPromptTemplate = new PromptTemplate("""
Tell me about {country}.
""");
Map<String,Object> model = Map.of("country", country);
var prompt = userPromptTemplate.create(model, OllamaOptions.builder()
.withModel(OllamaModel.LLAMA3_2.getName())
.withFormat(outputConverter.getJsonSchemaMap())
.build());
var chatResponse = chatModel.call(prompt);
return outputConverter.convert(chatResponse.getResult().getOutput().getText());
}
Using plain JSON Schema
Example with ChatClient
:
@PostMapping("/chat/json")
CountryInfo chatJsonOutput(String country) throws JsonProcessingException {
var userPromptTemplate = """
Tell me about {country}.
""";
var jsonSchema = """
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"capital": {
"type": "string"
},
"languages": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name",
"capital",
"languages"
]
}
""";
return chatClient.prompt()
.user(userSpec -> userSpec
.text(userPromptTemplate)
.param("country", country)
)
.options(OllamaOptions.builder()
.withFormat(new ObjectMapper().readValue(jsonSchema, Map.class))
.build())
.call()
.entity(CountryInfo.class);
}
Example with ChatModel
:
@GetMapping("/chat/json")
CountryInfo chatJsonOutput(String country) throws JsonProcessingException {
var outputConverter = new BeanOutputConverter<>(CountryInfo.class);
var userPromptTemplate = new PromptTemplate("""
Tell me about {country}.
""");
Map<String,Object> model = Map.of("country", country);
var jsonSchema = """
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"capital": {
"type": "string"
},
"languages": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name",
"capital",
"languages"
]
}
""";
var prompt = userPromptTemplate.create(model, OllamaOptions.builder()
.withModel(OllamaModel.LLAMA3_2.getName())
.withFormat(new ObjectMapper().readValue(jsonSchema, Map.class))
.build());
var chatResponse = chatModel.call(prompt);
return outputConverter.convert(chatResponse.getResult().getOutput().getText());
}
Comment From: tzolov
Thanks @ThomasVitale
Comment From: tzolov
Rebased and merged at 6ab7e20616a17fbe3e54400658d5c11961b4eb8a