rushdb 2.9.0__tar.gz → 2.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {rushdb-2.9.0 → rushdb-2.10.0}/PKG-INFO +32 -1
  2. {rushdb-2.9.0 → rushdb-2.10.0}/README.md +31 -0
  3. {rushdb-2.9.0 → rushdb-2.10.0}/pyproject.toml +1 -1
  4. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/ai.py +59 -26
  5. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/records.py +40 -0
  6. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/models/result.py +9 -0
  7. rushdb-2.10.0/tests/test_ai_vector_search.py +117 -0
  8. {rushdb-2.9.0 → rushdb-2.10.0}/uv.lock +1 -1
  9. {rushdb-2.9.0 → rushdb-2.10.0}/.env.example +0 -0
  10. {rushdb-2.9.0 → rushdb-2.10.0}/.github/workflows/ci.yml +0 -0
  11. {rushdb-2.9.0 → rushdb-2.10.0}/.gitignore +0 -0
  12. {rushdb-2.9.0 → rushdb-2.10.0}/CODE_OF_CONDUCT.md +0 -0
  13. {rushdb-2.9.0 → rushdb-2.10.0}/CONTRIBUTING.md +0 -0
  14. {rushdb-2.9.0 → rushdb-2.10.0}/LICENSE +0 -0
  15. {rushdb-2.9.0 → rushdb-2.10.0}/rushdb-logo.svg +0 -0
  16. {rushdb-2.9.0 → rushdb-2.10.0}/src/__init__.py +0 -0
  17. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/__init__.py +0 -0
  18. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/__init__.py +0 -0
  19. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/base.py +0 -0
  20. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/labels.py +0 -0
  21. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/properties.py +0 -0
  22. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/query.py +0 -0
  23. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/relationship_patterns.py +0 -0
  24. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/relationships.py +0 -0
  25. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/api/transactions.py +0 -0
  26. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/client.py +0 -0
  27. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/common.py +0 -0
  28. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/models/__init__.py +0 -0
  29. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/models/api_response.py +0 -0
  30. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/models/property.py +0 -0
  31. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/models/record.py +0 -0
  32. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/models/relationship.py +0 -0
  33. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/models/search_query.py +0 -0
  34. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/models/transaction.py +0 -0
  35. {rushdb-2.9.0 → rushdb-2.10.0}/src/rushdb/utils/token_prefix.py +0 -0
  36. {rushdb-2.9.0 → rushdb-2.10.0}/tests/__init__.py +0 -0
  37. {rushdb-2.9.0 → rushdb-2.10.0}/tests/test_base_setup.py +0 -0
  38. {rushdb-2.9.0 → rushdb-2.10.0}/tests/test_create_import.py +0 -0
  39. {rushdb-2.9.0 → rushdb-2.10.0}/tests/test_relationship_patterns.py +0 -0
  40. {rushdb-2.9.0 → rushdb-2.10.0}/tests/test_search_query.py +0 -0
  41. {rushdb-2.9.0 → rushdb-2.10.0}/tests/test_search_result.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rushdb
3
- Version: 2.9.0
3
+ Version: 2.10.0
4
4
  Summary: RushDB Python SDK — memory layer for AI agents and modern apps
5
5
  Project-URL: Homepage, https://github.com/rush-db/rushdb-python
6
6
  Project-URL: Repository, https://github.com/rush-db/rushdb-python
@@ -227,6 +227,37 @@ if result:
227
227
 
228
228
  ---
229
229
 
230
+ ## Vector Search And Smart Search
231
+
232
+ Use `db.records.vector_search()` for direct semantic/vector retrieval over an
233
+ embedding index:
234
+
235
+ ```python
236
+ results = db.records.vector_search({
237
+ 'labels': ['MEMORY'],
238
+ 'propertyName': 'content',
239
+ 'query': 'how agents remember things',
240
+ 'where': {'agent_id': 'agent-42'},
241
+ 'limit': 5,
242
+ })
243
+
244
+ for record in results:
245
+ print(record.score, record.get('content'))
246
+ ```
247
+
248
+ Use `db.ai.search()` when you want RushDB to turn a natural-language request
249
+ into a SearchQuery and execute it:
250
+
251
+ ```python
252
+ results = db.ai.search('Find active memories about Q4 results for agent-42')
253
+ print(results.search_query)
254
+ ```
255
+
256
+ `db.ai.search({...})` still works as a deprecated vector-search alias, but new
257
+ code should use `db.records.vector_search({...})`.
258
+
259
+ ---
260
+
230
261
  ## Record API
