Skip to content

Commit b25639d

Browse files
committed
iOS local model implementation
1 parent f3fbcae commit b25639d

2 files changed

Lines changed: 61 additions & 6 deletions

File tree

packages/firebase_ai/firebase_ai/example/lib/pages/hybrid_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ class _HybridPageState extends State<HybridPage> {
166166
const SizedBox(height: 16),
167167
Expanded(
168168
child: SingleChildScrollView(
169-
child: Text(_response),
169+
child: SelectableText(_response),
170170
),
171171
),
172172
],

packages/firebase_ai/firebase_ai/ios/firebase_ai/Sources/firebase_ai/LocalAIImpl.swift

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,35 @@ import FoundationModels
2525
#endif
2626

2727
class LocalAIImpl: LocalAIApi {
28+
private static var _isModelReady: Bool?
29+
2830
func isAvailable(completion: @escaping (Result<Bool, Error>) -> Void) {
2931
#if canImport(FoundationModels)
3032
if #available(iOS 26.0, macOS 26.0, *) {
31-
completion(.success(true))
33+
if let ready = LocalAIImpl._isModelReady {
34+
completion(.success(ready))
35+
return
36+
}
37+
38+
let model = FoundationModels.SystemLanguageModel.default
39+
guard model.isAvailable else {
40+
LocalAIImpl._isModelReady = false
41+
completion(.success(false))
42+
return
43+
}
44+
45+
// Perform a lightweight check to verify the model is actually loadable and functional
46+
Task {
47+
do {
48+
let session = FoundationModels.LanguageModelSession(model: model)
49+
_ = try await session.respond(to: Prompt("Hello"))
50+
LocalAIImpl._isModelReady = true
51+
completion(.success(true))
52+
} catch {
53+
LocalAIImpl._isModelReady = false
54+
completion(.success(false))
55+
}
56+
}
3257
} else {
3358
completion(.success(false))
3459
}
@@ -42,7 +67,22 @@ class LocalAIImpl: LocalAIApi {
4267
if #available(iOS 26.0, macOS 26.0, *) {
4368
Task {
4469
do {
45-
completion(.success("Local response from FoundationModels for: \(prompt)"))
70+
let model = FoundationModels.SystemLanguageModel.default
71+
guard model.isAvailable else {
72+
completion(.failure(PigeonError(code: "UNAVAILABLE", message: "SystemLanguageModel is not available on this device", details: nil)))
73+
return
74+
}
75+
let session = FoundationModels.LanguageModelSession(model: model)
76+
let response = try await session.respond(to: Prompt(prompt))
77+
let content = response.rawContent
78+
79+
let responseText: String
80+
if case let .string(text) = content.kind {
81+
responseText = text
82+
} else {
83+
responseText = content.jsonString
84+
}
85+
completion(.success(responseText))
4686
} catch {
4787
completion(.failure(error))
4888
}
@@ -64,9 +104,24 @@ class LocalAIImpl: LocalAIApi {
64104
if #available(iOS 26.0, macOS 26.0, *) {
65105
Task {
66106
do {
67-
// Simulate streaming by sending chunks to the shared stream handler.
68-
LocalAIStreamHandler.shared.sendEvent("Local chunk 1 for: \(prompt)")
69-
LocalAIStreamHandler.shared.sendEvent("Local chunk 2 for: \(prompt)")
107+
let model = FoundationModels.SystemLanguageModel.default
108+
guard model.isAvailable else {
109+
completion(.failure(PigeonError(code: "UNAVAILABLE", message: "SystemLanguageModel is not available on this device", details: nil)))
110+
return
111+
}
112+
let session = FoundationModels.LanguageModelSession(model: model)
113+
let stream = session.streamResponse(to: Prompt(prompt))
114+
115+
for try await snapshot in stream {
116+
let content = snapshot.rawContent
117+
let chunkText: String
118+
if case let .string(text) = content.kind {
119+
chunkText = text
120+
} else {
121+
chunkText = content.jsonString
122+
}
123+
LocalAIStreamHandler.shared.sendEvent(chunkText)
124+
}
70125
LocalAIStreamHandler.shared.closeStream()
71126
completion(.success(()))
72127
} catch {

0 commit comments

Comments
 (0)