Skip to content

Commit 07b03f9

Browse files
committed
Merge commit '54784005b57c2235a7669e0c12e8dafa68f0ca5f'
* commit '54784005b57c2235a7669e0c12e8dafa68f0ca5f': Log server error responses without `-GoogleGenerativeAIDebugLogEnabled` (google-gemini#177) Send `GenerateContentRequest` in `CountTokensRequest` (google-gemini#175) Add `responseSchema` to `GenerationConfig` (google-gemini#176) Update models in samples and README to `gemini-1.5-flash-latest` (google-gemini#173) Sync UI from VertexAI for Firebase (google-gemini#172) Increment SDK version to `0.5.4` (google-gemini#171) Add default `RequestOptions.timeout` of 300 seconds (google-gemini#170) Delete repo specific Issue Template (google-gemini#169) Increment SDK version to `0.5.3` (google-gemini#167) Make `text` computed property handle mixed-parts responses (google-gemini#165) update doc comments (google-gemini#166)
2 parents db301f5 + 5478400 commit 07b03f9

28 files changed

+448
-122
lines changed

.github/ISSUE_TEMPLATE/bug_report.yml

-23
This file was deleted.

.github/ISSUE_TEMPLATE/feature_request.yml

-23
This file was deleted.

Examples/GenerativeAICLI/Sources/GenerateContent.swift

+3-21
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,7 @@ struct GenerateContent: AsyncParsableCommand {
5555

5656
mutating func run() async throws {
5757
do {
58-
let safetySettings = [SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone)]
59-
// Let the server pick the default config.
60-
let config = GenerationConfig(
61-
temperature: 0.2,
62-
topP: 0.1,
63-
topK: 16,
64-
candidateCount: 1,
65-
maxOutputTokens: isStreaming ? nil : 256,
66-
stopSequences: nil
67-
)
68-
69-
let model = GenerativeModel(
70-
name: modelNameOrDefault(),
71-
apiKey: apiKey,
72-
generationConfig: config,
73-
safetySettings: safetySettings
74-
)
58+
let model = GenerativeModel(name: modelNameOrDefault(), apiKey: apiKey)
7559

7660
var parts = [ModelContent.Part]()
7761

@@ -115,12 +99,10 @@ struct GenerateContent: AsyncParsableCommand {
11599
}
116100

117101
func modelNameOrDefault() -> String {
118-
if let modelName = modelName {
102+
if let modelName {
119103
return modelName
120-
} else if imageURL != nil {
121-
return "gemini-1.0-pro-vision-latest"
122104
} else {
123-
return "gemini-1.0-pro"
105+
return "gemini-1.5-flash-latest"
124106
}
125107
}
126108
}

Examples/GenerativeAISample/ChatSample/Screens/ConversationScreen.swift

+2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ struct ConversationScreen: View {
9494
}
9595

9696
private func sendOrStop() {
97+
focusedField = nil
98+
9799
if viewModel.busy {
98100
viewModel.stop()
99101
} else {

Examples/GenerativeAISample/ChatSample/ViewModels/ConversationViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class ConversationViewModel: ObservableObject {
3636
private var chatTask: Task<Void, Never>?
3737

3838
init() {
39-
model = GenerativeModel(name: "gemini-1.0-pro", apiKey: APIKey.default)
39+
model = GenerativeModel(name: "gemini-1.5-flash-latest", apiKey: APIKey.default)
4040
chat = model.startChat()
4141
}
4242

Examples/GenerativeAISample/FunctionCallingSample/Screens/FunctionCallingScreen.swift

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ struct FunctionCallingScreen: View {
6565
}
6666
}
6767
})
68+
.onTapGesture {
69+
focusedField = nil
70+
}
6871
}
6972
InputField("Message...", text: $userPrompt) {
7073
Image(systemName: viewModel.busy ? "stop.circle.fill" : "arrow.up.circle.fill")

Examples/GenerativeAISample/FunctionCallingSample/ViewModels/FunctionCallingViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class FunctionCallingViewModel: ObservableObject {
3939

4040
init() {
4141
model = GenerativeModel(
42-
name: "gemini-1.0-pro",
42+
name: "gemini-1.5-flash-latest",
4343
apiKey: APIKey.default,
4444
tools: [Tool(functionDeclarations: [
4545
FunctionDeclaration(

Examples/GenerativeAISample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift

+13
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ import SwiftUI
2020
struct PhotoReasoningScreen: View {
2121
@StateObject var viewModel = PhotoReasoningViewModel()
2222

23+
enum FocusedField: Hashable {
24+
case message
25+
}
26+
27+
@FocusState
28+
var focusedField: FocusedField?
29+
2330
var body: some View {
2431
VStack {
2532
MultimodalInputField(text: $viewModel.userInput, selection: $viewModel.selectedItems)
33+
.focused($focusedField, equals: .message)
2634
.onSubmit {
2735
onSendTapped()
2836
}
@@ -47,11 +55,16 @@ struct PhotoReasoningScreen: View {
4755
}
4856
}
4957
.navigationTitle("Multimodal sample")
58+
.onAppear {
59+
focusedField = .message
60+
}
5061
}
5162

5263
// MARK: - Actions
5364

5465
private func onSendTapped() {
66+
focusedField = nil
67+
5568
Task {
5669
await viewModel.reason()
5770
}

Examples/GenerativeAISample/GenerativeAIMultimodalSample/ViewModels/PhotoReasoningViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class PhotoReasoningViewModel: ObservableObject {
4444
private var model: GenerativeModel?
4545

4646
init() {
47-
model = GenerativeModel(name: "gemini-1.0-pro-vision-latest", apiKey: APIKey.default)
47+
model = GenerativeModel(name: "gemini-1.5-flash-latest", apiKey: APIKey.default)
4848
}
4949

5050
func reason() async {

Examples/GenerativeAISample/GenerativeAITextSample/Screens/SummarizeScreen.swift

+15-9
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,23 @@ struct SummarizeScreen: View {
2828

2929
var body: some View {
3030
VStack {
31-
Text("Enter some text, then tap on _Go_ to summarize it.")
32-
HStack(alignment: .top) {
33-
TextField("Enter text summarize", text: $userInput, axis: .vertical)
34-
.textFieldStyle(.roundedBorder)
35-
.onSubmit {
31+
VStack(alignment: .leading) {
32+
Text("Enter some text, then tap on _Go_ to summarize it.")
33+
.padding(.horizontal, 6)
34+
HStack(alignment: .top) {
35+
TextField("Enter text summarize", text: $userInput, axis: .vertical)
36+
.focused($focusedField, equals: .message)
37+
.textFieldStyle(.roundedBorder)
38+
.onSubmit {
39+
onSummarizeTapped()
40+
}
41+
Button("Go") {
3642
onSummarizeTapped()
3743
}
38-
Button("Go") {
39-
onSummarizeTapped()
44+
.padding(.top, 4)
4045
}
41-
.padding(.top, 4)
4246
}
43-
.padding([.horizontal, .bottom])
47+
.padding(.horizontal, 16)
4448

4549
List {
4650
HStack(alignment: .top) {
@@ -61,6 +65,8 @@ struct SummarizeScreen: View {
6165
}
6266

6367
private func onSummarizeTapped() {
68+
focusedField = nil
69+
6470
Task {
6571
await viewModel.summarize(inputText: userInput)
6672
}

Examples/GenerativeAISample/GenerativeAITextSample/ViewModels/SummarizeViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class SummarizeViewModel: ObservableObject {
3232
private var model: GenerativeModel?
3333

3434
init() {
35-
model = GenerativeModel(name: "gemini-1.0-pro", apiKey: APIKey.default)
35+
model = GenerativeModel(name: "gemini-1.5-flash-latest", apiKey: APIKey.default)
3636
}
3737

3838
func summarize(inputText: String) async {

Examples/GenerativeAISample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ public struct InputField<Label>: View where Label: View {
6060
}
6161

6262
Button(action: submit, label: label)
63-
.padding(.top, 4)
63+
.padding(.bottom, 4)
6464
}
6565
}
66-
.padding(.horizontal)
66+
.padding(8)
6767
}
6868
}
6969

Examples/GenerativeAISample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/MultimodalInputField.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public struct MultimodalInputField: View {
6969
Button(action: showChooseAttachmentTypePicker) {
7070
Image(systemName: "plus")
7171
}
72-
.padding(.top, 4)
72+
.padding(.top, 10)
7373

7474
VStack(alignment: .leading) {
7575
TextField(
@@ -110,7 +110,7 @@ public struct MultimodalInputField: View {
110110
Button(action: submit) {
111111
Text("Go")
112112
}
113-
.padding(.top, 4)
113+
.padding(.top, 8)
114114
}
115115
}
116116
.padding(.horizontal)

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ For example, with just a few lines of code, you can access Gemini's multimodal c
1919
generate text from text-and-image input:
2020

2121
```swift
22-
let model = GenerativeModel(name: "gemini-1.5-pro-latest", apiKey: "YOUR_API_KEY")
22+
let model = GenerativeModel(name: "gemini-1.5-flash-latest", apiKey: "YOUR_API_KEY")
2323
let cookieImage = UIImage(...)
2424
let prompt = "Do these look store-bought or homemade?"
2525

Sources/GoogleAI/Chat.swift

+9-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ public class Chat {
3030
/// model. This will be provided to the model for each message sent as context for the discussion.
3131
public var history: [ModelContent]
3232

33-
/// See ``sendMessage(_:)-3ify5``.
33+
/// Sends a message using the existing history of this chat as context. If successful, the message
34+
/// and response will be added to the history. If unsuccessful, history will remain unchanged.
35+
/// - Parameter parts: The new content to send as a single chat message.
36+
/// - Returns: The model's response if no error occurred.
37+
/// - Throws: A ``GenerateContentError`` if an error occurred.
3438
public func sendMessage(_ parts: any ThrowingPartsRepresentable...) async throws
3539
-> GenerateContentResponse {
3640
return try await sendMessage([ModelContent(parts: parts)])
@@ -76,7 +80,10 @@ public class Chat {
7680
return result
7781
}
7882

79-
/// See ``sendMessageStream(_:)-4abs3``.
83+
/// Sends a message using the existing history of this chat as context. If successful, the message
84+
/// and response will be added to the history. If unsuccessful, history will remain unchanged.
85+
/// - Parameter parts: The new content to send as a single chat message.
86+
/// - Returns: A stream containing the model's response or an error if an error occurred.
8087
@available(macOS 12.0, *)
8188
public func sendMessageStream(_ parts: any ThrowingPartsRepresentable...)
8289
-> AsyncThrowingStream<GenerateContentResponse, Error> {

Sources/GoogleAI/CountTokensRequest.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Foundation
1717
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
1818
struct CountTokensRequest {
1919
let model: String
20-
let contents: [ModelContent]
20+
let generateContentRequest: GenerateContentRequest
2121
let options: RequestOptions
2222
}
2323

@@ -45,7 +45,7 @@ public struct CountTokensResponse {
4545
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
4646
extension CountTokensRequest: Encodable {
4747
enum CodingKeys: CodingKey {
48-
case contents
48+
case generateContentRequest
4949
}
5050
}
5151

Sources/GoogleAI/GenerateContentRequest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct GenerateContentRequest {
3131
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
3232
extension GenerateContentRequest: Encodable {
3333
enum CodingKeys: String, CodingKey {
34+
case model
3435
case contents
3536
case generationConfig
3637
case safetySettings

Sources/GoogleAI/GenerateContentResponse.swift

+8-2
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,17 @@ public struct GenerateContentResponse {
4545
Logging.default.error("Could not get text from a response that had no candidates.")
4646
return nil
4747
}
48-
guard let text = candidate.content.parts.first?.text else {
48+
let textValues: [String] = candidate.content.parts.compactMap { part in
49+
guard case let .text(text) = part else {
50+
return nil
51+
}
52+
return text
53+
}
54+
guard textValues.count > 0 else {
4955
Logging.default.error("Could not get a text part from the first candidate.")
5056
return nil
5157
}
52-
return text
58+
return textValues.joined(separator: " ")
5359
}
5460

5561
/// Returns function calls found in any `Part`s of the first candidate of the response, if any.

Sources/GoogleAI/GenerationConfig.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ public struct GenerationConfig {
7070
/// - `application/json`: JSON response in the candidates.
7171
public let responseMIMEType: String?
7272

73+
/// Output response schema of the generated candidate text.
74+
///
75+
/// - Note: This only applies when the specified ``responseMIMEType`` supports a schema; currently
76+
/// this is limited to `application/json`.
77+
public let responseSchema: Schema?
78+
7379
/// Creates a new `GenerationConfig` value.
7480
///
7581
/// - Parameters:
@@ -80,9 +86,11 @@ public struct GenerationConfig {
8086
/// - maxOutputTokens: See ``maxOutputTokens``.
8187
/// - stopSequences: See ``stopSequences``.
8288
/// - responseMIMEType: See ``responseMIMEType``.
89+
/// - responseSchema: See ``responseSchema``.
8390
public init(temperature: Float? = nil, topP: Float? = nil, topK: Int? = nil,
8491
candidateCount: Int? = nil, maxOutputTokens: Int? = nil,
85-
stopSequences: [String]? = nil, responseMIMEType: String? = nil) {
92+
stopSequences: [String]? = nil, responseMIMEType: String? = nil,
93+
responseSchema: Schema? = nil) {
8694
// Explicit init because otherwise if we re-arrange the above variables it changes the API
8795
// surface.
8896
self.temperature = temperature
@@ -92,6 +100,7 @@ public struct GenerationConfig {
92100
self.maxOutputTokens = maxOutputTokens
93101
self.stopSequences = stopSequences
94102
self.responseMIMEType = responseMIMEType
103+
self.responseSchema = responseSchema
95104
}
96105
}
97106

Sources/GoogleAI/GenerativeAIRequest.swift

+5-7
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,18 @@ protocol GenerativeAIRequest: Encodable {
2626
/// Configuration parameters for sending requests to the backend.
2727
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
2828
public struct RequestOptions {
29-
/// The request’s timeout interval in seconds; if not specified uses the default value for a
30-
/// `URLRequest`.
31-
let timeout: TimeInterval?
29+
/// The request’s timeout interval in seconds.
30+
let timeout: TimeInterval
3231

3332
/// The API version to use in requests to the backend.
3433
let apiVersion: String
3534

3635
/// Initializes a request options object.
3736
///
3837
/// - Parameters:
39-
/// - timeout The request’s timeout interval in seconds; if not specified uses the default value
40-
/// for a `URLRequest`.
41-
/// - apiVersion The API version to use in requests to the backend; defaults to "v1beta".
42-
public init(timeout: TimeInterval? = nil, apiVersion: String = "v1beta") {
38+
/// - timeout: The request’s timeout interval in seconds; defaults to 300 seconds (5 minutes).
39+
/// - apiVersion: The API version to use in requests to the backend; defaults to "v1beta".
40+
public init(timeout: TimeInterval = 300.0, apiVersion: String = "v1beta") {
4341
self.timeout = timeout
4442
self.apiVersion = apiVersion
4543
}

0 commit comments

Comments
 (0)