diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/annotation/McpValidation.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/annotation/McpValidation.java index ecf23c89..4353edf6 100644 --- a/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/annotation/McpValidation.java +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/annotation/McpValidation.java @@ -31,6 +31,6 @@ public @interface McpValidation { int maxLength() default -1; String[] allowedValues() default {}; String format() default ""; - String defaultValue() default ""; + boolean nullable() default false; String defaultValue() default ""; String[] examples() default {}; } diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java index a90efa00..d95dae6e 100644 --- a/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java @@ -103,6 +103,14 @@ public class JsonSchemaGenerator { if (validation != null && validation.examples().length > 0) { fieldSchema.put("examples", List.of(validation.examples())); } + if (validation != null && validation.nullable()) { + Map nonNullSchema = new HashMap<>(fieldSchema); + fieldSchema = new HashMap<>(); + fieldSchema.put("anyOf", List.of( + nonNullSchema, + Map.of("type", "null") + )); + } properties.put(field.getName(), fieldSchema); } diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java index 85a292ac..abd2d2d8 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java @@ -43,11 +43,11 @@ public class ClaimSearchResponse { private String statusLabel; @McpParameter(description = "Approved amount. Null before review; do not interpret null as zero.") - @McpValidation(minimum = 0) + @McpValidation(minimum = 0, nullable = true) private Long approvedAmount; @McpParameter(description = "Present only when status is REJECTED; otherwise null.") - @McpValidation(maxLength = 200) + @McpValidation(maxLength = 200, nullable = true) private String rejectionReason; @McpParameter(description = "Claim summaries, ordered by received date descending.") @@ -82,7 +82,7 @@ public class ClaimSearchResponse { private String receivedDate; @McpParameter(description = "Approved amount. Null before review; do not interpret null as zero.") - @McpValidation(minimum = 0) + @McpValidation(minimum = 0, nullable = true) private Long approvedAmount; } } \ No newline at end of file diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchSchemaSampleUseCase.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchSchemaSampleUseCase.java index 3be3a498..9a55c1a9 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchSchemaSampleUseCase.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchSchemaSampleUseCase.java @@ -17,8 +17,8 @@ public interface ClaimSearchSchemaSampleUseCase { name = "sample.claim.search.resource", description = "Claim search Tool sample using input and output JSON Schema resources.", prompt = "Search an insurance claim by claim number or contract number.", - inputSchemaResource = "classpath:tool-schemas/claim-search-resource-input-schema.json", - + inputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json", + outputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json", readOnlyHint = true, idempotentHint = true ) diff --git a/dap-tool-oth/src/main/resources/tool-schemas/claim-search-resource-input-schema.json b/dap-tool-oth/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json similarity index 100% rename from dap-tool-oth/src/main/resources/tool-schemas/claim-search-resource-input-schema.json rename to dap-tool-oth/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json diff --git a/dap-tool-oth/src/main/resources/tool-schemas/claim-search-resource-output-schema.json b/dap-tool-oth/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json similarity index 100% rename from dap-tool-oth/src/main/resources/tool-schemas/claim-search-resource-output-schema.json rename to dap-tool-oth/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json diff --git a/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequestSchemaTest.java b/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequestSchemaTest.java index 16d996de..cead0621 100644 --- a/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequestSchemaTest.java +++ b/dap-tool-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequestSchemaTest.java @@ -69,6 +69,14 @@ class ClaimSearchRequestSchemaTest { assertTrue(validator.validateValue(schema, response).isEmpty()); } + @Test + void sampleResponseConformsToAutomaticallyGeneratedOutputSchema() throws Exception { + Map schema = JsonSchemaGenerator.generateSchema(ClaimSearchResponse.class); + ClaimSearchResponse response = new ClaimSearchSchemaSampleUseCaseImpl().search(new ClaimSearchRequest()); + ToolArgumentSchemaValidator validator = new ToolArgumentSchemaValidator(new ObjectMapper()); + + assertTrue(validator.validateValue(schema, response).isEmpty()); + } @SuppressWarnings("unchecked") private Map> properties(Map schema) { return (Map>) schema.get("properties");