feat: add structured JSON output support for Anthropic provider - #3475
Conversation
Use Anthropic's output_config.format API field to guarantee valid JSON when shouldOutputJson=true and config.format="json_schema", matching the existing OpenAI structured output behavior.
📝 WalkthroughWalkthroughAdds conditional JSON schema output configuration to Anthropic API requests. Introduces OutputConfig and OutputFormat data structures to encapsulate output format specifications and schema for "output" and "contextDescription". Adds unit tests validating conditional inclusion of Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/unit/AnthropicApiServiceTest.kt (1)
47-63: Consider using a JSON assertion library for cleaner, type-safe assertions.The multiple
@Suppress("UNCHECKED_CAST")annotations indicate this could benefit from a JSON-path or structured assertion approach. This would improve readability and reduce type-casting noise.💡 Alternative using JsonPath (optional)
You could use JsonPath assertions for cleaner type-safe access:
import com.jayway.jsonpath.JsonPath // Then in the test: val json = JsonPath.parse(capturedRequestBody!!) assertThat(json.read<String>("$.output_config.format.type")).isEqualTo("json_schema") assertThat(json.read<String>("$.output_config.format.schema.type")).isEqualTo("object") assertThat(json.read<List<String>>("$.output_config.format.schema.required")) .containsExactly("output", "contextDescription")This is purely optional - the current approach works fine for test code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ee/backend/tests/src/test/kotlin/io/tolgee/ee/unit/AnthropicApiServiceTest.kt` around lines 47 - 63, Replace the manual Map casts and multiple `@Suppress`("UNCHECKED_CAST") usages in AnthropicApiServiceTest (the assertions around bodyMap, output_config, format, schema, properties) with a JSON-path based assertion: parse the captured request JSON (e.g., with JsonPath.parse(capturedRequestBody!!)) and then use json.read with explicit paths like "$.output_config.format.type", "$.output_config.format.schema.type", "$.output_config.format.schema.required" and "$.output_config.format.schema.additionalProperties" to assert values and required array contents; remove the unsafe casts and suppressions and update assertions to use the typed reads from JsonPath for clearer, type-safe checks.ee/backend/app/src/main/kotlin/io/tolgee/ee/component/llm/AnthropicApiService.kt (1)
119-121: Stale comment references AzureCognitive instead of Anthropic.The comment incorrectly states "Data structure for mapping the AzureCognitive JSON response objects" but this is the Anthropic API service.
📝 Proposed fix
/** - * Data structure for mapping the AzureCognitive JSON response objects. + * Data structure for mapping the Anthropic JSON response objects. */ companion object {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ee/backend/app/src/main/kotlin/io/tolgee/ee/component/llm/AnthropicApiService.kt` around lines 119 - 121, The comment above the response-mapping data structure in AnthropicApiService.kt incorrectly mentions "AzureCognitive"; update that Javadoc/KDoc to refer to Anthropic (e.g., "Data structure for mapping the Anthropic JSON response objects" or similar) so the comment matches the class/file purpose (AnthropicApiService and its response data classes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/component/llm/AnthropicApiService.kt`:
- Around line 119-121: The comment above the response-mapping data structure in
AnthropicApiService.kt incorrectly mentions "AzureCognitive"; update that
Javadoc/KDoc to refer to Anthropic (e.g., "Data structure for mapping the
Anthropic JSON response objects" or similar) so the comment matches the
class/file purpose (AnthropicApiService and its response data classes).
In
`@ee/backend/tests/src/test/kotlin/io/tolgee/ee/unit/AnthropicApiServiceTest.kt`:
- Around line 47-63: Replace the manual Map casts and multiple
`@Suppress`("UNCHECKED_CAST") usages in AnthropicApiServiceTest (the assertions
around bodyMap, output_config, format, schema, properties) with a JSON-path
based assertion: parse the captured request JSON (e.g., with
JsonPath.parse(capturedRequestBody!!)) and then use json.read with explicit
paths like "$.output_config.format.type", "$.output_config.format.schema.type",
"$.output_config.format.schema.required" and
"$.output_config.format.schema.additionalProperties" to assert values and
required array contents; remove the unsafe casts and suppressions and update
assertions to use the typed reads from JsonPath for clearer, type-safe checks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
ee/backend/tests/src/test/kotlin/io/tolgee/ee/unit/AnthropicApiServiceTest.kt (1)
67-78: Consider testing with a non-null, non-json_schema format value.The test verifies behavior when
format = null, but the test name suggests any non-json_schema format. Adding a test case with an explicit format like"text"would strengthen coverage and ensure the production code handles both null and other format values correctly.💡 Optional: Additional test case
`@Test` fun `omits output_config when format is different value`() { val config = createConfig(format = "text") val params = createParams(shouldOutputJson = true) val restTemplate = createCapturingRestTemplate() service.translate(params, config, restTemplate) val bodyMap = objectMapper.readValue<Map<String, Any>>(capturedRequestBody!!) assertThat(bodyMap).doesNotContainKey("output_config") }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ee/backend/tests/src/test/kotlin/io/tolgee/ee/unit/AnthropicApiServiceTest.kt` around lines 67 - 78, Add a second unit test that mirrors `omits output_config when format is not json_schema` but uses a non-null, non-json_schema format (e.g., "text") so we verify both null and explicit non-json_schema values are handled; use the same helpers (`createConfig(format = "text")`, `createParams(shouldOutputJson = true)`, `createCapturingRestTemplate()`), call `service.translate(params, config, restTemplate)`, parse `capturedRequestBody` with `objectMapper.readValue`, and assert the resulting map does not contain the "output_config" key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@ee/backend/tests/src/test/kotlin/io/tolgee/ee/unit/AnthropicApiServiceTest.kt`:
- Around line 67-78: Add a second unit test that mirrors `omits output_config
when format is not json_schema` but uses a non-null, non-json_schema format
(e.g., "text") so we verify both null and explicit non-json_schema values are
handled; use the same helpers (`createConfig(format = "text")`,
`createParams(shouldOutputJson = true)`, `createCapturingRestTemplate()`), call
`service.translate(params, config, restTemplate)`, parse `capturedRequestBody`
with `objectMapper.readValue`, and assert the resulting map does not contain the
"output_config" key.
Summary
output_configwithjson_schemaformat to Anthropic API requests whenshouldOutputJson=trueandconfig.format="json_schema", mirroring the existing OpenAI structured output supportoutputandcontextDescriptionstring fields, matching the OpenAI implementationTest plan
output_configis included when bothshouldOutputJsonandformat="json_schema"are setoutput_configis omitted whenformatis notjson_schemaoutput_configis omitted whenshouldOutputJsonis falseSummary by CodeRabbit
New Features
Tests