GenerateContentResponse.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. // Copyright 2023 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. import Foundation
  15. /// The model's response to a generate content request.
  16. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  17. public struct GenerateContentResponse {
  18. /// Token usage metadata for processing the generate content request.
  19. public struct UsageMetadata {
  20. /// The number of tokens in the request prompt.
  21. public let promptTokenCount: Int
  22. /// The total number of tokens across the generated response candidates.
  23. public let candidatesTokenCount: Int
  24. /// The total number of tokens in both the request and response.
  25. public let totalTokenCount: Int
  26. }
  27. /// A list of candidate response content, ordered from best to worst.
  28. public let candidates: [CandidateResponse]
  29. /// A value containing the safety ratings for the response, or, if the request was blocked, a
  30. /// reason for blocking the request.
  31. public let promptFeedback: PromptFeedback?
  32. /// Token usage metadata for processing the generate content request.
  33. public let usageMetadata: UsageMetadata?
  34. /// The response's content as text, if it exists.
  35. public var text: String? {
  36. guard let candidate = candidates.first else {
  37. Logging.default.error("Could not get text from a response that had no candidates.")
  38. return nil
  39. }
  40. guard let text = candidate.content.parts.first?.text else {
  41. Logging.default.error("Could not get a text part from the first candidate.")
  42. return nil
  43. }
  44. return text
  45. }
  46. /// Returns function calls found in any `Part`s of the first candidate of the response, if any.
  47. public var functionCalls: [FunctionCall] {
  48. guard let candidate = candidates.first else {
  49. return []
  50. }
  51. return candidate.content.parts.compactMap { part in
  52. guard case let .functionCall(functionCall) = part else {
  53. return nil
  54. }
  55. return functionCall
  56. }
  57. }
  58. /// Initializer for SwiftUI previews or tests.
  59. public init(candidates: [CandidateResponse], promptFeedback: PromptFeedback? = nil,
  60. usageMetadata: UsageMetadata? = nil) {
  61. self.candidates = candidates
  62. self.promptFeedback = promptFeedback
  63. self.usageMetadata = usageMetadata
  64. }
  65. }
  66. /// A struct representing a possible reply to a content generation prompt. Each content generation
  67. /// prompt may produce multiple candidate responses.
  68. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  69. public struct CandidateResponse {
  70. /// The response's content.
  71. public let content: ModelContent
  72. /// The safety rating of the response content.
  73. public let safetyRatings: [SafetyRating]
  74. /// The reason the model stopped generating content, if it exists; for example, if the model
  75. /// generated a predefined stop sequence.
  76. public let finishReason: FinishReason?
  77. /// Cited works in the model's response content, if it exists.
  78. public let citationMetadata: CitationMetadata?
  79. /// Initializer for SwiftUI previews or tests.
  80. public init(content: ModelContent, safetyRatings: [SafetyRating], finishReason: FinishReason?,
  81. citationMetadata: CitationMetadata?) {
  82. self.content = content
  83. self.safetyRatings = safetyRatings
  84. self.finishReason = finishReason
  85. self.citationMetadata = citationMetadata
  86. }
  87. }
  88. /// A collection of source attributions for a piece of content.
  89. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  90. public struct CitationMetadata {
  91. /// A list of individual cited sources and the parts of the content to which they apply.
  92. public let citationSources: [Citation]
  93. }
  94. /// A struct describing a source attribution.
  95. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  96. public struct Citation {
  97. /// The inclusive beginning of a sequence in a model response that derives from a cited source.
  98. public let startIndex: Int
  99. /// The exclusive end of a sequence in a model response that derives from a cited source.
  100. public let endIndex: Int
  101. /// A link to the cited source.
  102. public let uri: String
  103. /// The license the cited source work is distributed under, if specified.
  104. public let license: String?
  105. }
  106. /// A value enumerating possible reasons for a model to terminate a content generation request.
  107. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  108. public enum FinishReason: String {
  109. case unknown = "FINISH_REASON_UNKNOWN"
  110. case unspecified = "FINISH_REASON_UNSPECIFIED"
  111. /// Natural stop point of the model or provided stop sequence.
  112. case stop = "STOP"
  113. /// The maximum number of tokens as specified in the request was reached.
  114. case maxTokens = "MAX_TOKENS"
  115. /// The token generation was stopped because the response was flagged for safety reasons.
  116. /// NOTE: When streaming, the Candidate.content will be empty if content filters blocked the
  117. /// output.
  118. case safety = "SAFETY"
  119. /// The token generation was stopped because the response was flagged for unauthorized citations.
  120. case recitation = "RECITATION"
  121. /// All other reasons that stopped token generation.
  122. case other = "OTHER"
  123. }
  124. /// A metadata struct containing any feedback the model had on the prompt it was provided.
  125. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  126. public struct PromptFeedback {
  127. /// A type describing possible reasons to block a prompt.
  128. public enum BlockReason: String {
  129. /// The block reason is unknown.
  130. case unknown = "UNKNOWN"
  131. /// The block reason was not specified in the server response.
  132. case unspecified = "BLOCK_REASON_UNSPECIFIED"
  133. /// The prompt was blocked because it was deemed unsafe.
  134. case safety = "SAFETY"
  135. /// All other block reasons.
  136. case other = "OTHER"
  137. }
  138. /// The reason a prompt was blocked, if it was blocked.
  139. public let blockReason: BlockReason?
  140. /// The safety ratings of the prompt.
  141. public let safetyRatings: [SafetyRating]
  142. /// Initializer for SwiftUI previews or tests.
  143. public init(blockReason: BlockReason?, safetyRatings: [SafetyRating]) {
  144. self.blockReason = blockReason
  145. self.safetyRatings = safetyRatings
  146. }
  147. }
  148. // MARK: - Codable Conformances
  149. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  150. extension GenerateContentResponse: Decodable {
  151. enum CodingKeys: CodingKey {
  152. case candidates
  153. case promptFeedback
  154. case usageMetadata
  155. }
  156. public init(from decoder: Decoder) throws {
  157. let container = try decoder.container(keyedBy: CodingKeys.self)
  158. guard container.contains(CodingKeys.candidates) || container
  159. .contains(CodingKeys.promptFeedback) else {
  160. let context = DecodingError.Context(
  161. codingPath: [],
  162. debugDescription: "Failed to decode GenerateContentResponse;" +
  163. " missing keys 'candidates' and 'promptFeedback'."
  164. )
  165. throw DecodingError.dataCorrupted(context)
  166. }
  167. if let candidates = try container.decodeIfPresent(
  168. [CandidateResponse].self,
  169. forKey: .candidates
  170. ) {
  171. self.candidates = candidates
  172. } else {
  173. candidates = []
  174. }
  175. promptFeedback = try container.decodeIfPresent(PromptFeedback.self, forKey: .promptFeedback)
  176. usageMetadata = try container.decodeIfPresent(UsageMetadata.self, forKey: .usageMetadata)
  177. }
  178. }
  179. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  180. extension GenerateContentResponse.UsageMetadata: Decodable {
  181. enum CodingKeys: CodingKey {
  182. case promptTokenCount
  183. case candidatesTokenCount
  184. case totalTokenCount
  185. }
  186. public init(from decoder: any Decoder) throws {
  187. let container = try decoder.container(keyedBy: CodingKeys.self)
  188. promptTokenCount = try container.decodeIfPresent(Int.self, forKey: .promptTokenCount) ?? 0
  189. candidatesTokenCount = try container
  190. .decodeIfPresent(Int.self, forKey: .candidatesTokenCount) ?? 0
  191. totalTokenCount = try container.decodeIfPresent(Int.self, forKey: .totalTokenCount) ?? 0
  192. }
  193. }
  194. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  195. extension CandidateResponse: Decodable {
  196. enum CodingKeys: CodingKey {
  197. case content
  198. case safetyRatings
  199. case finishReason
  200. case finishMessage
  201. case citationMetadata
  202. }
  203. /// Initializes a response from a decoder. Used for decoding server responses; not for public
  204. /// use.
  205. public init(from decoder: Decoder) throws {
  206. let container = try decoder.container(keyedBy: CodingKeys.self)
  207. do {
  208. if let content = try container.decodeIfPresent(ModelContent.self, forKey: .content) {
  209. self.content = content
  210. } else {
  211. content = ModelContent(parts: [])
  212. }
  213. } catch {
  214. // Check if `content` can be decoded as an empty dictionary to detect the `"content": {}` bug.
  215. if let content = try? container.decode([String: String].self, forKey: .content),
  216. content.isEmpty {
  217. throw InvalidCandidateError.emptyContent(underlyingError: error)
  218. } else {
  219. throw InvalidCandidateError.malformedContent(underlyingError: error)
  220. }
  221. }
  222. if let safetyRatings = try container.decodeIfPresent(
  223. [SafetyRating].self,
  224. forKey: .safetyRatings
  225. ) {
  226. self.safetyRatings = safetyRatings
  227. } else {
  228. safetyRatings = []
  229. }
  230. finishReason = try container.decodeIfPresent(FinishReason.self, forKey: .finishReason)
  231. citationMetadata = try container.decodeIfPresent(
  232. CitationMetadata.self,
  233. forKey: .citationMetadata
  234. )
  235. }
  236. }
  237. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  238. extension CitationMetadata: Decodable {
  239. enum CodingKeys: String, CodingKey {
  240. case citationSources = "citations"
  241. }
  242. }
  243. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  244. extension Citation: Decodable {
  245. enum CodingKeys: CodingKey {
  246. case startIndex
  247. case endIndex
  248. case uri
  249. case license
  250. }
  251. public init(from decoder: any Decoder) throws {
  252. let container = try decoder.container(keyedBy: CodingKeys.self)
  253. startIndex = try container.decodeIfPresent(Int.self, forKey: .startIndex) ?? 0
  254. endIndex = try container.decode(Int.self, forKey: .endIndex)
  255. uri = try container.decode(String.self, forKey: .uri)
  256. license = try container.decodeIfPresent(String.self, forKey: .license)
  257. }
  258. }
  259. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  260. extension FinishReason: Decodable {
  261. public init(from decoder: Decoder) throws {
  262. let value = try decoder.singleValueContainer().decode(String.self)
  263. guard let decodedFinishReason = FinishReason(rawValue: value) else {
  264. Logging.default
  265. .error("[GoogleGenerativeAI] Unrecognized FinishReason with value \"\(value)\".")
  266. self = .unknown
  267. return
  268. }
  269. self = decodedFinishReason
  270. }
  271. }
  272. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  273. extension PromptFeedback.BlockReason: Decodable {
  274. public init(from decoder: Decoder) throws {
  275. let value = try decoder.singleValueContainer().decode(String.self)
  276. guard let decodedBlockReason = PromptFeedback.BlockReason(rawValue: value) else {
  277. Logging.default
  278. .error("[GoogleGenerativeAI] Unrecognized BlockReason with value \"\(value)\".")
  279. self = .unknown
  280. return
  281. }
  282. self = decodedBlockReason
  283. }
  284. }
  285. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *)
  286. extension PromptFeedback: Decodable {
  287. enum CodingKeys: CodingKey {
  288. case blockReason
  289. case safetyRatings
  290. }
  291. public init(from decoder: Decoder) throws {
  292. let container = try decoder.container(keyedBy: CodingKeys.self)
  293. blockReason = try container.decodeIfPresent(
  294. PromptFeedback.BlockReason.self,
  295. forKey: .blockReason
  296. )
  297. if let safetyRatings = try container.decodeIfPresent(
  298. [SafetyRating].self,
  299. forKey: .safetyRatings
  300. ) {
  301. self.safetyRatings = safetyRatings
  302. } else {
  303. safetyRatings = []
  304. }
  305. }
  306. }