| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- // 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
- @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
- extension [ModelContent] {
- // TODO: Rename and refactor this.
- func throwIfError() throws {
- for content in self {
- for part in content.parts {
- switch part {
- case let errorPart as ErrorPart:
- throw errorPart.error
- default:
- break
- }
- }
- }
- }
- }
- @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
- struct InternalPart: Equatable, Sendable {
- enum OneOfData: Equatable, Sendable {
- case text(String)
- case inlineData(InlineData)
- case fileData(FileData)
- case functionCall(FunctionCall)
- case functionResponse(FunctionResponse)
- }
- let data: OneOfData
- let isThought: Bool?
- let thoughtSignature: String?
- init(_ data: OneOfData, isThought: Bool?, thoughtSignature: String?) {
- self.data = data
- self.isThought = isThought
- self.thoughtSignature = thoughtSignature
- }
- }
- /// A type describing data in media formats interpretable by an AI model. Each generative AI
- /// request or response contains an `Array` of ``ModelContent``s, and each ``ModelContent`` value
- /// may comprise multiple heterogeneous ``Part``s.
- @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
- public struct ModelContent: Equatable, Sendable {
- /// The role of the entity creating the ``ModelContent``. For user-generated client requests,
- /// for example, the role is `user`.
- public let role: String?
- /// The data parts comprising this ``ModelContent`` value.
- public var parts: [any Part] {
- return internalParts.map { part -> any Part in
- switch part.data {
- case let .text(text):
- return TextPart(text, isThought: part.isThought, thoughtSignature: part.thoughtSignature)
- case let .inlineData(inlineData):
- return InlineDataPart(
- inlineData, isThought: part.isThought, thoughtSignature: part.thoughtSignature
- )
- case let .fileData(fileData):
- return FileDataPart(
- fileData, isThought: part.isThought, thoughtSignature: part.thoughtSignature
- )
- case let .functionCall(functionCall):
- return FunctionCallPart(
- functionCall, isThought: part.isThought, thoughtSignature: part.thoughtSignature
- )
- case let .functionResponse(functionResponse):
- return FunctionResponsePart(
- functionResponse, isThought: part.isThought, thoughtSignature: part.thoughtSignature
- )
- }
- }
- }
- // TODO: Refactor this
- let internalParts: [InternalPart]
- /// Creates a new value from a list of ``Part``s.
- public init(role: String? = "user", parts: [any Part]) {
- self.role = role
- var convertedParts = [InternalPart]()
- for part in parts {
- switch part {
- case let textPart as TextPart:
- convertedParts.append(InternalPart(
- .text(textPart.text),
- isThought: textPart._isThought,
- thoughtSignature: textPart.thoughtSignature
- ))
- case let inlineDataPart as InlineDataPart:
- convertedParts.append(InternalPart(
- .inlineData(inlineDataPart.inlineData),
- isThought: inlineDataPart._isThought,
- thoughtSignature: inlineDataPart.thoughtSignature
- ))
- case let fileDataPart as FileDataPart:
- convertedParts.append(InternalPart(
- .fileData(fileDataPart.fileData),
- isThought: fileDataPart._isThought,
- thoughtSignature: fileDataPart.thoughtSignature
- ))
- case let functionCallPart as FunctionCallPart:
- convertedParts.append(InternalPart(
- .functionCall(functionCallPart.functionCall),
- isThought: functionCallPart._isThought,
- thoughtSignature: functionCallPart.thoughtSignature
- ))
- case let functionResponsePart as FunctionResponsePart:
- convertedParts.append(InternalPart(
- .functionResponse(functionResponsePart.functionResponse),
- isThought: functionResponsePart._isThought,
- thoughtSignature: functionResponsePart.thoughtSignature
- ))
- default:
- fatalError()
- }
- }
- internalParts = convertedParts
- }
- /// Creates a new value from any data interpretable as a ``Part``.
- /// See ``PartsRepresentable`` for types that can be interpreted as `Part`s.
- public init(role: String? = "user", parts: any PartsRepresentable...) {
- let content = parts.flatMap { $0.partsValue }
- self.init(role: role, parts: content)
- }
- init(role: String?, parts: [InternalPart]) {
- self.role = role
- internalParts = parts
- }
- }
- // MARK: Codable Conformances
- @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
- extension ModelContent: Codable {
- enum CodingKeys: String, CodingKey {
- case role
- case internalParts = "parts"
- }
- public init(from decoder: any Decoder) throws {
- let container = try decoder.container(keyedBy: CodingKeys.self)
- role = try container.decodeIfPresent(String.self, forKey: .role)
- internalParts = try container.decodeIfPresent([InternalPart].self, forKey: .internalParts) ?? []
- }
- }
- @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
- extension InternalPart: Codable {
- enum CodingKeys: String, CodingKey {
- case isThought = "thought"
- case thoughtSignature
- }
- public func encode(to encoder: Encoder) throws {
- try data.encode(to: encoder)
- var container = encoder.container(keyedBy: CodingKeys.self)
- try container.encodeIfPresent(isThought, forKey: .isThought)
- try container.encodeIfPresent(thoughtSignature, forKey: .thoughtSignature)
- }
- public init(from decoder: Decoder) throws {
- data = try OneOfData(from: decoder)
- let container = try decoder.container(keyedBy: CodingKeys.self)
- isThought = try container.decodeIfPresent(Bool.self, forKey: .isThought)
- thoughtSignature = try container.decodeIfPresent(String.self, forKey: .thoughtSignature)
- }
- }
- @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
- extension InternalPart.OneOfData: Codable {
- enum CodingKeys: String, CodingKey {
- case text
- case inlineData
- case fileData
- case functionCall
- case functionResponse
- }
- public func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: CodingKeys.self)
- switch self {
- case let .text(text):
- try container.encode(text, forKey: .text)
- case let .inlineData(inlineData):
- try container.encode(inlineData, forKey: .inlineData)
- case let .fileData(fileData):
- try container.encode(fileData, forKey: .fileData)
- case let .functionCall(functionCall):
- try container.encode(functionCall, forKey: .functionCall)
- case let .functionResponse(functionResponse):
- try container.encode(functionResponse, forKey: .functionResponse)
- }
- }
- public init(from decoder: Decoder) throws {
- let values = try decoder.container(keyedBy: CodingKeys.self)
- if values.contains(.text) {
- self = try .text(values.decode(String.self, forKey: .text))
- } else if values.contains(.inlineData) {
- self = try .inlineData(values.decode(InlineData.self, forKey: .inlineData))
- } else if values.contains(.fileData) {
- self = try .fileData(values.decode(FileData.self, forKey: .fileData))
- } else if values.contains(.functionCall) {
- self = try .functionCall(values.decode(FunctionCall.self, forKey: .functionCall))
- } else if values.contains(.functionResponse) {
- self = try .functionResponse(values.decode(FunctionResponse.self, forKey: .functionResponse))
- } else {
- let unexpectedKeys = values.allKeys.map { $0.stringValue }
- throw DecodingError.dataCorrupted(DecodingError.Context(
- codingPath: values.codingPath,
- debugDescription: "Unexpected Part type(s): \(unexpectedKeys)"
- ))
- }
- }
- }
|