Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 158 additions & 7 deletions Sources/OpenAI/Public/Models/ChatQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,28 @@ public struct ChatQuery: Equatable, Codable, Streamable, Sendable {
case auto
case function(String)
case required
/// Let the model choose between a restricted set of function tools.
case allowedTools(AllowedTools)

public enum AllowedToolsMode: String, Codable, Equatable, Sendable {
case auto
case required
}

public struct AllowedTools: Codable, Equatable, Sendable {
public let mode: AllowedToolsMode?
public let tools: [String]

public init(mode: AllowedToolsMode? = nil, tools: [String]) {
self.mode = mode
self.tools = tools
}
}

/// Convenience overload that preserves `.allowedTools(...)` call site while adding optional `mode`.
public static func allowedTools(_ tools: [String], mode: AllowedToolsMode? = nil) -> Self {
.allowedTools(.init(mode: mode, tools: tools))
}

public func encode(to encoder: Encoder) throws {
switch self {
Expand All @@ -1092,33 +1114,162 @@ public struct ChatQuery: Equatable, Codable, Streamable, Sendable {
var container = encoder.singleValueContainer()
try container.encode(CodingKeys.auto.rawValue)
case .function(let name):
var container = encoder.container(keyedBy: Self.ChatCompletionFunctionCallNameParam.CodingKeys.self)
try container.encode("function", forKey: .type)
try container.encode(["name": name], forKey: .function)
try ToolChoice(
type: "function",
function: FunctionNameParam(name: name),
mode: nil,
tools: nil,
legacyAllowedTools: nil
).encode(to: encoder)
case .required:
var container = encoder.singleValueContainer()
try container.encode(CodingKeys.required.rawValue)
case .allowedTools(let config):
try ToolChoice(
type: "allowed_tools",
function: nil,
mode: config.mode?.rawValue,
tools: config.tools.map { .functionTool(.init(name: $0)) },
legacyAllowedTools: nil
).encode(to: encoder)
}
}

public init(from decoder: Decoder) throws {
if let container = try? decoder.singleValueContainer(),
let stringValue = try? container.decode(String.self) {
switch stringValue {
case CodingKeys.none.rawValue:
self = .none
case CodingKeys.auto.rawValue:
self = .auto
case CodingKeys.required.rawValue:
self = .required
default:
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Unknown tool_choice value: \(stringValue)"
)
}
return
}

let object = try ToolChoice(from: decoder)
switch object.type {
case "function":
guard let function = object.function else {
throw DecodingError.keyNotFound(
ToolChoice.CodingKeys.function,
.init(
codingPath: decoder.codingPath,
debugDescription: "Expected `function` for tool_choice.type == \"function\"."
)
)
}
self = .function(function.name)
case "allowed_tools":
let allowedTools: [AllowedToolNamePayload]
if let tools = object.tools {
allowedTools = tools
} else if let legacyAllowedTools = object.legacyAllowedTools {
allowedTools = legacyAllowedTools
} else {
throw DecodingError.keyNotFound(
ToolChoice.CodingKeys.tools,
.init(
codingPath: decoder.codingPath,
debugDescription: "Expected `tools` (or legacy `allowed_tools`) in allowed_tools tool_choice."
)
)
}
let names = allowedTools.map(\.name)
let parsedMode = object.mode.flatMap(AllowedToolsMode.init(rawValue:))
self = .allowedTools(.init(mode: parsedMode, tools: names))
default:
throw DecodingError.dataCorruptedError(
forKey: ToolChoice.CodingKeys.type,
in: try decoder.container(keyedBy: ToolChoice.CodingKeys.self),
debugDescription: "Unknown tool_choice.type: \(object.type)"
)
}
}

public init(function: String) {
self = .function(function)
}

public init(allowedTools: [String]) {
self = .allowedTools(.init(mode: nil, tools: allowedTools))
}

enum CodingKeys: String, CodingKey {
case none = "none"
case auto = "auto"
case function = "name"
case required = "required"
}

private enum ChatCompletionFunctionCallNameParam: Codable, Equatable {
case type
case function
private struct FunctionNameParam: Codable, Equatable {
let name: String
}

enum CodingKeys: CodingKey {
private enum AllowedToolNamePayload: Codable, Equatable {
case name(String)
case functionTool(AllowedToolParam)

init(from decoder: Decoder) throws {
if let container = try? decoder.singleValueContainer(),
let name = try? container.decode(String.self) {
self = .name(name)
return
}

self = .functionTool(try AllowedToolParam(from: decoder))
}

func encode(to encoder: Encoder) throws {
switch self {
case .name(let name):
var container = encoder.singleValueContainer()
try container.encode(name)
case .functionTool(let tool):
try tool.encode(to: encoder)
}
}

var name: String {
switch self {
case .name(let name):
return name
case .functionTool(let tool):
return tool.function.name
}
}
}

private struct AllowedToolParam: Codable, Equatable {
let type: String
let function: FunctionNameParam

init(type: String = "function", name: String) {
self.type = type
self.function = .init(name: name)
}
}

private struct ToolChoice: Codable, Equatable {
let type: String
let function: FunctionNameParam?
let mode: String?
let tools: [AllowedToolNamePayload]?
let legacyAllowedTools: [AllowedToolNamePayload]?

enum CodingKeys: String, CodingKey {
case type
case function
case mode
case tools
case legacyAllowedTools = "allowed_tools"
}
}
}
Expand Down
133 changes: 133 additions & 0 deletions Sources/OpenAI/Public/Schemas/Generated/Components.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7493,6 +7493,8 @@ public enum Components {
case ToolChoiceTypes(Components.Schemas.ToolChoiceTypes)
/// - Remark: Generated from `#/components/schemas/ResponseProperties/tool_choice/case3`.
case ToolChoiceFunction(Components.Schemas.ToolChoiceFunction)
/// - Remark: Generated from `#/components/schemas/ResponseProperties/tool_choice/case4`.
case ToolChoiceAllowedTools(Components.Schemas.ToolChoiceAllowedTools)
public init(from decoder: any Decoder) throws {
var errors: [any Error] = []
do {
Expand All @@ -7513,6 +7515,12 @@ public enum Components {
} catch {
errors.append(error)
}
do {
self = .ToolChoiceAllowedTools(try .init(from: decoder))
return
} catch {
errors.append(error)
}
throw Swift.DecodingError.failedToDecodeOneOfSchema(
type: Self.self,
codingPath: decoder.codingPath,
Expand All @@ -7527,6 +7535,8 @@ public enum Components {
try value.encode(to: encoder)
case let .ToolChoiceFunction(value):
try value.encode(to: encoder)
case let .ToolChoiceAllowedTools(value):
try value.encode(to: encoder)
}
}
}
Expand Down Expand Up @@ -9931,6 +9941,129 @@ public enum Components {
case _type = "type"
}
}
/// Use this option to let the model pick from a restricted set of tools.
///
/// - Remark: Matches the `allowed_tools` tool choice shape.
public struct ToolChoiceAllowedTools: Codable, Hashable, Sendable {
/// For allowed tools selection, the type is always `allowed_tools`.
@frozen public enum _TypePayload: String, Codable, Hashable, Sendable, CaseIterable {
case allowedTools = "allowed_tools"
}
/// Controls how the model chooses among the allowed tools.
@frozen public enum ModePayload: String, Codable, Hashable, Sendable, CaseIterable {
case auto = "auto"
case required = "required"
}
/// For allowed tools selection, the type is always `allowed_tools`.
public var _type: Components.Schemas.ToolChoiceAllowedTools._TypePayload
/// Controls how the model chooses among the allowed tools.
public var mode: Components.Schemas.ToolChoiceAllowedTools.ModePayload?
/// The list of tools the model is allowed to call.
@frozen public enum AllowedToolPayload: Codable, Hashable, Sendable {
case toolName(Swift.String)
case ToolChoiceTypes(Components.Schemas.ToolChoiceTypes)
case ToolChoiceFunction(Components.Schemas.ToolChoiceFunction)
public init(from decoder: any Decoder) throws {
var errors: [any Error] = []
do {
self = .toolName(try decoder.decodeFromSingleValueContainer())
return
} catch {
errors.append(error)
}
do {
self = .ToolChoiceTypes(try .init(from: decoder))
return
} catch {
errors.append(error)
}
do {
self = .ToolChoiceFunction(try .init(from: decoder))
return
} catch {
errors.append(error)
}
throw Swift.DecodingError.failedToDecodeOneOfSchema(
type: Self.self,
codingPath: decoder.codingPath,
errors: errors
)
}
public func encode(to encoder: any Encoder) throws {
switch self {
case let .toolName(value):
try encoder.encodeToSingleValueContainer(value)
case let .ToolChoiceTypes(value):
try value.encode(to: encoder)
case let .ToolChoiceFunction(value):
try value.encode(to: encoder)
}
}
}
public var allowedTools: [Components.Schemas.ToolChoiceAllowedTools.AllowedToolPayload]
public init(
_type: Components.Schemas.ToolChoiceAllowedTools._TypePayload = .allowedTools,
mode: Components.Schemas.ToolChoiceAllowedTools.ModePayload? = nil,
allowedTools: [Components.Schemas.ToolChoiceAllowedTools.AllowedToolPayload]
) {
self._type = _type
self.mode = mode
self.allowedTools = allowedTools
}
public init(allowedToolNames: [Swift.String]) {
self._type = .allowedTools
self.mode = nil
self.allowedTools = allowedToolNames.map { .toolName($0) }
}
public enum CodingKeys: String, CodingKey {
case _type = "type"
case mode
case tools
case allowedTools = "allowed_tools"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self._type = try container.decode(
Components.Schemas.ToolChoiceAllowedTools._TypePayload.self,
forKey: ._type
)
self.mode = try container.decodeIfPresent(
Components.Schemas.ToolChoiceAllowedTools.ModePayload.self,
forKey: .mode
)
if let tools = try container.decodeIfPresent(
[Components.Schemas.ToolChoiceAllowedTools.AllowedToolPayload].self,
forKey: .tools
) {
self.allowedTools = tools
} else if let allowedTools = try container.decodeIfPresent(
[Components.Schemas.ToolChoiceAllowedTools.AllowedToolPayload].self,
forKey: .allowedTools
) {
self.allowedTools = allowedTools
} else {
throw Swift.DecodingError.keyNotFound(
CodingKeys.tools,
.init(
codingPath: decoder.codingPath,
debugDescription: "Expected `tools` (or legacy `allowed_tools`) in allowed_tools tool_choice."
)
)
}
try decoder.ensureNoAdditionalProperties(knownKeys: [
"type",
"mode",
"tools",
"allowed_tools"
])
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self._type, forKey: ._type)
try container.encodeIfPresent(self.mode, forKey: .mode)
try container.encode(self.allowedTools, forKey: .tools)
}
}
/// Emitted when there is an additional text delta. This is also the first event emitted when the transcription starts. Only emitted when you [create a transcription](/docs/api-reference/audio/create-transcription) with the `Stream` parameter set to `true`.
///
/// - Remark: Generated from `#/components/schemas/TranscriptTextDeltaEvent`.
Expand Down
Loading
Loading