Skip to content

Commit

Permalink
put errors into api methods
Browse files Browse the repository at this point in the history
  • Loading branch information
morganchen12 committed Jan 24, 2024
1 parent 5088f30 commit 9913ae5
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ struct ErrorDetailsView: View {
NavigationView {
let _ = GenerateContentError.promptBlocked(
response: GenerateContentResponse(candidates: [
CandidateResponse(content: ModelContent(role: "model", [
CandidateResponse(content: try! ModelContent(role: "model", [
"""
A _hypothetical_ model response.
Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo.
Expand All @@ -183,7 +183,7 @@ struct ErrorDetailsView: View {
let errorFinishedEarly = GenerateContentError.responseStoppedEarly(
reason: .maxTokens,
response: GenerateContentResponse(candidates: [
CandidateResponse(content: ModelContent(role: "model", [
CandidateResponse(content: try! ModelContent(role: "model", [
"""
A _hypothetical_ model response.
Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct ErrorView: View {
NavigationView {
let errorPromptBlocked = GenerateContentError.promptBlocked(
response: GenerateContentResponse(candidates: [
CandidateResponse(content: ModelContent(role: "model", [
CandidateResponse(content: try! ModelContent(role: "model", [
"""
A _hypothetical_ model response.
Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo.
Expand Down
22 changes: 18 additions & 4 deletions Sources/GoogleAI/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ public class Chat {
/// - Parameter content: The new content to send as a single chat message.
/// - Returns: The model's response if no error occurred.
/// - Throws: A ``GenerateContentError`` if an error occurred.
public func sendMessage(_ content: [ModelContent]) async throws -> GenerateContentResponse {
public func sendMessage(_ content: @autoclosure () throws -> [ModelContent]) async throws -> GenerateContentResponse {
// Ensure that the new content has the role set.
let newContent: [ModelContent] = content.map(populateContentRole(_:))
let newContent: [ModelContent]
do {
newContent = try content().map(populateContentRole(_:))
} catch(let underlying) {
throw GenerateContentError.internalError(underlying: underlying)
}

// Send the history alongside the new message as context.
let request = history + newContent
Expand All @@ -69,16 +74,25 @@ public class Chat {
@available(macOS 12.0, *)
public func sendMessageStream(_ parts: any PartsRepresentable...)
-> AsyncThrowingStream<GenerateContentResponse, Error> {
return sendMessageStream([ModelContent(parts: parts)])
return sendMessageStream(try [ModelContent(parts: parts)])
}

/// Sends a message using the existing history of this chat as context. If successful, the message
/// and response will be added to the history. If unsuccessful, history will remain unchanged.
/// - Parameter content: The new content to send as a single chat message.
/// - Returns: A stream containing the model's response or an error if an error occurred.
@available(macOS 12.0, *)
public func sendMessageStream(_ content: [ModelContent])
public func sendMessageStream(_ contentClosure: @autoclosure () throws -> [ModelContent])
-> AsyncThrowingStream<GenerateContentResponse, Error> {
let content: [ModelContent]
do {
content = try contentClosure()
} catch(let underlying) {
return AsyncThrowingStream { continuation in
continuation.finish(throwing: GenerateContentError.internalError(underlying: underlying))
}
}

return AsyncThrowingStream { continuation in
Task {
var aggregatedContent: [ModelContent] = []
Expand Down
13 changes: 11 additions & 2 deletions Sources/GoogleAI/GenerativeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public final class GenerativeModel {
@available(macOS 12.0, *)
public func generateContentStream(_ parts: any PartsRepresentable...)
-> AsyncThrowingStream<GenerateContentResponse, Error> {
return generateContentStream([ModelContent(parts: parts)])
return generateContentStream(try [ModelContent(parts: parts)])
}

/// Generates new content from input content given to the model as a prompt.
Expand All @@ -150,8 +150,17 @@ public final class GenerativeModel {
/// - Returns: A stream wrapping content generated by the model or a ``GenerateContentError``
/// error if an error occurred.
@available(macOS 12.0, *)
public func generateContentStream(_ content: [ModelContent])
public func generateContentStream(_ contentClosure: @autoclosure () throws -> [ModelContent])
-> AsyncThrowingStream<GenerateContentResponse, Error> {
let content: [ModelContent]
do {
content = try contentClosure()
} catch(let underlying) {
return AsyncThrowingStream { continuation in
continuation.finish(throwing: GenerateContentError.internalError(underlying: underlying))
}
}

let generateContentRequest = GenerateContentRequest(model: modelResourceName,
contents: content,
generationConfig: generationConfig,
Expand Down
13 changes: 4 additions & 9 deletions Sources/GoogleAI/ModelContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,9 @@ public struct ModelContent: Codable, Equatable {

/// Creates a new value from any data or `Array` of data interpretable as a
/// ``Part``. See ``PartsRepresentable`` for types that can be interpreted as `Part`s.
public init(role: String? = "user", parts: some PartsRepresentable) {
public init(role: String? = "user", parts: some PartsRepresentable) throws {
self.role = role
do {
try self.parts = parts.tryPartsValue()
} catch {
Logging.default.error("Error creating parts: \(error)")
self.parts = []
}
try self.parts = parts.tryPartsValue()
}

/// Creates a new value from a list of ``Part``s.
Expand All @@ -122,7 +117,7 @@ public struct ModelContent: Codable, Equatable {

/// 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...) {
self.init(role: role, parts: parts)
public init(role: String? = "user", _ parts: any PartsRepresentable...) throws {
try self.init(role: role, parts: parts)
}
}
2 changes: 1 addition & 1 deletion Tests/GoogleAITests/ChatTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ final class ChatTests: XCTestCase {
XCTAssertEqual(chat.history[0].parts[0].text, input)

let finalText = "1 2 3 4 5 6 7 8 9 10"
let assembledExpectation = ModelContent(role: "model", parts: finalText)
let assembledExpectation = try ModelContent(role: "model", parts: finalText)
XCTAssertEqual(chat.history[0].parts[0].text, input)
XCTAssertEqual(chat.history[1], assembledExpectation)
}
Expand Down
38 changes: 19 additions & 19 deletions Tests/GoogleAITests/GoogleAITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ final class GoogleGenerativeAITests: XCTestCase {
.generateContent([str, UIImage(), ModelContent.Part.text(str)])
_ = try await genAI.generateContent(str, UIImage(), "def", UIImage())
_ = try await genAI.generateContent([str, UIImage(), "def", UIImage()])
_ = try await genAI.generateContent([ModelContent("def", UIImage()),
ModelContent("def", UIImage())])
_ = try await genAI.generateContent([try ModelContent("def", UIImage()),
try ModelContent("def", UIImage())])
#elseif canImport(AppKit)
_ = try await genAI.generateContent(NSImage())
_ = try await genAI.generateContent([NSImage()])
Expand All @@ -81,40 +81,40 @@ final class GoogleGenerativeAITests: XCTestCase {
// PartsRepresentable combinations.
let _ = ModelContent(parts: [.text(str)])
let _ = ModelContent(role: "model", parts: [.text(str)])
let _ = ModelContent(parts: "Constant String")
let _ = ModelContent(parts: str)
let _ = ModelContent(parts: [str])
let _ = try ModelContent(parts: "Constant String")
let _ = try ModelContent(parts: str)
let _ = try ModelContent(parts: [str])
// Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert
// value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a
// way we can get it to work.
let _ = ModelContent(parts: [str, ModelContent.Part.data(
let _ = try ModelContent(parts: [str, ModelContent.Part.data(
mimetype: "foo",
Data()
)] as [any PartsRepresentable])
#if canImport(UIKit)
_ = ModelContent(role: "user", parts: UIImage())
_ = ModelContent(role: "user", parts: [UIImage()])
_ = try ModelContent(role: "user", parts: UIImage())
_ = try ModelContent(role: "user", parts: [UIImage()])
// Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert
// value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a
// way we can get it to work.
_ = ModelContent(parts: [str, UIImage()] as [any PartsRepresentable])
_ = try ModelContent(parts: [str, UIImage()] as [any PartsRepresentable])
// Alternatively, you can explicitly declare the type in a variable and pass it in.
let representable2: [any PartsRepresentable] = [str, UIImage()]
_ = ModelContent(parts: representable2)
_ = ModelContent(parts: [str, UIImage(),
_ = try ModelContent(parts: representable2)
_ = try ModelContent(parts: [str, UIImage(),
ModelContent.Part.text(str)] as [any PartsRepresentable])
#elseif canImport(AppKit)
_ = ModelContent(role: "user", parts: NSImage())
_ = ModelContent(role: "user", parts: [NSImage()])
_ = try ModelContent(role: "user", parts: NSImage())
_ = try ModelContent(role: "user", parts: [NSImage()])
// Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert
// value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a
// way we can get it to work.
_ = ModelContent(parts: [str, NSImage()] as [any PartsRepresentable])
_ = try ModelContent(parts: [str, NSImage()] as [any PartsRepresentable])
// Alternatively, you can explicitly declare the type in a variable and pass it in.
let representable2: [any PartsRepresentable] = [str, NSImage()]
_ = ModelContent(parts: representable2)
_ = try ModelContent(parts: representable2)
_ =
ModelContent(parts: [str, NSImage(),
try ModelContent(parts: [str, NSImage(),
ModelContent.Part.text(str)] as [any PartsRepresentable])
#endif

Expand All @@ -124,14 +124,14 @@ final class GoogleGenerativeAITests: XCTestCase {
let _: CountTokensResponse = try await genAI.countTokens("What color is the Sky?",
UIImage())
let _: CountTokensResponse = try await genAI.countTokens([
ModelContent("What color is the Sky?", UIImage()),
ModelContent(UIImage(), "What color is the Sky?", UIImage()),
try ModelContent("What color is the Sky?", UIImage()),
try ModelContent(UIImage(), "What color is the Sky?", UIImage()),
])
#endif

// Chat
_ = genAI.startChat()
_ = genAI.startChat(history: [ModelContent(parts: "abc")])
_ = genAI.startChat(history: [try ModelContent(parts: "abc")])
}

// Result builder alternative
Expand Down

0 comments on commit 9913ae5

Please sign in to comment.