Skip to content

Commit 1096408

Browse files
VyasGurushubhammalhotra28
authored andcommitted
Complete RAG Flutter implementation (full state) (#419)
RAG Flutter SDK. ->there are a bunch of UI/UX issues with this like button not loading, the round spinny download thing not being proper etc, but the rag pipeline works. -> also onnx and rag when built together were giving duplicate symbol errors as rag requires onnx, so a future task that should be done soon is to include onnx in core as well, and perhaps add a conditional?
1 parent 4568d7c commit 1096408

30 files changed

Lines changed: 2638 additions & 157 deletions

examples/flutter/RunAnywhereAI/lib/app/runanywhere_ai_app.dart

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import 'package:runanywhere_ai/core/design_system/app_spacing.dart';
1010
import 'package:runanywhere_ai/core/services/model_manager.dart';
1111
import 'package:runanywhere_ai/core/utilities/constants.dart';
1212
import 'package:runanywhere_ai/core/utilities/keychain_helper.dart';
13+
import 'package:runanywhere/public/extensions/rag_module.dart';
1314
import 'package:runanywhere_llamacpp/runanywhere_llamacpp.dart';
14-
import 'package:runanywhere_onnx/runanywhere_onnx.dart';
1515

1616
/// RunAnywhereAIApp (mirroring iOS RunAnywhereAIApp.swift)
1717
///
@@ -140,11 +140,8 @@ class _RunAnywhereAIAppState extends State<RunAnywhereAIApp> {
140140
Future<void> _registerModulesAndModels() async {
141141
debugPrint('📦 Registering modules with their models...');
142142

143-
// LlamaCPP module with LLM models
144-
// Using explicit IDs ensures models are recognized after download across app restarts
143+
// --- LLAMACPP MODULE ---
145144
await LlamaCpp.register();
146-
147-
// Yield after heavy backend registration
148145
await Future<void>.delayed(Duration.zero);
149146

150147
LlamaCpp.addModel(
@@ -190,8 +187,6 @@ class _RunAnywhereAIAppState extends State<RunAnywhereAIApp> {
190187
memoryRequirement: 400000000,
191188
);
192189

193-
// Tool Calling Optimized Models
194-
// LFM2-1.2B-Tool - Designed for concise and precise tool calling (Liquid AI)
195190
LlamaCpp.addModel(
196191
id: 'lfm2-1.2b-tool-q4_k_m',
197192
name: 'LiquidAI LFM2 1.2B Tool Q4_K_M',
@@ -206,16 +201,10 @@ class _RunAnywhereAIAppState extends State<RunAnywhereAIApp> {
206201
'https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q8_0.gguf',
207202
memoryRequirement: 1400000000,
208203
);
209-
debugPrint('✅ LlamaCPP module registered with LLM models (including tool-calling optimized models)');
210-
211-
// Yield between module registrations
204+
debugPrint('✅ LlamaCPP module registered');
212205
await Future<void>.delayed(Duration.zero);
213206

214-
// Register VLM (Vision Language) models
215-
// VLM models require 2 files: main model + mmproj (vision projector)
216-
// Bundled as tar.gz archives for easy download/extraction
217-
218-
// SmolVLM 500M - Ultra-lightweight VLM for mobile (~500MB total)
207+
// --- VLM MODULE ---
219208
RunAnywhere.registerModel(
220209
id: 'smolvlm-500m-instruct-q8_0',
221210
name: 'SmolVLM 500M Instruct',
@@ -229,57 +218,81 @@ class _RunAnywhereAIAppState extends State<RunAnywhereAIApp> {
229218
memoryRequirement: 600000000,
230219
);
231220
debugPrint('✅ VLM models registered');
232-
233-
// Yield between module registrations
234-
await Future<void>.delayed(Duration.zero);
235-
236-
// Diffusion (image generation) is not registered here. CoreML diffusion is supported
237-
// only in the Swift SDK and Swift example app; Flutter/RN do not register diffusion.
238-
239-
// ONNX module with STT and TTS models
240-
// Using tar.gz format hosted on RunanywhereAI/sherpa-onnx for fast native extraction
241-
// Using explicit IDs ensures models are recognized after download across app restarts
242-
await Onnx.register();
243-
244-
// Yield after heavy backend registration
245221
await Future<void>.delayed(Duration.zero);
246222

223+
// --- ONNX MODULE (STT/TTS via Core SDK) ---
247224
// STT Models (Sherpa-ONNX Whisper)
248-
Onnx.addModel(
225+
RunAnywhere.registerModel(
249226
id: 'sherpa-onnx-whisper-tiny.en',
250227
name: 'Sherpa Whisper Tiny (ONNX)',
251-
url:
252-
'https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/sherpa-onnx-whisper-tiny.en.tar.gz',
228+
url: Uri.parse('https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/sherpa-onnx-whisper-tiny.en.tar.gz'),
229+
framework: InferenceFramework.onnx,
253230
modality: ModelCategory.speechRecognition,
254231
memoryRequirement: 75000000,
255232
);
256-
Onnx.addModel(
233+
234+
RunAnywhere.registerModel(
257235
id: 'sherpa-onnx-whisper-small.en',
258236
name: 'Sherpa Whisper Small (ONNX)',
259-
url:
260-
'https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/sherpa-onnx-whisper-small.en.tar.gz',
237+
url: Uri.parse('https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/sherpa-onnx-whisper-small.en.tar.gz'),
238+
framework: InferenceFramework.onnx,
261239
modality: ModelCategory.speechRecognition,
262240
memoryRequirement: 250000000,
263241
);
264242

265243
// TTS Models (Piper VITS)
266-
Onnx.addModel(
244+
RunAnywhere.registerModel(
267245
id: 'vits-piper-en_US-lessac-medium',
268246
name: 'Piper TTS (US English - Medium)',
269-
url:
270-
'https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/vits-piper-en_US-lessac-medium.tar.gz',
247+
url: Uri.parse('https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/vits-piper-en_US-lessac-medium.tar.gz'),
248+
framework: InferenceFramework.onnx,
271249
modality: ModelCategory.speechSynthesis,
272250
memoryRequirement: 65000000,
273251
);
274-
Onnx.addModel(
252+
253+
RunAnywhere.registerModel(
275254
id: 'vits-piper-en_GB-alba-medium',
276255
name: 'Piper TTS (British English)',
277-
url:
278-
'https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/vits-piper-en_GB-alba-medium.tar.gz',
256+
url: Uri.parse('https://github.com/RunanywhereAI/sherpa-onnx/releases/download/runanywhere-models-v1/vits-piper-en_GB-alba-medium.tar.gz'),
257+
framework: InferenceFramework.onnx,
279258
modality: ModelCategory.speechSynthesis,
280259
memoryRequirement: 65000000,
281260
);
282-
debugPrint('✅ ONNX module registered with STT/TTS models');
261+
debugPrint('✅ STT/TTS models registered via Core SDK');
262+
await Future<void>.delayed(Duration.zero);
263+
264+
// --- RAG EMBEDDINGS ---
265+
RunAnywhere.registerMultiFileModel(
266+
id: 'all-minilm-l6-v2',
267+
name: 'All MiniLM L6 v2 (Embedding)',
268+
files: [
269+
ModelFileDescriptor(
270+
relativePath: 'model.onnx',
271+
destinationPath: 'model.onnx',
272+
url: Uri.parse(
273+
'https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/onnx/model.onnx'),
274+
),
275+
ModelFileDescriptor(
276+
relativePath: 'vocab.txt',
277+
destinationPath: 'vocab.txt',
278+
url: Uri.parse(
279+
'https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/vocab.txt'),
280+
),
281+
],
282+
framework: InferenceFramework.onnx,
283+
modality: ModelCategory.embedding,
284+
memoryRequirement: 25500000,
285+
);
286+
debugPrint('✅ ONNX Embedding models registered');
287+
await Future<void>.delayed(Duration.zero);
288+
289+
// --- RAG BACKEND ---
290+
try {
291+
await RAGModule.register();
292+
debugPrint('✅ RAG backend registered');
293+
} catch (e) {
294+
debugPrint('⚠️ RAG backend not available (RAG features disabled): $e');
295+
}
283296

284297
debugPrint('🎉 All modules and models registered');
285298
}

examples/flutter/RunAnywhereAI/lib/features/chat/chat_interface_view.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'package:runanywhere_ai/features/models/model_selection_sheet.dart';
1616
import 'package:runanywhere_ai/features/models/model_status_components.dart';
1717
import 'package:runanywhere_ai/features/models/model_types.dart';
1818
import 'package:runanywhere_ai/features/settings/tool_settings_view_model.dart';
19+
import 'package:runanywhere_ai/features/rag/rag_demo_view.dart';
1920
import 'package:runanywhere_ai/features/structured_output/structured_output_view.dart';
2021
import 'package:shared_preferences/shared_preferences.dart';
2122

@@ -454,6 +455,17 @@ class _ChatInterfaceViewState extends State<ChatInterfaceView> {
454455
appBar: AppBar(
455456
title: const Text('Chat'),
456457
actions: [
458+
IconButton(
459+
icon: const Icon(Icons.article_outlined),
460+
onPressed: () {
461+
Navigator.of(context).push<void>(
462+
MaterialPageRoute<void>(
463+
builder: (context) => const RagDemoView(),
464+
),
465+
);
466+
},
467+
tooltip: 'Document Q&A',
468+
),
457469
IconButton(
458470
icon: const Icon(Icons.data_object),
459471
onPressed: () {
@@ -947,4 +959,4 @@ class _MessageBubbleState extends State<_MessageBubble> {
947959
),
948960
);
949961
}
950-
}
962+
}

examples/flutter/RunAnywhereAI/lib/features/models/model_list_view_model.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class ModelListViewModel extends ChangeNotifier {
106106
return ModelCategory.imageGeneration;
107107
case sdk.ModelCategory.audio:
108108
return ModelCategory.audio;
109+
case sdk.ModelCategory.embedding:
110+
return ModelCategory.embedding;
109111
}
110112
}
111113

examples/flutter/RunAnywhereAI/lib/features/models/model_selection_sheet.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,15 @@ class _ModelSelectionSheetState extends State<ModelSelectionSheet> {
507507
});
508508

509509
try {
510-
// Update view model selection state
511-
await _viewModel.selectModel(model);
510+
// RAG contexts record the selection only — do NOT pre-load into memory.
511+
// The RAG pipeline loads models on demand when the document is ingested.
512+
final isRagContext = widget.context == ModelSelectionContext.ragEmbedding ||
513+
widget.context == ModelSelectionContext.ragLLM;
514+
515+
if (!isRagContext) {
516+
// Update view model selection state (loads the model into memory)
517+
await _viewModel.selectModel(model);
518+
}
512519

513520
// Call the callback - this is where the actual model loading happens
514521
// The callback knows the correct context and how to load the model

examples/flutter/RunAnywhereAI/lib/features/models/model_status_components.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ class ModelRequiredOverlay extends StatelessWidget {
251251
return Icons.mic;
252252
case ModelSelectionContext.vlm:
253253
return Icons.center_focus_strong;
254+
case ModelSelectionContext.ragEmbedding:
255+
return Icons.data_object;
256+
case ModelSelectionContext.ragLLM:
257+
return Icons.question_answer_outlined;
254258
}
255259
}
256260

@@ -266,6 +270,10 @@ class ModelRequiredOverlay extends StatelessWidget {
266270
return 'Voice Assistant';
267271
case ModelSelectionContext.vlm:
268272
return 'Vision Language Model';
273+
case ModelSelectionContext.ragEmbedding:
274+
return 'Document RAG';
275+
case ModelSelectionContext.ragLLM:
276+
return 'Document RAG';
269277
}
270278
}
271279

@@ -281,6 +289,10 @@ class ModelRequiredOverlay extends StatelessWidget {
281289
return 'Voice assistant requires multiple models. Let\'s set them up together.';
282290
case ModelSelectionContext.vlm:
283291
return 'Select a vision-language model to analyze images. Point your camera or pick a photo to get AI descriptions.';
292+
case ModelSelectionContext.ragEmbedding:
293+
return 'Select an embedding model to encode document chunks for retrieval.';
294+
case ModelSelectionContext.ragLLM:
295+
return 'Select a language model to generate answers from retrieved document context.';
284296
}
285297
}
286298
}

examples/flutter/RunAnywhereAI/lib/features/models/model_types.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ enum ModelSelectionContext {
124124
stt,
125125
tts,
126126
voice,
127-
vlm;
127+
vlm,
128+
ragEmbedding,
129+
ragLLM;
128130

129131
String get title {
130132
switch (this) {
@@ -138,6 +140,10 @@ enum ModelSelectionContext {
138140
return 'Select Model';
139141
case ModelSelectionContext.vlm:
140142
return 'Select VLM Model';
143+
case ModelSelectionContext.ragEmbedding:
144+
return 'Select Embedding Model';
145+
case ModelSelectionContext.ragLLM:
146+
return 'Select LLM Model';
141147
}
142148
}
143149

@@ -158,6 +164,10 @@ enum ModelSelectionContext {
158164
};
159165
case ModelSelectionContext.vlm:
160166
return {ModelCategory.vision, ModelCategory.multimodal};
167+
case ModelSelectionContext.ragEmbedding:
168+
return {ModelCategory.embedding};
169+
case ModelSelectionContext.ragLLM:
170+
return {ModelCategory.language};
161171
}
162172
}
163173
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Document Service
2+
//
3+
// Utility for extracting plain text from PDF and JSON files.
4+
// Used to prepare document content for RAG ingestion.
5+
6+
import 'dart:convert';
7+
import 'dart:io';
8+
9+
import 'package:syncfusion_flutter_pdf/pdf.dart';
10+
11+
// MARK: - DocumentService
12+
13+
/// Extracts plain text from PDF and JSON files.
14+
///
15+
/// Supports:
16+
/// - PDF — All page text concatenated with newlines (via syncfusion_flutter_pdf).
17+
/// - JSON — Raw UTF-8 file content returned as-is.
18+
class DocumentService {
19+
// Private constructor — static-only class.
20+
DocumentService._();
21+
22+
/// Extract plain text from a file at [filePath].
23+
///
24+
/// Determines the document type from the file extension.
25+
///
26+
/// Throws [UnsupportedError] for unsupported file extensions.
27+
/// Throws [Exception] if the file cannot be read or parsed.
28+
static Future<String> extractText(String filePath) async {
29+
final extension = filePath.split('.').last.toLowerCase();
30+
31+
switch (extension) {
32+
case 'pdf':
33+
return _extractPDFText(filePath);
34+
case 'json':
35+
return _extractJSONText(filePath);
36+
default:
37+
throw UnsupportedError(
38+
'Unsupported document format: .$extension. Only PDF and JSON files are supported.',
39+
);
40+
}
41+
}
42+
43+
// MARK: - Private Helpers
44+
45+
static Future<String> _extractPDFText(String filePath) async {
46+
final file = File(filePath);
47+
final bytes = await file.readAsBytes();
48+
49+
final document = PdfDocument(inputBytes: bytes);
50+
final extractor = PdfTextExtractor(document);
51+
52+
final buffer = StringBuffer();
53+
final pageCount = document.pages.count;
54+
55+
for (var i = 0; i < pageCount; i++) {
56+
final pageText = extractor.extractText(startPageIndex: i, endPageIndex: i);
57+
if (pageText.trim().isNotEmpty) {
58+
if (buffer.isNotEmpty) buffer.write('\n');
59+
buffer.write(pageText);
60+
}
61+
}
62+
63+
document.dispose();
64+
65+
final result = buffer.toString();
66+
if (result.isEmpty) {
67+
throw Exception(
68+
'Failed to extract text from PDF. The file may be corrupted or image-only.',
69+
);
70+
}
71+
72+
return result;
73+
}
74+
75+
static Future<String> _extractJSONText(String filePath) async {
76+
final file = File(filePath);
77+
return file.readAsString(encoding: utf8);
78+
}
79+
}

0 commit comments

Comments
 (0)