Expected Behavior

I'd like to see @JsonProperty style of enums with BeanOutputConverter - it'd be great to have an ability to give AI assistant a range of constant values, which aren't just Java String names. That is, I'd love to see something like:

    @PostConstruct
    public void doSomething() {
        var myWrapper = ChatClient.create(chatModel)
                .prompt("Select randomly either \"value.a\" or \"value.b\"")
                .call()
                .entity(MyWrapper.class);
        System.out.println(myWrapper.values);
    }


    public record MyWrapper(EnumValues values) {

    }

    public enum EnumValues {
        @JsonProperty("value.a")
        A,
        @JsonProperty("value.b")
        B
    }

being a correct way to use structured outputs

Current Behavior

As far as I can see, BeanOutputConverter is not really that much customizable. All the parametrization is done inside of a private method (generateSchema). In theory, one could even use @JsonProperty - it is recognized by the underling Jackson, but not by the schema generator, leading to interesting problems.

Generated schema for the code above:

{
  "$schema" : "https://json-schema.org/draft/2020-12/schema",
  "type" : "object",
  "properties" : {
    "values" : {
      "type" : "string",
      "enum" : [ "A", "B" ]
    }
  },
  "additionalProperties" : false
}

Stack trace:

Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `xyz.SpringAiTest$EnumValues` from String "A": not one of the values accepted for Enum class: [value.a, value.b]
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 2, column: 13] (through reference chain: xyz.SpringAiTest$MyWrapper["values"])
    at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67) ~[jackson-databind-2.18.2.jar:2.18.2]

Context

I wanted to integrate with a service that uses non-standard constants directly, and to select values using AI assistant.

There are other ways this can be done, but as far as I understand, they either include: 1. Copying BeanOutputConverter and modifying its code 2. Implementing your own StructuredOutputConverter. For a similar case, I've tried to modify the generated schema myself to account for @JsonProperties and it works, but it's very manual and brittle 3. Mapping this enum to a String via some different code. Not the worst thing in the world, but it means that I need to refer in the conversations with the AI to the values using Java enum values, as well, as also having some code to map them.

Comment From: markpollack

I'm not sure how to address the binding to enums and in the march up to the GA release, this can probably wait. In the meantime, I've made the current BeanOutputConverter more sublcass friendly in this PR - https://github.com/spring-projects/spring-ai/pull/2788

sorry for the long delay in responding.

Comment From: xak2000

@markpollack The Jackson Module from SchemaGenerator allows to do this out of the box:

  1. Optionally: treat enum types as plain strings as per the @JsonValue annotated method, if there is one and the JacksonOption.FLATTENED_ENUMS_FROM_JSONVALUE was provided (i.e. this is an "opt-in").
  2. Optionally: treat enum types as plain strings, as per each enum constant's @JsonProperty annotation, if all values of an enum have such annotations and the JacksonOption.FLATTENED_ENUMS_FROM_JSONPROPERTY was provided (i.e. this is an "opt-in"). ... To use it, just pass a module instance into SchemaGeneratorConfigBuilder.with(Module), optionally providing JacksonOption values in the module's constructor.

The only problem is that currently there is no way to augment SchemaGeneratorConfigBuilder from the user's code.

I think these 2 options could be enabled by default by Spring AI (solves this issue), they should not harm.

It would be also great to have ability to customize SchemaGeneratorConfigBuilder. But this feature request could be extracted as a separate issue if you agree that FLATTENED_ENUMS_FROM_JSONVALUE and FLATTENED_ENUMS_FROM_JSONPROPERTY should be enabled by default.

Also an important note: BeanOutputConverter is not the only component where schema generation is used. Another example is schema generation for the tool parameters. In my case I need to redefine the enum values for tool parameters, because otherwise the LLM tends to use the ALL_CAPS names in its text responses (e.g. when it suggests to expand the tool search filters), which makes the responses non-user friendly. E.g. I would like the LLM to say "nonbinary" instead of "NON_BINARY". Current workaround is to just use a lowercase enum names, but it will not solve all problems, like the PM's value.a example or I could imagine an example of enum with spaces, semicolons, etc. So, support of @JsonProperty and @JsonValue would be great.