231
262
 
232
263
  ```python
@@ -202,6 +202,37 @@ if result:
202
202
 
203
203
  ---
204
204
 
205
+ ## Vector Search And Smart Search
206
+
207
+ Use `db.records.vector_search()` for direct semantic/vector retrieval over an
208
+ embedding index:
209
+
210
+ ```python
211
+ results = db.records.vector_search({
212
+ 'labels': ['MEMORY'],
213
+ 'propertyName': 'content',
214
+ 'query': 'how agents remember things',
215
+ 'where': {'agent_id': 'agent-42'},
216
+ 'limit': 5,
217
+ })
218
+
219
+ for record in results:
220
+ print(record.score, record.get('content'))
221
+ ```
222
+
223
+ Use `db.ai.search()` when you want RushDB to turn a natural-language request
224
+ into a SearchQuery and execute it:
225
+
226
+ ```python
227
+ results = db.ai.search('Find active memories about Q4 results for agent-42')
228
+ print(results.search_query)
229
+ ```
230
+
231
+ `db.ai.search({...})` still works as a deprecated vector-search alias, but new
232
+ code should use `db.records.vector_search({...})`.
233
+
234
+ ---
235
+
205
236
  ## Record API
206
237
 
207
238
  ```python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rushdb"
3
- version = "2.9.0"
3
+ version = "2.10.0"
4
4
  description = "RushDB Python SDK — memory layer for AI agents and modern apps"
