From 3c970ea63d16d0c6069c9884b7befce7700a837e Mon Sep 17 00:00:00 2001 From: Neel Virdy Date: Fri, 30 Aug 2024 16:00:05 -0400 Subject: [PATCH 1/2] Support optional 'strict' parameter in function definition --- Sources/OpenAI/Public/Models/ChatQuery.swift | 7 ++- Tests/OpenAITests/OpenAITestsDecoder.swift | 64 ++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index c7a88649..ae535de3 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -676,14 +676,19 @@ public struct ChatQuery: Equatable, Codable, Streamable { /// **Python library defines only [String: Object] dictionary. public let parameters: Self.FunctionParameters? + /// Whether to enable strict schema adherence when generating the function call. If set to true, the model will follow the exact schema defined in the parameters field. Only a subset of JSON Schema is supported when strict is true. + public let strict: Bool? + public init( name: String, description: String? = nil, - parameters: Self.FunctionParameters? = nil + parameters: Self.FunctionParameters? = nil, + strict: Bool? = nil ) { self.name = name self.description = description self.parameters = parameters + self.strict = strict } /// See the [guide](/docs/guides/gpt/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format. diff --git a/Tests/OpenAITests/OpenAITestsDecoder.swift b/Tests/OpenAITests/OpenAITestsDecoder.swift index 7a3f13bb..b69d2618 100644 --- a/Tests/OpenAITests/OpenAITestsDecoder.swift +++ b/Tests/OpenAITests/OpenAITestsDecoder.swift @@ -247,6 +247,70 @@ class OpenAITestsDecoder: XCTestCase { XCTAssertEqual(chatQueryAsDict, expectedValueAsDict) } + func testChatQueryWithFunctionCallStrict() async throws { + let chatQuery = ChatQuery( + messages: [ + .user(.init(content: .string("What's the weather like in Boston?"))) + ], + model: .gpt3_5Turbo, + responseFormat: ChatQuery.ResponseFormat.jsonObject, + tools: [ + .init(function: .init( + name: "get_current_weather", + description: "Get the current weather in a given location", + parameters: .init( + type: .object, + properties: [ + "location": .init(type: .string, description: "The city and state, e.g. San Francisco, CA"), + "unit": .init(type: .string, enum: ["celsius", "fahrenheit"]) + ], + required: ["location"] + ), + strict: true + )) + ] + ) + let expectedValue = """ + { + "model": "gpt-3.5-turbo", + "messages": [ + { "role": "user", "content": "What's the weather like in Boston?" } + ], + "response_format": { + "type": "json_object" + }, + "tools": [ + { + "function": { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] } + }, + "required": ["location"] + }, + "strict": true + }, + "type": "function" + } + ], + "stream": false + } + """ + + // To compare serialized JSONs we first convert them both into NSDictionary which are comparable (unline native swift dictionaries) + let chatQueryAsDict = try jsonDataAsNSDictionary(JSONEncoder().encode(chatQuery)) + let expectedValueAsDict = try jsonDataAsNSDictionary(expectedValue.data(using: .utf8)!) + + XCTAssertEqual(chatQueryAsDict, expectedValueAsDict) + } + func testChatCompletionWithFunctionCall() async throws { let data = """ { From c5bbd808c6c66a7092bb30454e87b00769028ace Mon Sep 17 00:00:00 2001 From: Neel Virdy Date: Fri, 30 Aug 2024 16:23:53 -0400 Subject: [PATCH 2/2] Add optional 'additionalProperties' param --- Sources/OpenAI/Public/Models/ChatQuery.swift | 5 ++++- Tests/OpenAITests/OpenAITestsDecoder.swift | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/OpenAI/Public/Models/ChatQuery.swift b/Sources/OpenAI/Public/Models/ChatQuery.swift index ae535de3..2e9b0ad6 100644 --- a/Sources/OpenAI/Public/Models/ChatQuery.swift +++ b/Sources/OpenAI/Public/Models/ChatQuery.swift @@ -703,6 +703,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { public let multipleOf: Int? public let minimum: Int? public let maximum: Int? + public let additionalProperties: Bool? public init( type: Self.JSONType, @@ -713,7 +714,8 @@ public struct ChatQuery: Equatable, Codable, Streamable { enum: [String]? = nil, multipleOf: Int? = nil, minimum: Int? = nil, - maximum: Int? = nil + maximum: Int? = nil, + additionalProperties: Bool? = nil ) { self.type = type self.properties = properties @@ -724,6 +726,7 @@ public struct ChatQuery: Equatable, Codable, Streamable { self.multipleOf = multipleOf self.minimum = minimum self.maximum = maximum + self.additionalProperties = additionalProperties } public struct Property: Codable, Equatable { diff --git a/Tests/OpenAITests/OpenAITestsDecoder.swift b/Tests/OpenAITests/OpenAITestsDecoder.swift index b69d2618..0864ff85 100644 --- a/Tests/OpenAITests/OpenAITestsDecoder.swift +++ b/Tests/OpenAITests/OpenAITestsDecoder.swift @@ -264,7 +264,8 @@ class OpenAITestsDecoder: XCTestCase { "location": .init(type: .string, description: "The city and state, e.g. San Francisco, CA"), "unit": .init(type: .string, enum: ["celsius", "fahrenheit"]) ], - required: ["location"] + required: ["location"], + additionalProperties: false ), strict: true )) @@ -293,7 +294,8 @@ class OpenAITestsDecoder: XCTestCase { }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] } }, - "required": ["location"] + "required": ["location"], + "additionalProperties": false }, "strict": true },