-
Notifications
You must be signed in to change notification settings - Fork 169
/
Copy pathGenerateContentResponse.swift
382 lines (326 loc) · 12.6 KB
/
GenerateContentResponse.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
/// The model's response to a generate content request.
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
public struct GenerateContentResponse {
/// Token usage metadata for processing the generate content request.
public struct UsageMetadata {
/// The number of tokens in the request prompt.
public let promptTokenCount: Int
/// The total number of tokens across the generated response candidates.
public let candidatesTokenCount: Int
/// The total number of tokens in both the request and response.
public let totalTokenCount: Int
}
/// A list of candidate response content, ordered from best to worst.
public let candidates: [CandidateResponse]
/// A value containing the safety ratings for the response, or, if the request was blocked, a
/// reason for blocking the request.
public let promptFeedback: PromptFeedback?
/// Token usage metadata for processing the generate content request.
public let usageMetadata: UsageMetadata?
/// The response's content as text, if it exists.
public var text: String? {
guard let candidate = candidates.first else {
Logging.default.error("Could not get text from a response that had no candidates.")
return nil
}
let textValues: [String] = candidate.content.parts.compactMap { part in
switch part {
case let .text(text):
return text
case let .executableCode(executableCode):
let codeBlockLanguage: String
if executableCode.language == "LANGUAGE_UNSPECIFIED" {
codeBlockLanguage = ""
} else {
codeBlockLanguage = executableCode.language.lowercased()
}
return "```\(codeBlockLanguage)\n\(executableCode.code)\n```"
case let .codeExecutionResult(codeExecutionResult):
if codeExecutionResult.output.isEmpty {
return nil
}
return "```\n\(codeExecutionResult.output)\n```"
case .data, .fileData, .functionCall, .functionResponse:
return nil
}
}
guard textValues.count > 0 else {
Logging.default.error("Could not get a text part from the first candidate.")
return nil
}
return textValues.joined(separator: "\n")
}
/// Returns function calls found in any `Part`s of the first candidate of the response, if any.
public var functionCalls: [FunctionCall] {
guard let candidate = candidates.first else {
return []
}
return candidate.content.parts.compactMap { part in
guard case let .functionCall(functionCall) = part else {
return nil
}
return functionCall
}
}
/// Initializer for SwiftUI previews or tests.
public init(candidates: [CandidateResponse], promptFeedback: PromptFeedback? = nil,
usageMetadata: UsageMetadata? = nil) {
self.candidates = candidates
self.promptFeedback = promptFeedback
self.usageMetadata = usageMetadata
}
}
/// A struct representing a possible reply to a content generation prompt. Each content generation
/// prompt may produce multiple candidate responses.
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
public struct CandidateResponse {
/// The response's content.
public let content: ModelContent
/// The safety rating of the response content.
public let safetyRatings: [SafetyRating]
/// The reason the model stopped generating content, if it exists; for example, if the model
/// generated a predefined stop sequence.
public let finishReason: FinishReason?
/// Cited works in the model's response content, if it exists.
public let citationMetadata: CitationMetadata?
/// Initializer for SwiftUI previews or tests.
public init(content: ModelContent, safetyRatings: [SafetyRating], finishReason: FinishReason?,
citationMetadata: CitationMetadata?) {
self.content = content
self.safetyRatings = safetyRatings
self.finishReason = finishReason
self.citationMetadata = citationMetadata
}
}
/// A collection of source attributions for a piece of content.
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
public struct CitationMetadata {
/// A list of individual cited sources and the parts of the content to which they apply.
public let citationSources: [Citation]
}
/// A struct describing a source attribution.
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
public struct Citation {
/// The inclusive beginning of a sequence in a model response that derives from a cited source.
public let startIndex: Int
/// The exclusive end of a sequence in a model response that derives from a cited source.
public let endIndex: Int
/// A link to the cited source.
public let uri: String
/// The license the cited source work is distributed under, if specified.
public let license: String?
}
/// A value enumerating possible reasons for a model to terminate a content generation request.
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
public enum FinishReason: String {
case unknown = "FINISH_REASON_UNKNOWN"
case unspecified = "FINISH_REASON_UNSPECIFIED"
/// Natural stop point of the model or provided stop sequence.
case stop = "STOP"
/// The maximum number of tokens as specified in the request was reached.
case maxTokens = "MAX_TOKENS"
/// The token generation was stopped because the response was flagged for safety reasons.
/// NOTE: When streaming, the Candidate.content will be empty if content filters blocked the
/// output.
case safety = "SAFETY"
/// The token generation was stopped because the response was flagged for unauthorized citations.
case recitation = "RECITATION"
/// All other reasons that stopped token generation.
case other = "OTHER"
}
/// A metadata struct containing any feedback the model had on the prompt it was provided.
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
public struct PromptFeedback {
/// A type describing possible reasons to block a prompt.
public enum BlockReason: String {
/// The block reason is unknown.
case unknown = "UNKNOWN"
/// The block reason was not specified in the server response.
case unspecified = "BLOCK_REASON_UNSPECIFIED"
/// The prompt was blocked because it was deemed unsafe.
case safety = "SAFETY"
/// All other block reasons.
case other = "OTHER"
}
/// The reason a prompt was blocked, if it was blocked.
public let blockReason: BlockReason?
/// The safety ratings of the prompt.
public let safetyRatings: [SafetyRating]
/// Initializer for SwiftUI previews or tests.
public init(blockReason: BlockReason?, safetyRatings: [SafetyRating]) {
self.blockReason = blockReason
self.safetyRatings = safetyRatings
}
}
// MARK: - Codable Conformances
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
extension GenerateContentResponse: Decodable {
enum CodingKeys: CodingKey {
case candidates
case promptFeedback
case usageMetadata
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard container.contains(CodingKeys.candidates) || container
.contains(CodingKeys.promptFeedback) else {
let context = DecodingError.Context(
codingPath: [],
debugDescription: "Failed to decode GenerateContentResponse;" +
" missing keys 'candidates' and 'promptFeedback'."
)
throw DecodingError.dataCorrupted(context)
}
if let candidates = try container.decodeIfPresent(
[CandidateResponse].self,
forKey: .candidates
) {
self.candidates = candidates
} else {
candidates = []
}
promptFeedback = try container.decodeIfPresent(PromptFeedback.self, forKey: .promptFeedback)
usageMetadata = try container.decodeIfPresent(UsageMetadata.self, forKey: .usageMetadata)
}
}
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
extension GenerateContentResponse.UsageMetadata: Decodable {
enum CodingKeys: CodingKey {
case promptTokenCount
case candidatesTokenCount
case totalTokenCount
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
promptTokenCount = try container.decodeIfPresent(Int.self, forKey: .promptTokenCount) ?? 0
candidatesTokenCount = try container
.decodeIfPresent(Int.self, forKey: .candidatesTokenCount) ?? 0
totalTokenCount = try container.decodeIfPresent(Int.self, forKey: .totalTokenCount) ?? 0
}
}
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
extension CandidateResponse: Decodable {
enum CodingKeys: CodingKey {
case content
case safetyRatings
case finishReason
case finishMessage
case citationMetadata
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
if let content = try container.decodeIfPresent(ModelContent.self, forKey: .content) {
self.content = content
} else {
content = ModelContent(parts: [])
}
} catch {
// Check if `content` can be decoded as an empty dictionary to detect the `"content": {}` bug.
if let content = try? container.decode([String: String].self, forKey: .content),
content.isEmpty {
throw InvalidCandidateError.emptyContent(underlyingError: error)
} else {
throw InvalidCandidateError.malformedContent(underlyingError: error)
}
}
if let safetyRatings = try container.decodeIfPresent(
[SafetyRating].self,
forKey: .safetyRatings
) {
self.safetyRatings = safetyRatings
} else {
safetyRatings = []
}
finishReason = try container.decodeIfPresent(FinishReason.self, forKey: .finishReason)
citationMetadata = try container.decodeIfPresent(
CitationMetadata.self,
forKey: .citationMetadata
)
}
}
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
extension CitationMetadata: Decodable {}
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
extension Citation: Decodable {
enum CodingKeys: CodingKey {
case startIndex
case endIndex
case uri
case license
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
startIndex = try container.decodeIfPresent(Int.self, forKey: .startIndex) ?? 0
endIndex = try container.decode(Int.self, forKey: .endIndex)
uri = try container.decode(String.self, forKey: .uri)
if let license = try container.decodeIfPresent(String.self, forKey: .license),
!license.isEmpty {
self.license = license
} else {
license = nil
}
}
}
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
extension FinishReason: Decodable {
public init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer().decode(String.self)
guard let decodedFinishReason = FinishReason(rawValue: value) else {
Logging.default
.error("[GoogleGenerativeAI] Unrecognized FinishReason with value \"\(value)\".")
self = .unknown
return
}
self = decodedFinishReason
}
}
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
extension PromptFeedback.BlockReason: Decodable {
public init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer().decode(String.self)
guard let decodedBlockReason = PromptFeedback.BlockReason(rawValue: value) else {
Logging.default
.error("[GoogleGenerativeAI] Unrecognized BlockReason with value \"\(value)\".")
self = .unknown
return
}
self = decodedBlockReason
}
}
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
extension PromptFeedback: Decodable {
enum CodingKeys: CodingKey {
case blockReason
case safetyRatings
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
blockReason = try container.decodeIfPresent(
PromptFeedback.BlockReason.self,
forKey: .blockReason
)
if let safetyRatings = try container.decodeIfPresent(
[SafetyRating].self,
forKey: .safetyRatings
) {
self.safetyRatings = safetyRatings
} else {
safetyRatings = []
}
}
}