5
5
  authors = [
6
6
  {name = "RushDB Team", email = "hi@rushdb.com"}
@@ -1,13 +1,16 @@
1
1
  """AI API for RushDB Python SDK.
2
2
 
3
- Provides methods for graph schema exploration, semantic vector search,
3
+ Provides methods for AI-assisted graph exploration, smart search,
4
4
  and embedding index management.
5
5
  """
6
6
 
7
- from typing import TYPE_CHECKING, Any, Dict, Optional, Union
7
+ import warnings
8
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Union, cast
8
9
 
9
10
  from ..models.api_response import ApiResponse
10
11
  from ..models.record import Record
12
+ from ..models.result import RecordSearchResult
13
+ from ..models.search_query import SearchQuery
11
14
  from ..models.transaction import Transaction
12
15
  from .base import BaseAPI
13
16
 
@@ -124,7 +127,12 @@ class AIAPI(BaseAPI):
124
127
 
125
128
  >>> db = RushDB(api_key="...")
126
129
  >>> schema = db.ai.get_schema()
127
- >>> results = db.ai.search({"query": "fast cars", "propertyName": "description"})
130
+ >>> results = db.ai.search("books about fast cars")
131
+ >>> vectors = db.records.vector_search({
132
+ ... "query": "fast cars",
133
+ ... "propertyName": "description",
134
+ ... "labels": ["Book"],
135
+ ... })
128
136
  >>> db.ai.indexes.create({"propertyName": "description", "label": "Book"})
129
137
  """
130
138
 
@@ -145,7 +153,7 @@ class AIAPI(BaseAPI):
145
153
  indexes exist for that property — each entry exposes ``id``,
146
154
  ``sourceType``, ``similarityFunction``, ``dimensions``, ``status``, and
147
155
  ``modelKey``. A non-empty ``vectorIndexes`` list means the property is
148
- queryable with ``db.ai.search()``.
156
+ queryable with ``db.records.vector_search()``.
149
157
 
150
158
  Args:
151
159
  params: Optional filter. Pass ``{"labels": ["Label1"]}`` to scope
@@ -214,35 +222,60 @@ class AIAPI(BaseAPI):
214
222
  total=response.get("total"),
215
223
  )
216
224
 
217
- def search(self, params: Dict[str, Any]) -> ApiResponse:
218
- """Perform semantic (vector) search over indexed record properties.
225
+ def search(
226
+ self,
227
+ prompt: Union[str, Dict[str, Any]],
228
+ current_query: Optional[SearchQuery] = None,
229
+ transaction: Optional[Union[Transaction, str]] = None,
230
+ ) -> RecordSearchResult:
231
+ """Perform AI-assisted smart search from natural language.
219
232
 
220
- **Direct vector-index mode** (default, fast): used when no ``where``
221
- filter and at most one ``labels`` entry. Queries the shared global
222
- vector index directly.
233
+ RushDB converts ``prompt`` into a SearchQuery using the project schema,
234
+ executes it, and returns matching records with the generated query
235
+ attached to ``result.search_query``.
223
236
 
224
- **Prefilter mode** (exact, slower): activated when a ``where``
225
- filter is supplied or ``labels`` contains more than one value.
226
- Candidates are first narrowed by MATCH/WHERE, then ranked by exact
227
- cosine similarity.
237
+ Use ``db.records.vector_search({...})`` for direct vector similarity
238
+ over embedding indexes.
228
239
 
229
240
  Args:
230
- params: Search parameters. Expected keys:
231
-
232
- - ``query`` (str): The natural-language query text.
233
- - ``propertyName`` (str): Property that has been embedded.
234
- - ``labels`` (list[str], optional): Scope to specific labels.
235
- - ``where`` (dict, optional): Additional property filters.
236
- - ``limit`` (int, optional): Maximum number of results.
241
+ prompt: Natural-language search request. Passing a dict is
242
+ deprecated and delegates to ``db.records.vector_search``.
243
+ current_query: Optional current SearchQuery context from a
244
+ dashboard/query-builder session.
245
+ transaction: Optional transaction context.
237
246
 
238
247
  Returns:
239
- ApiResponse: Response whose ``data`` is a list of semantic search
240
- result objects (each includes the matched record and a score).
248
+ RecordSearchResult: Matching records. The generated SearchQuery is
249
+ available as ``result.search_query`` and server warnings as
250
+ ``result.warnings``.
241
251
  """
242
- response = self.client._make_request("POST", "/ai/search", params)
252
+ if isinstance(prompt, dict):
253
+ warnings.warn(
254
+ "db.ai.search({...}) is deprecated for vector search; "
255
+ "use db.records.vector_search({...}) instead.",
256
+ DeprecationWarning,
257
+ stacklevel=2,
258
+ )
259
+ return self.client.records.vector_search(prompt, transaction=transaction)
260
+
261
+ headers = Transaction._build_transaction_header(transaction)
262
+ generated = self.client._make_request(
263
+ "POST",
264
+ "/ai/search-query",
265
+ {"prompt": prompt, "currentQuery": current_query},
266
+ headers,
267
+ )
268
+ generated_data = generated.get("data") or {}
269
+ search_query = generated_data.get("searchQuery") or {}
270
+
271
+ response = self.client._make_request(
272
+ "POST", "/records/search", search_query, headers
273
+ )
243
274
  records = [Record(self.client, item) for item in response.get("data", [])]
244
- return ApiResponse(
275
+ return RecordSearchResult(
245
276
  data=records,
246
- success=response.get("success", True),
247
- total=response.get("total"),
277
+ total=response.get("total", len(records)),
278
+ search_query=cast(SearchQuery, search_query),
279
+ client=self.client,
280
+ warnings=generated_data.get("warnings") or [],
248
281
  )
@@ -688,6 +688,46 @@ class RecordsAPI(BaseAPI):
688
688
  except Exception:
689
689
  return RecordSearchResult(data=[], total=0, client=self.client)
690
690
 
691
+ def vector_search(
692
+ self,
693
+ params: Dict[str, Any],
694
+ transaction: Optional[Union[Transaction, str]] = None,
695
+ ) -> RecordSearchResult:
696
+ """Perform vector similarity search over indexed record properties.
697
+
698
+ This is the Python SDK counterpart to the TypeScript
699
+ ``db.records.vectorSearch({...})`` method. RushDB narrows candidates by
700
+ ``labels`` and optional ``where`` filters first, then ranks them by
701
+ vector similarity. Pass ``query`` for managed indexes or ``queryVector``
702
+ for external/custom vectors.
703
+
704
+ Args:
705
+ params: Vector search parameters. Expected keys:
706
+
707
+ - ``propertyName`` (str): Property that has an embedding index.
708
+ - ``labels`` (list[str]): Labels to scope the search.
709
+ - ``query`` (str, optional): Text query for managed indexes.
710
+ - ``queryVector`` (list[float], optional): Pre-computed vector.
711
+ - ``where`` (dict, optional): Additional property filters.
712
+ - ``limit`` (int, optional): Maximum number of results.
713
+ - ``skip`` (int, optional): Number of results to skip.
714
+ - ``topK`` (int, optional): Candidate count in direct vector mode.
715
+ transaction: Optional transaction context.
716
+
717
+ Returns:
718
+ RecordSearchResult: Matching records ranked by similarity. Each
719
+ record may include ``__score``.
720
+ """
721
+ headers = Transaction._build_transaction_header(transaction)
722
+ response = self.client._make_request("POST", "/ai/search", params, headers)
723
+ records = [Record(self.client, item) for item in response.get("data", [])]
724
+ return RecordSearchResult(
725
+ data=records,
726
+ total=response.get("total", len(records)),
727
+ search_query=typing.cast(SearchQuery, params),
728
+ client=self.client,
729
+ )
730
+
691
731
  def find_one(
692
732
  self,
693
733
  search_query: Optional[SearchQuery] = None,
@@ -30,6 +30,7 @@ class SearchResult(Generic[T]):
30
30
  total: Optional[int] = None,
31
31
  search_query: Optional[SearchQuery] = None,
32
32
  client: Optional["RushDB"] = None,
33
+ warnings: Optional[List[str]] = None,
33
34
  ):
34
35
  """
35
36
  Initialize search result.
@@ -39,11 +40,13 @@ class SearchResult(Generic[T]):
39
40
  total: Total number of matching records (may be larger than len(data))
40
41
  search_query: The search query used to generate this result
41
42
  client: Optional RushDB client instance (required for delete_all, next, set_properties)
43
+ warnings: Optional warnings returned by AI-assisted query generation
42
44
  """
43
45
  self._data = data
44
46
  self._total = total or len(data)
45
47
  self._search_query = search_query or {}
46
48
  self._client = client
49
+ self._warnings = warnings or []
47
50
 
48
51
  @property
49
52
  def data(self) -> List[T]:
@@ -60,6 +63,11 @@ class SearchResult(Generic[T]):
60
63
  """Get the search query used to generate this result."""
61
64
  return self._search_query
62
65
 
66
+ @property
67
+ def warnings(self) -> List[str]:
68
+ """Get warnings returned by AI-assisted query generation."""
69
+ return self._warnings
70
+
63
71
  @property
64
72
  def has_more(self) -> bool:
65
73
  """Check if there are more records available beyond this result set."""
@@ -106,6 +114,7 @@ class SearchResult(Generic[T]):
106
114
  "total": self.total,
107
115
  "data": self.data,
108
116
  "search_query": self.search_query,
117
+ "warnings": self.warnings,
109
118
  }
110
119
 
111
120
  def get_page_info(self) -> dict:
@@ -0,0 +1,117 @@
1
+ import unittest
2
+ import warnings
3
+ from unittest.mock import Mock
4
+
5
+ from src.rushdb.api.ai import AIAPI
6
+ from src.rushdb.api.records import RecordsAPI
7
+ from src.rushdb.models.result import RecordSearchResult
8
+
9
+
10
+ class TestVectorAndSmartSearch(unittest.TestCase):
11
+ def test_records_vector_search_posts_to_ai_search(self):
12
+ client = Mock()
13
+ client._make_request.return_value = {
14
+ "success": True,
15
+ "total": 1,
16
+ "data": [
17
+ {"__id": "doc_1", "__label": "Doc", "title": "Alpha", "__score": 0.91}
18
+ ],
19
+ }
20
+
21
+ result = RecordsAPI(client).vector_search(
22
+ {
23
+ "labels": ["Doc"],
24
+ "propertyName": "description",
25
+ "queryVector": [1, 0, 0],
26
+ "limit": 5,
27
+ }
28
+ )
29
+
30
+ client._make_request.assert_called_once_with(
31
+ "POST",
32
+ "/ai/search",
33
+ {
34
+ "labels": ["Doc"],
35
+ "propertyName": "description",
36
+ "queryVector": [1, 0, 0],
37
+ "limit": 5,
38
+ },
39
+ None,
40
+ )
41
+ self.assertIsInstance(result, RecordSearchResult)
42
+ self.assertEqual(result.total, 1)
43
+ self.assertEqual(result[0].get("title"), "Alpha")
44
+ self.assertEqual(result[0].score, 0.91)
45
+
46
+ def test_ai_search_prompt_generates_and_executes_search_query(self):
47
+ client = Mock()
48
+ client._make_request.side_effect = [
49
+ {
50
+ "success": True,
51
+ "data": {
52
+ "searchQuery": {"labels": ["Pilot"], "where": {"ship": "Falcon"}},
53
+ "warnings": ["ambiguous ship name"],
54
+ },
55
+ },
56
+ {
57
+ "success": True,
58
+ "total": 1,
59
+ "data": [{"__id": "pilot_1", "__label": "Pilot", "name": "Han"}],
60
+ },
61
+ ]
62
+
63
+ result = AIAPI(client).search(
64
+ "Who are piloting Falcon?",
65
+ current_query={"labels": ["Pilot"]},
66
+ transaction="tx_123",
67
+ )
68
+
69
+ self.assertEqual(
70
+ client._make_request.call_args_list[0].args,
71
+ (
72
+ "POST",
73
+ "/ai/search-query",
74
+ {
75
+ "prompt": "Who are piloting Falcon?",
76
+ "currentQuery": {"labels": ["Pilot"]},
77
+ },
78
+ {"X-Transaction-Id": "tx_123"},
79
+ ),
80
+ )
81
+ self.assertEqual(
82
+ client._make_request.call_args_list[1].args,
83
+ (
84
+ "POST",
85
+ "/records/search",
86
+ {"labels": ["Pilot"], "where": {"ship": "Falcon"}},
87
+ {"X-Transaction-Id": "tx_123"},
88
+ ),
89
+ )
90
+ self.assertEqual(
91
+ result.search_query, {"labels": ["Pilot"], "where": {"ship": "Falcon"}}
92
+ )
93
+ self.assertEqual(result.warnings, ["ambiguous ship name"])
94
+ self.assertEqual(result[0].get("name"), "Han")
95
+
96
+ def test_ai_search_dict_is_deprecated_vector_search_alias(self):
97
+ client = Mock()
98
+ client.records = Mock()
99
+ client.records.vector_search.return_value = RecordSearchResult([], total=0)
100
+
101
+ with warnings.catch_warnings(record=True) as caught:
102
+ warnings.simplefilter("always")
103
+ result = AIAPI(client).search(
104
+ {"labels": ["Doc"], "propertyName": "body", "query": "graph"}
105
+ )
106
+
107
+ client.records.vector_search.assert_called_once_with(
108
+ {"labels": ["Doc"], "propertyName": "body", "query": "graph"},
109
+ transaction=None,
110
+ )
111
+ self.assertIsInstance(result, RecordSearchResult)
112
+ self.assertEqual(len(caught), 1)
113
+ self.assertIs(caught[0].category, DeprecationWarning)
114
+
115
+
116
+ if __name__ == "__main__":
117
+ unittest.main()
@@ -785,7 +785,7 @@ wheels = [
785
785
 
786
786
  [[package]]
787
787
  name = "rushdb"
788
- version = "2.9.0"
788
+ version = "2.10.0"
789
789
  source = { editable = "." }
790
790
  dependencies = [
791
791
  { name = "python-dotenv", version = "1.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes