├── .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 |
--------------------------------------------------------------------------------