diff --git a/.pubignore b/.pubignore index e323b5d..ca42740 100644 --- a/.pubignore +++ b/.pubignore @@ -24,6 +24,7 @@ artifacts/faer_int8_* .venv/ *.env .env +*.log # 빌드 산출물 build/ @@ -37,6 +38,7 @@ rust/tests/ # 대용량 모델 파일 (사용자가 직접 다운로드) assets/* !assets/readme-sources/ +/models/ *.onnx *.bin @@ -72,5 +74,6 @@ example/assets/evalsets/ # Example evaluation/performance runners (local only) example/lib/*_runner.dart +example/lib/*_smoke.dart example/lib/profiling/ example/test/profiling/ diff --git a/CHANGELOG.md b/CHANGELOG.md index e6b719a..24ad612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.19.1 +* **Packaging**: + - Fixed the publish archive so `lib/models/*.dart` is included again. + +## 0.19.0 +* **Compatibility**: + - Replaced the unmaintained `onnxruntime` package dependency with `flutter_onnxruntime: ^1.8.0`. + - Updated ONNX session, tensor, output, and cleanup handling to the `flutter_onnxruntime` API while keeping the existing `EmbeddingService` public API. + - Raised the documented iOS/macOS runtime requirements to match `flutter_onnxruntime`: iOS 16.0+ and macOS 14.0+. +* **Validation**: + - Validated the runtime swap with local init/embed/repeat/dispose/reinit smoke coverage. + ## 0.18.6 * **PDF extraction UX**: - Added OCR-needed classification helpers for scanned/image-only PDF extraction errors. diff --git a/README.md b/README.md index 50c3294..acaecc8 100644 --- a/README.md +++ b/README.md @@ -88,11 +88,11 @@ Data never leaves the user's device. Perfect for privacy-focused apps (journals, | Platform | Minimum Version | |:---------|:----------------| -| **iOS** | 13.0+ | +| **iOS** | 16.0+ | | **Android** | API 21+ (Android 5.0 Lollipop) | -| **macOS** | 10.15+ (Catalina) | +| **macOS** | 14.0+ | -> **ONNX Runtime** is bundled automatically via the [`onnxruntime`](https://pub.dev/packages/onnxruntime) plugin. No additional native setup required. +> ONNX Runtime is provided through [`flutter_onnxruntime`](https://pub.dev/packages/flutter_onnxruntime). CocoaPods iOS builds require static framework linkage (`use_frameworks! :linkage => :static`), and Android release builds should keep ONNX Runtime classes in ProGuard/R8 rules. --- @@ -102,7 +102,7 @@ Data never leaves the user's device. Perfect for privacy-focused apps (journals, ```yaml dependencies: - mobile_rag_engine: + mobile_rag_engine: ^0.19.0 ``` ### 2. Download Model Files diff --git a/analysis_options.yaml b/analysis_options.yaml index 9b86078..e466fbb 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -9,6 +9,10 @@ # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml +analyzer: + exclude: + - docs/promo/** + linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` diff --git a/docs/guides/model_setup.md b/docs/guides/model_setup.md index fcd2039..235227f 100644 --- a/docs/guides/model_setup.md +++ b/docs/guides/model_setup.md @@ -154,12 +154,15 @@ python -m onnxruntime.quantization.quantize \ ## ONNX Runtime Notes -This package uses the [`onnxruntime`](https://pub.dev/packages/onnxruntime) Flutter plugin which bundles ONNX Runtime binaries for each platform. +This package uses the [`flutter_onnxruntime`](https://pub.dev/packages/flutter_onnxruntime) Flutter plugin for ONNX Runtime. -**No additional setup required** - the plugin automatically includes: -- iOS: CoreML execution provider (uses Neural Engine on A12+ chips) -- Android: NNAPI execution provider (hardware acceleration) -- macOS: CoreML execution provider +Runtime setup notes: +- iOS apps need minimum deployment target 16.0. +- macOS apps need minimum deployment target 14.0. +- CocoaPods iOS builds require static framework linkage: `use_frameworks! :linkage => :static`. +- Android release builds should keep ONNX Runtime classes in ProGuard/R8 rules. + +`mobile_rag_engine` uses the default ONNX Runtime execution path for embedding inference. Do not assume CoreML, NNAPI, or another hardware execution provider is active unless you configure and validate that path in your host app. ### Performance Tips diff --git a/docs/guides/quick_start.md b/docs/guides/quick_start.md index 88a0085..8d36263 100644 --- a/docs/guides/quick_start.md +++ b/docs/guides/quick_start.md @@ -7,7 +7,7 @@ Get started with `mobile_rag_engine` in 5 minutes. ## Prerequisites - Flutter 3.9+ -- iOS 13.0+ / Android API 21+ / macOS 10.15+ +- iOS 16.0+ / Android API 21+ / macOS 14.0+ --- @@ -16,7 +16,7 @@ Get started with `mobile_rag_engine` in 5 minutes. ```yaml # pubspec.yaml dependencies: - mobile_rag_engine: + mobile_rag_engine: ^0.19.0 ``` ```bash diff --git a/example/ios/Podfile b/example/ios/Podfile index 620e46e..6897dd7 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +platform :ios, '16.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -28,7 +28,7 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do - use_frameworks! + use_frameworks! :linkage => :static flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 2b248cf..2c197b2 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -34,16 +34,16 @@ PODS: - DKImagePickerController/PhotoGallery - Flutter - Flutter (1.0.0) - - integration_test (0.0.1): + - flutter_onnxruntime (0.0.1): - Flutter - - onnxruntime (0.0.1): + - onnxruntime-objc (= 1.24.2) + - integration_test (0.0.1): - Flutter - - onnxruntime-objc (= 1.15.1) - - onnxruntime-c (1.15.1) - - onnxruntime-objc (1.15.1): - - onnxruntime-objc/Core (= 1.15.1) - - onnxruntime-objc/Core (1.15.1): - - onnxruntime-c (= 1.15.1) + - onnxruntime-c (1.24.2) + - onnxruntime-objc (1.24.2): + - onnxruntime-objc/Core (= 1.24.2) + - onnxruntime-objc/Core (1.24.2): + - onnxruntime-c (= 1.24.2) - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -82,8 +82,8 @@ PODS: DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) + - flutter_onnxruntime (from `.symlinks/plugins/flutter_onnxruntime/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - onnxruntime (from `.symlinks/plugins/onnxruntime/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - rag_engine_flutter (from `.symlinks/plugins/rag_engine_flutter/ios`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) @@ -103,10 +103,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter + flutter_onnxruntime: + :path: ".symlinks/plugins/flutter_onnxruntime/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" - onnxruntime: - :path: ".symlinks/plugins/onnxruntime/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" rag_engine_flutter: @@ -119,10 +119,10 @@ SPEC CHECKSUMS: DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_onnxruntime: ce51ab1eeff3bc7251b14471067a23ccd9aa096b integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 - onnxruntime: e9346181d75b8dea8733bdae512a22c298962e00 - onnxruntime-c: ebdcfd8650bcbd10121c125262f99dea681b92a3 - onnxruntime-objc: ae7acec7a3d03eaf072d340afed7a35635c1c2a6 + onnxruntime-c: b58bafbc58707d165404665e46a7467a16e8e0e4 + onnxruntime-objc: 99e0fe4b6d606970c6c1350c194c7629ef6e6fc9 path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba rag_engine_flutter: f3dbe3eeb8de973a624800ac549837972cded6b2 SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 @@ -130,6 +130,6 @@ SPEC CHECKSUMS: sqlite3_flutter_libs: f9114e4bbe1f2e03dd543373c53d23245982ca13 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 -PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e +PODFILE CHECKSUM: c0915517eae1a513480956f02c7781054ca94770 COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 098a1cd..6753ead 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -198,7 +198,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - DC72460C826EAFDD93401E06 /* [CP] Embed Pods Frameworks */, + 38096AEDBDD0A0C79EEEBE18 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -270,6 +270,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 38096AEDBDD0A0C79EEEBE18 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -345,23 +362,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - DC72460C826EAFDD93401E06 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -455,7 +455,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -585,7 +585,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -636,7 +636,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 8bc93fe..cd98afe 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,11 +6,13 @@ import FlutterMacOS import Foundation import file_picker +import flutter_onnxruntime import path_provider_foundation import sqlite3_flutter_libs func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FlutterOnnxruntimePlugin.register(with: registry.registrar(forPlugin: "FlutterOnnxruntimePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) } diff --git a/example/macos/Podfile b/example/macos/Podfile index ff5ddb3..fa8465e 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.15' +platform :osx, '14.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -27,7 +27,7 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_macos_podfile_setup target 'Runner' do - use_frameworks! + use_frameworks! :linkage => :static flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 8847b30..455e330 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,9 +1,15 @@ PODS: - file_picker (0.0.1): - FlutterMacOS - - FlutterMacOS (1.0.0) - - onnxruntime (0.0.1): + - flutter_onnxruntime (0.0.1): - FlutterMacOS + - onnxruntime-objc (= 1.24.2) + - FlutterMacOS (1.0.0) + - onnxruntime-c (1.24.2) + - onnxruntime-objc (1.24.2): + - onnxruntime-objc/Core (= 1.24.2) + - onnxruntime-objc/Core (1.24.2): + - onnxruntime-c (= 1.24.2) - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -37,23 +43,25 @@ PODS: DEPENDENCIES: - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) + - flutter_onnxruntime (from `Flutter/ephemeral/.symlinks/plugins/flutter_onnxruntime/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - onnxruntime (from `Flutter/ephemeral/.symlinks/plugins/onnxruntime/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - rag_engine_flutter (from `Flutter/ephemeral/.symlinks/plugins/rag_engine_flutter/macos`) - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) SPEC REPOS: trunk: + - onnxruntime-c + - onnxruntime-objc - sqlite3 EXTERNAL SOURCES: file_picker: :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos + flutter_onnxruntime: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_onnxruntime/macos FlutterMacOS: :path: Flutter/ephemeral - onnxruntime: - :path: Flutter/ephemeral/.symlinks/plugins/onnxruntime/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin rag_engine_flutter: @@ -63,13 +71,15 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af + flutter_onnxruntime: b195bce8c50601372e976ed9726508b576ebff82 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - onnxruntime: 922a85d3c9ed1b3c2b515ec903fc4d9b8f51d96b + onnxruntime-c: b58bafbc58707d165404665e46a7467a16e8e0e4 + onnxruntime-objc: 99e0fe4b6d606970c6c1350c194c7629ef6e6fc9 path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba rag_engine_flutter: a9b5919020d1c7fb0e097bfcf6f8abb423e89a22 sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921 sqlite3_flutter_libs: f9114e4bbe1f2e03dd543373c53d23245982ca13 -PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 +PODFILE CHECKSUM: 429f567d9ab401e946f77b31dd025355fafef34e COCOAPODS: 1.16.2 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 929f1d9..8bd8fa7 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -240,7 +240,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - E3ED5274B15E2C2E5AB9C942 /* [CP] Embed Pods Frameworks */, + 6729B0AF3A76B7C0ED588A66 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -361,29 +361,24 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 94D2722D23F248381A35F2DA /* [CP] Check Pods Manifest.lock */ = { + 6729B0AF3A76B7C0ED588A66 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; - CA847B0746EECA0F4614813F /* [CP] Check Pods Manifest.lock */ = { + 94D2722D23F248381A35F2DA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -398,28 +393,33 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - E3ED5274B15E2C2E5AB9C942 /* [CP] Embed Pods Frameworks */ = { + CA847B0746EECA0F4614813F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -557,7 +557,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -639,7 +639,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -689,7 +689,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/example/pubspec.lock b/example/pubspec.lock index f916b67..81f63bf 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -115,6 +115,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_onnxruntime: + dependency: transitive + description: + name: flutter_onnxruntime + sha256: "616f0e296840edb63278c647baf4475232d21cba45478ff26348e474f3f6a913" + url: "https://pub.dev" + source: hosted + version: "1.8.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -229,15 +237,7 @@ packages: path: ".." relative: true source: path - version: "0.18.6" - onnxruntime: - dependency: transitive - description: - name: onnxruntime - sha256: e77ec05acafc135cc5fe7bcdf11b101b39f06513c9d5e9fa02cb1929f6bac72a - url: "https://pub.dev" - source: hosted - version: "1.4.1" + version: "0.19.1" path: dependency: transitive description: diff --git a/lib/services/embedding_service.dart b/lib/services/embedding_service.dart index 11201e7..6ad82f7 100644 --- a/lib/services/embedding_service.dart +++ b/lib/services/embedding_service.dart @@ -3,8 +3,9 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_onnxruntime/flutter_onnxruntime.dart'; import 'package:mobile_rag_engine/src/internal/embedding_dimension_state.dart'; -import 'package:onnxruntime/onnxruntime.dart'; import 'package:mobile_rag_engine/src/rust/api/tokenizer.dart'; import 'package:mobile_rag_engine/src/rust/frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart' @@ -77,6 +78,7 @@ class EmbeddingService { 'modelPath': modelPath, 'modelBytes': modelBytes, 'intraOpNumThreads': intraOpNumThreads, + 'rootIsolateToken': RootIsolateToken.instance, 'replyPort': replyPort.sendPort, }); @@ -193,8 +195,10 @@ Future _workerEntryPoint(SendPort mainSendPort) async { final receivePort = ReceivePort(); mainSendPort.send(receivePort.sendPort); + final ort = OnnxRuntime(); OrtSession? session; Set requiredInputNames = {}; + File? temporaryModelFile; await for (final message in receivePort) { final msg = message as Map; @@ -204,6 +208,13 @@ Future _workerEntryPoint(SendPort mainSendPort) async { switch (cmd) { case 'init': try { + final rootIsolateToken = msg['rootIsolateToken'] as RootIsolateToken?; + if (rootIsolateToken != null) { + BackgroundIsolateBinaryMessenger.ensureInitialized( + rootIsolateToken, + ); + } + // Initialize Rust FFI (required for tokenizer) if (Platform.isMacOS) { await RustLib.init( @@ -215,26 +226,28 @@ Future _workerEntryPoint(SendPort mainSendPort) async { await RustLib.init(); } - // Initialize ONNX Runtime - OrtEnv.instance.init(); - - final sessionOptions = OrtSessionOptions(); - final threads = msg['intraOpNumThreads'] as int?; - if (threads != null) { - try { - sessionOptions.setIntraOpNumThreads(threads); - } catch (_) { - // Best-effort; fall back to ONNX default. - } - } + final sessionOptions = OrtSessionOptions( + intraOpNumThreads: msg['intraOpNumThreads'] as int?, + ); final modelPath = msg['modelPath'] as String?; final modelBytes = msg['modelBytes'] as Uint8List?; if (modelPath != null) { - session = OrtSession.fromFile(File(modelPath), sessionOptions); + session = await ort.createSession( + modelPath, + options: sessionOptions, + ); } else if (modelBytes != null) { - session = OrtSession.fromBuffer(modelBytes, sessionOptions); + temporaryModelFile = File( + '${Directory.systemTemp.path}${Platform.pathSeparator}' + 'mobile_rag_engine_${DateTime.now().microsecondsSinceEpoch}.onnx', + ); + await temporaryModelFile.writeAsBytes(modelBytes, flush: true); + session = await ort.createSession( + temporaryModelFile.path, + options: sessionOptions, + ); } else { replyPort.send({ 'error': 'Either modelBytes or modelPath must be provided', @@ -279,15 +292,15 @@ Future _workerEntryPoint(SendPort mainSendPort) async { ); final shape = [1, seqLen]; - final inputIdsTensor = OrtValueTensor.createTensorWithDataList( + final inputIdsTensor = await OrtValue.fromList( inputIdsData, shape, ); - final attentionMaskTensor = OrtValueTensor.createTensorWithDataList( + final attentionMaskTensor = await OrtValue.fromList( attentionMaskData, shape, ); - OrtValueTensor? tokenTypeIdsTensor; + OrtValue? tokenTypeIdsTensor; final inputs = { 'input_ids': inputIdsTensor, @@ -298,7 +311,7 @@ Future _workerEntryPoint(SendPort mainSendPort) async { final tokenTypeIdsData = Int64List.fromList( List.filled(seqLen, 0), ); - tokenTypeIdsTensor = OrtValueTensor.createTensorWithDataList( + tokenTypeIdsTensor = await OrtValue.fromList( tokenTypeIdsData, shape, ); @@ -307,9 +320,9 @@ Future _workerEntryPoint(SendPort mainSendPort) async { // 3. Run inference final runOptions = OrtRunOptions(); - List? outputs; + Map? outputs; try { - outputs = await session.runAsync(runOptions, inputs); + outputs = await session.run(inputs, options: runOptions); } catch (e) { // Wrap input-signature errors with helpful context. final message = e.toString(); @@ -327,10 +340,9 @@ Future _workerEntryPoint(SendPort mainSendPort) async { } rethrow; } finally { - inputIdsTensor.release(); - attentionMaskTensor.release(); - tokenTypeIdsTensor?.release(); - runOptions.release(); + await inputIdsTensor.dispose(); + await attentionMaskTensor.dispose(); + await tokenTypeIdsTensor?.dispose(); } // 4. Extract results and apply mean pooling. @@ -338,13 +350,15 @@ Future _workerEntryPoint(SendPort mainSendPort) async { // and transfer the Float32List across the isolate boundary without // copying via TransferableTypedData. try { - final outputTensor = outputs?[0]; + final outputTensor = outputs['sentence_embedding'] ?? + outputs['sentence_embeddings'] ?? + outputs.values.firstOrNull; if (outputTensor == null) { replyPort.send({'error': 'ONNX inference returned null output'}); continue; } - final outputData = outputTensor.value as List; + final outputData = await outputTensor.asList(); Float32List embedding; if (outputData.isNotEmpty && outputData[0] is List) { @@ -394,8 +408,8 @@ Future _workerEntryPoint(SendPort mainSendPort) async { replyPort.send(TransferableTypedData.fromList([embedding])); } finally { - for (final output in outputs ?? []) { - output?.release(); + for (final output in outputs.values) { + await output.dispose(); } } } catch (e) { @@ -404,10 +418,11 @@ Future _workerEntryPoint(SendPort mainSendPort) async { case 'dispose': try { - session?.release(); + await session?.close(); session = null; requiredInputNames = {}; - OrtEnv.instance.release(); + await temporaryModelFile?.delete(); + temporaryModelFile = null; } catch (_) { // Best-effort cleanup. } diff --git a/pubspec.lock b/pubspec.lock index 02e87a7..1ca873e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -206,6 +206,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_onnxruntime: + dependency: "direct main" + description: + name: flutter_onnxruntime + sha256: "616f0e296840edb63278c647baf4475232d21cba45478ff26348e474f3f6a913" + url: "https://pub.dev" + source: hosted + version: "1.8.0" flutter_rust_bridge: dependency: "direct main" description: @@ -219,6 +227,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" freezed: dependency: "direct dev" description: @@ -363,14 +376,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - onnxruntime: - dependency: "direct main" - description: - name: onnxruntime - sha256: e77ec05acafc135cc5fe7bcdf11b101b39f06513c9d5e9fa02cb1929f6bac72a - url: "https://pub.dev" - source: hosted - version: "1.4.1" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0ee6518..7c30a15 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: mobile_rag_engine description: Build local/on-device RAG in Flutter with Dart APIs, offline semantic search, HNSW+BM25 hybrid retrieval, and text-layer PDF indexing. -version: 0.18.6 +version: 0.19.1 homepage: https://github.com/dev07060/mobile_rag_engine repository: https://github.com/dev07060/mobile_rag_engine issue_tracker: https://github.com/dev07060/mobile_rag_engine/issues @@ -19,10 +19,10 @@ environment: dependencies: flutter: sdk: flutter - flutter_rust_bridge: 2.11.1 + flutter_rust_bridge: ^2.11.1 rag_engine_flutter: ^0.18.4 path_provider: ^2.1.5 - onnxruntime: ^1.4.1 + flutter_onnxruntime: ^1.8.0 freezed_annotation: ^3.1.0 dev_dependencies: diff --git a/rust_builder/rust/src/api/hnsw_index.rs b/rust_builder/rust/src/api/hnsw_index.rs index 5fe4630..afb2979 100644 --- a/rust_builder/rust/src/api/hnsw_index.rs +++ b/rust_builder/rust/src/api/hnsw_index.rs @@ -66,7 +66,7 @@ pub fn build_hnsw_index(points: Vec<(i64, Vec)>) -> anyhow::Result<()> { // Adaptive parameters based on dataset size // - Small datasets (<1000): faster build, adequate recall // - Large datasets (>10000): higher quality, better recall - let (m, m0, ef_construction, size_category) = if count > 10_000 { + let (m, m0, ef_construction, _size_category) = if count > 10_000 { (24, 48, 200, "large (>10K)") } else if count > 1_000 { (20, 40, 150, "medium (1K-10K)") @@ -77,7 +77,7 @@ pub fn build_hnsw_index(points: Vec<(i64, Vec)>) -> anyhow::Result<()> { // Debug output for Flutter console (only in debug builds) #[cfg(debug_assertions)] { - println!("[HNSW] Dataset size: {} points ({})", count, size_category); + println!("[HNSW] Dataset size: {} points ({})", count, _size_category); println!( "[HNSW] Parameters: M={}, M0={}, efConstruction={}", m, m0, ef_construction diff --git a/rust_builder/rust/src/api/source_rag.rs b/rust_builder/rust/src/api/source_rag.rs index 267f72f..411d552 100644 --- a/rust_builder/rust/src/api/source_rag.rs +++ b/rust_builder/rust/src/api/source_rag.rs @@ -2277,13 +2277,6 @@ pub fn search_chunks_in_collection( Ok(results) } -fn search_chunks_linear( - query_embedding: Vec, - top_k: u32, -) -> Result, RagError> { - search_chunks_linear_in_collection(DEFAULT_COLLECTION_ID, query_embedding, top_k) -} - fn search_chunks_linear_in_collection( collection_id: &str, query_embedding: Vec, diff --git a/rust_builder/rust/src/api/vector_quant.rs b/rust_builder/rust/src/api/vector_quant.rs index f0e17f8..a3e2eb6 100644 --- a/rust_builder/rust/src/api/vector_quant.rs +++ b/rust_builder/rust/src/api/vector_quant.rs @@ -34,6 +34,7 @@ pub fn dequantize_i8_to_f32(input: &[i8], scale: f32) -> Vec { input.iter().map(|v| (*v as f32) * scale).collect() } +#[cfg(any(test, feature = "bench"))] #[inline] pub fn i8_blob_from_slice(input: &[i8]) -> Vec { input.iter().map(|v| *v as u8).collect() @@ -41,10 +42,9 @@ pub fn i8_blob_from_slice(input: &[i8]) -> Vec { /// Quantize an `f32` embedding directly into the SQLite `BLOB` /// representation, skipping the intermediate `Vec` that -/// [`quantize_f32_to_i8`] + [`i8_blob_from_slice`] would otherwise -/// produce. Returns the quantized bytes together with the scale used -/// to dequantize them later. Behaviour is bit-for-bit equivalent to -/// calling the two helpers sequentially. +/// [`quantize_f32_to_i8`] plus a byte conversion would otherwise produce. +/// Returns the quantized bytes together with the scale used to dequantize +/// them later. Behaviour is bit-for-bit equivalent to the older two-step path. #[inline] pub fn quantize_f32_to_u8_blob(input: &[f32]) -> (Vec, f32) { if input.is_empty() {