Skip to content

GenericJsonSchemaGenerator crashes on @Contextual properties in executeStructured #1486

@JohanSaur

Description

@JohanSaur

Description

When using executeStructured with a @Serializable data class that contains a @Contextual property (e.g. java.util.UUID), the JSON schema generator throws an IllegalArgumentException before any LLM request is made. The GenericJsonSchemaGenerator.process() method does not handle the CONTEXTUAL serial kind.

@Serializable
data class ExpectedResponse(
    @Contextual
    @property:LLMDescription("The ID of the one asking a question")
    val id: UUID,
    @property:LLMDescription("Your response")
    val response: String
)

val promptExecutor = SingleLLMPromptExecutor(GoogleLLMClient(apiKey))

promptExecutor.executeStructured<ExpectedResponse>(
    prompt = prompt("my-prompt") {
        user("Hi, my id is: 2d81bd6d-b60a-41e3-a652-f01f4a9386ea. How can you help me?")
    },
    model = GoogleModels.Gemini2_5Flash
)

Stack trace

java.lang.IllegalArgumentException: Encountered unsupported type while generating JSON schema: CONTEXTUAL
    at ai.koog.prompt.structure.json.generator.GenericJsonSchemaGenerator.process(GenericJsonSchemaGenerator.kt:66)
    at ai.koog.prompt.structure.json.generator.GenericJsonSchemaGenerator.processObject(GenericJsonSchemaGenerator.kt:172)
    at ai.koog.prompt.structure.json.generator.StandardJsonSchemaGenerator.processObject(StandardJsonSchemaGenerator.kt:155)

Expected behavior

The JSON schema generator should resolve @Contextual properties by looking up the actual serializer from the SerializersModule and generating the schema based on the resolved serializer's descriptor (e.g. for UUID with a string serializer, it should emit {"type": "string"}). Alternatively, if resolution is not feasible, the error message should clearly explain that @Contextual is unsupported and suggest using @Serializable(with = ...) as a workaround.

Workaround

Use an explicit serializer annotation instead of @Contextual:

@Serializable
data class ExpectedResponse(
    @Serializable(with = UUIDSerializer::class)
    @property:LLMDescription("The ID of the one asking a question")
    val id: UUID,
    @property:LLMDescription("Your response")
    val response: String
)

Or represent the field as a String and parse it manually.

Related issues

Environment

Koog version: 0.6.2
Kotlin version: 2.3.0
Platform: JVM

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions