-
-
Notifications
You must be signed in to change notification settings - Fork 3
Support JSON schema for function parameters in chat models #413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Coverage summary from CodacySee diff coverage on Codacy
Coverage variation details
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: Diff coverage details
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: See your quality gate settings Change summary preferencesFootnotes
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a SchemaHelper singleton for JSON Schema parsing/introspection; changes FunctionObject.parameters to Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Spec as RequestSpec
participant Req as RequestBody
participant M as OpenaiCompletionsMatchers
participant SH as SchemaHelper
Spec->>Req: toolHasParameter(fn, param)
Spec->>Req: addMatcher(M.toolHasParameter(fn,param))
Note over Req,M: During verification
Req->>M: execute matcher(request)
M->>M: locate tool by function name
alt tool has parameters (JsonElement)
M->>SH: parseSchema(parametersJson)
SH-->>M: SchemaDefinition?
M->>SH: getProperty / getPropertyType / isPropertyRequired
SH-->>M: property info
else parameters absent or non-schema
M-->>M: treat as missing/inapplicable
end
M-->>Req: match result (pass/fail)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
ai-mocks-core/src/commonMain/kotlin/me/kpavlov/aimocks/core/json/schema/SchemaHelper.kt
(1 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/Model.kt
(2 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiChatCompletionRequestSpecification.kt
(1 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiCompletionsMatchers.kt
(2 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt
(4 hunks)ai-mocks-openai/src/commonTest/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModelsTest.kt
(5 hunks)ai-mocks-openai/src/jvmTest/resources/junit-platform.properties
(1 hunks)
🧰 Additional context used
🪛 detekt (1.23.8)
ai-mocks-core/src/commonMain/kotlin/me/kpavlov/aimocks/core/json/schema/SchemaHelper.kt
[warning] 27-27: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
[warning] 27-27: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Codacy Static Code Analysis
- GitHub Check: build
ai-mocks-core/src/commonMain/kotlin/me/kpavlov/aimocks/core/json/schema/SchemaHelper.kt
Show resolved
Hide resolved
...ks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt
Show resolved
Hide resolved
5f8b418
to
be0f4c8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
ai-mocks-core/src/commonMain/kotlin/me/kpavlov/aimocks/core/json/schema/SchemaHelper.kt
(1 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/Model.kt
(2 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiChatCompletionRequestSpecification.kt
(1 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiCompletionsMatchers.kt
(2 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt
(4 hunks)ai-mocks-openai/src/commonTest/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModelsTest.kt
(5 hunks)ai-mocks-openai/src/commonTest/kotlin/me/kpavlov/aimocks/openai/model/embeddings/EmbeddingModelsTest.kt
(1 hunks)ai-mocks-openai/src/jvmTest/resources/junit-platform.properties
(1 hunks)
🧰 Additional context used
🪛 detekt (1.23.8)
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiCompletionsMatchers.kt
[warning] 187-190: In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty.
(detekt.performance.SpreadOperator)
🪛 GitHub Check: Codacy Static Code Analysis
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt
[warning] 409-409: ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt#L409
Method deserialize has a cyclomatic complexity of 9 (limit is 8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Codacy Static Code Analysis
- GitHub Check: build
🔇 Additional comments (10)
ai-mocks-openai/src/jvmTest/resources/junit-platform.properties (1)
4-4
: Confirm test readiness for method-level concurrency.Switching
junit.jupiter.execution.parallel.mode.default
toconcurrent
means sibling test methods inside the same class now execute in parallel by default. Please re-run the suite (ideally multiple times) and ensure no tests depend on ordered execution or shared mutable state—especially those using@TestInstance(PER_CLASS)
, class-level fixtures, or static mocks. Update or guard any fragile tests before landing this change.ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt (1)
425-476
: Serializer fix looks solidThanks for handling both string and object payloads while re-emitting the required
"type":"function"
discriminator—this resolves the earlier decode/encode failure for spec-compliant tool choices.ai-mocks-core/src/commonMain/kotlin/me/kpavlov/aimocks/core/json/schema/SchemaHelper.kt (3)
24-31
: Past review comment has been addressed.The exception handling now correctly catches only
SerializationException
, allowing other failures (like coroutine cancellations) to surface. The required import was also added.
82-85
: Function implementation is correct.The use of
all
provides early termination when a property is not required, making this efficient for the validation use case.
106-115
: LGTM!The null-safe property access pattern is consistent with other helper functions and correctly handles missing properties and unsupported variants.
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiCompletionsMatchers.kt (5)
56-69
: LGTM!The matcher correctly checks for tool existence by function name with proper null safety.
78-98
: LGTM!The matcher correctly chains nullable operations and uses SchemaHelper for property introspection. The implementation handles both missing tools and missing parameters correctly.
108-133
: LGTM!The description matching logic correctly uses SchemaHelper and handles missing descriptions appropriately.
143-168
: LGTM!The type checking logic correctly delegates to SchemaHelper and handles missing or null types appropriately.
177-218
: LGTM with minor performance note.The matcher correctly validates required parameters using SchemaHelper. The spread operator at line 189 creates an array copy, but in the context of request validation (not a hot path), the ergonomic vararg API is more valuable than micro-optimization.
ai-mocks-core/src/commonMain/kotlin/me/kpavlov/aimocks/core/json/schema/SchemaHelper.kt
Show resolved
Hide resolved
Introduces SchemaHelper for JSON Schema parsing/introspection. Expands OpenAI chat models: FunctionObject.parameters now JsonElement and adds ToolChoiceSerializer. Adds new request-spec APIs and matchers for tool/function parameter assertions using SchemaHelper. Updates tests for tools, tool calls, multimodal parts, and adds JUnit parallel execution property.
be0f4c8
to
d872c5a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/Model.kt (1)
237-245
: Avoid duplicated FunctionObject definitions across modulesThis class also exists in model/chat. Consider centralizing to one definition to prevent drift.
♻️ Duplicate comments (1)
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt (1)
370-385
: ToolChoice now uses a custom serializer — resolves earlier decoding/encoding issuesAnnotating the sealed class with ToolChoiceSerializer is the right fix; ensures proper string and object forms.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
ai-mocks-core/src/commonMain/kotlin/me/kpavlov/aimocks/core/json/schema/SchemaHelper.kt
(1 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/Model.kt
(2 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiChatCompletionRequestSpecification.kt
(1 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiCompletionsMatchers.kt
(2 hunks)ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt
(4 hunks)ai-mocks-openai/src/commonTest/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModelsTest.kt
(5 hunks)ai-mocks-openai/src/commonTest/kotlin/me/kpavlov/aimocks/openai/model/embeddings/EmbeddingModelsTest.kt
(1 hunks)ai-mocks-openai/src/jvmTest/resources/junit-platform.properties
(1 hunks)
🧰 Additional context used
🪛 GitHub Check: Codacy Static Code Analysis
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt
[warning] 409-409: ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt#L409
Method deserialize has a cyclomatic complexity of 9 (limit is 8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Codacy Static Code Analysis
- GitHub Check: build
🔇 Additional comments (28)
ai-mocks-openai/src/commonTest/kotlin/me/kpavlov/aimocks/openai/model/embeddings/EmbeddingModelsTest.kt (1)
13-13
: LGTM: prettyPrint disabledThis reduces formatting overhead; decoding tests remain unaffected.
ai-mocks-openai/src/commonTest/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModelsTest.kt (9)
12-14
: LGTM: imports for JsonElement assertionsjsonObject/jsonPrimitive imports are appropriate for JsonElement-based parameters.
75-139
: LGTM: single tool request with JSON Schema parametersCovers object-shaped parameters and validates tool list; matches new API surface.
141-197
: LGTM: multiple tools scenarioValidates presence of two functions and key request fields.
563-571
: LGTM: Tool.parameters assertions migrated to JsonElementAccess via jsonObject/jsonPrimitive is correct after parameters → JsonElement change.
577-633
: LGTM: image URL multimodal message testAsserts typed parts; aligns with MessageContent.Parts and ImageUrlObject.
634-691
: LGTM: tool_choice "auto" path coveredConfirms ToolChoiceSerializer handles string variant and tools array shape.
693-762
: LGTM: assistant tool calls in ChatResponseChecks function name and raw JSON arguments string as per API behavior.
764-813
: LGTM: audio input partCovers input_audio structure and fields.
815-839
: LGTM: reasoning_effort fieldValidates new request field deserialization.
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/Model.kt (1)
241-244
: FunctionObject.parameters → JsonElement: good changeAllows full JSON Schema. Please ensure call sites migrated from Map access to JsonObject navigation.
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt (2)
20-27
: LGTM: required JSON imports addedNeeded for JsonElement parameters and ToolChoiceSerializer.
305-315
: FunctionObject.parameters → JsonElementCorrect to accept arbitrary JSON Schema. Verify downstream matchers and usages handle non-object shapes (arrays/enums).
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiCompletionsMatchers.kt (6)
5-5
: LGTM!The import is correctly added to support the new schema introspection functionality in the matchers below.
50-69
: LGTM!The matcher correctly validates the presence of a tool with the specified function name using null-safe operations.
78-98
: LGTM!The matcher correctly uses SchemaHelper to parse the schema and verify property existence with proper null-safety.
108-133
: LGTM!The matcher correctly validates both parameter existence and its description using exact string matching.
143-168
: LGTM!The matcher correctly handles type validation, including multi-type schemas, by checking if the expected type is contained in the list returned by
getPropertyType
.
177-218
: LGTM!The matcher correctly validates that all specified parameters are marked as required in the schema, with clear error messages.
ai-mocks-core/src/commonMain/kotlin/me/kpavlov/aimocks/core/json/schema/SchemaHelper.kt (9)
1-6
: LGTM!Package and imports are correctly organized, including the
SerializationException
import that addresses the previous review feedback.
12-16
: LGTM!The singleton design with a private, robustly configured
Json
instance is appropriate for schema parsing utilities.
24-31
: LGTM! Previous feedback addressed.The function now correctly catches only
SerializationException
, allowing other exceptions (including cancellations) to propagate while gracefully handling schema parse failures.
40-43
: LGTM!The property existence check is straightforward and correct.
52-61
: LGTM! Previous feedback addressed.The function now returns
List<String>?
to properly handle multi-type schemas, addressing the earlier limitation.
70-73
: LGTM!The required property check is straightforward and correct.
82-85
: LGTM!The implementation correctly validates that all specified properties are marked as required using idiomatic Kotlin.
94-97
: LGTM!The property retrieval is straightforward and provides access to the full property definition.
106-115
: LGTM!The description retrieval correctly handles different property types and returns null for non-value properties.
|
||
/** | ||
* Adds a matcher to verify that the request includes a tool with the specified function name. | ||
* | ||
* @param functionName The name of the function to match | ||
*/ | ||
public fun hasToolWithFunction(functionName: String) { | ||
requestBody.add(OpenaiCompletionsMatchers.hasToolWithFunction(functionName)) | ||
} | ||
|
||
/** | ||
* Adds a matcher to verify that a tool's function has a parameter with the specified name. | ||
* | ||
* @param functionName The name of the function | ||
* @param parameterName The name of the parameter to check | ||
*/ | ||
public fun toolHasParameter( | ||
functionName: String, | ||
parameterName: String, | ||
) { | ||
requestBody.add(OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName)) | ||
} | ||
|
||
/** | ||
* Adds a matcher to verify that a tool's function has a parameter with the specified name and description. | ||
* | ||
* @param functionName The name of the function | ||
* @param parameterName The name of the parameter to check | ||
* @param description The expected description of the parameter | ||
*/ | ||
public fun toolHasParameter( | ||
functionName: String, | ||
parameterName: String, | ||
description: String, | ||
) { | ||
requestBody.add(OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName, description)) | ||
} | ||
|
||
/** | ||
* Adds a matcher to verify that a tool's function parameter has the specified type. | ||
* | ||
* @param functionName The name of the function | ||
* @param parameterName The name of the parameter | ||
* @param expectedType The expected type (e.g., "string", "integer", "number", "boolean", "object", "array") | ||
*/ | ||
public fun toolParameterHasType( | ||
functionName: String, | ||
parameterName: String, | ||
expectedType: String, | ||
) { | ||
requestBody.add( | ||
OpenaiCompletionsMatchers.toolParameterHasType(functionName, parameterName, expectedType), | ||
) | ||
} | ||
|
||
/** | ||
* Adds a matcher to verify that a tool's function requires specific parameters. | ||
* | ||
* @param functionName The name of the function | ||
* @param requiredParams The parameter names that should be required | ||
*/ | ||
public fun toolRequiresParameters( | ||
functionName: String, | ||
vararg requiredParams: String, | ||
) { | ||
requestBody.add(OpenaiCompletionsMatchers.toolRequiresParameters(functionName, *requiredParams)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider returning this for fluent API consistency
seed(...) is chainable; these new methods return Unit. Returning the spec improves ergonomics without behavior change.
- public fun hasToolWithFunction(functionName: String) {
- requestBody.add(OpenaiCompletionsMatchers.hasToolWithFunction(functionName))
- }
+ public fun hasToolWithFunction(functionName: String): OpenaiChatCompletionRequestSpecification =
+ apply { requestBody.add(OpenaiCompletionsMatchers.hasToolWithFunction(functionName)) }
- public fun toolHasParameter(
+ public fun toolHasParameter(
functionName: String,
parameterName: String,
- ) {
- requestBody.add(OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName))
- }
+ ): OpenaiChatCompletionRequestSpecification =
+ apply { requestBody.add(OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName)) }
- public fun toolHasParameter(
+ public fun toolHasParameter(
functionName: String,
parameterName: String,
description: String,
- ) {
- requestBody.add(OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName, description))
- }
+ ): OpenaiChatCompletionRequestSpecification =
+ apply {
+ requestBody.add(
+ OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName, description),
+ )
+ }
- public fun toolParameterHasType(
+ public fun toolParameterHasType(
functionName: String,
parameterName: String,
expectedType: String,
- ) {
- requestBody.add(
- OpenaiCompletionsMatchers.toolParameterHasType(functionName, parameterName, expectedType),
- )
- }
+ ): OpenaiChatCompletionRequestSpecification =
+ apply {
+ requestBody.add(
+ OpenaiCompletionsMatchers.toolParameterHasType(functionName, parameterName, expectedType),
+ )
+ }
- public fun toolRequiresParameters(
+ public fun toolRequiresParameters(
functionName: String,
vararg requiredParams: String,
- ) {
- requestBody.add(OpenaiCompletionsMatchers.toolRequiresParameters(functionName, *requiredParams))
- }
+ ): OpenaiChatCompletionRequestSpecification =
+ apply { requestBody.add(OpenaiCompletionsMatchers.toolRequiresParameters(functionName, *requiredParams)) }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
/** | |
* Adds a matcher to verify that the request includes a tool with the specified function name. | |
* | |
* @param functionName The name of the function to match | |
*/ | |
public fun hasToolWithFunction(functionName: String) { | |
requestBody.add(OpenaiCompletionsMatchers.hasToolWithFunction(functionName)) | |
} | |
/** | |
* Adds a matcher to verify that a tool's function has a parameter with the specified name. | |
* | |
* @param functionName The name of the function | |
* @param parameterName The name of the parameter to check | |
*/ | |
public fun toolHasParameter( | |
functionName: String, | |
parameterName: String, | |
) { | |
requestBody.add(OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName)) | |
} | |
/** | |
* Adds a matcher to verify that a tool's function has a parameter with the specified name and description. | |
* | |
* @param functionName The name of the function | |
* @param parameterName The name of the parameter to check | |
* @param description The expected description of the parameter | |
*/ | |
public fun toolHasParameter( | |
functionName: String, | |
parameterName: String, | |
description: String, | |
) { | |
requestBody.add(OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName, description)) | |
} | |
/** | |
* Adds a matcher to verify that a tool's function parameter has the specified type. | |
* | |
* @param functionName The name of the function | |
* @param parameterName The name of the parameter | |
* @param expectedType The expected type (e.g., "string", "integer", "number", "boolean", "object", "array") | |
*/ | |
public fun toolParameterHasType( | |
functionName: String, | |
parameterName: String, | |
expectedType: String, | |
) { | |
requestBody.add( | |
OpenaiCompletionsMatchers.toolParameterHasType(functionName, parameterName, expectedType), | |
) | |
} | |
/** | |
* Adds a matcher to verify that a tool's function requires specific parameters. | |
* | |
* @param functionName The name of the function | |
* @param requiredParams The parameter names that should be required | |
*/ | |
public fun toolRequiresParameters( | |
functionName: String, | |
vararg requiredParams: String, | |
) { | |
requestBody.add(OpenaiCompletionsMatchers.toolRequiresParameters(functionName, *requiredParams)) | |
} | |
/** | |
* Adds a matcher to verify that the request includes a tool with the specified function name. | |
* | |
* @param functionName The name of the function to match | |
*/ | |
public fun hasToolWithFunction(functionName: String): OpenaiChatCompletionRequestSpecification = | |
apply { requestBody.add(OpenaiCompletionsMatchers.hasToolWithFunction(functionName)) } | |
/** | |
* Adds a matcher to verify that a tool's function has a parameter with the specified name. | |
* | |
* @param functionName The name of the function | |
* @param parameterName The name of the parameter to check | |
*/ | |
public fun toolHasParameter( | |
functionName: String, | |
parameterName: String, | |
): OpenaiChatCompletionRequestSpecification = | |
apply { requestBody.add(OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName)) } | |
/** | |
* Adds a matcher to verify that a tool's function has a parameter with the specified name and description. | |
* | |
* @param functionName The name of the function | |
* @param parameterName The name of the parameter to check | |
* @param description The expected description of the parameter | |
*/ | |
public fun toolHasParameter( | |
functionName: String, | |
parameterName: String, | |
description: String, | |
): OpenaiChatCompletionRequestSpecification = | |
apply { | |
requestBody.add( | |
OpenaiCompletionsMatchers.toolHasParameter(functionName, parameterName, description), | |
) | |
} | |
/** | |
* Adds a matcher to verify that a tool's function parameter has the specified type. | |
* | |
* @param functionName The name of the function | |
* @param parameterName The name of the parameter | |
* @param expectedType The expected type (e.g., "string", "integer", "number", "boolean", "object", "array") | |
*/ | |
public fun toolParameterHasType( | |
functionName: String, | |
parameterName: String, | |
expectedType: String, | |
): OpenaiChatCompletionRequestSpecification = | |
apply { | |
requestBody.add( | |
OpenaiCompletionsMatchers.toolParameterHasType(functionName, parameterName, expectedType), | |
) | |
} | |
/** | |
* Adds a matcher to verify that a tool's function requires specific parameters. | |
* | |
* @param functionName The name of the function | |
* @param requiredParams The parameter names that should be required | |
*/ | |
public fun toolRequiresParameters( | |
functionName: String, | |
vararg requiredParams: String, | |
): OpenaiChatCompletionRequestSpecification = | |
apply { requestBody.add(OpenaiCompletionsMatchers.toolRequiresParameters(functionName, *requiredParams)) } |
🤖 Prompt for AI Agents
In
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/completions/OpenaiChatCompletionRequestSpecification.kt
around lines 42 to 108, the new helper methods (hasToolWithFunction, both
overloads of toolHasParameter, toolParameterHasType, and toolRequiresParameters)
currently return Unit which breaks fluent/chaining ergonomics; change each
method signature to return OpenaiChatCompletionRequestSpecification and add a
final "return this" after adding the matcher so callers can chain calls
(preserve existing matcher-add logic and vararg handling).
/** | ||
* Custom serializer for [ToolChoice] that handles both string and object formats. | ||
* | ||
* According to the OpenAI specification, tool_choice can be: | ||
* - A simple string ("auto" or "none") | ||
* - An object with type "function" and a function field | ||
* | ||
* This serializer automatically converts between these formats. | ||
*/ | ||
public class ToolChoiceSerializer : KSerializer<ToolChoice> { | ||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ToolChoice") | ||
|
||
override fun deserialize(decoder: Decoder): ToolChoice { | ||
val jsonDecoder = | ||
decoder as? JsonDecoder | ||
?: throw SerializationException("This serializer can only be used with JSON") | ||
|
||
return when (val element = jsonDecoder.decodeJsonElement()) { | ||
is JsonPrimitive -> { | ||
when (element.contentOrNull) { | ||
"auto" -> ToolChoice.Auto | ||
"none" -> ToolChoice.None | ||
else -> throw SerializationException( | ||
"Unknown tool choice: ${element.contentOrNull}", | ||
) | ||
} | ||
} | ||
|
||
is JsonObject -> { | ||
val type = element["type"]?.jsonPrimitive?.contentOrNull ?: "function" | ||
if (type != "function") { | ||
throw SerializationException("Unknown tool choice type: $type") | ||
} | ||
val functionElement = | ||
element["function"] | ||
?: throw SerializationException("tool_choice.function is required") | ||
val function = | ||
jsonDecoder.json.decodeFromJsonElement( | ||
ToolChoiceFunction.serializer(), | ||
functionElement, | ||
) | ||
ToolChoice.Function(function) | ||
} | ||
|
||
else -> throw SerializationException("Unsupported tool choice payload: $element") | ||
} | ||
} | ||
|
||
override fun serialize( | ||
encoder: Encoder, | ||
value: ToolChoice, | ||
) { | ||
val jsonEncoder = | ||
encoder as? JsonEncoder | ||
?: throw SerializationException("This serializer can only be used with JSON") | ||
|
||
when (value) { | ||
is ToolChoice.Auto -> { | ||
jsonEncoder.encodeJsonElement(JsonPrimitive("auto")) | ||
} | ||
|
||
is ToolChoice.None -> { | ||
jsonEncoder.encodeJsonElement(JsonPrimitive("none")) | ||
} | ||
|
||
is ToolChoice.Function -> { | ||
val functionElement = | ||
jsonEncoder.json.encodeToJsonElement( | ||
ToolChoiceFunction.serializer(), | ||
value.function, | ||
) | ||
jsonEncoder.encodeJsonElement( | ||
JsonObject( | ||
mapOf( | ||
"type" to JsonPrimitive("function"), | ||
"function" to functionElement, | ||
), | ||
), | ||
) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
ToolChoiceSerializer correctly handles string/object forms; minor complexity nit
- Validates "type":"function", requires "function", encodes discriminator — spec-compliant. Nice.
- Cyclomatic complexity slightly above threshold; extract tiny helpers to appease static checks.
class ToolChoiceSerializer : KSerializer<ToolChoice> {
- override fun deserialize(decoder: Decoder): ToolChoice {
+ override fun deserialize(decoder: Decoder): ToolChoice {
val jsonDecoder = decoder as? JsonDecoder
?: throw SerializationException("This serializer can only be used with JSON")
- return when (val element = jsonDecoder.decodeJsonElement()) {
- is JsonPrimitive -> {
- when (element.contentOrNull) {
- "auto" -> ToolChoice.Auto
- "none" -> ToolChoice.None
- else -> throw SerializationException("Unknown tool choice: ${element.contentOrNull}")
- }
- }
- is JsonObject -> {
- val type = element["type"]?.jsonPrimitive?.contentOrNull ?: "function"
- if (type != "function") {
- throw SerializationException("Unknown tool choice type: $type")
- }
- val functionElement = element["function"]
- ?: throw SerializationException("tool_choice.function is required")
- val function = jsonDecoder.json.decodeFromJsonElement(
- ToolChoiceFunction.serializer(), functionElement)
- ToolChoice.Function(function)
- }
- else -> throw SerializationException("Unsupported tool choice payload: $element")
- }
+ val element = jsonDecoder.decodeJsonElement()
+ return when (element) {
+ is JsonPrimitive -> decodePrimitive(element)
+ is JsonObject -> decodeObject(jsonDecoder, element)
+ else -> throw SerializationException("Unsupported tool choice payload: $element")
+ }
}
+
+ private fun decodePrimitive(p: JsonPrimitive): ToolChoice =
+ when (p.contentOrNull) {
+ "auto" -> ToolChoice.Auto
+ "none" -> ToolChoice.None
+ else -> throw SerializationException("Unknown tool choice: ${p.contentOrNull}")
+ }
+
+ private fun decodeObject(dec: JsonDecoder, obj: JsonObject): ToolChoice {
+ val type = obj["type"]?.jsonPrimitive?.contentOrNull ?: "function"
+ if (type != "function") throw SerializationException("Unknown tool choice type: $type")
+ val fnEl = obj["function"] ?: throw SerializationException("tool_choice.function is required")
+ val fn = dec.json.decodeFromJsonElement(ToolChoiceFunction.serializer(), fnEl)
+ return ToolChoice.Function(fn)
+ }
🧰 Tools
🪛 GitHub Check: Codacy Static Code Analysis
[warning] 409-409: ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt#L409
Method deserialize has a cyclomatic complexity of 9 (limit is 8)
🤖 Prompt for AI Agents
ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/model/chat/ChatCompletionModels.kt
around lines 397 to 479: the ToolChoiceSerializer is functionally correct but
exceeds cyclomatic complexity; refactor by extracting small private helpers to
reduce branching in deserialize/serialize. Concretely, keep the
JsonDecoder/JsonEncoder casts and top-level deserialize/serialize methods, but
move the JsonPrimitive handling into a private fun parseFromPrimitive(element:
JsonPrimitive): ToolChoice and the JsonObject handling into private fun
parseFromObject(obj: JsonObject, json: Json): ToolChoice (validate type, require
function, decode function), and for serialization move primitive and object
branches into private helpers like encodePrimitive(value: ToolChoice,
jsonEncoder: JsonEncoder) and encodeFunctionObject(function, jsonEncoder).
Ensure all thrown SerializationException messages and semantics remain unchanged
and wire the helpers from the original methods so behavior is identical while
reducing cyclomatic complexity.
## https://docs.junit.org/5.3.0-M1/user-guide/index.html#writing-tests-parallel-execution | ||
junit.jupiter.execution.parallel.enabled=true | ||
junit.jupiter.execution.parallel.config.strategy=dynamic | ||
junit.jupiter.execution.parallel.mode.default=concurrent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Parallel mode default set to concurrent — verify test/thread safety
Good for speed. Please ensure:
- No shared mutable state in tests/mocks (singletons like SchemaHelper, Json instances).
- Unique ports/resources if any HTTP servers are used.
- Deterministic seeds where order-sensitive.
Support JSON schema for function parameters in chat models
Introduces SchemaHelper for JSON Schema parsing/introspection. Expands OpenAI chat models: FunctionObject.parameters now JsonElement and adds ToolChoiceSerializer. Adds new request-spec APIs and matchers for tool/function parameter assertions using SchemaHelper. Updates tests for tools, tool calls, multimodal parts, and adds JUnit parallel execution property.