Skip to content

Commit abe3582

Browse files
nathanfalletkarloti
authored andcommitted
Add support for excluding properties in JSON schema generation (JetBrains#638)
Fixes JetBrains#377 Changes are the same as JetBrains#378, it's just the updated version after the refactor of JetBrains#443 Proposed changes: Add an `excludedProperties` parameter like this: ```kotlin simpleSchemaGenerator.generate( "TestClass", serializer<TestClass>(), descriptionOverrides = emptyMap(), excludedProperties = setOf("TestClass.someProperty") ) ``` --- #### Type of the change - [x] New feature - [ ] Bug fix - [ ] Documentation fix #### Checklist for all pull requests - [x] The pull request has a description of the proposed change - [x] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [x] The pull request uses **`develop`** as the base branch - [x] Tests for the changes have been added - [x] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [x] An issue describing the proposed change exists - [x] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated
1 parent e2f913e commit abe3582

File tree

7 files changed

+83
-5
lines changed

7 files changed

+83
-5
lines changed

prompt/prompt-executor/prompt-executor-clients/prompt-executor-openai-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/openai/structure/OpenAIStandardJsonSchemaGenerator.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ public open class OpenAIStandardJsonSchemaGenerator : StandardJsonSchemaGenerato
3636
json: Json,
3737
name: String,
3838
serializer: KSerializer<*>,
39-
descriptionOverrides: Map<String, String>
39+
descriptionOverrides: Map<String, String>,
40+
excludedProperties: Set<String>,
4041
): LLMParams.Schema.JSON.Standard {
41-
val param = super.generate(json, name, serializer, descriptionOverrides)
42+
val param = super.generate(json, name, serializer, descriptionOverrides, excludedProperties)
4243
val schema = param.schema.toMutableMap()
4344

4445
// OpenAI doesn't accept "$ref" at the root of the schema, so copying this definition explicitly.

prompt/prompt-structure/src/commonMain/kotlin/ai/koog/prompt/structure/json/JsonStructuredData.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ public class JsonStructuredData<TStruct>(
167167
* @param schemaGenerator JSON schema generator
168168
* @param descriptionOverrides Optional map of serial class names and property names to descriptions.
169169
* If a property/type is already described with [ai.koog.agents.core.tools.annotations.LLMDescription] annotation, value from the map will override this description.
170+
* @param excludedProperties Optional set of property names to exclude from the schema generation.
170171
* @param examples List of example data items that conform to the structure, used for demonstrating valid formats.
171172
* @param definitionPrompt Prompt with definition, explaining the structure to the LLM when the manual mode for
172173
* structured output is used. Default is [JsonStructuredData.defaultDefinitionPrompt]
@@ -177,6 +178,7 @@ public class JsonStructuredData<TStruct>(
177178
json: Json = defaultJson,
178179
schemaGenerator: JsonSchemaGenerator = StandardJsonSchemaGenerator.Default,
179180
descriptionOverrides: Map<String, String> = emptyMap(),
181+
excludedProperties: Set<String> = emptySet(),
180182
examples: List<TStruct> = emptyList(),
181183
definitionPrompt: (
182184
builder: TextContentBuilderBase<*>,
@@ -185,7 +187,7 @@ public class JsonStructuredData<TStruct>(
185187
): JsonStructuredData<TStruct> {
186188
return JsonStructuredData(
187189
id = id,
188-
schema = schemaGenerator.generate(json, id, serializer, descriptionOverrides),
190+
schema = schemaGenerator.generate(json, id, serializer, descriptionOverrides, excludedProperties),
189191
examples = examples,
190192
serializer = serializer,
191193
json = json,

prompt/prompt-structure/src/commonMain/kotlin/ai/koog/prompt/structure/json/generator/BasicJsonSchemaGenerator.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public open class BasicJsonSchemaGenerator : GenericJsonSchemaGenerator() {
3232
json: Json,
3333
name: String,
3434
serializer: KSerializer<*>,
35-
descriptionOverrides: Map<String, String>
35+
descriptionOverrides: Map<String, String>,
36+
excludedProperties: Set<String>,
3637
): LLMParams.Schema.JSON.Basic {
3738
val descriptorKind = serializer.descriptor.kind
3839

@@ -46,6 +47,7 @@ public open class BasicJsonSchemaGenerator : GenericJsonSchemaGenerator() {
4647
processedTypeDefs = mutableMapOf(),
4748
currentDefPath = listOf(),
4849
descriptionOverrides = descriptionOverrides,
50+
excludedProperties = excludedProperties,
4951
currentDescription = null,
5052
)
5153

prompt/prompt-structure/src/commonMain/kotlin/ai/koog/prompt/structure/json/generator/GenericJsonSchemaGenerator.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ public abstract class GenericJsonSchemaGenerator : JsonSchemaGenerator() {
150150
val propertyName = context.descriptor.getElementName(i)
151151
val propertyDescriptor = context.descriptor.getElementDescriptor(i)
152152

153+
// Check if the property is excluded
154+
val lookupKey = "${context.descriptor.serialName}.$propertyName"
155+
if (context.excludedProperties.contains(lookupKey)) {
156+
if (!context.descriptor.isElementOptional(i)) {
157+
throw IllegalArgumentException("Property '$lookupKey' is marked as excluded, but it is required in the schema.")
158+
}
159+
continue
160+
}
161+
153162
put(
154163
propertyName,
155164
process(

prompt/prompt-structure/src/commonMain/kotlin/ai/koog/prompt/structure/json/generator/JsonSchemaGenerator.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public abstract class JsonSchemaGenerator {
3131
* during schema generation processing.
3232
* @property descriptionOverrides A map of user-defined properties and types descriptions to override [LLMDescription]
3333
* from the provided class.
34+
* @property excludedProperties A set of property names to exclude from the schema generation.
3435
* @property currentDescription Description for the current element
3536
*/
3637
public data class GenerationContext(
@@ -39,6 +40,7 @@ public abstract class JsonSchemaGenerator {
3940
public val processedTypeDefs: MutableMap<SerialDescriptor, JsonObject>,
4041
public val currentDefPath: List<SerialDescriptor>,
4142
public val descriptionOverrides: Map<String, String>,
43+
public val excludedProperties: Set<String>,
4244
public val currentDescription: String?,
4345
) {
4446
/**
@@ -89,12 +91,14 @@ public abstract class JsonSchemaGenerator {
8991
* @param name The name of the schema.
9092
* @param serializer The serializer for the type for which the schema has to be generated.
9193
* @param descriptionOverrides A map containing overrides for [LLMDescription].
94+
* @param excludedProperties A set of property names to exclude from the schema generation.
9295
*/
9396
public abstract fun generate(
9497
json: Json,
9598
name: String,
9699
serializer: KSerializer<*>,
97100
descriptionOverrides: Map<String, String>,
101+
excludedProperties: Set<String> = emptySet(),
98102
): LLMParams.Schema.JSON
99103

100104
/**

prompt/prompt-structure/src/commonMain/kotlin/ai/koog/prompt/structure/json/generator/StandardJsonSchemaGenerator.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public open class StandardJsonSchemaGenerator : GenericJsonSchemaGenerator() {
3939
json: Json,
4040
name: String,
4141
serializer: KSerializer<*>,
42-
descriptionOverrides: Map<String, String>
42+
descriptionOverrides: Map<String, String>,
43+
excludedProperties: Set<String>,
4344
): LLMParams.Schema.JSON.Standard {
4445
val descriptorKind = serializer.descriptor.kind
4546

@@ -56,6 +57,7 @@ public open class StandardJsonSchemaGenerator : GenericJsonSchemaGenerator() {
5657
processedTypeDefs = mutableMapOf(),
5758
currentDefPath = listOf(),
5859
descriptionOverrides = descriptionOverrides,
60+
excludedProperties = excludedProperties,
5961
currentDescription = null
6062
)
6163

prompt/prompt-structure/src/commonTest/kotlin/ai/koog/prompt/structure/json/generator/JsonSchemaGeneratorTest.kt

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,64 @@ class JsonSchemaGeneratorTest {
267267
assertEquals(expectedSchema, schema)
268268
}
269269

270+
@Test
271+
fun testGenerateBasicSchemaExcludingProperties() {
272+
val result = basicGenerator.generate(json, "TestClass", serializer<TestClass>(), emptyMap(), setOf("TestClass.nullableProperty"))
273+
val schema = json.encodeToString(result.schema)
274+
275+
val expectedSchema = """
276+
{
277+
"type": "object",
278+
"properties": {
279+
"stringProperty": {
280+
"type": "string",
281+
"description": "A string property"
282+
},
283+
"intProperty": {
284+
"type": "integer"
285+
},
286+
"booleanProperty": {
287+
"type": "boolean"
288+
},
289+
"listProperty": {
290+
"type": "array",
291+
"items": {
292+
"type": "string"
293+
}
294+
},
295+
"mapProperty": {
296+
"type": "object",
297+
"additionalProperties": {
298+
"type": "integer"
299+
}
300+
}
301+
},
302+
"required": [
303+
"stringProperty",
304+
"intProperty",
305+
"booleanProperty"
306+
],
307+
"additionalProperties": false
308+
}
309+
""".trimIndent()
310+
311+
assertEquals(expectedSchema, schema)
312+
}
313+
314+
@Test
315+
fun testGenerateBasicSchemaExcludingRequiredProperties() {
316+
val exception = assertFailsWith<IllegalArgumentException> {
317+
basicGenerator.generate(
318+
json,
319+
"TestClass",
320+
serializer<TestClass>(),
321+
emptyMap(),
322+
setOf("TestClass.stringProperty")
323+
)
324+
}
325+
assertEquals("Property 'TestClass.stringProperty' is marked as excluded, but it is required in the schema.", exception.message)
326+
}
327+
270328
@Test
271329
fun testGenerateStandardSchemaWithDescriptions() {
272330
val descriptions = mapOf(

0 commit comments

Comments
 (0)