Skip to content

Commit 65b70a5

Browse files
Merge pull request #11 from Compiler-Inc/fix/chunk-issues
Add tool call support
2 parents b38dc68 + a723697 commit 65b70a5

File tree

4 files changed

+213
-7
lines changed

4 files changed

+213
-7
lines changed

Sources/CompilerSwiftAI/Model Calling/Domain Models/ChatCompletionChunk.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,31 @@ public struct ChatDelta: Decodable, Sendable {
5656
public let role: ChatRole?
5757
/// The content added in this chunk (if any).
5858
public let content: String?
59+
/// The tool calls delta in this chunk (if any).
60+
public let toolCalls: [ToolCallDelta]?
61+
62+
enum CodingKeys: String, CodingKey {
63+
case role, content
64+
case toolCalls = "tool_calls"
65+
}
66+
}
67+
68+
/// Represents a delta for a tool call in a streamed response
69+
public struct ToolCallDelta: Decodable, Sendable {
70+
/// The index of this tool call in the array
71+
public let index: Int
72+
/// The ID of the tool call (only present in first delta)
73+
public let id: String?
74+
/// The type of the tool call (only present in first delta)
75+
public let type: String?
76+
/// The function call delta
77+
public let function: FunctionCallDelta?
78+
}
79+
80+
/// Represents a delta for a function call in a streamed response
81+
public struct FunctionCallDelta: Decodable, Sendable {
82+
/// The name of the function (only present in first delta)
83+
public let name: String?
84+
/// The arguments being built up
85+
public let arguments: String?
5986
}

Sources/CompilerSwiftAI/Model Calling/Domain Models/ChatCompletionResponse.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,42 @@ public struct ChatMessage: Decodable, Sendable {
5353
/// The role of the message sender (for example, "assistant", "user", or "system").
5454
public let role: ChatRole
5555
/// The content of the message.
56-
public let content: String
56+
public let content: String?
5757
/// An optional refusal message (if applicable).
5858
public let refusal: String?
59+
/// The tool calls made by the model in this message.
60+
public let toolCalls: [ToolCall]?
61+
62+
enum CodingKeys: String, CodingKey {
63+
case role, content, refusal
64+
case toolCalls = "tool_calls"
65+
}
5966
}
6067

6168
/// The role of a chat message.
6269
public enum ChatRole: String, Decodable, Sendable {
6370
case system
6471
case user
6572
case assistant
73+
case tool
74+
}
75+
76+
/// Represents a tool call made by the model
77+
public struct ToolCall: Decodable, Sendable {
78+
/// A unique identifier for the tool call
79+
public let id: String
80+
/// The type of tool call (currently only "function" is supported)
81+
public let type: String
82+
/// The function that was called
83+
public let function: FunctionCall
84+
}
85+
86+
/// Represents a function call made by the model
87+
public struct FunctionCall: Decodable, Sendable {
88+
/// The name of the function to call
89+
public let name: String
90+
/// The arguments to pass to the function, encoded as a JSON string
91+
public let arguments: String
6692
}
6793

6894
// MARK: - Usage and Token Details

Sources/CompilerSwiftAI/Model Calling/Domain Models/CompletionRequest.swift

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ public struct CompletionRequest {
5353

5454
/// A unique identifier representing your end user.
5555
public let user: String?
56-
56+
57+
/// Tools (including functions) that the model may call.
58+
public let tools: [Tool]?
59+
60+
/// Controls which (if any) function is called by the model.
61+
public let toolChoice: ToolChoice?
62+
5763
// MARK: - Initialization
5864

5965
public init(
@@ -71,7 +77,9 @@ public struct CompletionRequest {
7177
stop: StopSequence? = nil,
7278
temperature: Double? = nil,
7379
topP: Double? = nil,
74-
user: String? = nil
80+
user: String? = nil,
81+
tools: [Tool]? = nil,
82+
toolChoice: ToolChoice? = nil
7583
) {
7684
self.messages = messages
7785
self.model = model
@@ -88,6 +96,8 @@ public struct CompletionRequest {
8896
self.temperature = temperature
8997
self.topP = topP
9098
self.user = user
99+
self.tools = tools
100+
self.toolChoice = toolChoice
91101
}
92102

93103
// MARK: - DTO Conversion
@@ -109,7 +119,9 @@ public struct CompletionRequest {
109119
stream: stream,
110120
temperature: temperature,
111121
topP: topP,
112-
user: user
122+
user: user,
123+
tools: tools?.map { $0.toDTO() },
124+
toolChoice: toolChoice?.toDTO()
113125
)
114126
}
115127
}
@@ -133,4 +145,59 @@ public extension CompletionRequest {
133145
}
134146
}
135147
}
148+
149+
/// A tool that can be used by the model
150+
struct Tool {
151+
/// The type of the tool. Currently, only function is supported.
152+
let type: String
153+
let function: Function
154+
155+
init(function: Function) {
156+
self.type = "function"
157+
self.function = function
158+
}
159+
160+
func toDTO() -> CompletionRequestDTO.Tool {
161+
CompletionRequestDTO.Tool(function: function.toDTO())
162+
}
163+
}
164+
165+
/// A function that can be called by the model
166+
struct Function {
167+
/// The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
168+
let name: String
169+
/// A description of what the function does, used by the model to choose when and how to call the function.
170+
let description: String?
171+
/// The parameters the functions accepts, described as a JSON Schema object.
172+
let parameters: [String: Any]?
173+
/// Whether to enable strict schema adherence when generating the function call.
174+
let strict: Bool?
175+
176+
func toDTO() -> CompletionRequestDTO.Function {
177+
CompletionRequestDTO.Function(
178+
name: name,
179+
description: description,
180+
parameters: parameters,
181+
strict: strict
182+
)
183+
}
184+
}
185+
186+
/// Controls which (if any) function is called by the model.
187+
enum ToolChoice {
188+
case none
189+
case auto
190+
case function(name: String)
191+
192+
func toDTO() -> CompletionRequestDTO.ToolChoice {
193+
switch self {
194+
case .none:
195+
return .none
196+
case .auto:
197+
return .auto
198+
case .function(let name):
199+
return .function(name: name)
200+
}
201+
}
202+
}
136203
}

