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