@@ -17,7 +17,7 @@ import Foundation
1717/// An object that represents a back-and-forth chat with a model, capturing the history and saving
1818/// the context in memory between each message sent.
1919@available ( iOS 15 . 0 , macOS 12 . 0 , macCatalyst 15 . 0 , tvOS 15 . 0 , watchOS 8 . 0 , * )
20- public class Chat {
20+ public final class Chat : Sendable {
2121 private let model : GenerativeModel
2222
2323 /// Initializes a new chat representing a 1:1 conversation between model and user.
@@ -26,9 +26,28 @@ public class Chat {
2626 self . history = history
2727 }
2828
29+ private let historyLock = NSLock ( )
30+ #if compiler(>=6)
31+ private nonisolated ( unsafe) var _history : [ ModelContent ] = [ ]
32+ #else
33+ private var _history : [ ModelContent ] = [ ]
34+ #endif
2935 /// The previous content from the chat that has been successfully sent and received from the
3036 /// model. This will be provided to the model for each message sent as context for the discussion.
31- public var history : [ ModelContent ]
37+ public var history : [ ModelContent ] {
38+ get {
39+ historyLock. withLock { _history }
40+ }
41+ set {
42+ historyLock. withLock { _history = newValue }
43+ }
44+ }
45+
46+ private func appendHistory( contentsOf: [ ModelContent ] ) {
47+ historyLock. withLock {
48+ _history. append ( contentsOf: contentsOf)
49+ }
50+ }
3251
3352 /// Sends a message using the existing history of this chat as context. If successful, the message
3453 /// and response will be added to the history. If unsuccessful, history will remain unchanged.
@@ -66,7 +85,7 @@ public class Chat {
6685 let toAdd = ModelContent ( role: " model " , parts: reply. parts)
6786
6887 // Append the request and successful result to history, then return the value.
69- history . append ( contentsOf: newContent)
88+ appendHistory ( contentsOf: newContent)
7089 history. append ( toAdd)
7190 return result
7291 }
@@ -88,16 +107,16 @@ public class Chat {
88107 @available ( macOS 12 . 0 , * )
89108 public func sendMessageStream( _ content: [ ModelContent ] ) throws
90109 -> AsyncThrowingStream < GenerateContentResponse , Error > {
110+ // Ensure that the new content has the role set.
111+ let newContent : [ ModelContent ] = content. map ( populateContentRole ( _: ) )
112+
113+ // Send the history alongside the new message as context.
114+ let request = history + newContent
115+ let stream = try model. generateContentStream ( request)
91116 return AsyncThrowingStream { continuation in
92117 Task {
93118 var aggregatedContent : [ ModelContent ] = [ ]
94119
95- // Ensure that the new content has the role set.
96- let newContent : [ ModelContent ] = content. map ( populateContentRole ( _: ) )
97-
98- // Send the history alongside the new message as context.
99- let request = history + newContent
100- let stream = try model. generateContentStream ( request)
101120 do {
102121 for try await chunk in stream {
103122 // Capture any content that's streaming. This should be populated if there's no error.
@@ -115,12 +134,11 @@ public class Chat {
115134 }
116135
117136 // Save the request.
118- history . append ( contentsOf: newContent)
137+ appendHistory ( contentsOf: newContent)
119138
120139 // Aggregate the content to add it to the history before we finish.
121- let aggregated = aggregatedChunks ( aggregatedContent)
122- history. append ( aggregated)
123-
140+ let aggregated = self . aggregatedChunks ( aggregatedContent)
141+ self . history. append ( aggregated)
124142 continuation. finish ( )
125143 }
126144 }
0 commit comments