Skip to content

Commit 2817607

Browse files
authored
Merge pull request #46 from Eigen-DB/feature/python-client
Finalize the Python client (eigen-client)
2 parents 6f269d3 + 1e39692 commit 2817607

File tree

20 files changed

+2495
-95
lines changed

20 files changed

+2495
-95
lines changed

apps/eigendb/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ eigen/*
66
node_modules/
77
benchmarks/*.log
88
benchmarks/*_mean_*.csv
9+
notebooks/
910

1011
!eigen/.keep
1112

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package embeddings
2+
3+
import (
4+
"eigen_db/api/utils"
5+
"eigen_db/types"
6+
"eigen_db/vector_io"
7+
"fmt"
8+
"net/http"
9+
10+
"github.com/gin-gonic/gin"
11+
)
12+
13+
type deleteRequestBody struct {
14+
Ids []types.EmbId `json:"ids" binding:"required"`
15+
}
16+
17+
func Delete(c *gin.Context) {
18+
var body deleteRequestBody
19+
if err := utils.ValidateBody(c, &body); err != nil {
20+
return
21+
}
22+
23+
embeddingsDeleted := 0
24+
errors := make([]string, 0)
25+
for _, id := range body.Ids {
26+
if err := vector_io.GetMemoryIndex().Delete(id); err != nil {
27+
errors = append(errors, fmt.Sprintf("embedding with ID %d was not deleted - %s", id, err.Error()))
28+
} else {
29+
embeddingsDeleted++
30+
}
31+
}
32+
33+
if len(errors) != 0 {
34+
utils.SendResponse(
35+
c,
36+
http.StatusInternalServerError,
37+
fmt.Sprintf("%d/%d embeddings successfully deleted.", embeddingsDeleted, len(body.Ids)),
38+
nil,
39+
utils.CreateError("EMBEDDINGS_SKIPPED", errors),
40+
)
41+
} else {
42+
utils.SendResponse(
43+
c,
44+
200,
45+
fmt.Sprintf("%d/%d embeddings successfully deleted.", embeddingsDeleted, len(body.Ids)),
46+
nil,
47+
nil,
48+
)
49+
}
50+
}

apps/eigendb/api/setup.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
// Setups up the API router
1515
//
16-
// Returns the router at a pointer to a Gin Engine instance.
16+
// Returns the router as a pointer to a Gin Engine instance.
1717
func setupRouter() *gin.Engine {
1818
r := gin.Default()
1919

@@ -31,6 +31,7 @@ func setupRouter() *gin.Engine {
3131
// vector operation endpoints
3232
vectors.PUT("/insert", embeddings.Insert)
3333
vectors.PUT("/upsert", embeddings.Upsert)
34+
vectors.DELETE("/delete", embeddings.Delete)
3435
vectors.GET("/retrieve", embeddings.Retrieve)
3536
vectors.GET("/search", embeddings.Search)
3637
// config setter endpoints

apps/eigendb/e2e/vector_testsuite.venom.yml

Lines changed: 123 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,92 @@ testcases:
9090
- result.bodyjson.status ShouldEqual 200
9191
- result.bodyjson.data.nearest_neighbors ShouldJSONEqual '{"38":{"metadata":{},"rank":0},"15":{"metadata":{"team":"alpha"},"rank":1},"33":{"metadata":{"city":"berlin"},"rank":2},"45":{"metadata":{"foo":"qux"},"rank":3},"41":{"metadata":{},"rank":4}}'
9292

93+
- name: Test vector upsertion (whole initial dataset)
94+
steps:
95+
- type: http
96+
method: PUT
97+
url: "{{.url}}/embeddings/upsert"
98+
body: >
99+
{
100+
"embeddings": [
101+
{"data": [3.2, -1.5], "id": 1, "metadata": {"foo": "baz"}},
102+
{"data": [4.7, 2.1], "id": 2, "metadata": {}},
103+
{"data": [-6.3, 3.4], "id": 3, "metadata": {"color": "red"}},
104+
{"data": [0.9, -4.8], "id": 4, "metadata": {"shape": "circle"}},
105+
{"data": [-2.7, 5.6], "id": 5, "metadata": {}},
106+
{"data": [1.3, -3.9], "id": 6, "metadata": {"animal": "cat"}},
107+
{"data": [2.4, 6.1], "id": 7, "metadata": {"bar": "foo"}},
108+
{"data": [-1.1, 3.0], "id": 8, "metadata": {}},
109+
{"data": [5.5, -2.2], "id": 9, "metadata": {"fruit": "apple"}},
110+
{"data": [0.0, 4.4], "id": 10, "metadata": {"city": "paris"}},
111+
{"data": [-3.6, -0.7], "id": 11, "metadata": {}},
112+
{"data": [4.1, 5.3], "id": 12, "metadata": {"lang": "go"}},
113+
{"data": [-2.9, 2.8], "id": 13, "metadata": {"os": "linux"}},
114+
{"data": [3.7, -3.6], "id": 14, "metadata": {}},
115+
{"data": [1.0, 0.5], "id": 15, "metadata": {"team": "alpha"}},
116+
{"data": [5.9, 1.7], "id": 16, "metadata": {"env": "prod"}},
117+
{"data": [-4.4, -3.2], "id": 17, "metadata": {}},
118+
{"data": [2.8, 4.9], "id": 18, "metadata": {"user": "bob"}},
119+
{"data": [-1.5, -2.4], "id": 19, "metadata": {"status": "active"}},
120+
{"data": [3.3, 1.6], "id": 20, "metadata": {}},
121+
{"data": [4.6, -1.3], "id": 21, "metadata": {"type": "vector"}},
122+
{"data": [-2.1, 3.7], "id": 22, "metadata": {"code": "xyz"}},
123+
{"data": [1.8, -5.4], "id": 23, "metadata": {}},
124+
{"data": [3.9, 2.5], "id": 24, "metadata": {"foo": "bar"}},
125+
{"data": [-1.4, 4.2], "id": 25, "metadata": {"bar": "baz"}},
126+
{"data": [0.2, -3.1], "id": 26, "metadata": {}},
127+
{"data": [5.1, 1.3], "id": 27, "metadata": {"fruit": "banana"}},
128+
{"data": [-2.8, -1.7], "id": 28, "metadata": {"animal": "dog"}},
129+
{"data": [3.0, 5.5], "id": 29, "metadata": {}},
130+
{"data": [1.5, -2.8], "id": 30, "metadata": {"shape": "square"}},
131+
{"data": [-4.9, 3.1], "id": 31, "metadata": {"lang": "python"}},
132+
{"data": [2.6, -4.5], "id": 32, "metadata": {}},
133+
{"data": [0.7, 3.8], "id": 33, "metadata": {"city": "berlin"}},
134+
{"data": [-3.3, 2.2], "id": 34, "metadata": {"env": "dev"}},
135+
{"data": [4.0, -0.9], "id": 35, "metadata": {}},
136+
{"data": [-1.2, 4.9], "id": 36, "metadata": {"team": "beta"}},
137+
{"data": [3.4, -2.6], "id": 37, "metadata": {"os": "windows"}},
138+
{"data": [0.6, 1.8], "id": 38, "metadata": {}},
139+
{"data": [-2.5, -3.9], "id": 39, "metadata": {"status": "inactive"}},
140+
{"data": [5.3, 2.0], "id": 40, "metadata": {"user": "alice"}},
141+
{"data": [-0.8, 3.3], "id": 41, "metadata": {}},
142+
{"data": [2.1, -4.2], "id": 42, "metadata": {"code": "abc"}},
143+
{"data": [4.5, 1.4], "id": 43, "metadata": {"type": "matrix"}},
144+
{"data": [-3.7, -2.5], "id": 44, "metadata": {}},
145+
{"data": [1.9, 3.6], "id": 45, "metadata": {"foo": "qux"}},
146+
{"data": [0.3, -5.1], "id": 46, "metadata": {"bar": "quux"}},
147+
{"data": [4.8, -3.0], "id": 47, "metadata": {}},
148+
{"data": [-1.6, 2.9], "id": 48, "metadata": {"fruit": "pear"}},
149+
{"data": [2.9, -4.0], "id": 49, "metadata": {"animal": "fox"}}
150+
]
151+
}
152+
headers:
153+
X-Eigen-API-Key: test
154+
timeout: 5
155+
assertions:
156+
- result.statuscode ShouldEqual 200
157+
- result.bodyjson.message ShouldEqual "49/49 embeddings successfully upserted."
158+
- result.bodyjson.status ShouldEqual 200
159+
160+
- name: Test similarity search (successful with upserted whole dataset)
161+
steps:
162+
- type: http
163+
method: GET
164+
url: "{{.url}}/embeddings/search"
165+
body: >
166+
{
167+
"queryVector": [1.0, 2.0],
168+
"k": 5
169+
}
170+
headers:
171+
X-Eigen-API-Key: test
172+
timeout: 5
173+
assertions:
174+
- result.statuscode ShouldEqual 200
175+
- result.bodyjson.message ShouldEqual "Similarity search successfully performed."
176+
- result.bodyjson.status ShouldEqual 200
177+
- result.bodyjson.data.nearest_neighbors ShouldJSONEqual '{"38":{"metadata":{},"rank":0},"15":{"metadata":{"team":"alpha"},"rank":1},"33":{"metadata":{"city":"berlin"},"rank":2},"45":{"metadata":{"foo":"qux"},"rank":3},"41":{"metadata":{},"rank":4}}'
178+
93179
- name: Test similarity search (invalid values)
94180
steps:
95181
- type: http
@@ -189,26 +275,6 @@ testcases:
189275
- result.bodyjson.error.description ShouldContain "embedding with ID 100 was not inserted - provided a 3-dimensional embedding while the index is 2-dimensional"
190276
- result.bodyjson.error.description ShouldContain "embedding with ID 101 was not inserted - provided a 3-dimensional embedding while the index is 2-dimensional"
191277

192-
- name: Test vector upsertion (successful)
193-
steps:
194-
- type: http
195-
method: PUT
196-
url: "{{.url}}/embeddings/upsert"
197-
body: >
198-
{
199-
"embeddings": [
200-
{"data": [-4.2, 8.5], "id": 1, "metadata": {"hello": "world"}},
201-
{"data": [0.3, -9.4], "id": 2, "metadata": {"color": "blue"}}
202-
]
203-
}
204-
headers:
205-
X-Eigen-API-Key: test
206-
timeout: 5
207-
assertions:
208-
- result.statuscode ShouldEqual 200
209-
- result.bodyjson.message ShouldEqual "2/2 embeddings successfully upserted."
210-
- result.bodyjson.status ShouldEqual 200
211-
212278
- name: Test vector upsertion (invalid request body)
213279
steps:
214280
- type: http
@@ -282,4 +348,40 @@ testcases:
282348
- result.statuscode ShouldEqual 500
283349
- result.bodyjson.message ShouldEqual "5/6 embeddings successfully retrieved."
284350
- result.bodyjson.status ShouldEqual 500
285-
- result.bodyjson.error.code ShouldEqual "EMBEDDINGS_SKIPPED"
351+
- result.bodyjson.error.code ShouldEqual "EMBEDDINGS_SKIPPED"
352+
353+
- name: Test vector deletion (successful)
354+
steps:
355+
- type: http
356+
method: DELETE
357+
url: "{{.url}}/embeddings/delete"
358+
body: >
359+
{
360+
"ids": [1, 2, 3, 4, 5]
361+
}
362+
headers:
363+
X-Eigen-API-Key: test
364+
timeout: 5
365+
assertions:
366+
- result.statuscode ShouldEqual 200
367+
- result.bodyjson.message ShouldEqual "5/5 embeddings successfully deleted."
368+
- result.bodyjson.status ShouldEqual 200
369+
370+
- name: Test vector deletion (invalid id)
371+
steps:
372+
- type: http
373+
method: DELETE
374+
url: "{{.url}}/embeddings/delete"
375+
body: >
376+
{
377+
"ids": [6, 7, 8, 9, 10, 8394279]
378+
}
379+
headers:
380+
X-Eigen-API-Key: test
381+
timeout: 5
382+
assertions:
383+
- result.statuscode ShouldEqual 500
384+
- result.bodyjson.message ShouldEqual "5/6 embeddings successfully deleted."
385+
- result.bodyjson.status ShouldEqual 500
386+
- result.bodyjson.error.code ShouldEqual "EMBEDDINGS_SKIPPED"
387+

0 commit comments

Comments
 (0)