override fun processObject()

in prompt/prompt-structure/src/commonMain/kotlin/ai/koog/prompt/structure/json/generator/GenericJsonSchemaGenerator.kt [136:213]


    override fun processObject(context: GenerationContext): JsonObject {
        check(context.descriptor !in context.currentDefPath) {
            """
            Recursion detected in type definitions while generating JSON schema.
            This usually means you have recursive type where one of the fields in a class has a type of this class itself
            or its base class when using ${this::class.simpleName} generator, which is not supported by this generator.
            
            Consider some possible solutions:
            1. Use other JSON schema generator that supports such classes and if the format it produces is supported by the LLM you're using.
            2. Remove recursive type references.
            
            Current definition is ${context.descriptor.serialName} at path ${context.currentDefPath.map { it.serialName }}
            """.trimIndent()
        }

        // If this type was already processed, get it from the collection
        val schema = if (context.descriptor in context.processedTypeDefs) {
            context.processedTypeDefs.getValue(context.descriptor)
        } else { // Otherwise process and add it to the collection
            // Process all properties
            val properties = buildJsonObject {
                for (i in 0 until context.descriptor.elementsCount) {
                    val propertyName = context.descriptor.getElementName(i)
                    val propertyDescriptor = context.descriptor.getElementDescriptor(i)

                    // Check if the property is excluded
                    val lookupKey = "${context.descriptor.serialName}.$propertyName"
                    if (context.excludedProperties.contains(lookupKey)) {
                        if (!context.descriptor.isElementOptional(i)) {
                            throw IllegalArgumentException("Property '$lookupKey' is marked as excluded, but it is required in the schema.")
                        }
                        continue
                    }

                    put(
                        propertyName,
                        process(
                            context.copy(
                                descriptor = propertyDescriptor,
                                currentDefPath = context.currentDefPath + context.descriptor,
                                // Put description for a property or fallback to the description for a type of the property
                                currentDescription = context.getElementDescription(i)
                                    ?: context.copy(descriptor = propertyDescriptor).getTypeDescription()
                            )
                        )
                    )
                }
            }

            // Process required
            val required = buildJsonArray {
                // Add all non-optional properties
                for (i in 0 until context.descriptor.elementsCount) {
                    if (!context.descriptor.isElementOptional(i)) {
                        add(context.descriptor.getElementName(i))
                    }
                }
            }

            // Build type definition
            buildJsonObject {
                put(JsonSchemaConsts.Keys.TYPE, JsonSchemaConsts.Types.OBJECT)
                put(JsonSchemaConsts.Keys.PROPERTIES, properties)
                put(JsonSchemaConsts.Keys.REQUIRED, required)
                // Specify explicitly that additional unknown keys should not be provided
                put(JsonSchemaConsts.Keys.ADDITIONAL_PROPERTIES, false)
            }.also {
                // Also add it to the collection of processed types.
                context.processedTypeDefs[context.descriptor] = it
            }
        }

        // Add specific description from the context to the generated schema object
        return buildJsonObject {
            schema.forEach { (key, value) -> put(key, value) }
            putDescription(context.currentDescription)
        }
    }