Skip to content

Conversation

@ThomasVitale
Copy link
Contributor

@ThomasVitale ThomasVitale commented Dec 15, 2024

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());
}

@ThomasVitale ThomasVitale marked this pull request as draft December 15, 2024 03:35
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.

Signed-off-by: Thomas Vitale <[email protected]>
@ThomasVitale ThomasVitale force-pushed the ollama-strucured-output branch from c806a6a to 08d3c74 Compare December 15, 2024 03:46
@ThomasVitale ThomasVitale marked this pull request as ready for review December 15, 2024 03:47
@tzolov tzolov self-assigned this Dec 17, 2024
@tzolov tzolov added this to the 1.0.0-M5 milestone Dec 17, 2024
@tzolov tzolov added the ollama label Dec 17, 2024
@tzolov
Copy link
Contributor

tzolov commented Dec 18, 2024

Thanks @ThomasVitale

@tzolov
Copy link
Contributor

tzolov commented Dec 18, 2024

Rebased and merged at 6ab7e20

@tzolov tzolov closed this Dec 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants