From 99f3c1da1969e509f634ae7312136b87fd34803f Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Mon, 21 Aug 2023 18:15:37 -0700 Subject: [PATCH] add beanoutput parser --- pom.xml | 1 + spring-ai-core/pom.xml | 18 +++++ .../ai/parser/BeanOutputParser.java | 68 +++++++++++++++++++ spring-ai-openai/pom.xml | 12 ++++ .../ai/openai/client/ActorsFilms.java | 35 ++++++++++ .../openai/client/ClientIntegrationTests.java | 19 ++++++ 6 files changed, 153 insertions(+) create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/parser/BeanOutputParser.java create mode 100644 spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ActorsFilms.java diff --git a/pom.xml b/pom.xml index 9e381cce4..8f05ace92 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,7 @@ 0.12.0 1.0.0-beta.3 0.6.1 + 4.31.1 0.0.4 diff --git a/spring-ai-core/pom.xml b/spring-ai-core/pom.xml index aaeec7da7..cb15c5f3e 100644 --- a/spring-ai-core/pom.xml +++ b/spring-ai-core/pom.xml @@ -43,6 +43,24 @@ ${jtokkit.version} + + com.github.victools + jsonschema-generator + 4.31.1 + + + + com.github.victools + jsonschema-module-jackson + 4.31.1 + + + + + + + + org.springframework.boot diff --git a/spring-ai-core/src/main/java/org/springframework/ai/parser/BeanOutputParser.java b/spring-ai-core/src/main/java/org/springframework/ai/parser/BeanOutputParser.java new file mode 100644 index 000000000..a35f48bcd --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/parser/BeanOutputParser.java @@ -0,0 +1,68 @@ +package org.springframework.ai.parser; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.victools.jsonschema.generator.OptionPreset; +import com.github.victools.jsonschema.generator.SchemaGenerator; +import com.github.victools.jsonschema.generator.SchemaGeneratorConfig; +import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; +import com.github.victools.jsonschema.generator.SchemaVersion; +import org.springframework.ai.prompt.PromptTemplate; + +import java.util.Map; +import java.util.Objects; + +public class BeanOutputParser implements OutputParser { + + private String jsonSchema; + + private Class clazz; + + public BeanOutputParser(Class clazz) { + Objects.requireNonNull(clazz, "Java Class can not be null;"); + this.clazz = clazz; + generateSchema(); + } + + private void generateSchema() { + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, + OptionPreset.PLAIN_JSON); + SchemaGeneratorConfig config = configBuilder.build(); + SchemaGenerator generator = new SchemaGenerator(config); + JsonNode jsonSchema = generator.generateSchema(this.clazz); + this.jsonSchema = jsonSchema.toPrettyString(); + } + + @Override + public T parse(String text) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + return (T) objectMapper.readValue(text, this.clazz); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getFormat() { + + String raw = """ + Your response should be in JSON format. + Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. + Here is the JSON Schema instance your output must adhere to in the enclosed markdown codeblock: + ```json + {jsonSchema} + ``` + """; + PromptTemplate promptTemplate = new PromptTemplate(raw); + return promptTemplate.render(Map.of("jsonSchema", this.jsonSchema)); + } + // {"type":"object","properties":{"answer":{"type":"string","description":"answer to + // the user's question"},"source":{"type":"string","description":"source used to + // answer the user's question, should be a + // website."}},"required":["answer","source"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} + // + +} diff --git a/spring-ai-openai/pom.xml b/spring-ai-openai/pom.xml index a88ce2adb..fb960bae2 100644 --- a/spring-ai-openai/pom.xml +++ b/spring-ai-openai/pom.xml @@ -33,6 +33,18 @@ ${open-ai-client.version} + + com.github.victools + jsonschema-generator + ${victools.version} + + + + com.github.victools + jsonschema-module-jackson + ${victools.version} + + org.springframework diff --git a/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ActorsFilms.java b/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ActorsFilms.java new file mode 100644 index 000000000..6b9a1b81a --- /dev/null +++ b/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ActorsFilms.java @@ -0,0 +1,35 @@ +package org.springframework.ai.openai.client; + +import java.util.List; + +public class ActorsFilms { + + private String actor; + + private List movies; + + public ActorsFilms() { + } + + public String getActor() { + return actor; + } + + public void setActor(String actor) { + this.actor = actor; + } + + public List getMovies() { + return movies; + } + + public void setMovies(List movies) { + this.movies = movies; + } + + @Override + public String toString() { + return "ActorsFilms{" + "actor='" + actor + '\'' + ", movies=" + movies + '}'; + } + +} diff --git a/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ClientIntegrationTests.java b/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ClientIntegrationTests.java index cd7ee8eef..5a07d2ce9 100644 --- a/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ClientIntegrationTests.java +++ b/spring-ai-openai/src/test/java/org/springframework/ai/openai/client/ClientIntegrationTests.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.client.AiResponse; import org.springframework.ai.client.Generation; import org.springframework.ai.openai.testutils.AbstractIntegrationTest; +import org.springframework.ai.parser.BeanOutputParser; import org.springframework.ai.parser.ListOutputParser; import org.springframework.ai.parser.MapOutputParser; import org.springframework.ai.prompt.Prompt; @@ -81,4 +82,22 @@ class ClientIntegrationTests extends AbstractIntegrationTest { } + @Test + void beanOutputParser() { + + BeanOutputParser outputParser = new BeanOutputParser<>(ActorsFilms.class); + + String format = outputParser.getFormat(); + String template = """ + Generate the filmography for a random actor. + {format} + """; + PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + Generation generation = openAiClient.generate(prompt).getGeneration(); + + ActorsFilms actorsFilms = outputParser.parse(generation.getText()); + System.out.println(actorsFilms); + } + }