Skip to content

Commit 34e210b

Browse files
committed
Remove nesting of FunctionCall and FunctionResponse in parts
1 parent ba223a9 commit 34e210b

File tree

10 files changed

+133
-141
lines changed

10 files changed

+133
-141
lines changed

FirebaseVertexAI/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
- [changed] **Breaking Change**: The `CountTokensError` enum has been removed;
2929
errors occurring in `GenerativeModel.countTokens(...)` are now thrown directly
3030
instead of being wrapped in a `CountTokensError.internalError`. (#13736)
31+
- [changed] **Breaking Change**: The enum `ModelContent.Part` has been replaced
32+
with a protocol named `Part` to avoid future breaking changes with new part
33+
types. The new types `TextPart` and `FunctionCallPart` may be received when
34+
generating content the types `TextPart`; additionally the types
35+
`InlineDataPart`, `FileDataPart` and `FunctionResponsePart` may be provided
36+
as input. (#13767)
3137
- [changed] The default request timeout is now 180 seconds instead of the
3238
platform-default value of 60 seconds for a `URLRequest`; this timeout may
3339
still be customized in `RequestOptions`. (#13722)

FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class FunctionCallingViewModel: ObservableObject {
3030
}
3131

3232
/// Function calls pending processing
33-
private var functionCalls = [FunctionCall]()
33+
private var functionCalls = [FunctionCallPart]()
3434

3535
private var model: GenerativeModel
3636
private var chat: Chat
@@ -149,22 +149,21 @@ class FunctionCallingViewModel: ObservableObject {
149149
messages[messages.count - 1].message += textPart.text
150150
messages[messages.count - 1].pending = false
151151
case let functionCallPart as FunctionCallPart:
152-
let functionCall = functionCallPart.functionCall
153-
messages.insert(functionCall.chatMessage(), at: messages.count - 1)
154-
functionCalls.append(functionCall)
152+
messages.insert(functionCallPart.chatMessage(), at: messages.count - 1)
153+
functionCalls.append(functionCallPart)
155154
default:
156155
fatalError("Unsupported response content.")
157156
}
158157
}
159158
}
160159

161-
func processFunctionCalls() async throws -> [FunctionResponse] {
162-
var functionResponses = [FunctionResponse]()
160+
func processFunctionCalls() async throws -> [FunctionResponsePart] {
161+
var functionResponses = [FunctionResponsePart]()
163162
for functionCall in functionCalls {
164163
switch functionCall.name {
165164
case "get_exchange_rate":
166165
let exchangeRates = getExchangeRate(args: functionCall.args)
167-
functionResponses.append(FunctionResponse(
166+
functionResponses.append(FunctionResponsePart(
168167
name: "get_exchange_rate",
169168
response: exchangeRates
170169
))
@@ -209,7 +208,7 @@ class FunctionCallingViewModel: ObservableObject {
209208
}
210209
}
211210

212-
private extension FunctionCall {
211+
private extension FunctionCallPart {
213212
func chatMessage() -> ChatMessage {
214213
let encoder = JSONEncoder()
215214
encoder.outputFormatting = .prettyPrinted
@@ -229,7 +228,7 @@ private extension FunctionCall {
229228
}
230229
}
231230

232-
private extension FunctionResponse {
231+
private extension FunctionResponsePart {
233232
func chatMessage() -> ChatMessage {
234233
let encoder = JSONEncoder()
235234
encoder.outputFormatting = .prettyPrinted
@@ -249,12 +248,8 @@ private extension FunctionResponse {
249248
}
250249
}
251250

252-
private extension [FunctionResponse] {
251+
private extension [FunctionResponsePart] {
253252
func modelContent() -> [ModelContent] {
254-
return self.map { ModelContent(
255-
role: "function",
256-
parts: [FunctionResponsePart(functionResponse: $0)]
257-
)
258-
}
253+
return self.map { ModelContent(role: "function", parts: [$0]) }
259254
}
260255
}

FirebaseVertexAI/Sources/FunctionCalling.swift

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,6 @@
1414

1515
import Foundation
1616

17-
/// A predicted function call returned from the model.
18-
public struct FunctionCall: Equatable, Sendable {
19-
/// The name of the function to call.
20-
public let name: String
21-
22-
/// The function parameters and values.
23-
public let args: JSONObject
24-
25-
/// Constructs a new function call.
26-
///
27-
/// > Note: A `FunctionCall` is typically received from the model, rather than created manually.
28-
///
29-
/// - Parameters:
30-
/// - name: The name of the function to call.
31-
/// - args: The function parameters and values.
32-
public init(name: String, args: JSONObject) {
33-
self.name = name
34-
self.args = args
35-
}
36-
}
37-
3817
/// Structured representation of a function declaration.
3918
///
4019
/// This `FunctionDeclaration` is a representation of a block of code that can be used as a ``Tool``
@@ -136,50 +115,8 @@ public struct ToolConfig {
136115
}
137116
}
138117

139-
/// Result output from a ``FunctionCall``.
140-
///
141-
/// Contains a string representing the `FunctionDeclaration.name` and a structured JSON object
142-
/// containing any output from the function is used as context to the model. This should contain the
143-
/// result of a ``FunctionCall`` made based on model prediction.
144-
public struct FunctionResponse: Equatable, Sendable {
145-
/// The name of the function that was called.
146-
let name: String
147-
148-
/// The function's response.
149-
let response: JSONObject
150-
151-
/// Constructs a new `FunctionResponse`.
152-
///
153-
/// - Parameters:
154-
/// - name: The name of the function that was called.
155-
/// - response: The function's response.
156-
public init(name: String, response: JSONObject) {
157-
self.name = name
158-
self.response = response
159-
}
160-
}
161-
162118
// MARK: - Codable Conformance
163119

164-
extension FunctionCall: Decodable {
165-
enum CodingKeys: CodingKey {
166-
case name
167-
case args
168-
}
169-
170-
public init(from decoder: Decoder) throws {
171-
let container = try decoder.container(keyedBy: CodingKeys.self)
172-
name = try container.decode(String.self, forKey: .name)
173-
if let args = try container.decodeIfPresent(JSONObject.self, forKey: .args) {
174-
self.args = args
175-
} else {
176-
args = JSONObject()
177-
}
178-
}
179-
}
180-
181-
extension FunctionCall: Encodable {}
182-
183120
extension FunctionDeclaration: Encodable {
184121
enum CodingKeys: String, CodingKey {
185122
case name
@@ -202,5 +139,3 @@ extension FunctionCallingConfig: Encodable {}
202139
extension FunctionCallingConfig.Mode: Encodable {}
203140

204141
extension ToolConfig: Encodable {}
205-
206-
extension FunctionResponse: Codable {}

FirebaseVertexAI/Sources/GenerateContentResponse.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ public struct GenerateContentResponse: Sendable {
6767
}
6868

6969
/// Returns function calls found in any `Part`s of the first candidate of the response, if any.
70-
public var functionCalls: [FunctionCall] {
70+
public var functionCalls: [FunctionCallPart] {
7171
guard let candidate = candidates.first else {
7272
return []
7373
}
7474
return candidate.content.parts.compactMap { part in
7575
switch part {
7676
case let functionCallPart as FunctionCallPart:
77-
return functionCallPart.functionCall
77+
return functionCallPart
7878
default:
7979
return nil
8080
}

FirebaseVertexAI/Sources/ModelContent.swift

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -36,47 +36,12 @@ extension [ModelContent] {
3636
/// may comprise multiple heterogeneous ``ModelContent/Part``s.
3737
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
3838
public struct ModelContent: Equatable, Sendable {
39-
/// A discrete piece of data in a media format interpretable by an AI model. Within a single value
40-
/// of ``Part``, different data types may not mix.
4139
enum InternalPart: Equatable, Sendable {
42-
/// Text value.
4340
case text(String)
44-
45-
/// Data with a specified media type. Not all media types may be supported by the AI model.
4641
case inlineData(mimetype: String, Data)
47-
48-
/// File data stored in Cloud Storage for Firebase, referenced by URI.
49-
///
50-
/// > Note: Supported media types depends on the model; see [media requirements
51-
/// > ](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts#media_requirements)
52-
/// > for details.
53-
///
54-
/// - Parameters:
55-
/// - mimetype: The IANA standard MIME type of the uploaded file, for example, `"image/jpeg"`
56-
/// or `"video/mp4"`; see [media requirements
57-
/// ](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts#media_requirements)
58-
/// for supported values.
59-
/// - uri: The `"gs://"`-prefixed URI of the file in Cloud Storage for Firebase, for example,
60-
/// `"gs://bucket-name/path/image.jpg"`.
6142
case fileData(mimetype: String, uri: String)
62-
63-
/// A predicted function call returned from the model.
6443
case functionCall(FunctionCall)
65-
66-
/// A response to a function call.
6744
case functionResponse(FunctionResponse)
68-
69-
// MARK: Convenience Initializers
70-
71-
/// Convenience function for populating a Part with JPEG data.
72-
public static func jpeg(_ data: Data) -> Self {
73-
return .inlineData(mimetype: "image/jpeg", data)
74-
}
75-
76-
/// Convenience function for populating a Part with PNG data.
77-
public static func png(_ data: Data) -> Self {
78-
return .inlineData(mimetype: "image/png", data)
79-
}
8045
}
8146

8247
/// The role of the entity creating the ``ModelContent``. For user-generated client requests,
@@ -97,7 +62,7 @@ public struct ModelContent: Equatable, Sendable {
9762
case let .functionCall(functionCall):
9863
convertedParts.append(FunctionCallPart(functionCall))
9964
case let .functionResponse(functionResponse):
100-
convertedParts.append(FunctionResponsePart(functionResponse: functionResponse))
65+
convertedParts.append(FunctionResponsePart(functionResponse))
10166
}
10267
}
10368
return convertedParts

FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import Foundation
1616

1717
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
1818
struct InlineData: Codable, Equatable, Sendable {
19-
public let mimeType: String
20-
public let data: Data
19+
let mimeType: String
20+
let data: Data
2121

22-
public init(data: Data, mimeType: String) {
22+
init(data: Data, mimeType: String) {
2323
self.data = data
2424
self.mimeType = mimeType
2525
}
@@ -36,6 +36,28 @@ struct FileData: Codable, Equatable, Sendable {
3636
}
3737
}
3838

39+
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
40+
struct FunctionCall: Equatable, Sendable {
41+
let name: String
42+
let args: JSONObject
43+
44+
init(name: String, args: JSONObject) {
45+
self.name = name
46+
self.args = args
47+
}
48+
}
49+
50+
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
51+
struct FunctionResponse: Codable, Equatable, Sendable {
52+
let name: String
53+
let response: JSONObject
54+
55+
init(name: String, response: JSONObject) {
56+
self.name = name
57+
self.response = response
58+
}
59+
}
60+
3961
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
4062
struct ErrorPart: Part, Error {
4163
let error: Error
@@ -45,10 +67,18 @@ struct ErrorPart: Part, Error {
4567
}
4668
}
4769

70+
// MARK: - Codable Conformances
71+
4872
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
49-
extension ErrorPart: Equatable {
50-
static func == (lhs: ErrorPart, rhs: ErrorPart) -> Bool {
51-
fatalError("Comparing ErrorParts for equality is not supported.")
73+
extension FunctionCall: Codable {
74+
init(from decoder: Decoder) throws {
75+
let container = try decoder.container(keyedBy: CodingKeys.self)
76+
name = try container.decode(String.self, forKey: .name)
77+
if let args = try container.decodeIfPresent(JSONObject.self, forKey: .args) {
78+
self.args = args
79+
} else {
80+
args = JSONObject()
81+
}
5282
}
5383
}
5484

@@ -62,3 +92,12 @@ extension ErrorPart: Codable {
6292
fatalError("Encoding an ErrorPart is not supported.")
6393
}
6494
}
95+
96+
// MARK: - Equatable Conformances
97+
98+
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
99+
extension ErrorPart: Equatable {
100+
static func == (lhs: ErrorPart, rhs: ErrorPart) -> Bool {
101+
fatalError("Comparing ErrorParts for equality is not supported.")
102+
}
103+
}

0 commit comments

Comments
 (0)