GenerateContentResponse.swift 9.9 KB

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