Skip to content

Commit 7e5720a

Browse files
VyasGurusanchitmonga22
authored andcommitted
Thinking modde enabled + fixes
Toggle-able thinking mode from settings Fixed race issue in model loading/cancelling UTF-8 parser now matches llamacpp one Fixed error in vlm where camera stayed on even when you exit that section Default sys prompt when loading
1 parent b81095e commit 7e5720a

14 files changed

Lines changed: 367 additions & 127 deletions

File tree

examples/ios/RunAnywhereAI/RunAnywhereAI/App/RunAnywhereAIApp.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ struct RunAnywhereAIApp: App {
274274
name: "Qwen3 0.6B Q4_K_M",
275275
url: qwen3_06bURL,
276276
framework: .llamaCpp,
277-
memoryRequirement: 500_000_000
277+
memoryRequirement: 500_000_000,
278+
supportsThinking: true
278279
)
279280
}
280281
if let qwen3_17bURL = URL(string: "https://huggingface.co/unsloth/Qwen3-1.7B-GGUF/resolve/main/Qwen3-1.7B-Q4_K_M.gguf") {
@@ -283,7 +284,8 @@ struct RunAnywhereAIApp: App {
283284
name: "Qwen3 1.7B Q4_K_M",
284285
url: qwen3_17bURL,
285286
framework: .llamaCpp,
286-
memoryRequirement: 1_200_000_000
287+
memoryRequirement: 1_200_000_000,
288+
supportsThinking: true
287289
)
288290
}
289291
if let qwen3_4bURL = URL(string: "https://huggingface.co/unsloth/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf") {
@@ -292,7 +294,8 @@ struct RunAnywhereAIApp: App {
292294
name: "Qwen3 4B Q4_K_M",
293295
url: qwen3_4bURL,
294296
framework: .llamaCpp,
295-
memoryRequirement: 2_800_000_000
297+
memoryRequirement: 2_800_000_000,
298+
supportsThinking: true
296299
)
297300
}
298301

@@ -303,7 +306,8 @@ struct RunAnywhereAIApp: App {
303306
name: "Qwen3.5 0.8B Q4_K_M",
304307
url: qwen35_08bURL,
305308
framework: .llamaCpp,
306-
memoryRequirement: 600_000_000
309+
memoryRequirement: 600_000_000,
310+
supportsThinking: true
307311
)
308312
}
309313
if let qwen35_2bURL = URL(string: "https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/resolve/main/Qwen3.5-2B-Q4_K_M.gguf") {
@@ -312,7 +316,8 @@ struct RunAnywhereAIApp: App {
312316
name: "Qwen3.5 2B Q4_K_M",
313317
url: qwen35_2bURL,
314318
framework: .llamaCpp,
315-
memoryRequirement: 1_500_000_000
319+
memoryRequirement: 1_500_000_000,
320+
supportsThinking: true
316321
)
317322
}
318323
if let qwen35_4bURL = URL(string: "https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/Qwen3.5-4B-Q4_K_M.gguf") {
@@ -321,7 +326,8 @@ struct RunAnywhereAIApp: App {
321326
name: "Qwen3.5 4B Q4_K_M",
322327
url: qwen35_4bURL,
323328
framework: .llamaCpp,
324-
memoryRequirement: 2_800_000_000
329+
memoryRequirement: 2_800_000_000,
330+
supportsThinking: true
325331
)
326332
}
327333

examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+Events.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ extension LLMViewModel {
3737
if let id = modelId,
3838
let matchingModel = ModelListViewModel.shared.availableModels.first(where: { $0.id == id }) {
3939
self.updateLoadedModelInfo(name: matchingModel.name, framework: matchingModel.framework)
40+
self.setLoadedModelSupportsThinking(matchingModel.supportsThinking)
4041
}
4142
}
4243
}
@@ -89,6 +90,7 @@ extension LLMViewModel {
8990

9091
if let matchingModel = ModelListViewModel.shared.availableModels.first(where: { $0.id == modelId }) {
9192
updateLoadedModelInfo(name: matchingModel.name, framework: matchingModel.framework)
93+
setLoadedModelSupportsThinking(matchingModel.supportsThinking)
9294
}
9395

9496
if !wasLoaded {

examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel+ModelManagement.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extension LLMViewModel {
1919
await MainActor.run {
2020
self.updateModelLoadedState(isLoaded: true)
2121
self.updateLoadedModelInfo(name: modelInfo.name, framework: modelInfo.framework)
22+
self.setLoadedModelSupportsThinking(modelInfo.supportsThinking)
2223
self.updateSystemMessageAfterModelLoad()
2324
}
2425
} catch {
@@ -39,6 +40,7 @@ extension LLMViewModel {
3940
if let currentModel = modelListViewModel.currentModel {
4041
self.updateModelLoadedState(isLoaded: true)
4142
self.updateLoadedModelInfo(name: currentModel.name, framework: currentModel.framework)
43+
self.setLoadedModelSupportsThinking(currentModel.supportsThinking)
4244
verifyModelLoaded(currentModel)
4345
} else {
4446
self.updateModelLoadedState(isLoaded: false)

examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/ViewModels/LLMViewModel.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ final class LLMViewModel {
2929
private(set) var error: Error?
3030
private(set) var isModelLoaded = false
3131
private(set) var loadedModelName: String?
32+
private(set) var loadedModelSupportsThinking = false
3233
private(set) var selectedFramework: InferenceFramework?
3334
private(set) var modelSupportsStreaming = true
3435
private(set) var currentConversation: Conversation?
@@ -80,8 +81,13 @@ final class LLMViewModel {
8081
selectedFramework = framework
8182
}
8283

84+
func setLoadedModelSupportsThinking(_ value: Bool) {
85+
loadedModelSupportsThinking = value
86+
}
87+
8388
func clearLoadedModelInfo() {
8489
loadedModelName = nil
90+
loadedModelSupportsThinking = false
8591
selectedFramework = nil
8692
}
8793

@@ -244,14 +250,21 @@ final class LLMViewModel {
244250
do {
245251
try await ensureModelIsLoaded()
246252
let options = getGenerationOptions()
247-
try await performGeneration(prompt: prompt, options: options, messageIndex: messageIndex)
253+
let effectivePrompt = applyThinkingModePrefix(to: prompt)
254+
try await performGeneration(prompt: effectivePrompt, options: options, messageIndex: messageIndex)
248255
} catch {
249256
await handleGenerationError(error, at: messageIndex)
250257
}
251258

252259
await finalizeGeneration(at: messageIndex)
253260
}
254261

262+
private func applyThinkingModePrefix(to prompt: String) -> String {
263+
guard loadedModelSupportsThinking else { return prompt }
264+
let thinkingModeEnabled = SettingsViewModel.shared.thinkingModeEnabled
265+
return thinkingModeEnabled ? prompt : "/no_think\n\(prompt)"
266+
}
267+
255268
private func performGeneration(
256269
prompt: String,
257270
options: LLMGenerationOptions,
@@ -476,20 +489,17 @@ final class LLMViewModel {
476489
if !isModelLoaded {
477490
throw LLMError.noModelLoaded
478491
}
479-
480-
// Verify model is actually loaded in SDK
481-
if let model = ModelListViewModel.shared.currentModel {
482-
try await RunAnywhere.loadModel(model.id)
483-
}
484492
}
485493

486494
private func getGenerationOptions() -> LLMGenerationOptions {
487-
let savedTemperature = UserDefaults.standard.double(forKey: "defaultTemperature")
495+
// Use object(forKey:) to distinguish an unset key (nil) from a value explicitly set to 0.0
496+
let savedTemperature = UserDefaults.standard.object(forKey: "defaultTemperature") as? Double
488497
let savedMaxTokens = UserDefaults.standard.integer(forKey: "defaultMaxTokens")
489498
let savedSystemPrompt = UserDefaults.standard.string(forKey: "defaultSystemPrompt")
499+
let thinkingModeEnabled = SettingsViewModel.shared.thinkingModeEnabled
490500

491501
let effectiveSettings = (
492-
temperature: savedTemperature != 0 ? savedTemperature : Self.defaultTemperatureValue,
502+
temperature: savedTemperature ?? Self.defaultTemperatureValue,
493503
maxTokens: savedMaxTokens != 0 ? savedMaxTokens : Self.defaultMaxTokensValue
494504
)
495505

@@ -501,7 +511,7 @@ final class LLMViewModel {
501511
}()
502512

503513
logger.info(
504-
"[PARAMS] App getGenerationOptions: temperature=\(effectiveSettings.temperature), maxTokens=\(effectiveSettings.maxTokens), systemPrompt=\(systemPromptInfo)"
514+
"[PARAMS] App getGenerationOptions: temperature=\(effectiveSettings.temperature), maxTokens=\(effectiveSettings.maxTokens), thinkingMode=\(thinkingModeEnabled), systemPrompt=\(systemPromptInfo)"
505515
)
506516

507517
return LLMGenerationOptions(
@@ -519,8 +529,8 @@ final class LLMViewModel {
519529
}
520530

521531
private func ensureSettingsAreApplied() async {
522-
let savedTemperature = UserDefaults.standard.double(forKey: "defaultTemperature")
523-
let temperature = savedTemperature != 0 ? savedTemperature : Self.defaultTemperatureValue
532+
let savedTemperature = UserDefaults.standard.object(forKey: "defaultTemperature") as? Double
533+
let temperature = savedTemperature ?? Self.defaultTemperatureValue
524534

525535
let savedMaxTokens = UserDefaults.standard.integer(forKey: "defaultMaxTokens")
526536
let maxTokens = savedMaxTokens != 0 ? savedMaxTokens : Self.defaultMaxTokensValue
@@ -542,6 +552,7 @@ final class LLMViewModel {
542552
await MainActor.run {
543553
self.isModelLoaded = true
544554
self.loadedModelName = model.name
555+
self.loadedModelSupportsThinking = model.supportsThinking
545556
self.selectedFramework = model.framework
546557
self.modelSupportsStreaming = supportsStreaming
547558

examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Chat/Views/ChatInterfaceView.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct ChatInterfaceView: View {
3131
@State private var showingLoRAManagement = false
3232
@State private var pendingLoRAURL: URL?
3333
@State private var loraScale: Float = 1.0
34+
@AppStorage("thinkingModeEnabled") private var thinkingModeEnabled = false
3435
@FocusState private var isTextFieldFocused: Bool
3536

3637
private let logger = Logger(
@@ -445,8 +446,12 @@ extension ChatInterfaceView {
445446
VStack(spacing: 0) {
446447
Divider()
447448

448-
// Status badges (tool calling + LoRA)
449+
// Status badges (thinking mode + tool calling + LoRA)
449450
HStack(spacing: 8) {
451+
if thinkingModeEnabled && viewModel.loadedModelSupportsThinking {
452+
thinkingModeBadge
453+
}
454+
450455
if viewModel.useToolCalling {
451456
toolCallingBadge
452457
}
@@ -459,7 +464,7 @@ extension ChatInterfaceView {
459464
loraAddButton
460465
}
461466
}
462-
.padding(.top, (viewModel.useToolCalling || !viewModel.loraAdapters.isEmpty || hasModelSelected) ? 8 : 0)
467+
.padding(.top, ((thinkingModeEnabled && viewModel.loadedModelSupportsThinking) || viewModel.useToolCalling || !viewModel.loraAdapters.isEmpty || hasModelSelected) ? 8 : 0)
463468

464469
HStack(spacing: AppSpacing.mediumLarge) {
465470
TextField("Type a message...", text: $viewModel.currentInput, axis: .vertical)
@@ -493,6 +498,24 @@ extension ChatInterfaceView {
493498
}
494499
}
495500

501+
var thinkingModeBadge: some View {
502+
Button {
503+
thinkingModeEnabled.toggle()
504+
} label: {
505+
HStack(spacing: 6) {
506+
Image(systemName: "lightbulb.min.fill")
507+
.font(.system(size: 10))
508+
Text("Thinking")
509+
.font(AppTypography.caption2)
510+
}
511+
.foregroundColor(AppColors.primaryPurple)
512+
.padding(.horizontal, 10)
513+
.padding(.vertical, 4)
514+
.background(AppColors.primaryPurple.opacity(0.1))
515+
.cornerRadius(6)
516+
}
517+
}
518+
496519
var toolCallingBadge: some View {
497520
HStack(spacing: 6) {
498521
Image(systemName: "wrench.and.screwdriver")

examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Models/ModelListViewModel.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,14 @@ class ModelListViewModel: ObservableObject {
129129
await loadModelsFromRegistry()
130130
}
131131

132+
private var isLoadingModel = false
133+
132134
/// Select and load a model
133135
func selectModel(_ model: ModelInfo) async {
136+
guard !isLoadingModel else { return }
137+
isLoadingModel = true
138+
defer { isLoadingModel = false }
139+
134140
do {
135141
try await loadModel(model)
136142
setCurrentModel(model)

examples/ios/RunAnywhereAI/RunAnywhereAI/Features/Settings/CombinedSettingsView.swift

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Combine
1212

1313
struct CombinedSettingsView: View {
1414
// ViewModel - all business logic is here
15-
@StateObject private var viewModel = SettingsViewModel()
15+
@ObservedObject private var viewModel = SettingsViewModel.shared
1616
@StateObject private var toolViewModel = ToolSettingsViewModel.shared
1717

1818
var body: some View {
@@ -49,6 +49,18 @@ struct CombinedSettingsView: View {
4949
}
5050
}
5151

52+
// MARK: - Helpers
53+
54+
@MainActor
55+
private func thinkingModeDescription(for viewModel: SettingsViewModel) -> String {
56+
guard viewModel.loadedModelSupportsThinking else {
57+
return "Not available for the currently loaded model."
58+
}
59+
return viewModel.thinkingModeEnabled
60+
? "Model will use its default thinking/reasoning mode."
61+
: "Thinking disabled. The model will skip its reasoning step."
62+
}
63+
5264
// MARK: - iOS Layout
5365

5466
private struct IOSSettingsContent: View {
@@ -72,6 +84,13 @@ private struct IOSSettingsContent: View {
7284
in: 500...20000,
7385
step: 500
7486
)
87+
88+
Toggle("Thinking Mode", isOn: $viewModel.thinkingModeEnabled)
89+
.disabled(!viewModel.loadedModelSupportsThinking)
90+
91+
Text(thinkingModeDescription(for: viewModel))
92+
.font(AppTypography.caption)
93+
.foregroundColor(AppColors.textSecondary)
7594
}
7695

7796
// System Prompt
@@ -179,6 +198,7 @@ private struct IOSSettingsContent: View {
179198
}
180199
}
181200
.navigationTitle("Settings")
201+
.scrollDismissesKeyboard(.interactively)
182202
}
183203
}
184204

@@ -261,6 +281,28 @@ private struct GenerationSettingsCard: View {
261281
.frame(maxWidth: 400)
262282
}
263283
}
284+
285+
HStack {
286+
Text("Thinking Mode")
287+
.frame(width: 150, alignment: .leading)
288+
289+
Toggle("", isOn: $viewModel.thinkingModeEnabled)
290+
.disabled(!viewModel.loadedModelSupportsThinking)
291+
292+
Spacer()
293+
294+
Text(viewModel.thinkingModeEnabled ? "Enabled" : "Disabled")
295+
.font(AppTypography.caption)
296+
.foregroundColor(
297+
viewModel.thinkingModeEnabled
298+
? AppColors.primaryPurple
299+
: AppColors.textSecondary
300+
)
301+
}
302+
303+
Text(thinkingModeDescription(for: viewModel))
304+
.font(AppTypography.caption)
305+
.foregroundColor(AppColors.textSecondary)
264306
}
265307
}
266308
}

0 commit comments

Comments
 (0)