GenerateContentResponse.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  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 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  17. public struct GenerateContentResponse: Sendable {
  18. /// Token usage metadata for processing the generate content request.
  19. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  20. public struct UsageMetadata: Sendable {
  21. /// The number of tokens in the request prompt.
  22. public let promptTokenCount: Int
  23. /// The total number of tokens across the generated response candidates.
  24. public let candidatesTokenCount: Int
  25. /// The number of tokens used by the model's internal "thinking" process.
  26. ///
  27. /// For models that support thinking (like Gemini 2.5 Pro and Flash), this represents the actual
  28. /// number of tokens consumed for reasoning before the model generated a response. For models
  29. /// that do not support thinking, this value will be `0`.
  30. ///
  31. /// When thinking is used, this count will be less than or equal to the `thinkingBudget` set in
  32. /// the ``ThinkingConfig``.
  33. public let thoughtsTokenCount: Int
  34. /// The total number of tokens in both the request and response.
  35. public let totalTokenCount: Int
  36. /// The breakdown, by modality, of how many tokens are consumed by the prompt
  37. public let promptTokensDetails: [ModalityTokenCount]
  38. /// The breakdown, by modality, of how many tokens are consumed by the candidates
  39. public let candidatesTokensDetails: [ModalityTokenCount]
  40. }
  41. /// A list of candidate response content, ordered from best to worst.
  42. public let candidates: [Candidate]
  43. /// A value containing the safety ratings for the response, or, if the request was blocked, a
  44. /// reason for blocking the request.
  45. public let promptFeedback: PromptFeedback?
  46. /// Token usage metadata for processing the generate content request.
  47. public let usageMetadata: UsageMetadata?
  48. /// The response's content as text, if it exists.
  49. public var text: String? {
  50. guard let candidate = candidates.first else {
  51. AILog.error(
  52. code: .generateContentResponseNoCandidates,
  53. "Could not get text from a response that had no candidates."
  54. )
  55. return nil
  56. }
  57. let textValues: [String] = candidate.content.parts.compactMap { part in
  58. switch part {
  59. case let textPart as TextPart:
  60. return textPart.text
  61. default:
  62. return nil
  63. }
  64. }
  65. guard textValues.count > 0 else {
  66. AILog.error(
  67. code: .generateContentResponseNoText,
  68. "Could not get a text part from the first candidate."
  69. )
  70. return nil
  71. }
  72. return textValues.joined(separator: " ")
  73. }
  74. /// Returns function calls found in any `Part`s of the first candidate of the response, if any.
  75. public var functionCalls: [FunctionCallPart] {
  76. guard let candidate = candidates.first else {
  77. return []
  78. }
  79. return candidate.content.parts.compactMap { part in
  80. switch part {
  81. case let functionCallPart as FunctionCallPart:
  82. return functionCallPart
  83. default:
  84. return nil
  85. }
  86. }
  87. }
  88. /// Returns inline data parts found in any `Part`s of the first candidate of the response, if any.
  89. public var inlineDataParts: [InlineDataPart] {
  90. guard let candidate = candidates.first else {
  91. AILog.error(code: .generateContentResponseNoCandidates, """
  92. Could not get inline data parts because the response has no candidates. The accessor only \
  93. checks the first candidate.
  94. """)
  95. return []
  96. }
  97. return candidate.content.parts.compactMap { $0 as? InlineDataPart }
  98. }
  99. /// Initializer for SwiftUI previews or tests.
  100. public init(candidates: [Candidate], promptFeedback: PromptFeedback? = nil,
  101. usageMetadata: UsageMetadata? = nil) {
  102. self.candidates = candidates
  103. self.promptFeedback = promptFeedback
  104. self.usageMetadata = usageMetadata
  105. }
  106. }
  107. /// A struct representing a possible reply to a content generation prompt. Each content generation
  108. /// prompt may produce multiple candidate responses.
  109. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  110. public struct Candidate: Sendable {
  111. /// The response's content.
  112. public let content: ModelContent
  113. /// The safety rating of the response content.
  114. public let safetyRatings: [SafetyRating]
  115. /// The reason the model stopped generating content, if it exists; for example, if the model
  116. /// generated a predefined stop sequence.
  117. public let finishReason: FinishReason?
  118. /// Cited works in the model's response content, if it exists.
  119. public let citationMetadata: CitationMetadata?
  120. public let groundingMetadata: GroundingMetadata?
  121. /// Initializer for SwiftUI previews or tests.
  122. public init(content: ModelContent, safetyRatings: [SafetyRating], finishReason: FinishReason?,
  123. citationMetadata: CitationMetadata?, groundingMetadata: GroundingMetadata? = nil) {
  124. self.content = content
  125. self.safetyRatings = safetyRatings
  126. self.finishReason = finishReason
  127. self.citationMetadata = citationMetadata
  128. self.groundingMetadata = groundingMetadata
  129. }
  130. }
  131. /// A collection of source attributions for a piece of content.
  132. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  133. public struct CitationMetadata: Sendable {
  134. /// A list of individual cited sources and the parts of the content to which they apply.
  135. public let citations: [Citation]
  136. }
  137. /// A struct describing a source attribution.
  138. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  139. public struct Citation: Sendable, Equatable {
  140. /// The inclusive beginning of a sequence in a model response that derives from a cited source.
  141. public let startIndex: Int
  142. /// The exclusive end of a sequence in a model response that derives from a cited source.
  143. public let endIndex: Int
  144. /// A link to the cited source, if available.
  145. public let uri: String?
  146. /// The title of the cited source, if available.
  147. public let title: String?
  148. /// The license the cited source work is distributed under, if specified.
  149. public let license: String?
  150. /// The publication date of the cited source, if available.
  151. ///
  152. /// > Tip: `DateComponents` can be converted to a `Date` using the `date` computed property.
  153. public let publicationDate: DateComponents?
  154. init(startIndex: Int,
  155. endIndex: Int,
  156. uri: String? = nil,
  157. title: String? = nil,
  158. license: String? = nil,
  159. publicationDate: DateComponents? = nil) {
  160. self.startIndex = startIndex
  161. self.endIndex = endIndex
  162. self.uri = uri
  163. self.title = title
  164. self.license = license
  165. self.publicationDate = publicationDate
  166. }
  167. }
  168. /// A value enumerating possible reasons for a model to terminate a content generation request.
  169. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  170. public struct FinishReason: DecodableProtoEnum, Hashable, Sendable {
  171. enum Kind: String {
  172. case stop = "STOP"
  173. case maxTokens = "MAX_TOKENS"
  174. case safety = "SAFETY"
  175. case recitation = "RECITATION"
  176. case other = "OTHER"
  177. case blocklist = "BLOCKLIST"
  178. case prohibitedContent = "PROHIBITED_CONTENT"
  179. case spii = "SPII"
  180. case malformedFunctionCall = "MALFORMED_FUNCTION_CALL"
  181. }
  182. /// Natural stop point of the model or provided stop sequence.
  183. public static let stop = FinishReason(kind: .stop)
  184. /// The maximum number of tokens as specified in the request was reached.
  185. public static let maxTokens = FinishReason(kind: .maxTokens)
  186. /// The token generation was stopped because the response was flagged for safety reasons.
  187. ///
  188. /// > NOTE: When streaming, the ``Candidate/content`` will be empty if content filters blocked the
  189. /// > output.
  190. public static let safety = FinishReason(kind: .safety)
  191. /// The token generation was stopped because the response was flagged for unauthorized citations.
  192. public static let recitation = FinishReason(kind: .recitation)
  193. /// All other reasons that stopped token generation.
  194. public static let other = FinishReason(kind: .other)
  195. /// Token generation was stopped because the response contained forbidden terms.
  196. public static let blocklist = FinishReason(kind: .blocklist)
  197. /// Token generation was stopped because the response contained potentially prohibited content.
  198. public static let prohibitedContent = FinishReason(kind: .prohibitedContent)
  199. /// Token generation was stopped because of Sensitive Personally Identifiable Information (SPII).
  200. public static let spii = FinishReason(kind: .spii)
  201. /// Token generation was stopped because the function call generated by the model was invalid.
  202. public static let malformedFunctionCall = FinishReason(kind: .malformedFunctionCall)
  203. /// Returns the raw string representation of the `FinishReason` value.
  204. ///
  205. /// > Note: This value directly corresponds to the values in the [REST
  206. /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#FinishReason).
  207. public let rawValue: String
  208. static let unrecognizedValueMessageCode =
  209. AILog.MessageCode.generateContentResponseUnrecognizedFinishReason
  210. }
  211. /// A metadata struct containing any feedback the model had on the prompt it was provided.
  212. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  213. public struct PromptFeedback: Sendable {
  214. /// A type describing possible reasons to block a prompt.
  215. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  216. public struct BlockReason: DecodableProtoEnum, Hashable, Sendable {
  217. enum Kind: String {
  218. case safety = "SAFETY"
  219. case other = "OTHER"
  220. case blocklist = "BLOCKLIST"
  221. case prohibitedContent = "PROHIBITED_CONTENT"
  222. }
  223. /// The prompt was blocked because it was deemed unsafe.
  224. public static let safety = BlockReason(kind: .safety)
  225. /// All other block reasons.
  226. public static let other = BlockReason(kind: .other)
  227. /// The prompt was blocked because it contained terms from the terminology blocklist.
  228. public static let blocklist = BlockReason(kind: .blocklist)
  229. /// The prompt was blocked due to prohibited content.
  230. public static let prohibitedContent = BlockReason(kind: .prohibitedContent)
  231. /// Returns the raw string representation of the `BlockReason` value.
  232. ///
  233. /// > Note: This value directly corresponds to the values in the [REST
  234. /// > API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/GenerateContentResponse#BlockedReason).
  235. public let rawValue: String
  236. static let unrecognizedValueMessageCode =
  237. AILog.MessageCode.generateContentResponseUnrecognizedBlockReason
  238. }
  239. /// The reason a prompt was blocked, if it was blocked.
  240. public let blockReason: BlockReason?
  241. /// A human-readable description of the ``blockReason``.
  242. public let blockReasonMessage: String?
  243. /// The safety ratings of the prompt.
  244. public let safetyRatings: [SafetyRating]
  245. /// Initializer for SwiftUI previews or tests.
  246. public init(blockReason: BlockReason?, blockReasonMessage: String? = nil,
  247. safetyRatings: [SafetyRating]) {
  248. self.blockReason = blockReason
  249. self.blockReasonMessage = blockReasonMessage
  250. self.safetyRatings = safetyRatings
  251. }
  252. }
  253. /// Metadata returned to the client when grounding is enabled.
  254. ///
  255. /// > Important: If using Grounding with Google Search, you are required to comply with the
  256. /// "Grounding with Google Search" usage requirements for your chosen API provider:
  257. /// [Gemini Developer API](https://ai.google.dev/gemini-api/terms#grounding-with-google-search)
  258. /// or Vertex AI Gemini API (see [Service Terms](https://cloud.google.com/terms/service-terms)
  259. /// section within the Service Specific Terms).
  260. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  261. public struct GroundingMetadata: Sendable, Equatable, Hashable {
  262. /// A list of web search queries that the model performed to gather the grounding information.
  263. /// These can be used to allow users to explore the search results themselves.
  264. public let webSearchQueries: [String]
  265. /// A list of ``GroundingChunk`` structs. Each chunk represents a piece of retrieved content
  266. /// (e.g., from a web page) that the model used to ground its response.
  267. public let groundingChunks: [GroundingChunk]
  268. /// A list of ``GroundingSupport`` structs. Each object details how specific segments of the
  269. /// model's response are supported by the `groundingChunks`.
  270. public let groundingSupports: [GroundingSupport]
  271. /// Google Search entry point for web searches.
  272. /// This contains an HTML/CSS snippet that **must** be embedded in an app to display a Google
  273. /// Search entry point for follow-up web searches related to the model's "Grounded Response".
  274. public let searchEntryPoint: SearchEntryPoint?
  275. /// A struct representing the Google Search entry point.
  276. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  277. public struct SearchEntryPoint: Sendable, Equatable, Hashable {
  278. /// An HTML/CSS snippet that can be embedded in your app.
  279. ///
  280. /// To ensure proper rendering, it's recommended to display this content within a `WKWebView`.
  281. public let renderedContent: String
  282. }
  283. /// Represents a chunk of retrieved data that supports a claim in the model's response. This is
  284. /// part of the grounding information provided when grounding is enabled.
  285. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  286. public struct GroundingChunk: Sendable, Equatable, Hashable {
  287. /// Contains details if the grounding chunk is from a web source.
  288. public let web: WebGroundingChunk?
  289. }
  290. /// A grounding chunk sourced from the web.
  291. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  292. public struct WebGroundingChunk: Sendable, Equatable, Hashable {
  293. /// The URI of the retrieved web page.
  294. public let uri: String?
  295. /// The title of the retrieved web page.
  296. public let title: String?
  297. /// The domain of the original URI from which the content was retrieved.
  298. ///
  299. /// This field is only populated when using the Vertex AI Gemini API.
  300. public let domain: String?
  301. }
  302. /// Provides information about how a specific segment of the model's response is supported by the
  303. /// retrieved grounding chunks.
  304. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  305. public struct GroundingSupport: Sendable, Equatable, Hashable {
  306. /// Specifies the segment of the model's response content that this grounding support pertains
  307. /// to.
  308. public let segment: Segment
  309. /// A list of indices that refer to specific ``GroundingChunk`` structs within the
  310. /// ``GroundingMetadata/groundingChunks`` array. These referenced chunks are the sources that
  311. /// support the claim made in the associated `segment` of the response. For example, an array
  312. /// `[1, 3, 4]`
  313. /// means that `groundingChunks[1]`, `groundingChunks[3]`, `groundingChunks[4]` are the
  314. /// retrieved content supporting this part of the response.
  315. public let groundingChunkIndices: [Int]
  316. struct Internal {
  317. let segment: Segment?
  318. let groundingChunkIndices: [Int]
  319. func toPublic() -> GroundingSupport? {
  320. if segment == nil {
  321. return nil
  322. }
  323. return GroundingSupport(
  324. segment: segment!,
  325. groundingChunkIndices: groundingChunkIndices
  326. )
  327. }
  328. }
  329. }
  330. }
  331. /// Represents a specific segment within a ``ModelContent`` struct, often used to pinpoint the
  332. /// exact location of text or data that grounding information refers to.
  333. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  334. public struct Segment: Sendable, Equatable, Hashable {
  335. /// The zero-based index of the ``Part`` object within the `parts` array of its parent
  336. /// ``ModelContent`` object. This identifies which part of the content the segment belongs to.
  337. public let partIndex: Int
  338. /// The zero-based start index of the segment within the specified ``Part``, measured in UTF-8
  339. /// bytes. This offset is inclusive, starting from 0 at the beginning of the part's content.
  340. public let startIndex: Int
  341. /// The zero-based end index of the segment within the specified ``Part``, measured in UTF-8
  342. /// bytes. This offset is exclusive, meaning the character at this index is not included in the
  343. /// segment.
  344. public let endIndex: Int
  345. /// The text corresponding to the segment from the response.
  346. public let text: String
  347. }
  348. // MARK: - Codable Conformances
  349. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  350. extension GenerateContentResponse: Decodable {
  351. enum CodingKeys: CodingKey {
  352. case candidates
  353. case promptFeedback
  354. case usageMetadata
  355. }
  356. public init(from decoder: Decoder) throws {
  357. let container = try decoder.container(keyedBy: CodingKeys.self)
  358. guard container.contains(CodingKeys.candidates) || container
  359. .contains(CodingKeys.promptFeedback) else {
  360. let context = DecodingError.Context(
  361. codingPath: [],
  362. debugDescription: "Failed to decode GenerateContentResponse;" +
  363. " missing keys 'candidates' and 'promptFeedback'."
  364. )
  365. throw DecodingError.dataCorrupted(context)
  366. }
  367. if let candidates = try container.decodeIfPresent(
  368. [Candidate].self,
  369. forKey: .candidates
  370. ) {
  371. self.candidates = candidates
  372. } else {
  373. candidates = []
  374. }
  375. promptFeedback = try container.decodeIfPresent(PromptFeedback.self, forKey: .promptFeedback)
  376. usageMetadata = try container.decodeIfPresent(UsageMetadata.self, forKey: .usageMetadata)
  377. }
  378. }
  379. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  380. extension GenerateContentResponse.UsageMetadata: Decodable {
  381. enum CodingKeys: CodingKey {
  382. case promptTokenCount
  383. case candidatesTokenCount
  384. case thoughtsTokenCount
  385. case totalTokenCount
  386. case promptTokensDetails
  387. case candidatesTokensDetails
  388. }
  389. public init(from decoder: any Decoder) throws {
  390. let container = try decoder.container(keyedBy: CodingKeys.self)
  391. promptTokenCount = try container.decodeIfPresent(Int.self, forKey: .promptTokenCount) ?? 0
  392. candidatesTokenCount =
  393. try container.decodeIfPresent(Int.self, forKey: .candidatesTokenCount) ?? 0
  394. thoughtsTokenCount = try container.decodeIfPresent(Int.self, forKey: .thoughtsTokenCount) ?? 0
  395. totalTokenCount = try container.decodeIfPresent(Int.self, forKey: .totalTokenCount) ?? 0
  396. promptTokensDetails =
  397. try container.decodeIfPresent([ModalityTokenCount].self, forKey: .promptTokensDetails) ?? []
  398. candidatesTokensDetails = try container.decodeIfPresent(
  399. [ModalityTokenCount].self,
  400. forKey: .candidatesTokensDetails
  401. ) ?? []
  402. }
  403. }
  404. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  405. extension Candidate: Decodable {
  406. enum CodingKeys: CodingKey {
  407. case content
  408. case safetyRatings
  409. case finishReason
  410. case citationMetadata
  411. case groundingMetadata
  412. }
  413. /// Initializes a response from a decoder. Used for decoding server responses; not for public
  414. /// use.
  415. public init(from decoder: Decoder) throws {
  416. let container = try decoder.container(keyedBy: CodingKeys.self)
  417. do {
  418. if let content = try container.decodeIfPresent(ModelContent.self, forKey: .content) {
  419. self.content = content
  420. } else {
  421. content = ModelContent(parts: [])
  422. }
  423. } catch {
  424. throw InvalidCandidateError.malformedContent(underlyingError: error)
  425. }
  426. if let safetyRatings = try container.decodeIfPresent(
  427. [SafetyRating].self, forKey: .safetyRatings
  428. ) {
  429. self.safetyRatings = safetyRatings.filter {
  430. // Due to a bug in the backend, the SDK may receive invalid `SafetyRating` values that do
  431. // not include a category or probability; these are filtered out of the safety ratings.
  432. $0.category != HarmCategory.unspecified
  433. && $0.probability != SafetyRating.HarmProbability.unspecified
  434. }
  435. } else {
  436. safetyRatings = []
  437. }
  438. finishReason = try container.decodeIfPresent(FinishReason.self, forKey: .finishReason)
  439. // The `content` may only be empty if a `finishReason` is included; if neither are included in
  440. // the response then this is likely the `"content": {}` bug.
  441. guard !content.parts.isEmpty || finishReason != nil else {
  442. throw InvalidCandidateError.emptyContent(underlyingError: DecodingError.dataCorrupted(.init(
  443. codingPath: [CodingKeys.content, CodingKeys.finishReason],
  444. debugDescription: "Invalid Candidate: empty content and no finish reason"
  445. )))
  446. }
  447. citationMetadata = try container.decodeIfPresent(
  448. CitationMetadata.self,
  449. forKey: .citationMetadata
  450. )
  451. groundingMetadata = try container.decodeIfPresent(
  452. GroundingMetadata.self,
  453. forKey: .groundingMetadata
  454. )
  455. }
  456. }
  457. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  458. extension CitationMetadata: Decodable {
  459. enum CodingKeys: CodingKey {
  460. case citations // Vertex AI
  461. case citationSources // Google AI
  462. }
  463. public init(from decoder: any Decoder) throws {
  464. let container = try decoder.container(keyedBy: CodingKeys.self)
  465. // Decode for Google API if `citationSources` key is present.
  466. if container.contains(.citationSources) {
  467. citations = try container.decode([Citation].self, forKey: .citationSources)
  468. } else { // Fallback to default Vertex AI decoding.
  469. citations = try container.decode([Citation].self, forKey: .citations)
  470. }
  471. }
  472. }
  473. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  474. extension Citation: Decodable {
  475. enum CodingKeys: CodingKey {
  476. case startIndex
  477. case endIndex
  478. case uri
  479. case title
  480. case license
  481. case publicationDate
  482. }
  483. public init(from decoder: any Decoder) throws {
  484. let container = try decoder.container(keyedBy: CodingKeys.self)
  485. startIndex = try container.decodeIfPresent(Int.self, forKey: .startIndex) ?? 0
  486. endIndex = try container.decode(Int.self, forKey: .endIndex)
  487. if let uri = try container.decodeIfPresent(String.self, forKey: .uri), !uri.isEmpty {
  488. self.uri = uri
  489. } else {
  490. uri = nil
  491. }
  492. if let title = try container.decodeIfPresent(String.self, forKey: .title), !title.isEmpty {
  493. self.title = title
  494. } else {
  495. title = nil
  496. }
  497. if let license = try container.decodeIfPresent(String.self, forKey: .license),
  498. !license.isEmpty {
  499. self.license = license
  500. } else {
  501. license = nil
  502. }
  503. if let publicationProtoDate = try container.decodeIfPresent(
  504. ProtoDate.self,
  505. forKey: .publicationDate
  506. ) {
  507. publicationDate = publicationProtoDate.dateComponents
  508. if let publicationDate, !publicationDate.isValidDate {
  509. AILog.warning(
  510. code: .decodedInvalidCitationPublicationDate,
  511. "Decoded an invalid citation publication date: \(publicationDate)"
  512. )
  513. }
  514. } else {
  515. publicationDate = nil
  516. }
  517. }
  518. }
  519. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  520. extension PromptFeedback: Decodable {
  521. enum CodingKeys: CodingKey {
  522. case blockReason
  523. case blockReasonMessage
  524. case safetyRatings
  525. }
  526. public init(from decoder: Decoder) throws {
  527. let container = try decoder.container(keyedBy: CodingKeys.self)
  528. blockReason = try container.decodeIfPresent(
  529. PromptFeedback.BlockReason.self,
  530. forKey: .blockReason
  531. )
  532. blockReasonMessage = try container.decodeIfPresent(String.self, forKey: .blockReasonMessage)
  533. if let safetyRatings = try container.decodeIfPresent(
  534. [SafetyRating].self,
  535. forKey: .safetyRatings
  536. ) {
  537. self.safetyRatings = safetyRatings
  538. } else {
  539. safetyRatings = []
  540. }
  541. }
  542. }
  543. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  544. extension GroundingMetadata: Decodable {
  545. enum CodingKeys: String, CodingKey {
  546. case webSearchQueries
  547. case groundingChunks
  548. case groundingSupports
  549. case searchEntryPoint
  550. }
  551. public init(from decoder: Decoder) throws {
  552. let container = try decoder.container(keyedBy: CodingKeys.self)
  553. webSearchQueries = try container.decodeIfPresent([String].self, forKey: .webSearchQueries) ?? []
  554. groundingChunks = try container.decodeIfPresent(
  555. [GroundingChunk].self,
  556. forKey: .groundingChunks
  557. ) ?? []
  558. groundingSupports = try container.decodeIfPresent(
  559. [GroundingSupport.Internal].self,
  560. forKey: .groundingSupports
  561. )?.compactMap { $0.toPublic() } ?? []
  562. searchEntryPoint = try container.decodeIfPresent(
  563. SearchEntryPoint.self,
  564. forKey: .searchEntryPoint
  565. )
  566. }
  567. }
  568. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  569. extension GroundingMetadata.SearchEntryPoint: Decodable {}
  570. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  571. extension GroundingMetadata.GroundingChunk: Decodable {}
  572. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  573. extension GroundingMetadata.WebGroundingChunk: Decodable {}
  574. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  575. extension GroundingMetadata.GroundingSupport.Internal: Decodable {
  576. enum CodingKeys: String, CodingKey {
  577. case segment
  578. case groundingChunkIndices
  579. }
  580. public init(from decoder: Decoder) throws {
  581. let container = try decoder.container(keyedBy: CodingKeys.self)
  582. segment = try container.decodeIfPresent(Segment.self, forKey: .segment)
  583. groundingChunkIndices = try container.decodeIfPresent(
  584. [Int].self,
  585. forKey: .groundingChunkIndices
  586. ) ?? []
  587. }
  588. }
  589. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
  590. extension Segment: Decodable {
  591. enum CodingKeys: String, CodingKey {
  592. case partIndex
  593. case startIndex
  594. case endIndex
  595. case text
  596. }
  597. public init(from decoder: Decoder) throws {
  598. let container = try decoder.container(keyedBy: CodingKeys.self)
  599. partIndex = try container.decodeIfPresent(Int.self, forKey: .partIndex) ?? 0
  600. startIndex = try container.decodeIfPresent(Int.self, forKey: .startIndex) ?? 0
  601. endIndex = try container.decodeIfPresent(Int.self, forKey: .endIndex) ?? 0
  602. text = try container.decodeIfPresent(String.self, forKey: .text) ?? ""
  603. }
  604. }