Skip to content

Commit c88763e

Browse files
authored
[Vertex AI] Add URI-based file data support (#12886)
1 parent d675a68 commit c88763e

File tree

5 files changed

+75
-2
lines changed

5 files changed

+75
-2
lines changed

FirebaseVertexAI/Sample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ class FunctionCallingViewModel: ObservableObject {
156156
case let .functionCall(functionCall):
157157
messages.insert(functionCall.chatMessage(), at: messages.count - 1)
158158
functionCalls.append(functionCall)
159-
case .data, .functionResponse:
159+
case .data, .fileData, .functionResponse:
160160
fatalError("Unsupported response content.")
161161
}
162162
}

FirebaseVertexAI/Sources/Chat.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public class Chat {
153153
case let .text(str):
154154
combinedText += str
155155

156-
case .data, .functionCall, .functionResponse:
156+
case .data, .fileData, .functionCall, .functionResponse:
157157
// Don't combine it, just add to the content. If there's any text pending, add that as
158158
// a part.
159159
if !combinedText.isEmpty {

FirebaseVertexAI/Sources/ModelContent.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ public struct ModelContent: Equatable {
2828
/// Data with a specified media type. Not all media types may be supported by the AI model.
2929
case data(mimetype: String, Data)
3030

31+
/// URI-based data with a specified media type.
32+
///
33+
/// > Note: Supported media types depends on the model; see [supported file formats
34+
/// > ](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/send-multimodal-prompts#media_requirements)
35+
/// > for details.
36+
case fileData(mimetype: String, uri: String)
37+
3138
/// A predicted function call returned from the model.
3239
case functionCall(FunctionCall)
3340

@@ -109,6 +116,7 @@ extension ModelContent.Part: Codable {
109116
enum CodingKeys: String, CodingKey {
110117
case text
111118
case inlineData
119+
case fileData
112120
case functionCall
113121
case functionResponse
114122
}
@@ -118,6 +126,11 @@ extension ModelContent.Part: Codable {
118126
case bytes = "data"
119127
}
120128

129+
enum FileDataKeys: String, CodingKey {
130+
case mimeType = "mime_type"
131+
case uri = "file_uri"
132+
}
133+
121134
public func encode(to encoder: Encoder) throws {
122135
var container = encoder.container(keyedBy: CodingKeys.self)
123136
switch self {
@@ -130,6 +143,13 @@ extension ModelContent.Part: Codable {
130143
)
131144
try inlineDataContainer.encode(mimetype, forKey: .mimeType)
132145
try inlineDataContainer.encode(bytes, forKey: .bytes)
146+
case let .fileData(mimetype: mimetype, url):
147+
var fileDataContainer = container.nestedContainer(
148+
keyedBy: FileDataKeys.self,
149+
forKey: .fileData
150+
)
151+
try fileDataContainer.encode(mimetype, forKey: .mimeType)
152+
try fileDataContainer.encode(url, forKey: .uri)
133153
case let .functionCall(functionCall):
134154
try container.encode(functionCall, forKey: .functionCall)
135155
case let .functionResponse(functionResponse):
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
import XCTest
17+
18+
@testable import FirebaseVertexAI
19+
20+
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
21+
final class ModelContentTests: XCTestCase {
22+
let encoder = JSONEncoder()
23+
24+
override func setUp() {
25+
encoder.outputFormatting = .init(
26+
arrayLiteral: .prettyPrinted, .sortedKeys, .withoutEscapingSlashes
27+
)
28+
}
29+
30+
// MARK: ModelContent.Part Encoding
31+
32+
func testEncodeFileDataPart() throws {
33+
let mimeType = "image/jpeg"
34+
let fileURI = "gs://test-bucket/image.jpg"
35+
let fileDataPart = ModelContent.Part.fileData(mimetype: mimeType, uri: fileURI)
36+
37+
let jsonData = try encoder.encode(fileDataPart)
38+
39+
let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
40+
XCTAssertEqual(json, """
41+
{
42+
"fileData" : {
43+
"file_uri" : "\(fileURI)",
44+
"mime_type" : "\(mimeType)"
45+
}
46+
}
47+
""")
48+
}
49+
}

FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ final class VertexAIAPITests: XCTestCase {
9090
let _ = try await genAI.generateContent(str)
9191
let _ = try await genAI.generateContent([str])
9292
let _ = try await genAI.generateContent(str, "abc", "def")
93+
let _ = try await genAI.generateContent(
94+
str,
95+
ModelContent.Part.fileData(mimetype: "image/jpeg", uri: "gs://test-bucket/image.jpg")
96+
)
9397
#if canImport(UIKit)
9498
_ = try await genAI.generateContent(UIImage())
9599
_ = try await genAI.generateContent([UIImage()])

0 commit comments

Comments
 (0)