Sources/CompilerSwiftAI/Network Models/CompletionRequestDTO.swift

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ struct CompletionRequestDTO: Encodable {
6666
/// A unique identifier representing your end user.
6767
let user: String?
6868

69+
/// Tools (including functions) that the model may call.
70+
let tools: [Tool]?
71+
72+
/// Controls which (if any) function is called by the model.
73+
/// Can be "none", "auto", or a FunctionCall object.
74+
let toolChoice: ToolChoice?
75+
6976
// MARK: - Coding Keys
7077

7178
enum CodingKeys: String, CodingKey {
@@ -80,6 +87,8 @@ struct CompletionRequestDTO: Encodable {
8087
case temperature
8188
case topP = "top_p"
8289
case user
90+
case tools
91+
case toolChoice = "tool_choice"
8392
}
8493

8594
/// Initialize a new completion request with the given parameters.
@@ -99,7 +108,9 @@ struct CompletionRequestDTO: Encodable {
99108
stream: Bool? = false,
100109
temperature: Double? = 1,
101110
topP: Double? = 1,
102-
user: String? = nil
111+
user: String? = nil,
112+
tools: [Tool]? = nil,
113+
toolChoice: ToolChoice? = nil
103114
) {
104115
self.messages = messages
105116
self.model = model
@@ -117,6 +128,8 @@ struct CompletionRequestDTO: Encodable {
117128
self.temperature = temperature
118129
self.topP = topP
119130
self.user = user
131+
self.tools = tools
132+
self.toolChoice = toolChoice
120133
}
121134

122135
func copy(
@@ -135,7 +148,9 @@ struct CompletionRequestDTO: Encodable {
135148
stream: Bool? = nil,
136149
temperature: Double? = nil,
137150
topP: Double? = nil,
138-
user: String? = nil
151+
user: String? = nil,
152+
tools: [Tool]? = nil,
153+
toolChoice: ToolChoice? = nil
139154
) -> CompletionRequestDTO {
140155
CompletionRequestDTO(
141156
messages: messages ?? self.messages,
@@ -153,7 +168,9 @@ struct CompletionRequestDTO: Encodable {
153168
stream: stream ?? self.stream,
154169
temperature: temperature ?? self.temperature,
155170
topP: topP ?? self.topP,
156-
user: user ?? self.user
171+
user: user ?? self.user,
172+
tools: tools ?? self.tools,
173+
toolChoice: toolChoice ?? self.toolChoice
157174
)
158175
}
159176
}
@@ -176,3 +193,72 @@ extension CompletionRequestDTO {
176193
}
177194
}
178195
}
196+
197+
extension CompletionRequestDTO {
198+
/// A tool that can be used by the model.
199+
struct Tool: Encodable {
200+
/// The type of the tool. Currently, only function is supported.
201+
let type: String
202+
let function: Function
203+
204+
init(function: Function) {
205+
self.type = "function"
206+
self.function = function
207+
}
208+
}
209+
210+
/// A function that can be called by the model.
211+
struct Function: Encodable {
212+
/// The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
213+
let name: String
214+
/// A description of what the function does, used by the model to choose when and how to call the function.
215+
let description: String?
216+
/// The parameters the functions accepts, described as a JSON Schema object.
217+
/// Omitting parameters defines a function with an empty parameter list.
218+
let parameters: [String: Any]?
219+
/// Whether to enable strict schema adherence when generating the function call.
220+
/// If set to true, the model will follow the exact schema defined in the parameters field.
221+
let strict: Bool?
222+
223+
enum CodingKeys: String, CodingKey {
224+
case name, description, parameters, strict
225+
}
226+
227+
func encode(to encoder: Encoder) throws {
228+
var container = encoder.container(keyedBy: CodingKeys.self)
229+
try container.encode(name, forKey: .name)
230+
try container.encodeIfPresent(description, forKey: .description)
231+
232+
// Encode parameters as raw JSON if provided
233+
if let params = parameters {
234+
let jsonData = try JSONSerialization.data(withJSONObject: params)
235+
let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}"
236+
try container.encode(jsonString, forKey: .parameters)
237+
}
238+
239+
try container.encodeIfPresent(strict, forKey: .strict)
240+
}
241+
}
242+
243+
/// Controls which (if any) function is called by the model.
244+
enum ToolChoice: Encodable {
245+
case none
246+
case auto
247+
case function(name: String)
248+
249+
func encode(to encoder: Encoder) throws {
250+
var container = encoder.singleValueContainer()
251+
switch self {
252+
case .none:
253+
try container.encode("none")
254+
case .auto:
255+
try container.encode("auto")
256+
case .function(let name):
257+
let dict: [String: Any] = ["type": "function", "function": ["name": name]]
258+
let jsonData = try JSONSerialization.data(withJSONObject: dict)
259+
let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}"
260+
try container.encode(jsonString)
261+
}
262+
}
263+
}
264+
}

0 commit comments

Comments
 (0)