├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Package.swift ├── README.md ├── SVDBDemo ├── SVDBDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── SVDBDemo │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── ContentView.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── SVDBDemoApp.swift │ └── WORDS.swift ├── Sources └── SVDB │ ├── API │ ├── Collection.swift │ └── SVDB.swift │ └── Internals │ ├── Models │ ├── Document.swift │ ├── Errors.swift │ └── SearchResult.swift │ └── TheMathFile.swift └── Tests └── SVDBTests ├── CollectionTests.swift ├── SVDBTests.swift └── SearchTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SVDB", 8 | products: [ 9 | // Products define the executables and libraries a package produces, and make them visible to other packages. 10 | .library( 11 | name: "SVDB", 12 | targets: ["SVDB"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 21 | .target( 22 | name: "SVDB", 23 | dependencies: []), 24 | .testTarget( 25 | name: "SVDBTests", 26 | dependencies: ["SVDB"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Vector Database (SVDB) 2 | 3 | A new fast local on-device vector database for Swift Apps. 4 | 5 | Built for those building the next-generation of user experiences only possible with on-device intelligence. 6 | 7 | Local on-device vector databases are just the beginning. 8 | 9 | ## Installation 10 | To install it using the Swift Package Manager, either directly add it to your project using Xcode 11, or specify it as dependency in the Package.swift file: 11 | 12 | ``` 13 | // ... 14 | dependencies: [ 15 | .package(url: "https://github.com/Dripfarm/SVDB.git", from: "1.0.0"), 16 | ], 17 | //... 18 | ``` 19 | 20 | 21 | ## Usage 22 | 23 | ### 1. Create Embeddings 24 | ``` 25 | let document = "cat" 26 | ``` 27 | 28 | **ChatGPT:** 29 | 30 | I find [This Swift OpenAI package to be the best](https://github.com/MacPaw/OpenAI) 31 | 32 | ``` 33 | import OpenAI 34 | 35 | func embed(text: String) async -> [Double]? { 36 | let query = EmbeddingsQuery(model: .textEmbeddingAda, input: text) 37 | 38 | let result = try! await openAI.embeddings(query: query) 39 | 40 | return result.data.first?.embedding 41 | } 42 | 43 | let wordEmbedding = embed(text: document) 44 | ``` 45 | 46 | **NLEmbeddings** 47 | 48 | ``` 49 | import NaturalLanguage 50 | 51 | let embedding: NLEmbedding? = NLEmbedding.wordEmbedding(for: .english) 52 | 53 | let wordEmedding = embedding?.vector(for: document) //returns double array 54 | ``` 55 | 56 | ### 2. Add Documents 57 | 58 | ``` 59 | let animalCollection = SVDB.shared.collection("animals") 60 | 61 | SVDB.shared.addDocument(text: document, embedding: wordEmbedding) 62 | 63 | ``` 64 | 65 | ### 3. Search 66 | 67 | ``` 68 | let dogEmedding = embedding?.vector(for: "dog") 69 | 70 | let results = animalCollection.search(query: dogEmedding) 71 | ``` 72 | 73 | ## Demo 74 | 75 | Check out the demo [Demo](https://github.com/Dripfarm/SVDB/tree/master/SVDBDemo) 76 | 77 | ## Todo 78 | Not sure. I want to make it easier to add documents and take care of the embeddings for you. Any suggestions? -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 177872D12A7DF0FE00D52548 /* SVDBDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 177872D02A7DF0FE00D52548 /* SVDBDemoApp.swift */; }; 11 | 177872D32A7DF0FE00D52548 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 177872D22A7DF0FE00D52548 /* ContentView.swift */; }; 12 | 177872D52A7DF10000D52548 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 177872D42A7DF10000D52548 /* Assets.xcassets */; }; 13 | 177872D82A7DF10000D52548 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 177872D72A7DF10000D52548 /* Preview Assets.xcassets */; }; 14 | 17C2FE882A7DF61B00A3D246 /* WORDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C2FE872A7DF61B00A3D246 /* WORDS.swift */; }; 15 | 17C2FE8B2A7DF79300A3D246 /* SVDB in Frameworks */ = {isa = PBXBuildFile; productRef = 17C2FE8A2A7DF79300A3D246 /* SVDB */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 177872CD2A7DF0FE00D52548 /* SVDBDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SVDBDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 177872D02A7DF0FE00D52548 /* SVDBDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SVDBDemoApp.swift; sourceTree = ""; }; 21 | 177872D22A7DF0FE00D52548 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 22 | 177872D42A7DF10000D52548 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 177872D72A7DF10000D52548 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 24 | 17C2FE872A7DF61B00A3D246 /* WORDS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WORDS.swift; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | 177872CA2A7DF0FE00D52548 /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | 17C2FE8B2A7DF79300A3D246 /* SVDB in Frameworks */, 33 | ); 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXFrameworksBuildPhase section */ 37 | 38 | /* Begin PBXGroup section */ 39 | 177872C42A7DF0FE00D52548 = { 40 | isa = PBXGroup; 41 | children = ( 42 | 177872CF2A7DF0FE00D52548 /* SVDBDemo */, 43 | 177872CE2A7DF0FE00D52548 /* Products */, 44 | ); 45 | sourceTree = ""; 46 | }; 47 | 177872CE2A7DF0FE00D52548 /* Products */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | 177872CD2A7DF0FE00D52548 /* SVDBDemo.app */, 51 | ); 52 | name = Products; 53 | sourceTree = ""; 54 | }; 55 | 177872CF2A7DF0FE00D52548 /* SVDBDemo */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 177872D02A7DF0FE00D52548 /* SVDBDemoApp.swift */, 59 | 177872D22A7DF0FE00D52548 /* ContentView.swift */, 60 | 177872D42A7DF10000D52548 /* Assets.xcassets */, 61 | 177872D62A7DF10000D52548 /* Preview Content */, 62 | 17C2FE872A7DF61B00A3D246 /* WORDS.swift */, 63 | ); 64 | path = SVDBDemo; 65 | sourceTree = ""; 66 | }; 67 | 177872D62A7DF10000D52548 /* Preview Content */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 177872D72A7DF10000D52548 /* Preview Assets.xcassets */, 71 | ); 72 | path = "Preview Content"; 73 | sourceTree = ""; 74 | }; 75 | /* End PBXGroup section */ 76 | 77 | /* Begin PBXNativeTarget section */ 78 | 177872CC2A7DF0FE00D52548 /* SVDBDemo */ = { 79 | isa = PBXNativeTarget; 80 | buildConfigurationList = 177872DB2A7DF10000D52548 /* Build configuration list for PBXNativeTarget "SVDBDemo" */; 81 | buildPhases = ( 82 | 177872C92A7DF0FE00D52548 /* Sources */, 83 | 177872CA2A7DF0FE00D52548 /* Frameworks */, 84 | 177872CB2A7DF0FE00D52548 /* Resources */, 85 | ); 86 | buildRules = ( 87 | ); 88 | dependencies = ( 89 | ); 90 | name = SVDBDemo; 91 | packageProductDependencies = ( 92 | 17C2FE8A2A7DF79300A3D246 /* SVDB */, 93 | ); 94 | productName = SVDBDemo; 95 | productReference = 177872CD2A7DF0FE00D52548 /* SVDBDemo.app */; 96 | productType = "com.apple.product-type.application"; 97 | }; 98 | /* End PBXNativeTarget section */ 99 | 100 | /* Begin PBXProject section */ 101 | 177872C52A7DF0FE00D52548 /* Project object */ = { 102 | isa = PBXProject; 103 | attributes = { 104 | BuildIndependentTargetsInParallel = 1; 105 | LastSwiftUpdateCheck = 1430; 106 | LastUpgradeCheck = 1430; 107 | TargetAttributes = { 108 | 177872CC2A7DF0FE00D52548 = { 109 | CreatedOnToolsVersion = 14.3.1; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = 177872C82A7DF0FE00D52548 /* Build configuration list for PBXProject "SVDBDemo" */; 114 | compatibilityVersion = "Xcode 14.0"; 115 | developmentRegion = en; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = 177872C42A7DF0FE00D52548; 122 | packageReferences = ( 123 | 17C2FE892A7DF79300A3D246 /* XCRemoteSwiftPackageReference "SVDB" */, 124 | ); 125 | productRefGroup = 177872CE2A7DF0FE00D52548 /* Products */; 126 | projectDirPath = ""; 127 | projectRoot = ""; 128 | targets = ( 129 | 177872CC2A7DF0FE00D52548 /* SVDBDemo */, 130 | ); 131 | }; 132 | /* End PBXProject section */ 133 | 134 | /* Begin PBXResourcesBuildPhase section */ 135 | 177872CB2A7DF0FE00D52548 /* Resources */ = { 136 | isa = PBXResourcesBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | 177872D82A7DF10000D52548 /* Preview Assets.xcassets in Resources */, 140 | 177872D52A7DF10000D52548 /* Assets.xcassets in Resources */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXResourcesBuildPhase section */ 145 | 146 | /* Begin PBXSourcesBuildPhase section */ 147 | 177872C92A7DF0FE00D52548 /* Sources */ = { 148 | isa = PBXSourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | 177872D32A7DF0FE00D52548 /* ContentView.swift in Sources */, 152 | 17C2FE882A7DF61B00A3D246 /* WORDS.swift in Sources */, 153 | 177872D12A7DF0FE00D52548 /* SVDBDemoApp.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin XCBuildConfiguration section */ 160 | 177872D92A7DF10000D52548 /* Debug */ = { 161 | isa = XCBuildConfiguration; 162 | buildSettings = { 163 | ALWAYS_SEARCH_USER_PATHS = NO; 164 | CLANG_ANALYZER_NONNULL = YES; 165 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 166 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 167 | CLANG_ENABLE_MODULES = YES; 168 | CLANG_ENABLE_OBJC_ARC = YES; 169 | CLANG_ENABLE_OBJC_WEAK = YES; 170 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 171 | CLANG_WARN_BOOL_CONVERSION = YES; 172 | CLANG_WARN_COMMA = YES; 173 | CLANG_WARN_CONSTANT_CONVERSION = YES; 174 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 175 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 176 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 177 | CLANG_WARN_EMPTY_BODY = YES; 178 | CLANG_WARN_ENUM_CONVERSION = YES; 179 | CLANG_WARN_INFINITE_RECURSION = YES; 180 | CLANG_WARN_INT_CONVERSION = YES; 181 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 182 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 183 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 184 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 185 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 186 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 187 | CLANG_WARN_STRICT_PROTOTYPES = YES; 188 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 189 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | COPY_PHASE_STRIP = NO; 193 | DEBUG_INFORMATION_FORMAT = dwarf; 194 | ENABLE_STRICT_OBJC_MSGSEND = YES; 195 | ENABLE_TESTABILITY = YES; 196 | GCC_C_LANGUAGE_STANDARD = gnu11; 197 | GCC_DYNAMIC_NO_PIC = NO; 198 | GCC_NO_COMMON_BLOCKS = YES; 199 | GCC_OPTIMIZATION_LEVEL = 0; 200 | GCC_PREPROCESSOR_DEFINITIONS = ( 201 | "DEBUG=1", 202 | "$(inherited)", 203 | ); 204 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 205 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 206 | GCC_WARN_UNDECLARED_SELECTOR = YES; 207 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 208 | GCC_WARN_UNUSED_FUNCTION = YES; 209 | GCC_WARN_UNUSED_VARIABLE = YES; 210 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 211 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 212 | MTL_FAST_MATH = YES; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = iphoneos; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | }; 218 | name = Debug; 219 | }; 220 | 177872DA2A7DF10000D52548 /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 227 | CLANG_ENABLE_MODULES = YES; 228 | CLANG_ENABLE_OBJC_ARC = YES; 229 | CLANG_ENABLE_OBJC_WEAK = YES; 230 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 231 | CLANG_WARN_BOOL_CONVERSION = YES; 232 | CLANG_WARN_COMMA = YES; 233 | CLANG_WARN_CONSTANT_CONVERSION = YES; 234 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INFINITE_RECURSION = YES; 240 | CLANG_WARN_INT_CONVERSION = YES; 241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 245 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | COPY_PHASE_STRIP = NO; 253 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu11; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 265 | MTL_ENABLE_DEBUG_INFO = NO; 266 | MTL_FAST_MATH = YES; 267 | SDKROOT = iphoneos; 268 | SWIFT_COMPILATION_MODE = wholemodule; 269 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 270 | VALIDATE_PRODUCT = YES; 271 | }; 272 | name = Release; 273 | }; 274 | 177872DC2A7DF10000D52548 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 278 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 279 | CODE_SIGN_STYLE = Automatic; 280 | CURRENT_PROJECT_VERSION = 1; 281 | DEVELOPMENT_ASSET_PATHS = "\"SVDBDemo/Preview Content\""; 282 | DEVELOPMENT_TEAM = PPVB2USUND; 283 | ENABLE_PREVIEWS = YES; 284 | GENERATE_INFOPLIST_FILE = YES; 285 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 286 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 287 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 288 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 289 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 290 | LD_RUNPATH_SEARCH_PATHS = ( 291 | "$(inherited)", 292 | "@executable_path/Frameworks", 293 | ); 294 | MARKETING_VERSION = 1.0; 295 | PRODUCT_BUNDLE_IDENTIFIER = co.beginagain.SVDBDemo; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | SWIFT_EMIT_LOC_STRINGS = YES; 298 | SWIFT_VERSION = 5.0; 299 | TARGETED_DEVICE_FAMILY = "1,2"; 300 | }; 301 | name = Debug; 302 | }; 303 | 177872DD2A7DF10000D52548 /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 308 | CODE_SIGN_STYLE = Automatic; 309 | CURRENT_PROJECT_VERSION = 1; 310 | DEVELOPMENT_ASSET_PATHS = "\"SVDBDemo/Preview Content\""; 311 | DEVELOPMENT_TEAM = PPVB2USUND; 312 | ENABLE_PREVIEWS = YES; 313 | GENERATE_INFOPLIST_FILE = YES; 314 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 315 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 316 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 317 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 318 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 319 | LD_RUNPATH_SEARCH_PATHS = ( 320 | "$(inherited)", 321 | "@executable_path/Frameworks", 322 | ); 323 | MARKETING_VERSION = 1.0; 324 | PRODUCT_BUNDLE_IDENTIFIER = co.beginagain.SVDBDemo; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | SWIFT_EMIT_LOC_STRINGS = YES; 327 | SWIFT_VERSION = 5.0; 328 | TARGETED_DEVICE_FAMILY = "1,2"; 329 | }; 330 | name = Release; 331 | }; 332 | /* End XCBuildConfiguration section */ 333 | 334 | /* Begin XCConfigurationList section */ 335 | 177872C82A7DF0FE00D52548 /* Build configuration list for PBXProject "SVDBDemo" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | 177872D92A7DF10000D52548 /* Debug */, 339 | 177872DA2A7DF10000D52548 /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | 177872DB2A7DF10000D52548 /* Build configuration list for PBXNativeTarget "SVDBDemo" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | 177872DC2A7DF10000D52548 /* Debug */, 348 | 177872DD2A7DF10000D52548 /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | /* End XCConfigurationList section */ 354 | 355 | /* Begin XCRemoteSwiftPackageReference section */ 356 | 17C2FE892A7DF79300A3D246 /* XCRemoteSwiftPackageReference "SVDB" */ = { 357 | isa = XCRemoteSwiftPackageReference; 358 | repositoryURL = "git@github.com:Dripfarm/SVDB.git"; 359 | requirement = { 360 | kind = upToNextMajorVersion; 361 | minimumVersion = 1.0.0; 362 | }; 363 | }; 364 | /* End XCRemoteSwiftPackageReference section */ 365 | 366 | /* Begin XCSwiftPackageProductDependency section */ 367 | 17C2FE8A2A7DF79300A3D246 /* SVDB */ = { 368 | isa = XCSwiftPackageProductDependency; 369 | package = 17C2FE892A7DF79300A3D246 /* XCRemoteSwiftPackageReference "SVDB" */; 370 | productName = SVDB; 371 | }; 372 | /* End XCSwiftPackageProductDependency section */ 373 | }; 374 | rootObject = 177872C52A7DF0FE00D52548 /* Project object */; 375 | } 376 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "svdb", 5 | "kind" : "remoteSourceControl", 6 | "location" : "git@github.com:Dripfarm/SVDB.git", 7 | "state" : { 8 | "revision" : "cd44e9db1786b811277db1b151825e714b1995ac", 9 | "version" : "1.0.1" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SVDBDemo 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | import Accelerate 9 | import CoreML 10 | import NaturalLanguage 11 | import SVDB 12 | import SwiftUI 13 | 14 | struct EmbeddingEntry: Codable { 15 | let id: UUID 16 | let text: String 17 | let embedding: [Double] 18 | let magnitude: Double 19 | } 20 | 21 | func generateRandomSentence() -> String { 22 | var sentence = "" 23 | for _ in 1...5 { 24 | if let randomWord = words.randomElement() { 25 | sentence += randomWord + " " 26 | } 27 | } 28 | return sentence.trimmingCharacters(in: .whitespaces) 29 | } 30 | 31 | struct ContentView: View { 32 | let collectionName: String = "testCollection" 33 | @State private var collection: Collection? 34 | @State private var query: String = "emotions" 35 | @State private var newEntry: String = "" 36 | @State private var neighbors: [(String, Double)] = [] 37 | 38 | var body: some View { 39 | VStack { 40 | TextField("Enter query", text: $query) 41 | .padding() 42 | .textFieldStyle(RoundedBorderTextFieldStyle()) 43 | 44 | HStack { 45 | TextField("New Entry", text: $newEntry) 46 | .textFieldStyle(RoundedBorderTextFieldStyle()) 47 | Button("Add Entry") { 48 | Task { 49 | await addEntry(newEntry) 50 | } 51 | } 52 | } 53 | .padding() 54 | 55 | Button("Find Neighbors") { 56 | self.neighbors.removeAll() 57 | Task { 58 | await findNeighbors() 59 | } 60 | } 61 | 62 | Button("Generate Random Embeddings") { 63 | Task { 64 | await generateRandomEmbeddings() 65 | } 66 | } 67 | .padding() 68 | 69 | List(neighbors, id: \.0) { neighbor in 70 | Text("\(neighbor.0) - \(neighbor.1)") 71 | } 72 | } 73 | .padding() 74 | .onAppear { 75 | Task { 76 | await loadCollection() 77 | } 78 | } 79 | } 80 | 81 | func loadCollection() async { 82 | do { 83 | collection = try SVDB.shared.collection(collectionName) 84 | } catch { 85 | print("Failed to load collection:", error) 86 | } 87 | } 88 | 89 | func generateRandomEmbeddings() async { 90 | var randomSentences: [String] = [] 91 | for _ in 1...100 { 92 | let sentence = generateRandomSentence() 93 | randomSentences.append(sentence) 94 | } 95 | 96 | for sentence in randomSentences { 97 | await addEntry(sentence) 98 | } 99 | 100 | print("Done creating") 101 | } 102 | 103 | func addEntry(_ entry: String) async { 104 | guard let collection = collection else { return } 105 | guard let embedding = generateEmbedding(for: entry) else { 106 | return 107 | } 108 | 109 | collection.addDocument(text: entry, embedding: embedding) 110 | } 111 | 112 | func generateEmbedding(for sentence: String) -> [Double]? { 113 | guard let embedding = NLEmbedding.wordEmbedding(for: .english) else { 114 | return nil 115 | } 116 | 117 | let words = sentence.lowercased().split(separator: " ") 118 | guard let firstVector = embedding.vector(for: String(words.first!)) else { 119 | return nil 120 | } 121 | 122 | var vectorSum = [Double](firstVector) 123 | 124 | for word in words.dropFirst() { 125 | if let vector = embedding.vector(for: String(word)) { 126 | vDSP_vaddD(vectorSum, 1, vector, 1, &vectorSum, 1, vDSP_Length(vectorSum.count)) 127 | } 128 | } 129 | 130 | var vectorAverage = [Double](repeating: 0, count: vectorSum.count) 131 | var divisor = Double(words.count) 132 | vDSP_vsdivD(vectorSum, 1, &divisor, &vectorAverage, 1, vDSP_Length(vectorAverage.count)) 133 | 134 | return vectorAverage 135 | } 136 | 137 | func findNeighbors() async { 138 | guard let collection = collection else { return } 139 | guard let queryEmbedding = generateEmbedding(for: query) else { 140 | return 141 | } 142 | 143 | let results = collection.search(query: queryEmbedding) 144 | neighbors = results.map { ($0.text, $0.score) } 145 | } 146 | } 147 | 148 | struct ContentView_Previews: PreviewProvider { 149 | static var previews: some View { 150 | ContentView() 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo/SVDBDemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SVDBDemoApp.swift 3 | // SVDBDemo 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct SVDBDemoApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SVDBDemo/SVDBDemo/WORDS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WORDS.swift 3 | // SVDBDemo 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | let words = [ 9 | "apple", "banana", "cherry", "date", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon", 10 | 11 | "mango", "nectarine", "orange", "papaya", "quince", "raspberry", "strawberry", "tangerine", "watermelon", "blueberry", 12 | 13 | "dog", "cat", "mouse", "bird", "fish", "elephant", "giraffe", "tiger", "lion", "bear", 14 | 15 | "happy", "sad", "excited", "angry", "bored", "confused", "calm", "elated", "frustrated", "nervous", 16 | 17 | "love", "hate", "trust", "fear", "joy", "surprise", "anticipation", "disgust", "sadness", "acceptance", 18 | 19 | "run", "walk", "jump", "dance", "sing", "read", "write", "draw", "play", "swim", 20 | 21 | "sun", "moon", "star", "sky", "cloud", "rain", "snow", "wind", "storm", "thunder", 22 | 23 | "red", "blue", "green", "yellow", "purple", "pink", "brown", "black", "white", "gray", 24 | 25 | "spring", "summer", "fall", "winter", "morning", "noon", "evening", "night", "dawn", "dusk", 26 | 27 | "car", "truck", "boat", "plane", "train", "bicycle", "skateboard", "rollerblades", "scooter", "unicycle", 28 | 29 | "tree", "flower", "bush", "cactus", "fern", "moss", "mushroom", "vine", "grass", "weed", 30 | 31 | "shirt", "pants", "skirt", "dress", "socks", "shoes", "hat", "scarf", "gloves", "coat", 32 | 33 | "eat", "drink", "sleep", "wake", "talk", "listen", "learn", "teach", "laugh", "cry", 34 | 35 | "big", "small", "fast", "slow", "old", "young", "tall", "short", "loud", "quiet", 36 | 37 | "ocean", "lake", "river", "stream", "pond", "puddle", "fountain", "waterfall", "wave", "ripple", 38 | 39 | "rock", "stone", "pebble", "boulder", "gravel", "sand", "dirt", "mud", "clay", "silt", 40 | 41 | "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", 42 | 43 | "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", 44 | 45 | "breakfast", "lunch", "dinner", "snack", "appetizer", "dessert", "meal", "feast", "picnic", "buffet" 46 | ] 47 | -------------------------------------------------------------------------------- /Sources/SVDB/API/Collection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | import Accelerate 9 | import CoreML 10 | import NaturalLanguage 11 | 12 | @available(macOS 10.15, *) 13 | @available(iOS 13.0, *) 14 | public class Collection { 15 | private var documents: [UUID: Document] = [:] 16 | private let name: String 17 | 18 | init(name: String) { 19 | self.name = name 20 | } 21 | 22 | public func addDocument(id: UUID? = nil, text: String, embedding: [Double]) { 23 | let document = Document( 24 | id: id ?? UUID(), 25 | text: text, 26 | embedding: embedding 27 | ) 28 | 29 | documents[document.id] = document 30 | save() 31 | } 32 | 33 | public func addDocuments(_ docs: [Document]) { 34 | docs.forEach { documents[$0.id] = $0 } 35 | save() 36 | } 37 | 38 | public func removeDocument(byId id: UUID) { 39 | documents[id] = nil 40 | save() 41 | } 42 | 43 | public func search( 44 | query: [Double], 45 | num_results: Int = 10, 46 | threshold: Double? = nil 47 | ) -> [SearchResult] { 48 | let queryMagnitude = sqrt(query.reduce(0) { $0 + $1 * $1 }) 49 | 50 | var similarities: [SearchResult] = [] 51 | for document in documents.values { 52 | let id = document.id 53 | let text = document.text 54 | let vector = document.embedding 55 | let magnitude = sqrt(vector.reduce(0) { $0 + $1 * $1 }) 56 | let similarity = MathFunctions.cosineSimilarity(query, vector, magnitudeA: queryMagnitude, magnitudeB: magnitude) 57 | 58 | if let thresholdValue = threshold, similarity < thresholdValue { 59 | continue 60 | } 61 | 62 | similarities.append(SearchResult(id: id, text: text, score: similarity)) 63 | } 64 | 65 | return Array(similarities.sorted(by: { $0.score > $1.score }).prefix(num_results)) 66 | } 67 | 68 | private func save() { 69 | let svdbDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("SVDB") 70 | try? FileManager.default.createDirectory(at: svdbDirectory, withIntermediateDirectories: true, attributes: nil) 71 | 72 | let fileURL = svdbDirectory.appendingPathComponent("\(name).json") 73 | 74 | do { 75 | let encodedDocuments = try JSONEncoder().encode(documents) 76 | let compressedData = try (encodedDocuments as NSData).compressed(using: .zlib) 77 | try compressedData.write(to: fileURL) 78 | } catch { 79 | print("Failed to save documents: \(error.localizedDescription)") 80 | } 81 | } 82 | 83 | public func load() throws { 84 | let svdbDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("SVDB") 85 | let fileURL = svdbDirectory.appendingPathComponent("\(name).json") 86 | 87 | // Check if file exists 88 | guard FileManager.default.fileExists(atPath: fileURL.path) else { 89 | print("File does not exist for collection \(name), initializing with empty documents.") 90 | documents = [:] 91 | return 92 | } 93 | 94 | do { 95 | let compressedData = try Data(contentsOf: fileURL) 96 | 97 | let decompressedData = try (compressedData as NSData).decompressed(using: .zlib) 98 | documents = try JSONDecoder().decode([UUID: Document].self, from: decompressedData as Data) 99 | 100 | print("Successfully loaded collection: \(name)") 101 | } catch { 102 | print("Failed to load collection \(name): \(error.localizedDescription)") 103 | throw CollectionError.loadFailed(error.localizedDescription) 104 | } 105 | } 106 | 107 | public func clear() { 108 | documents.removeAll() 109 | save() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/SVDB/API/SVDB.swift: -------------------------------------------------------------------------------- 1 | import Accelerate 2 | import CoreML 3 | import NaturalLanguage 4 | 5 | @available(macOS 10.15, *) 6 | @available(iOS 13.0, *) 7 | public class SVDB { 8 | public static let shared = SVDB() 9 | private var collections: [String: Collection] = [:] 10 | 11 | private init() {} 12 | 13 | public func collection(_ name: String) throws -> Collection { 14 | if collections[name] != nil { 15 | throw SVDBError.collectionAlreadyExists 16 | } 17 | 18 | let collection = Collection(name: name) 19 | collections[name] = collection 20 | try collection.load() 21 | return collection 22 | } 23 | 24 | public func getCollection(_ name: String) -> Collection? { 25 | return collections[name] 26 | } 27 | 28 | public func releaseCollection(_ name: String) { 29 | collections[name] = nil 30 | } 31 | 32 | public func reset() { 33 | for (_, collection) in collections { 34 | collection.clear() 35 | } 36 | collections.removeAll() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/SVDB/Internals/Models/Document.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Document: Codable, Identifiable { 11 | public let id: UUID 12 | public let text: String 13 | public let embedding: [Double] 14 | public let magnitude: Double 15 | 16 | public init(id: UUID? = nil, text: String, embedding: [Double]) { 17 | self.id = id ?? UUID() 18 | self.text = text 19 | self.embedding = embedding 20 | self.magnitude = sqrt(embedding.reduce(0) { $0 + $1 * $1 }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SVDB/Internals/Models/Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum SVDBError: Error { 11 | case collectionAlreadyExists 12 | } 13 | 14 | public enum CollectionError: Error { 15 | case fileNotFound 16 | case loadFailed(String) 17 | } 18 | -------------------------------------------------------------------------------- /Sources/SVDB/Internals/Models/SearchResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct SearchResult { 11 | public let id: UUID 12 | public let text: String 13 | public let score: Double 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SVDB/Internals/TheMathFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MathFunctions.swift 3 | // 4 | 5 | import Accelerate 6 | 7 | enum MathFunctions { 8 | static func cosineSimilarity(_ a: [Double], _ b: [Double], magnitudeA: Double, magnitudeB: Double) -> Double { 9 | var result = 0.0 10 | vDSP_dotprD(a, 1, b, 1, &result, vDSP_Length(a.count)) 11 | return result / (magnitudeA * magnitudeB) 12 | } 13 | 14 | static func euclideanDistance(_ a: [Double], _ b: [Double]) -> Double { 15 | var differences = [Double](repeating: 0.0, count: a.count) 16 | vDSP_vsubD(a, 1, b, 1, &differences, 1, vDSP_Length(a.count)) 17 | 18 | var squaredDifferences = [Double](repeating: 0.0, count: a.count) 19 | vDSP_vsqD(differences, 1, &squaredDifferences, 1, vDSP_Length(a.count)) 20 | 21 | var sumOfSquaredDifferences = 0.0 22 | vDSP_sveD(squaredDifferences, 1, &sumOfSquaredDifferences, vDSP_Length(a.count)) 23 | 24 | return sqrt(sumOfSquaredDifferences) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/SVDBTests/CollectionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | @testable import SVDB 9 | import XCTest 10 | 11 | class SVDBCollectionTests: XCTestCase { 12 | override func setUp() { 13 | super.setUp() 14 | SVDB.shared.reset() 15 | } 16 | 17 | override func tearDown() { 18 | super.tearDown() 19 | SVDB.shared.reset() 20 | try! SVDB.shared.collection("test").clear() 21 | } 22 | 23 | func testAddDocument_WithProvidedID() { 24 | let collection = Collection(name: "test") 25 | let id = UUID() 26 | let text = "Test text awesome 2" 27 | let embedding = [1.0, 2.0, 3.0] 28 | 29 | collection.addDocument(id: id, text: text, embedding: embedding) 30 | 31 | let results = collection.search(query: embedding, num_results: 5) 32 | XCTAssertEqual(results.count, 1) 33 | XCTAssertEqual(results.first?.text, text) 34 | } 35 | 36 | func testAddDocument_Duplicate() { 37 | let collection = Collection(name: "test") 38 | let id = UUID() 39 | let text = "Test text awesome 2" 40 | let expected = "Test text we expect" 41 | let embedding = [1.0, 2.0, 3.0] 42 | 43 | collection.addDocument(id: id, text: text, embedding: embedding) 44 | collection.addDocument(id: id, text: expected, embedding: embedding) 45 | 46 | let results = collection.search(query: embedding, num_results: 5) 47 | XCTAssertEqual(results.count, 1, "Documents with duplicate ids should be overwritten") 48 | XCTAssertEqual(results.first?.text, expected) 49 | } 50 | 51 | func testAddDocument_WithoutProvidedID() { 52 | let collection = Collection(name: "test") 53 | let text = "Test text Awesome" 54 | let embedding = [1.0, 2.0, 3.0] 55 | 56 | collection.addDocument(id: nil, text: text, embedding: embedding) 57 | 58 | let results = collection.search(query: embedding, num_results: 5) 59 | XCTAssertEqual(results.count, 1) 60 | XCTAssertEqual(results.first?.text, text) 61 | } 62 | 63 | func testAddDocument_MagnitudeCalculation() { 64 | let collection = Collection(name: "test") 65 | let embedding = [3.0, 4.0] 66 | 67 | collection.addDocument(id: nil, text: "text", embedding: embedding) 68 | 69 | let results = collection.search(query: embedding, num_results: 5) 70 | XCTAssertEqual(results.count, 1) 71 | XCTAssertEqual(results.first?.text, "text") 72 | } 73 | 74 | func testAddDocuments() { 75 | let svdb = SVDB.shared 76 | SVDB.shared.reset() 77 | let collectionName = "test" 78 | let collection = try! svdb.collection(collectionName) 79 | 80 | let document1 = Document(id: UUID(), text: "test1", embedding: [1.0, 2.0, 3.0]) 81 | let document2 = Document(id: UUID(), text: "test2", embedding: [4.0, 5.0, 6.0]) 82 | let query = [2.5, 3.5, 4.5] 83 | 84 | collection.addDocuments([document1, document2]) 85 | 86 | let searchResults = collection.search(query: query, num_results: 5) 87 | 88 | let resultTexts = searchResults.map { $0.text } 89 | XCTAssertTrue(resultTexts.contains(document1.text)) 90 | XCTAssertTrue(resultTexts.contains(document2.text)) 91 | XCTAssertTrue(resultTexts.count == 2) 92 | } 93 | 94 | func testRemoveDocument_Existing() { 95 | let collection = Collection(name: "test") 96 | let id1 = UUID() 97 | let id2 = UUID() 98 | let document1 = Document(id: id1, text: "test1", embedding: [1.0, 2.0, 3.0]) 99 | let document2 = Document(id: id2, text: "test2", embedding: [4.0, 5.0, 6.0]) 100 | 101 | collection.addDocuments([document1, document2]) 102 | XCTAssertFalse(collection.search(query: []).isEmpty, "There should be a hit for this query") 103 | 104 | collection.removeDocument(byId: id1) 105 | XCTAssertEqual(collection.search(query: []).count, 1) 106 | 107 | collection.removeDocument(byId: id2) 108 | XCTAssertEqual(collection.search(query: []).count, 0) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Tests/SVDBTests/SVDBTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SVDB 2 | import XCTest 3 | 4 | final class SVDBTests: XCTestCase { 5 | override func setUp() { 6 | super.setUp() 7 | SVDB.shared.reset() 8 | } 9 | 10 | func testCreateCollection_Success() { 11 | let name = "uniqueCollectionName" 12 | 13 | do { 14 | _ = try SVDB.shared.collection(name) 15 | } catch { 16 | XCTFail("Unexpected error: \(error)") 17 | } 18 | } 19 | 20 | func testCreateCollection_CollectionAlreadyExists() { 21 | let name = "existingCollectionName" 22 | do { 23 | _ = try SVDB.shared.collection(name) 24 | print("created") 25 | } catch { 26 | XCTFail("Unexpected error: \(error)") 27 | } 28 | 29 | do { 30 | _ = try SVDB.shared.collection(name) 31 | XCTFail("Expected collectionAlreadyExists error, but no error was thrown.") 32 | } catch let error as SVDBError { 33 | XCTAssertEqual(error, SVDBError.collectionAlreadyExists) 34 | } catch { 35 | XCTFail("Unexpected error: \(error)") 36 | } 37 | } 38 | 39 | func testRemoveDocument() throws { 40 | let svdb = SVDB.shared 41 | let collectionName = "test" 42 | let collection = try svdb.collection(collectionName) 43 | 44 | let document1 = Document(id: UUID(), text: "test1", embedding: [1.0, 2.0, 3.0]) 45 | let document2 = Document(id: UUID(), text: "test2", embedding: [4.0, 5.0, 6.0]) 46 | 47 | collection.addDocuments([document1, document2]) 48 | 49 | collection.removeDocument(byId: document1.id) 50 | 51 | let query = [2.5, 3.5, 4.5] 52 | let searchResults = collection.search(query: query, num_results: 2) 53 | let resultIds = searchResults.map { $0.id } 54 | 55 | XCTAssertFalse(resultIds.contains(document1.id)) 56 | XCTAssertTrue(resultIds.contains(document2.id)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/SVDBTests/SearchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Jordan Howlett on 8/4/23. 6 | // 7 | 8 | @testable import SVDB 9 | import XCTest 10 | 11 | class SVDBSearchTests: XCTestCase { 12 | override func setUp() { 13 | super.setUp() 14 | SVDB.shared.reset() 15 | } 16 | 17 | func testSearchWithDefaultParameters() throws { 18 | let svdb = SVDB.shared 19 | let collectionName = "test" 20 | let collection = try svdb.collection(collectionName) 21 | 22 | let documents = [ 23 | Document(id: UUID(), text: "test1", embedding: [1.0, 2.0, 3.0]), 24 | Document(id: UUID(), text: "test2", embedding: [4.0, 5.0, 6.0]) 25 | ] 26 | 27 | collection.addDocuments(documents) 28 | let query = [2.5, 3.5, 4.5] 29 | 30 | let results = collection.search(query: query) 31 | 32 | XCTAssertEqual(results.count, 2) 33 | } 34 | 35 | func testSearchWithNumResults() throws { 36 | let svdb = SVDB.shared 37 | let collectionName = "test" 38 | let collection = try svdb.collection(collectionName) 39 | 40 | let documents = [ 41 | Document(id: UUID(), text: "test1", embedding: [1.0, 2.0, 3.0]), 42 | Document(id: UUID(), text: "test2", embedding: [4.0, 5.0, 6.0]) 43 | ] 44 | 45 | collection.addDocuments(documents) 46 | let query = [2.5, 3.5, 4.5] 47 | 48 | let results = collection.search(query: query, num_results: 1) 49 | 50 | XCTAssertEqual(results.count, 1) 51 | } 52 | 53 | func testSearchWithThreshold() throws { 54 | let svdb = SVDB.shared 55 | let collectionName = "test" 56 | let collection = try svdb.collection(collectionName) 57 | 58 | let documents = [ 59 | Document(id: UUID(), text: "test1", embedding: [900.0, 5000.0, 13.0]), 60 | Document(id: UUID(), text: "test2", embedding: [4.0, 5.0, 6.0]) 61 | ] 62 | 63 | collection.addDocuments(documents) 64 | let query = [4.0, 5.0, 6.0] 65 | 66 | let results = collection.search(query: query, threshold: 0.9) 67 | 68 | XCTAssertEqual(results.count, 1) 69 | } 70 | } 71 | --------------------------------------------------------------------------------