From 2015067f88b253f96e14bfcea77f41507421e1d0 Mon Sep 17 00:00:00 2001 From: Thomas Vitale Date: Thu, 19 Dec 2024 07:04:45 +0100 Subject: [PATCH] Add Ollama structured output JSON example --- .../structured-output-ollama/README.md | 6 +++++ .../ai/spring/ChatController.java | 23 ++++++++++++++++++- .../thomasvitale/ai/spring/CountryInfo.java | 11 +++++++++ .../ai/spring/model/ChatModelController.java | 21 ++++++++++++++++- 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/CountryInfo.java diff --git a/patterns/structured-output/structured-output-ollama/README.md b/patterns/structured-output/structured-output-ollama/README.md index 75b36f2..2875958 100644 --- a/patterns/structured-output/structured-output-ollama/README.md +++ b/patterns/structured-output/structured-output-ollama/README.md @@ -43,3 +43,9 @@ http POST :8080/chat/map -b ```shell http :8080/chat/list genre="rock" instrument="piano" -b ``` + +Ollama has also a native structured output feature, used in the following request. + +```shell +http :8080/chat/json country=="Denmark" -b +``` diff --git a/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java b/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java index 8575316..ba121b4 100644 --- a/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java +++ b/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/ChatController.java @@ -1,10 +1,12 @@ package com.thomasvitale.ai.spring; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.ai.converter.ListOutputConverter; import org.springframework.ai.converter.MapOutputConverter; import org.springframework.ai.ollama.api.OllamaOptions; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -37,7 +39,7 @@ ArtistInfo chatBeanOutput(@RequestBody MusicQuestion question) { .param("instrument", question.instrument()) ) .options(OllamaOptions.builder() - .withFormat("json") + .format("json") .build()) .call() .entity(ArtistInfo.class); @@ -69,4 +71,23 @@ List chatListOutput(@RequestBody MusicQuestion question) { .entity(new ListOutputConverter(new DefaultConversionService())); } + @GetMapping("/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() + .format(outputConverter.getJsonSchemaMap()) + .build()) + .call() + .entity(outputConverter); + } + } diff --git a/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/CountryInfo.java b/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/CountryInfo.java new file mode 100644 index 0000000..4789e22 --- /dev/null +++ b/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/CountryInfo.java @@ -0,0 +1,11 @@ +package com.thomasvitale.ai.spring; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record CountryInfo( + @JsonProperty(required = true) String name, + @JsonProperty(required = true) String capital, + @JsonProperty(required = true) List languages +) {} diff --git a/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java b/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java index a8c3078..0da388a 100644 --- a/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java +++ b/patterns/structured-output/structured-output-ollama/src/main/java/com/thomasvitale/ai/spring/model/ChatModelController.java @@ -1,14 +1,17 @@ package com.thomasvitale.ai.spring.model; import com.thomasvitale.ai.spring.ArtistInfo; +import com.thomasvitale.ai.spring.CountryInfo; import com.thomasvitale.ai.spring.MusicQuestion; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.ai.converter.ListOutputConverter; import org.springframework.ai.converter.MapOutputConverter; +import org.springframework.ai.ollama.api.OllamaModel; import org.springframework.ai.ollama.api.OllamaOptions; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -42,7 +45,7 @@ ArtistInfo chatBeanOutput(@RequestBody MusicQuestion question) { "genre", question.genre(), "format", outputConverter.getFormat()); var prompt = userPromptTemplate.create(model, OllamaOptions.builder() - .withFormat("json") + .format("json") .build()); var chatResponse = chatModel.call(prompt); @@ -77,6 +80,22 @@ List chatListOutput(@RequestBody MusicQuestion question) { "format", outputConverter.getFormat()); var prompt = userPromptTemplate.create(model); + var chatResponse = chatModel.call(prompt); + return outputConverter.convert(chatResponse.getResult().getOutput().getContent()); + } + + @GetMapping("/chat/json") + CountryInfo chatJsonOutput(String country) { + var outputConverter = new BeanOutputConverter<>(CountryInfo.class); + var userPromptTemplate = new PromptTemplate(""" + Tell me about {country}. + """); + Map model = Map.of("country", country); + var prompt = userPromptTemplate.create(model, OllamaOptions.builder() + .model(OllamaModel.LLAMA3_2.getName()) + .format(outputConverter.getJsonSchemaMap()) + .build()); + var chatResponse = chatModel.call(prompt); return outputConverter.convert(chatResponse.getResult().getOutput().getText()); }