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)
}
}