From fdbff8b016c8fb92c0cc8d27e5d3327dc060b5d9 Mon Sep 17 00:00:00 2001 From: Bruno Lannoo Date: Thu, 17 Jul 2025 20:44:11 +0200 Subject: [PATCH 1/2] :bug: Fix Anthropic json schema validation error This commit fixes an issue with the Anthropic API integration where complex nested tool structures were causing JSON schema validation errors. The Anthropic API requires JSON schemas to comply with JSON Schema draft 2020-12, but our current implementation was generating invalid nested structures. --- .../anthropic/AnthropicSchemaErrorAgent.kt | 241 +++++++++++++++++ ...nthropicSchemaValidationIntegrationTest.kt | 253 ++++++++++++++++++ .../clients/anthropic/AnthropicLLMClient.kt | 47 +++- 3 files changed, 528 insertions(+), 13 deletions(-) create mode 100644 examples/src/main/kotlin/ai/koog/agents/example/anthropic/AnthropicSchemaErrorAgent.kt create mode 100644 integration-tests/src/jvmTest/kotlin/ai/koog/integration/tests/AnthropicSchemaValidationIntegrationTest.kt diff --git a/examples/src/main/kotlin/ai/koog/agents/example/anthropic/AnthropicSchemaErrorAgent.kt b/examples/src/main/kotlin/ai/koog/agents/example/anthropic/AnthropicSchemaErrorAgent.kt new file mode 100644 index 0000000000..89b7858f57 --- /dev/null +++ b/examples/src/main/kotlin/ai/koog/agents/example/anthropic/AnthropicSchemaErrorAgent.kt @@ -0,0 +1,241 @@ +package ai.koog.agents.example.anthropic + +import ai.koog.agents.core.agent.AIAgent +import ai.koog.agents.core.tools.SimpleTool +import ai.koog.agents.core.tools.ToolArgs +import ai.koog.agents.core.tools.ToolDescriptor +import ai.koog.agents.core.tools.ToolParameterDescriptor +import ai.koog.agents.core.tools.ToolParameterType +import ai.koog.agents.core.tools.ToolRegistry +import ai.koog.agents.example.ApiKeyService +import ai.koog.agents.ext.tool.AskUser +import ai.koog.agents.ext.tool.SayToUser +import ai.koog.agents.features.eventHandler.feature.EventHandler +import ai.koog.prompt.executor.clients.anthropic.AnthropicModels +import ai.koog.prompt.executor.llms.all.simpleAnthropicExecutor +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.Serializable + +/** + * This example demonstrates the JSON schema validation error in the Anthropic API + * when using complex nested structures in tool parameters. + * + * The issue occurs in the AnthropicLLMClient.kt file, specifically in the getTypeMapForParameter() function + * that converts ToolDescriptor objects to JSON schemas for the Anthropic API. + * + * The problem is that when processing ToolParameterType.Object, the function creates invalid nested structures + * by placing type information under a "type" key, resulting in invalid schema structures like: + * { + * "type": {"type": "string"} // Invalid nesting + * } + */ + +/** + * Address type enum. + */ +enum class AddressType { + HOME, WORK, OTHER +} + +/** + * An address with multiple fields. + */ +@Serializable +data class Address( + val type: AddressType, + val street: String, + val city: String, + val state: String, + val zipCode: String +) + +/** + * A user profile with nested structures. + */ +@Serializable +data class UserProfile( + val name: String, + val email: String, + val addresses: List
+) + +/** + * Arguments for the complex nested tool. + */ +@Serializable +data class ComplexNestedToolArgs( + val profile: UserProfile +) : ToolArgs + +/** + * A complex nested tool that demonstrates the JSON schema validation error. + * This tool has parameters with complex nested structures that will trigger + * the error in the Anthropic API. + */ +object ComplexNestedTool : SimpleTool() { + override val argsSerializer = ComplexNestedToolArgs.serializer() + + override val descriptor = ToolDescriptor( + name = "complex_nested_tool", + description = "A tool that processes user profiles with complex nested structures.", + requiredParameters = listOf( + ToolParameterDescriptor( + name = "profile", + description = "The user profile to process", + type = ToolParameterType.Object( + properties = listOf( + ToolParameterDescriptor( + name = "name", + description = "The user's full name", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "email", + description = "The user's email address", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "addresses", + description = "The user's addresses", + type = ToolParameterType.List( + ToolParameterType.Object( + properties = listOf( + ToolParameterDescriptor( + name = "type", + description = "The type of address (HOME, WORK, or OTHER)", + type = ToolParameterType.Enum(AddressType.entries.map { it.name }.toTypedArray()) + ), + ToolParameterDescriptor( + name = "street", + description = "The street address", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "city", + description = "The city", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "state", + description = "The state or province", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "zipCode", + description = "The ZIP or postal code", + type = ToolParameterType.String + ) + ), + requiredProperties = listOf("type", "street", "city", "state", "zipCode") + ) + ) + ) + ), + requiredProperties = listOf("name", "email", "addresses") + ) + ) + ) + ) + + override suspend fun doExecute(args: ComplexNestedToolArgs): String { + // Process the user profile + val profile = args.profile + val addressesInfo = profile.addresses.joinToString("\n") { address -> + "- ${address.type} Address: ${address.street}, ${address.city}, ${address.state} ${address.zipCode}" + } + + return """ + Successfully processed user profile: + Name: ${profile.name} + Email: ${profile.email} + Addresses: + $addressesInfo + """.trimIndent() + } +} + +/** + * Main function that demonstrates the Anthropic API JSON schema validation error. + */ +fun main(): Unit = runBlocking { + println("Starting Anthropic Schema Error Agent example...") + println("This example demonstrates the JSON schema validation error in the Anthropic API.") + println("The error occurs when using tools with complex nested structures.") + println() + + try { + // Create an agent with the Anthropic API + val agent = AIAgent( + executor = simpleAnthropicExecutor(ApiKeyService.anthropicApiKey), + llmModel = AnthropicModels.Sonnet_3_7, + systemPrompt = "You are a helpful assistant that can process user profiles. Please use the complex_nested_tool to process the user profile I provide.", + toolRegistry = ToolRegistry { + tool(AskUser) + tool(SayToUser) + tool(ComplexNestedTool) + }, + installFeatures = { + install(EventHandler) { + onAgentRunError { eventContext -> + println("ERROR: ${eventContext.throwable.javaClass.simpleName}(${eventContext.throwable.message})") + println(eventContext.throwable.stackTraceToString()) + true + } + onToolCall { eventContext -> + println("Calling tool: ${eventContext.tool.name}") + println("Arguments: ${eventContext.toolArgs.toString().take(100)}...") + } + } + } + ) + + // Run the agent with a request to process a user profile + val result = agent.run(""" + Please process this user profile: + + Name: John Doe + Email: john.doe@example.com + Addresses: + 1. HOME: 123 Main St, Springfield, IL 62701 + 2. WORK: 456 Business Ave, Springfield, IL 62701 + """.trimIndent()) + + println("\nResult: $result") + } catch (e: IllegalStateException) { + // Check if this is the specific JSON schema validation error we're looking for + if (e.message?.contains("JSON schema is invalid") == true || + e.message?.contains("input_schema") == true) { + println("\nERROR: ${e.javaClass.simpleName}(${e.message})") + println(e.stackTraceToString()) + + println("\nThis error is expected and demonstrates the JSON schema validation issue in the Anthropic API.") + println("The error occurs because the getTypeMapForParameter() function in AnthropicLLMClient.kt") + println("generates invalid JSON schemas when dealing with complex nested structures.") + println("\nThe issue is that when processing ToolParameterType.Object, the function creates invalid nested structures") + println("by placing type information under a \"type\" key, resulting in invalid schema structures like:") + println(""" + { + "type": {"type": "string"} // Invalid nesting + } + """.trimIndent()) + + println("\nTo fix this issue, the getTypeMapForParameter() function needs to be modified to correctly") + println("generate JSON Schema draft 2020-12 compliant schemas by:") + println("1. Eliminating invalid nesting by merging type information directly into property schemas") + println("2. Adding the required 'required' field for object schemas when applicable") + println("3. Including schema metadata like 'additionalProperties: false' for stricter validation") + } else { + // This is some other Anthropic API error + println("\nERROR: An unexpected error occurred with the Anthropic API: ${e.message}") + println(e.stackTraceToString()) + } + } catch (e: IllegalArgumentException) { + // This is likely a missing API key error + println("\nERROR: Missing or invalid API key: ${e.message}") + println("Make sure you have set the ANTHROPIC_API_KEY environment variable.") + } catch (e: Exception) { + // Handle any other unexpected errors + println("\nUnexpected error: ${e.javaClass.simpleName}(${e.message})") + println(e.stackTraceToString()) + } +} \ No newline at end of file diff --git a/integration-tests/src/jvmTest/kotlin/ai/koog/integration/tests/AnthropicSchemaValidationIntegrationTest.kt b/integration-tests/src/jvmTest/kotlin/ai/koog/integration/tests/AnthropicSchemaValidationIntegrationTest.kt new file mode 100644 index 0000000000..ca022b2070 --- /dev/null +++ b/integration-tests/src/jvmTest/kotlin/ai/koog/integration/tests/AnthropicSchemaValidationIntegrationTest.kt @@ -0,0 +1,253 @@ +package ai.koog.integration.tests + +import ai.koog.agents.core.agent.AIAgent +import ai.koog.agents.core.tools.SimpleTool +import ai.koog.agents.core.tools.ToolArgs +import ai.koog.agents.core.tools.ToolDescriptor +import ai.koog.agents.core.tools.ToolParameterDescriptor +import ai.koog.agents.core.tools.ToolParameterType +import ai.koog.agents.core.tools.ToolRegistry +import ai.koog.agents.features.eventHandler.feature.EventHandler +import ai.koog.integration.tests.utils.TestUtils.readTestAnthropicKeyFromEnv +import ai.koog.prompt.executor.clients.anthropic.AnthropicModels +import ai.koog.prompt.executor.llms.all.simpleAnthropicExecutor +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.Serializable +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assumptions.assumeTrue +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +/** + * Integration test for verifying the fix for the Anthropic API JSON schema validation error + * when using complex nested structures in tool parameters. + * + * The issue was in the AnthropicLLMClient.kt file, specifically in the getTypeMapForParameter() function + * that converts ToolDescriptor objects to JSON schemas for the Anthropic API. + * + * The problem was that when processing ToolParameterType.Object, the function created invalid nested structures + * by placing type information under a "type" key, resulting in invalid schema structures like: + * { + * "type": {"type": "string"} // Invalid nesting + * } + * + * This test verifies that the fix works by creating an agent with the Anthropic API and a tool + * with complex nested structures, and then running it with a sample input. + */ +class AnthropicSchemaValidationIntegrationTest { + + companion object { + private var anthropicApiKey: String? = null + private var apiKeyAvailable = false + + @BeforeAll + @JvmStatic + fun setup() { + try { + anthropicApiKey = readTestAnthropicKeyFromEnv() + // Check that the API key is not empty or blank + apiKeyAvailable = !anthropicApiKey.isNullOrBlank() + if (!apiKeyAvailable) { + println("Anthropic API key is empty or blank") + println("Tests requiring Anthropic API will be skipped") + } + } catch (e: Exception) { + println("Anthropic API key not available: ${e.message}") + println("Tests requiring Anthropic API will be skipped") + apiKeyAvailable = false + } + } + } + + /** + * Address type enum. + */ + @Serializable + enum class AddressType { + HOME, WORK, OTHER + } + + /** + * An address with multiple fields. + */ + @Serializable + data class Address( + val type: AddressType, + val street: String, + val city: String, + val state: String, + val zipCode: String + ) + + /** + * A user profile with nested structures. + */ + @Serializable + data class UserProfile( + val name: String, + val email: String, + val addresses: List
+ ) + + /** + * Arguments for the complex nested tool. + */ + @Serializable + data class ComplexNestedToolArgs( + val profile: UserProfile + ) : ToolArgs + + /** + * A complex nested tool that demonstrates the JSON schema validation error. + * This tool has parameters with complex nested structures that would trigger + * the error in the Anthropic API before the fix. + */ + object ComplexNestedTool : SimpleTool() { + override val argsSerializer = ComplexNestedToolArgs.serializer() + + override val descriptor = ToolDescriptor( + name = "complex_nested_tool", + description = "A tool that processes user profiles with complex nested structures.", + requiredParameters = listOf( + ToolParameterDescriptor( + name = "profile", + description = "The user profile to process", + type = ToolParameterType.Object( + properties = listOf( + ToolParameterDescriptor( + name = "name", + description = "The user's full name", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "email", + description = "The user's email address", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "addresses", + description = "The user's addresses", + type = ToolParameterType.List( + ToolParameterType.Object( + properties = listOf( + ToolParameterDescriptor( + name = "type", + description = "The type of address (HOME, WORK, or OTHER)", + type = ToolParameterType.Enum(AddressType.entries.map { it.name }.toTypedArray()) + ), + ToolParameterDescriptor( + name = "street", + description = "The street address", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "city", + description = "The city", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "state", + description = "The state or province", + type = ToolParameterType.String + ), + ToolParameterDescriptor( + name = "zipCode", + description = "The ZIP or postal code", + type = ToolParameterType.String + ) + ), + requiredProperties = listOf("type", "street", "city", "state", "zipCode") + ) + ) + ) + ), + requiredProperties = listOf("name", "email", "addresses") + ) + ) + ) + ) + + override suspend fun doExecute(args: ComplexNestedToolArgs): String { + // Process the user profile + val profile = args.profile + val addressesInfo = profile.addresses.joinToString("\n") { address -> + "- ${address.type} Address: ${address.street}, ${address.city}, ${address.state} ${address.zipCode}" + } + + return """ + Successfully processed user profile: + Name: ${profile.name} + Email: ${profile.email} + Addresses: + $addressesInfo + """.trimIndent() + } + } + + /** + * Test that verifies the fix for the Anthropic API JSON schema validation error + * when using complex nested structures in tool parameters. + * + * Before the fix, this test would fail with an error like: + * "tools.0.custom.input_schema: JSON schema is invalid. It must match JSON Schema draft 2020-12" + * + * After the fix, the test should pass, demonstrating that the Anthropic API + * can now correctly handle complex nested structures in tool parameters. + * + * Note: This test requires a valid Anthropic API key to be set in the environment variable + * ANTHROPIC_API_TEST_KEY. If the key is not available, the test will be skipped. + */ + @Test + fun integration_testAnthropicComplexNestedStructures() { + // Skip the test if the Anthropic API key is not available + assumeTrue(apiKeyAvailable, "Anthropic API key is not available") + + runBlocking { + // Create an agent with the Anthropic API and the complex nested tool + val agent = AIAgent( + executor = simpleAnthropicExecutor(anthropicApiKey!!), + llmModel = AnthropicModels.Sonnet_3_7, + systemPrompt = "You are a helpful assistant that can process user profiles. Please use the complex_nested_tool to process the user profile I provide.", + toolRegistry = ToolRegistry { + tool(ComplexNestedTool) + }, + installFeatures = { + install(EventHandler) { + onAgentRunError { eventContext -> + println("ERROR: ${eventContext.throwable.javaClass.simpleName}(${eventContext.throwable.message})") + println(eventContext.throwable.stackTraceToString()) + true + } + onToolCall { eventContext -> + println("Calling tool: ${eventContext.tool.name}") + println("Arguments: ${eventContext.toolArgs.toString().take(100)}...") + } + } + } + ) + + // Run the agent with a request to process a user profile + val result = agent.run(""" + Please process this user profile: + + Name: John Doe + Email: john.doe@example.com + Addresses: + 1. HOME: 123 Main St, Springfield, IL 62701 + 2. WORK: 456 Business Ave, Springfield, IL 62701 + """.trimIndent()) + + // Verify the result + println("\nResult: $result") + assertNotNull(result, "Result should not be null") + assertTrue(result.isNotBlank(), "Result should not be empty or blank") + + // Check that the result contains expected information + assertTrue(result.lowercase().contains("john doe"), "Result should contain the user's name") + assertTrue(result.lowercase().contains("john.doe@example.com"), "Result should contain the user's email") + assertTrue(result.lowercase().contains("main st"), "Result should contain the home address street") + assertTrue(result.lowercase().contains("business ave"), "Result should contain the work address street") + } + } +} \ No newline at end of file diff --git a/prompt/prompt-executor/prompt-executor-clients/prompt-executor-anthropic-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/anthropic/AnthropicLLMClient.kt b/prompt/prompt-executor/prompt-executor-clients/prompt-executor-anthropic-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/anthropic/AnthropicLLMClient.kt index 767611f647..96664818cd 100644 --- a/prompt/prompt-executor/prompt-executor-clients/prompt-executor-anthropic-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/anthropic/AnthropicLLMClient.kt +++ b/prompt/prompt-executor/prompt-executor-clients/prompt-executor-anthropic-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/anthropic/AnthropicLLMClient.kt @@ -399,19 +399,40 @@ public open class AnthropicLLMClient( ) ) - is ToolParameterType.Object -> JsonObject( - mapOf( - "type" to JsonPrimitive("object"), - "properties" to JsonObject(type.properties.associate { - it.name to JsonObject( - mapOf( - "type" to getTypeMapForParameter(it.type), - "description" to JsonPrimitive(it.description) - ) - ) - }) - ) - ) + is ToolParameterType.Object -> { + // Create properties map with proper type information + val propertiesMap = mutableMapOf() + + for (prop in type.properties) { + // Get type information for the property + val typeInfo = getTypeMapForParameter(prop.type) + + // Create a map with all type properties and description + val propMap = mutableMapOf() + for (entry in typeInfo.entries) { + propMap[entry.key] = entry.value + } + propMap["description"] = JsonPrimitive(prop.description) + + // Add to properties map + propertiesMap[prop.name] = JsonObject(propMap) + } + + // Create the final object schema + val objectMap = mutableMapOf() + objectMap["type"] = JsonPrimitive("object") + objectMap["properties"] = JsonObject(propertiesMap) + + // Add required field if requiredProperties is not empty + if (type.requiredProperties.isNotEmpty()) { + objectMap["required"] = JsonArray(type.requiredProperties.map { JsonPrimitive(it) }) + } + + // Add additionalProperties for strict validation + objectMap["additionalProperties"] = JsonPrimitive(type.additionalProperties ?: false) + + JsonObject(objectMap) + } } } From 01f86cc020a3296bae43ed431c6a4d903db49fd1 Mon Sep 17 00:00:00 2001 From: Bruno Lannoo Date: Fri, 18 Jul 2025 10:58:59 +0200 Subject: [PATCH 2/2] :burn: remove unnecessary example, given test already covers that --- .../anthropic/AnthropicSchemaErrorAgent.kt | 241 ------------------ 1 file changed, 241 deletions(-) delete mode 100644 examples/src/main/kotlin/ai/koog/agents/example/anthropic/AnthropicSchemaErrorAgent.kt diff --git a/examples/src/main/kotlin/ai/koog/agents/example/anthropic/AnthropicSchemaErrorAgent.kt b/examples/src/main/kotlin/ai/koog/agents/example/anthropic/AnthropicSchemaErrorAgent.kt deleted file mode 100644 index 89b7858f57..0000000000 --- a/examples/src/main/kotlin/ai/koog/agents/example/anthropic/AnthropicSchemaErrorAgent.kt +++ /dev/null @@ -1,241 +0,0 @@ -package ai.koog.agents.example.anthropic - -import ai.koog.agents.core.agent.AIAgent -import ai.koog.agents.core.tools.SimpleTool -import ai.koog.agents.core.tools.ToolArgs -import ai.koog.agents.core.tools.ToolDescriptor -import ai.koog.agents.core.tools.ToolParameterDescriptor -import ai.koog.agents.core.tools.ToolParameterType -import ai.koog.agents.core.tools.ToolRegistry -import ai.koog.agents.example.ApiKeyService -import ai.koog.agents.ext.tool.AskUser -import ai.koog.agents.ext.tool.SayToUser -import ai.koog.agents.features.eventHandler.feature.EventHandler -import ai.koog.prompt.executor.clients.anthropic.AnthropicModels -import ai.koog.prompt.executor.llms.all.simpleAnthropicExecutor -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.Serializable - -/** - * This example demonstrates the JSON schema validation error in the Anthropic API - * when using complex nested structures in tool parameters. - * - * The issue occurs in the AnthropicLLMClient.kt file, specifically in the getTypeMapForParameter() function - * that converts ToolDescriptor objects to JSON schemas for the Anthropic API. - * - * The problem is that when processing ToolParameterType.Object, the function creates invalid nested structures - * by placing type information under a "type" key, resulting in invalid schema structures like: - * { - * "type": {"type": "string"} // Invalid nesting - * } - */ - -/** - * Address type enum. - */ -enum class AddressType { - HOME, WORK, OTHER -} - -/** - * An address with multiple fields. - */ -@Serializable -data class Address( - val type: AddressType, - val street: String, - val city: String, - val state: String, - val zipCode: String -) - -/** - * A user profile with nested structures. - */ -@Serializable -data class UserProfile( - val name: String, - val email: String, - val addresses: List
-) - -/** - * Arguments for the complex nested tool. - */ -@Serializable -data class ComplexNestedToolArgs( - val profile: UserProfile -) : ToolArgs - -/** - * A complex nested tool that demonstrates the JSON schema validation error. - * This tool has parameters with complex nested structures that will trigger - * the error in the Anthropic API. - */ -object ComplexNestedTool : SimpleTool() { - override val argsSerializer = ComplexNestedToolArgs.serializer() - - override val descriptor = ToolDescriptor( - name = "complex_nested_tool", - description = "A tool that processes user profiles with complex nested structures.", - requiredParameters = listOf( - ToolParameterDescriptor( - name = "profile", - description = "The user profile to process", - type = ToolParameterType.Object( - properties = listOf( - ToolParameterDescriptor( - name = "name", - description = "The user's full name", - type = ToolParameterType.String - ), - ToolParameterDescriptor( - name = "email", - description = "The user's email address", - type = ToolParameterType.String - ), - ToolParameterDescriptor( - name = "addresses", - description = "The user's addresses", - type = ToolParameterType.List( - ToolParameterType.Object( - properties = listOf( - ToolParameterDescriptor( - name = "type", - description = "The type of address (HOME, WORK, or OTHER)", - type = ToolParameterType.Enum(AddressType.entries.map { it.name }.toTypedArray()) - ), - ToolParameterDescriptor( - name = "street", - description = "The street address", - type = ToolParameterType.String - ), - ToolParameterDescriptor( - name = "city", - description = "The city", - type = ToolParameterType.String - ), - ToolParameterDescriptor( - name = "state", - description = "The state or province", - type = ToolParameterType.String - ), - ToolParameterDescriptor( - name = "zipCode", - description = "The ZIP or postal code", - type = ToolParameterType.String - ) - ), - requiredProperties = listOf("type", "street", "city", "state", "zipCode") - ) - ) - ) - ), - requiredProperties = listOf("name", "email", "addresses") - ) - ) - ) - ) - - override suspend fun doExecute(args: ComplexNestedToolArgs): String { - // Process the user profile - val profile = args.profile - val addressesInfo = profile.addresses.joinToString("\n") { address -> - "- ${address.type} Address: ${address.street}, ${address.city}, ${address.state} ${address.zipCode}" - } - - return """ - Successfully processed user profile: - Name: ${profile.name} - Email: ${profile.email} - Addresses: - $addressesInfo - """.trimIndent() - } -} - -/** - * Main function that demonstrates the Anthropic API JSON schema validation error. - */ -fun main(): Unit = runBlocking { - println("Starting Anthropic Schema Error Agent example...") - println("This example demonstrates the JSON schema validation error in the Anthropic API.") - println("The error occurs when using tools with complex nested structures.") - println() - - try { - // Create an agent with the Anthropic API - val agent = AIAgent( - executor = simpleAnthropicExecutor(ApiKeyService.anthropicApiKey), - llmModel = AnthropicModels.Sonnet_3_7, - systemPrompt = "You are a helpful assistant that can process user profiles. Please use the complex_nested_tool to process the user profile I provide.", - toolRegistry = ToolRegistry { - tool(AskUser) - tool(SayToUser) - tool(ComplexNestedTool) - }, - installFeatures = { - install(EventHandler) { - onAgentRunError { eventContext -> - println("ERROR: ${eventContext.throwable.javaClass.simpleName}(${eventContext.throwable.message})") - println(eventContext.throwable.stackTraceToString()) - true - } - onToolCall { eventContext -> - println("Calling tool: ${eventContext.tool.name}") - println("Arguments: ${eventContext.toolArgs.toString().take(100)}...") - } - } - } - ) - - // Run the agent with a request to process a user profile - val result = agent.run(""" - Please process this user profile: - - Name: John Doe - Email: john.doe@example.com - Addresses: - 1. HOME: 123 Main St, Springfield, IL 62701 - 2. WORK: 456 Business Ave, Springfield, IL 62701 - """.trimIndent()) - - println("\nResult: $result") - } catch (e: IllegalStateException) { - // Check if this is the specific JSON schema validation error we're looking for - if (e.message?.contains("JSON schema is invalid") == true || - e.message?.contains("input_schema") == true) { - println("\nERROR: ${e.javaClass.simpleName}(${e.message})") - println(e.stackTraceToString()) - - println("\nThis error is expected and demonstrates the JSON schema validation issue in the Anthropic API.") - println("The error occurs because the getTypeMapForParameter() function in AnthropicLLMClient.kt") - println("generates invalid JSON schemas when dealing with complex nested structures.") - println("\nThe issue is that when processing ToolParameterType.Object, the function creates invalid nested structures") - println("by placing type information under a \"type\" key, resulting in invalid schema structures like:") - println(""" - { - "type": {"type": "string"} // Invalid nesting - } - """.trimIndent()) - - println("\nTo fix this issue, the getTypeMapForParameter() function needs to be modified to correctly") - println("generate JSON Schema draft 2020-12 compliant schemas by:") - println("1. Eliminating invalid nesting by merging type information directly into property schemas") - println("2. Adding the required 'required' field for object schemas when applicable") - println("3. Including schema metadata like 'additionalProperties: false' for stricter validation") - } else { - // This is some other Anthropic API error - println("\nERROR: An unexpected error occurred with the Anthropic API: ${e.message}") - println(e.stackTraceToString()) - } - } catch (e: IllegalArgumentException) { - // This is likely a missing API key error - println("\nERROR: Missing or invalid API key: ${e.message}") - println("Make sure you have set the ANTHROPIC_API_KEY environment variable.") - } catch (e: Exception) { - // Handle any other unexpected errors - println("\nUnexpected error: ${e.javaClass.simpleName}(${e.message})") - println(e.stackTraceToString()) - } -} \ No newline at end of file