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