Skip to content

Commit 3b0e417

Browse files
authored
[PLT-4347] Route embedding metadata operations through GraphQL (#2074)
1 parent 6d75412 commit 3b0e417

4 files changed

Lines changed: 179 additions & 24 deletions

File tree

libs/labelbox/src/labelbox/adv_client.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import io
22
import json
33
import logging
4-
from typing import Any, Callable, Dict, List, Optional
4+
from typing import Any, Callable, Dict, Optional
55
from urllib.parse import urlparse
66

77
import requests
@@ -17,19 +17,6 @@ def __init__(self, endpoint: str, api_key: str):
1717
self.api_key = api_key
1818
self.session = self._create_session()
1919

20-
def create_embedding(self, name: str, dims: int) -> Dict[str, Any]:
21-
data = {"name": name, "dims": dims}
22-
return self._request("POST", "/adv/v1/embeddings", data)
23-
24-
def delete_embedding(self, id: str):
25-
return self._request("DELETE", f"/adv/v1/embeddings/{id}")
26-
27-
def get_embedding(self, id: str) -> Dict[str, Any]:
28-
return self._request("GET", f"/adv/v1/embeddings/{id}")
29-
30-
def get_embeddings(self) -> List[Dict[str, Any]]:
31-
return self._request("GET", "/adv/v1/embeddings").get("results", [])
32-
3320
def import_vectors_from_file(self, id: str, file_path: str, callback=None):
3421
self._send_ndjson(
3522
f"/adv/v1/embeddings/{id}/_import_ndjson", file_path, callback

libs/labelbox/src/labelbox/client.py

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2150,8 +2150,21 @@ def create_embedding(self, name: str, dims: int) -> Embedding:
21502150
Returns:
21512151
A new Embedding object.
21522152
"""
2153-
data = self._adv_client.create_embedding(name, dims)
2154-
return Embedding(self._adv_client, **data)
2153+
mutation = """
2154+
mutation CreateEmbeddingPyApi($data: CreateEmbeddingInput!) {
2155+
createEmbedding(data: $data) {
2156+
id
2157+
name
2158+
dims
2159+
custom
2160+
}
2161+
}
2162+
"""
2163+
data = self.execute(
2164+
mutation,
2165+
{"data": {"name": name, "dims": dims}},
2166+
)["createEmbedding"]
2167+
return Embedding(self, **data)
21552168

21562169
def get_embeddings(self) -> List[Embedding]:
21572170
"""
@@ -2160,8 +2173,18 @@ def get_embeddings(self) -> List[Embedding]:
21602173
Returns:
21612174
A list of embedding objects.
21622175
"""
2163-
results = self._adv_client.get_embeddings()
2164-
return [Embedding(self._adv_client, **data) for data in results]
2176+
query = """
2177+
query GetEmbeddingsPyApi {
2178+
embeddings {
2179+
id
2180+
name
2181+
dims
2182+
custom
2183+
}
2184+
}
2185+
"""
2186+
results = self.execute(query)["embeddings"]
2187+
return [Embedding(self, **data) for data in results]
21652188

21662189
def get_embedding_by_id(self, id: str) -> Embedding:
21672190
"""
@@ -2173,8 +2196,34 @@ def get_embedding_by_id(self, id: str) -> Embedding:
21732196
Returns:
21742197
The embedding object.
21752198
"""
2176-
data = self._adv_client.get_embedding(id)
2177-
return Embedding(self._adv_client, **data)
2199+
for embedding in self.get_embeddings():
2200+
if embedding.id == id:
2201+
return embedding
2202+
raise ResourceNotFoundError(Embedding, dict(id=id))
2203+
2204+
def delete_embedding(self, id: str):
2205+
"""
2206+
Delete a custom embedding through the GraphQL API.
2207+
2208+
Args:
2209+
id: The embedding ID.
2210+
"""
2211+
mutation = """
2212+
mutation DeleteEmbeddingPyApi($data: DeleteEmbeddingInput!) {
2213+
deleteEmbedding(data: $data)
2214+
}
2215+
"""
2216+
return self.execute(mutation, {"data": {"id": id}})["deleteEmbedding"]
2217+
2218+
def import_vectors_from_file(self, id: str, file_path: str, callback=None):
2219+
"""Upload embedding vectors directly to ADV."""
2220+
return self._adv_client.import_vectors_from_file(
2221+
id, file_path, callback
2222+
)
2223+
2224+
def get_imported_vector_count(self, id: str) -> int:
2225+
"""Return an embedding's imported vector count directly from ADV."""
2226+
return self._adv_client.get_imported_vector_count(id)
21782227

21792228
def get_embedding_by_name(self, name: str) -> Embedding:
21802229
"""

libs/labelbox/src/labelbox/schema/embedding.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
from typing import Optional, Callable, Dict, Any, List
1+
from typing import Any, Callable, Dict, List, Optional, Protocol
22

3-
from labelbox.adv_client import AdvClient
43
from pydantic import BaseModel, PrivateAttr
54

65

6+
class EmbeddingClient(Protocol):
7+
def delete_embedding(self, id: str): ...
8+
9+
def import_vectors_from_file(
10+
self,
11+
id: str,
12+
file_path: str,
13+
callback: Optional[Callable[[Dict[str, Any]], None]] = None,
14+
): ...
15+
16+
def get_imported_vector_count(self, id: str) -> int: ...
17+
18+
719
class EmbeddingVector(BaseModel):
820
"""
921
A Vector Embedding for Custom Embedding.
@@ -43,9 +55,9 @@ class Embedding(BaseModel):
4355
name: str
4456
custom: bool
4557
dims: int
46-
_client: AdvClient = PrivateAttr()
58+
_client: EmbeddingClient = PrivateAttr()
4759

48-
def __init__(self, client: AdvClient, **data):
60+
def __init__(self, client: EmbeddingClient, **data):
4961
super().__init__(**data)
5062
self._client = client
5163

libs/labelbox/tests/unit/test_client.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
from unittest.mock import Mock
2+
3+
import pytest
4+
from lbox.exceptions import ResourceNotFoundError
5+
16
from labelbox.client import Client
7+
from labelbox.schema.embedding import Embedding
28

39

410
# @patch.dict(os.environ, {'LABELBOX_API_KEY': 'bar'})
@@ -14,3 +20,104 @@ def test_headers():
1420
def test_enable_experimental():
1521
client = Client(api_key="api_key", enable_experimental=True)
1622
assert client.enable_experimental
23+
24+
25+
def test_create_embedding_uses_graphql():
26+
client = Client(api_key="api_key")
27+
client.execute = Mock(
28+
return_value={
29+
"createEmbedding": {
30+
"id": "embedding-id",
31+
"name": "custom",
32+
"dims": 8,
33+
"custom": True,
34+
}
35+
}
36+
)
37+
38+
embedding = client.create_embedding("custom", 8)
39+
40+
assert embedding.id == "embedding-id"
41+
query, variables = client.execute.call_args.args
42+
assert "createEmbedding" in query
43+
assert variables == {"data": {"name": "custom", "dims": 8}}
44+
45+
46+
def test_get_embeddings_uses_graphql():
47+
client = Client(api_key="api_key")
48+
client.execute = Mock(
49+
return_value={
50+
"embeddings": [
51+
{
52+
"id": "embedding-id",
53+
"name": "custom",
54+
"dims": 8,
55+
"custom": True,
56+
}
57+
]
58+
}
59+
)
60+
61+
embeddings = client.get_embeddings()
62+
63+
assert [embedding.id for embedding in embeddings] == ["embedding-id"]
64+
assert "embeddings" in client.execute.call_args.args[0]
65+
66+
67+
def test_get_embedding_by_id_filters_graphql_results():
68+
client = Client(api_key="api_key")
69+
client.get_embeddings = Mock(
70+
return_value=[
71+
Embedding(
72+
client,
73+
id="embedding-id",
74+
name="custom",
75+
dims=8,
76+
custom=True,
77+
)
78+
]
79+
)
80+
81+
assert client.get_embedding_by_id("embedding-id").name == "custom"
82+
83+
with pytest.raises(ResourceNotFoundError):
84+
client.get_embedding_by_id("missing")
85+
86+
87+
def test_embedding_delete_uses_graphql():
88+
client = Client(api_key="api_key")
89+
client.execute = Mock(return_value={"deleteEmbedding": True})
90+
embedding = Embedding(
91+
client,
92+
id="embedding-id",
93+
name="custom",
94+
dims=8,
95+
custom=True,
96+
)
97+
98+
embedding.delete()
99+
100+
query, variables = client.execute.call_args.args
101+
assert "deleteEmbedding" in query
102+
assert variables == {"data": {"id": "embedding-id"}}
103+
104+
105+
def test_embedding_vector_operations_remain_on_adv():
106+
client = Client(api_key="api_key")
107+
client._adv_client.import_vectors_from_file = Mock()
108+
client._adv_client.get_imported_vector_count = Mock(return_value=12)
109+
callback = Mock()
110+
embedding = Embedding(
111+
client,
112+
id="embedding-id",
113+
name="custom",
114+
dims=8,
115+
custom=True,
116+
)
117+
118+
embedding.import_vectors_from_file("vectors.ndjson", callback)
119+
120+
client._adv_client.import_vectors_from_file.assert_called_once_with(
121+
"embedding-id", "vectors.ndjson", callback
122+
)
123+
assert embedding.get_imported_vector_count() == 12

0 commit comments

Comments
 (0)