GenerateContentResponse.swift 12 KB

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