Repository navigation
Description
Search before asking
- I searched in the issues and found nothing similar.
Description
Since #1215, a Java agent's output schema is sent to the provider natively. The OpenAI, Azure OpenAI and Anthropic connections build that schema through their SDKs' structured-output builders (toNativeResponseFormat in OpenAICompletionsConnection and AzureOpenAIChatModelConnection, toNativeOutputConfig in AnthropicChatModelConnection). Both SDKs configure victools with a bare JacksonModule(). They ignore @JsonProperty on enum constants and @JsonValue methods, so the schema lists the Java constant names.
When an enum's Jackson values differ from its names, the model follows the schema. The reply then fails to parse in ChatModelAction.generateStructuredOutput with InvalidFormatException, reported as a model-output parse error.
Since #1120, the other Java providers (Ollama, Gemini, Bedrock, watsonx) use PojoJsonSchemaGenerator, which lists the Jackson values. As a result, the same enum needs opposite annotations depending on the provider:
| Enum | OpenAI / Azure / Anthropic | Ollama / Gemini / Bedrock / watsonx |
|---|---|---|
@JsonProperty on every constant |
["POSITIVE", ...], fails to parse |
["positive", ...], works |
@JsonValue returning a String |
["POSITIVE", ...], fails to parse |
["positive", ...], works |
| No annotations | ["POSITIVE", ...], works |
["POSITIVE", ...], works |
Upgrading the SDKs does not help. The latest openai-java (4.80.0) and anthropic-java (2.71.0) still use a bare JacksonModule().
Proposed fix: build these three schemas with PojoJsonSchemaGenerator as well. Then one enum rule holds for every Java provider, and it matches Python, where pydantic also lists enum values. Two differences need handling so the requests stay valid:
- OpenAI strict mode needs every property in
requiredandadditionalProperties: false. Today the shared generator leavesOptionalfields out ofrequiredand adds noadditionalProperties. So it needs a way to require every field (anOptionalfield stays nullable), plusFORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT. - Anthropic also needs
additionalProperties: false.
One visible side effect: properties come out in declaration order instead of alphabetical order, the same as the other providers and Python.
Each of the three connections gets an enum round-trip test, like the existing derivedSchemaFollowsJacksonEnumValues tests.
How to reproduce
enum Sentiment {
@JsonProperty("positive") POSITIVE,
@JsonProperty("negative") NEGATIVE
}
class Result {
public String summary;
public Sentiment sentiment;
}Use Result.class as the output schema of a Java agent on OpenAI, Azure OpenAI or Anthropic. The request carries "sentiment": {"type": "string", "enum": ["POSITIVE", "NEGATIVE"]}. A reply that follows it, for example {"summary": "ok", "sentiment": "POSITIVE"}, fails new ObjectMapper().readValue(reply, Result.class).
No credentials are needed to see the schema. The SDK builder the connections call returns it offline:
ChatCompletionCreateParams.builder()
.model(ChatModel.of(""))
.addUserMessage("")
.responseFormat(Result.class, JsonSchemaLocalValidation.NO)
.build()
.rawParams()
.responseFormat();Version and environment
main at 6d0a261d (after #1215), openai-java 4.8.0, anthropic-java 2.12.0, Java 11.
Are you willing to submit a PR?
- I'm willing to submit a PR!
Activity