├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Package.swift ├── LICENSE ├── README.md └── Sources └── PineconeSwift ├── EmbedModel.swift ├── PineconeModels.swift └── PineconeSwift.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 10 | -------------------------------------------------------------------------------- /.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: "PineconeSwift", 8 | platforms: [ 9 | .macOS(.v12), .iOS(.v14), .tvOS(.v14) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "PineconeSwift", 15 | targets: ["PineconeSwift"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "PineconeSwift", 26 | dependencies: []), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Sergey Dikarev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PineconeSwift 2 | 3 | PineconeSwift is an open-source Swift Package designed to seamlessly integrate Pinecone, a powerful vector database, into your Swift applications. With PineconeSwift, you can easily leverage Pinecone's capabilities to efficiently store and query high-dimensional vectors, enabling you to build state-of-the-art machine learning and recommendation systems. 4 | 5 | ## Features 6 | 7 | - **Effortless integration** with Pinecone, a high-performance vector database 8 | - Support for **key database operations**, including upsert, query, fetch, and update 9 | - **Intuitive and easy-to-use API** designed specifically for Swift developers 10 | - Built with **performance and scalability** in mind, suitable for various application types 11 | - Thoroughly **tested and documented**, ensuring a smooth development experience 12 | 13 | PineconeSwift is your go-to solution for integrating Pinecone's cutting-edge vector database technology into your Swift projects, enabling you to build advanced, scalable, and robust applications with ease. 14 | 15 | ## Usage 16 | 17 | For example for upsert: 18 | 19 | Create an array of EmbedResult structs: 20 | 21 | ``` 22 | public struct EmbedResult: Codable { 23 | public let id: String? = UUID().uuidString 24 | public let index: Int 25 | public let embedding: [Double] 26 | public let text: String 27 | 28 | public init(index: Int, embedding: [Double], text: String) { 29 | self.index = index 30 | self.embedding = embedding 31 | self.text = text 32 | } 33 | } 34 | ``` 35 | Then simply create PineconeSwift object and call for upsert method: 36 | 37 | ``` 38 | let pai = PineconeSwift(apikey: {your_Pinecone_API_key}}, baseURL: {your Pinecone_base_url_to_index}}) 39 | 40 | let result = try await pai.upsertVectors(with: embeddings, namespace: {string}}) 41 | ``` 42 | -------------------------------------------------------------------------------- /Sources/PineconeSwift/EmbedModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmbedModel.swift 3 | // Summarly 4 | // 5 | // Created by Sergey Dikarev on 25.03.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct EmbedModel: Codable { 11 | public let model: String 12 | public let input: [String] 13 | 14 | public init(model: String, input: [String]) { 15 | self.model = model 16 | self.input = input 17 | } 18 | } 19 | 20 | 21 | public struct EmbedResponse: Codable { 22 | public let object: String 23 | public let model: String 24 | public let data: [Embedding] 25 | public let usage: Usage 26 | 27 | public init(object: String, model: String, data: [Embedding], usage: Usage) { 28 | self.object = object 29 | self.model = model 30 | self.data = data 31 | self.usage = usage 32 | } 33 | } 34 | 35 | public struct Embedding: Codable { 36 | public let object: String 37 | public let embedding: [Double] 38 | public let index: Int 39 | 40 | public init(object: String, embedding: [Double], index: Int) { 41 | self.object = object 42 | self.embedding = embedding 43 | self.index = index 44 | } 45 | } 46 | 47 | public struct Usage: Codable { 48 | public let prompt_tokens: Int 49 | public let total_tokens: Int 50 | 51 | public init(prompt_tokens: Int, total_tokens: Int) { 52 | self.prompt_tokens = prompt_tokens 53 | self.total_tokens = total_tokens 54 | } 55 | } 56 | 57 | public struct EmbedResult: Codable { 58 | public let id: String? = UUID().uuidString 59 | public let index: Int 60 | public let embedding: [Double] 61 | public let text: String 62 | 63 | public init(index: Int, embedding: [Double], text: String) { 64 | self.index = index 65 | self.embedding = embedding 66 | self.text = text 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/PineconeSwift/PineconeModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PineconeModels.swift 3 | // Summarly 4 | // 5 | // Created by Sergey Dikarev on 25.03.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | public struct PineconeInsertRequest: Codable { 12 | let vectors: [PineconeVector] 13 | let namespace: String 14 | 15 | public init(vectors: [PineconeVector], namespace: String) { 16 | self.vectors = vectors 17 | self.namespace = namespace 18 | } 19 | } 20 | 21 | public struct PineconeQueryRequest: Codable { 22 | let vector: [Double] 23 | let namespace: String 24 | let topK: Int 25 | let includeValues: Bool 26 | let includeMetadata: Bool 27 | let id: String? 28 | 29 | public init(vector: [Double], namespace: String, topK: Int, includeValues: Bool, includeMetadata: Bool, id: String?) { 30 | self.vector = vector 31 | self.namespace = namespace 32 | self.topK = topK 33 | self.includeValues = includeValues 34 | self.includeMetadata = includeMetadata 35 | self.id = id 36 | } 37 | } 38 | 39 | 40 | public struct PineconeVector: Codable { 41 | public let id: String 42 | public let values: [Double] 43 | public let metadata: [String: String] 44 | public let score: Double? 45 | 46 | public init(id: String, values: [Double], metadata: [String : String], score: Double?) { 47 | self.id = id 48 | self.values = values 49 | self.metadata = metadata 50 | self.score = score 51 | } 52 | } 53 | 54 | public struct PineconeQueryResponse: Codable { 55 | public let matches: [PineconeVector] 56 | public let namespace: String 57 | 58 | public init(matches: [PineconeVector], namespace: String) { 59 | self.matches = matches 60 | self.namespace = namespace 61 | } 62 | } 63 | 64 | public struct PineconeUpsertResponse: Codable { 65 | public let upsertedCount: Int 66 | 67 | public init(upsertedCount: Int) { 68 | self.upsertedCount = upsertedCount 69 | } 70 | } 71 | 72 | public struct PineconeUpdateRequest: Codable { 73 | let setMetadata: [String: String] 74 | let namespace: String 75 | let id: String 76 | let values: [Double] 77 | 78 | public init(setMetadata: [String : String], namespace: String, id: String, values: [Double]) { 79 | self.setMetadata = setMetadata 80 | self.namespace = namespace 81 | self.id = id 82 | self.values = values 83 | } 84 | } 85 | 86 | public struct PineconeFetchRequest: Codable { 87 | let namespace: String? 88 | let ids: [String] 89 | 90 | public init(namespace: String?, ids: [String]) { 91 | self.namespace = namespace 92 | self.ids = ids 93 | } 94 | } 95 | 96 | public struct PineconeFetchResponse: Codable { 97 | public let vectors: FetchVectors 98 | public let namespace: String 99 | 100 | public init(vectors: FetchVectors, namespace: String) { 101 | self.vectors = vectors 102 | self.namespace = namespace 103 | } 104 | } 105 | 106 | public struct FetchVectors: Codable { 107 | public let additionalProp: [PineconeVector] 108 | 109 | public init(additionalProp: [PineconeVector]) { 110 | self.additionalProp = additionalProp 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/PineconeSwift/PineconeSwift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PineconeApi.swift 3 | // Summarly 4 | // 5 | // Created by Sergey Dikarev on 25.03.2023. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | public class PineconeSwift { 12 | fileprivate(set) var apikey: String 13 | fileprivate(set) var baseURL: String 14 | 15 | public init(apikey: String, baseURL: String) { 16 | self.apikey = apikey 17 | self.baseURL = baseURL 18 | } 19 | } 20 | 21 | 22 | extension PineconeSwift { 23 | 24 | public func queryVectors(with vector: EmbedResult, namespace: String, topK: Int, includeValues: Bool = true, includeMetadata: Bool, endpoint: PineconeEndpoint = .query) async throws -> [PineconeVector] { 25 | 26 | let pineconeVector = PineconeVector(id: vector.id!, values: vector.embedding, metadata: ["text": vector.text], score: nil) 27 | 28 | let body = PineconeQueryRequest(vector: pineconeVector.values, namespace: namespace, topK: topK, includeValues: includeValues, includeMetadata: includeMetadata, id: nil) 29 | let request = prepareRequest(endpoint, body: body) 30 | 31 | let session = URLSession.shared 32 | let (data, _) = try await session.data(for: request) 33 | print(NSString(string: String(decoding: data, as: UTF8.self))) 34 | 35 | let res = try JSONDecoder().decode(PineconeQueryResponse.self, from: data) 36 | 37 | return res.matches 38 | 39 | } 40 | 41 | public func fetchVectors(with ids: [String], namespace: String?, endpoint: PineconeEndpoint = .fetch) async throws -> [PineconeVector] { 42 | 43 | let body = PineconeFetchRequest(namespace: namespace, ids: ids) 44 | let request = prepareRequest(endpoint, body: body) 45 | 46 | let session = URLSession.shared 47 | let (data, _) = try await session.data(for: request) 48 | print(NSString(string: String(decoding: data, as: UTF8.self))) 49 | 50 | let res = try JSONDecoder().decode(PineconeFetchResponse.self, from: data) 51 | 52 | return res.vectors.additionalProp 53 | 54 | } 55 | 56 | 57 | public func upsertVectors(with vectors: [EmbedResult], namespace: String, endpoint: PineconeEndpoint = .upsert) async throws -> Bool { 58 | 59 | let pineconeVectors = vectors.map { embRes in 60 | return PineconeVector(id: embRes.id!, values: embRes.embedding, metadata: ["text" : embRes.text], score: nil) 61 | } 62 | 63 | let body = PineconeInsertRequest( vectors: pineconeVectors, namespace: namespace) 64 | let request = prepareRequest(endpoint, body: body) 65 | 66 | let session = URLSession.shared 67 | let (data, response) = try await session.data(for: request) 68 | print(NSString(string: String(decoding: data, as: UTF8.self))) 69 | 70 | let res = try JSONDecoder().decode(PineconeUpsertResponse.self, from: data) 71 | print("upsertVectors", res.upsertedCount) 72 | if let httpResponse = response as? HTTPURLResponse { 73 | print("Status Code: \(httpResponse.statusCode)") 74 | return httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 75 | } 76 | 77 | return false 78 | 79 | } 80 | 81 | public func updateVectors(with vector: EmbedResult, metadata: [String: String], namespace: String, id: String, endpoint: PineconeEndpoint = .update) async throws -> Bool { 82 | 83 | let body = PineconeUpdateRequest(setMetadata: metadata, namespace: namespace, id: id, values: vector.embedding) 84 | let request = prepareRequest(endpoint, body: body) 85 | 86 | let session = URLSession.shared 87 | let (data, response) = try await session.data(for: request) 88 | print(NSString(string: String(decoding: data, as: UTF8.self))) 89 | 90 | let res = try JSONDecoder().decode(PineconeUpsertResponse.self, from: data) 91 | print("upsertVectors", res.upsertedCount) 92 | if let httpResponse = response as? HTTPURLResponse { 93 | print("Status Code: \(httpResponse.statusCode)") 94 | return httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 95 | } 96 | 97 | return false 98 | 99 | } 100 | 101 | 102 | private func prepareRequest(_ endpoint: PineconeEndpoint, body: BodyType) -> URLRequest { 103 | var urlComponents = URLComponents(url: URL(string: baseURL)!, resolvingAgainstBaseURL: true) 104 | urlComponents?.path = endpoint.path 105 | var request = URLRequest(url: urlComponents!.url!) 106 | request.httpMethod = endpoint.method 107 | 108 | request.setValue(self.apikey, forHTTPHeaderField: "Api-Key") 109 | 110 | request.setValue("application/json", forHTTPHeaderField: "content-type") 111 | request.setValue("application/json", forHTTPHeaderField: "accept") 112 | 113 | let encoder = JSONEncoder() 114 | if let encoded = try? encoder.encode(body) { 115 | request.httpBody = encoded 116 | } 117 | 118 | return request 119 | } 120 | } 121 | 122 | 123 | public enum PineconeEndpoint { 124 | case upsert 125 | case query 126 | case update 127 | case fetch 128 | } 129 | 130 | 131 | public extension PineconeEndpoint { 132 | var path: String { 133 | switch self { 134 | case .upsert: 135 | return "/vectors/upsert" 136 | case .query: 137 | return "/query" 138 | case .update: 139 | return "/vectors/update" 140 | case .fetch: 141 | return "/vectors/fetch" 142 | } 143 | } 144 | 145 | var method: String { 146 | switch self { 147 | case .upsert: 148 | return "POST" 149 | case .query: 150 | return "POST" 151 | case .update: 152 | return "POST" 153 | case .fetch: 154 | return "GET" 155 | } 156 | } 157 | 158 | 159 | } 160 | --------------------------------------------------------------------------------