camel-ai 0.2.73a3__py3-none-any.whl → 0.2.73a5__py3-none-any.whl

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.

Potentially problematic release.


This version of camel-ai might be problematic. Click here for more details.

camel/__init__.py CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  from camel.logger import disable_logging, enable_logging, set_log_level
16
16
 
17
- __version__ = '0.2.73a3'
17
+ __version__ = '0.2.73a5'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
@@ -25,6 +25,7 @@ from .milvus import MilvusStorage
25
25
  from .oceanbase import OceanBaseStorage
26
26
  from .pgvector import PgVectorStorage
27
27
  from .qdrant import QdrantStorage
28
+ from .surreal import SurrealStorage
28
29
  from .tidb import TiDBStorage
29
30
  from .weaviate import WeaviateStorage
30
31
 
@@ -68,7 +68,9 @@ class SurrealStorage(BaseVectorStorage):
68
68
  url: str = "ws://localhost:8000/rpc",
69
69
  table: str = "vector_store",
70
70
  vector_dim: int = 786,
71
+ vector_type: str = "F64",
71
72
  distance: VectorDistance = VectorDistance.COSINE,
73
+ hnsw_effort: int = 40,
72
74
  namespace: str = "default",
73
75
  database: str = "demo",
74
76
  user: str = "root",
@@ -96,6 +98,8 @@ class SurrealStorage(BaseVectorStorage):
96
98
  (default: :obj:`"root"`)
97
99
  """
98
100
 
101
+ from surrealdb import Surreal
102
+
99
103
  self.url = url
100
104
  self.table = table
101
105
  self.ns = namespace
@@ -103,9 +107,14 @@ class SurrealStorage(BaseVectorStorage):
103
107
  self.user = user
104
108
  self.password = password
105
109
  self.vector_dim = vector_dim
110
+ self.vector_type = vector_type
106
111
  self.distance = distance
112
+ self._hnsw_effort = hnsw_effort
113
+ self._surreal_client = Surreal(self.url)
114
+ self._surreal_client.signin({"username": user, "password": password})
115
+ self._surreal_client.use(namespace, database)
116
+
107
117
  self._check_and_create_table()
108
- self._surreal_client = None
109
118
 
110
119
  def _table_exists(self) -> bool:
111
120
  r"""Check whether the target table exists in the database.
@@ -113,74 +122,66 @@ class SurrealStorage(BaseVectorStorage):
113
122
  Returns:
114
123
  bool: True if the table exists, False otherwise.
115
124
  """
116
- from surrealdb import Surreal # type: ignore[import-not-found]
125
+ res = self._surreal_client.query("INFO FOR DB;")
126
+ tables = res.get('tables', {})
127
+ logger.debug(f"_table_exists: {res}")
128
+ return self.table in tables
117
129
 
118
- with Surreal(self.url) as db:
119
- db.signin({"username": self.user, "password": self.password})
120
- db.use(self.ns, self.db)
121
- res = db.query_raw("INFO FOR DB;")
122
- tables = res['result'][0]['result'].get('tables', {})
123
- return self.table in tables
124
-
125
- def _get_table_info(self) -> Dict[str, int]:
130
+ def _get_table_info(self) -> dict[str, int | None]:
126
131
  r"""Retrieve dimension and record count from the table metadata.
127
132
 
128
133
  Returns:
129
134
  Dict[str, int]: A dictionary with 'dim' and 'count' keys.
130
135
  """
131
- from surrealdb import Surreal # type: ignore[import-not-found]
132
-
133
136
  if not self._table_exists():
134
137
  return {"dim": self.vector_dim, "count": 0}
135
- with Surreal(self.url) as db:
136
- db.signin({"username": self.user, "password": self.password})
137
- db.use(self.ns, self.db)
138
- res = db.query_raw(f"INFO FOR TABLE {self.table};")
139
-
140
- indexes = res['result'][0]['result'].get("indexes", {})
141
-
142
- dim = self.vector_dim
143
- idx_def = indexes.get("hnsw_idx")
144
- if idx_def and isinstance(idx_def, str):
145
- m = re.search(r"DIMENSION\s+(\d+)", idx_def)
146
- if m:
147
- dim = int(m.group(1))
148
- cnt = db.query_raw(f"SELECT COUNT() AS count FROM {self.table};")
149
- try:
150
- count = cnt['result'][0]['result'][0]['count']
151
- except (KeyError, IndexError, TypeError):
152
- logger.warning(
153
- "Unexpected result format when counting records: %s", cnt
154
- )
155
- count = 0
156
-
157
- return {"dim": dim, "count": count}
138
+ res = self._surreal_client.query(f"INFO FOR TABLE {self.table};")
139
+ logger.debug(f"_get_table_info: {res}")
140
+ indexes = res.get("indexes", {})
141
+
142
+ dim = self.vector_dim
143
+ idx_def = indexes.get("hnsw_idx")
144
+ if idx_def and isinstance(idx_def, str):
145
+ m = re.search(r"DIMENSION\s+(\d+)", idx_def)
146
+ if m:
147
+ dim = int(m.group(1))
148
+ cnt = self._surreal_client.query(
149
+ f"SELECT COUNT() FROM ONLY {self.table} GROUP ALL LIMIT 1;"
150
+ )
151
+ count = cnt.get("count", 0)
152
+ return {"dim": dim, "count": count}
158
153
 
159
154
  def _create_table(self):
160
- r"""Define and create the vector storage table with HNSW index."""
161
- from surrealdb import Surreal # type: ignore[import-not-found]
162
-
163
- with Surreal(self.url) as db:
164
- db.signin({"username": self.user, "password": self.password})
165
- db.use(self.ns, self.db)
166
- db.query_raw(
167
- f"""DEFINE TABLE {self.table} SCHEMALESS;
168
- DEFINE FIELD payload ON {self.table} TYPE object;
169
- DEFINE FIELD embedding ON {self.table} TYPE array;
170
- DEFINE INDEX hnsw_idx ON {self.table}
171
- FIELDS embedding HNSW DIMENSION {self.vector_dim};
172
- """
155
+ r"""Define and create the vector storage table with HNSW index.
156
+
157
+ Documentation: https://surrealdb.com/docs/surrealdb/reference-guide/
158
+ vector-search#vector-search-cheat-sheet
159
+ """
160
+ if self.distance.value not in ["cosine", "euclidean", "manhattan"]:
161
+ raise ValueError(
162
+ f"Unsupported distance metric: {self.distance.value}"
173
163
  )
164
+ surql_query = f"""
165
+ DEFINE TABLE {self.table} SCHEMALESS;
166
+ DEFINE FIELD payload ON {self.table} FLEXIBLE TYPE object;
167
+ DEFINE FIELD embedding ON {self.table} TYPE array<float>;
168
+ DEFINE INDEX hnsw_idx ON {self.table}
169
+ FIELDS embedding
170
+ HNSW DIMENSION {self.vector_dim}
171
+ DIST {self.distance.value}
172
+ TYPE {self.vector_type}
173
+ EFC 150 M 12 M0 24;
174
+ """
175
+ logger.debug(f"_create_table query: {surql_query}")
176
+ res = self._surreal_client.query_raw(surql_query)
177
+ logger.debug(f"_create_table response: {res}")
178
+ if "error" in res:
179
+ raise ValueError(f"Failed to create table: {res['error']}")
174
180
  logger.info(f"Table '{self.table}' created successfully.")
175
181
 
176
182
  def _drop_table(self):
177
183
  r"""Drop the vector storage table if it exists."""
178
- from surrealdb import Surreal # type: ignore[import-not-found]
179
-
180
- with Surreal(self.url) as db:
181
- db.signin({"username": self.user, "password": self.password})
182
- db.use(self.ns, self.db)
183
- db.query_raw(f"REMOVE TABLE IF EXISTS {self.table};")
184
+ self._surreal_client.query_raw(f"REMOVE TABLE IF EXISTS {self.table};")
184
185
  logger.info(f"Table '{self.table}' deleted successfully.")
185
186
 
186
187
  def _check_and_create_table(self):
@@ -240,62 +241,34 @@ class SurrealStorage(BaseVectorStorage):
240
241
  List[VectorDBQueryResult]: Ranked list of matching records
241
242
  with similarity scores.
242
243
  """
243
- from surrealdb import Surreal # type: ignore[import-not-found]
244
-
245
- metric = {
246
- VectorDistance.COSINE: "cosine",
247
- VectorDistance.EUCLIDEAN: "euclidean",
248
- VectorDistance.DOT: "dot",
249
- }[self.distance]
250
-
251
- metric_func = {
252
- VectorDistance.COSINE: "vector::similarity::cosine",
253
- VectorDistance.EUCLIDEAN: "vector::distance::euclidean",
254
- VectorDistance.DOT: "vector::dot",
255
- }.get(self.distance)
256
-
257
- if not metric_func:
258
- raise ValueError(f"Unsupported distance metric: {self.distance}")
259
-
260
- with Surreal(self.url) as db:
261
- db.signin({"username": self.user, "password": self.password})
262
- db.use(self.ns, self.db)
263
-
264
- # Use parameterized query to prevent SQL injection
265
- sql_query = f"""SELECT payload, embedding,
266
- {metric_func}(embedding, $query_vec) AS score
267
- FROM {self.table}
268
- WHERE embedding <|{query.top_k},{metric}|> $query_vec
269
- ORDER BY score;
270
- """
271
-
272
- response = db.query_raw(
273
- sql_query, {"query_vec": query.query_vector}
274
- )
244
+ surql_query = f"""
245
+ SELECT id, embedding, payload, vector::distance::knn() AS dist
246
+ FROM {self.table}
247
+ WHERE embedding <|{query.top_k},{self._hnsw_effort}|> $vector
248
+ ORDER BY dist;
249
+ """
250
+ logger.debug(
251
+ f"query surql: {surql_query} with $vector = {query.query_vector}"
252
+ )
275
253
 
276
- if not response.get("result") or not response["result"]:
277
- return []
278
-
279
- results = response["result"][0]
280
-
281
- if "result" not in results:
282
- return []
283
-
284
- return [
285
- VectorDBQueryResult(
286
- record=VectorRecord(
287
- vector=row["embedding"], payload=row.get("payload", {})
288
- ),
289
- similarity=(
290
- 1.0 - row["score"]
291
- if self.distance == VectorDistance.COSINE
292
- else 1.0 / (1.0 + row["score"])
293
- if self.distance == VectorDistance.EUCLIDEAN
294
- else row["score"]
295
- ),
296
- )
297
- for row in results["result"]
298
- ]
254
+ response = self._surreal_client.query(
255
+ surql_query, {"vector": query.query_vector}
256
+ )
257
+ logger.debug(f"query response: {response}")
258
+
259
+ return [
260
+ VectorDBQueryResult(
261
+ record=VectorRecord(
262
+ id=row["id"].id,
263
+ vector=row["embedding"],
264
+ payload=row["payload"],
265
+ ),
266
+ similarity=1.0 - row["dist"]
267
+ if self.distance == VectorDistance.COSINE
268
+ else -row["score"],
269
+ )
270
+ for row in response
271
+ ]
299
272
 
300
273
  def add(self, records: List[VectorRecord], **kwargs) -> None:
301
274
  r"""Insert validated vector records into the SurrealDB table.
@@ -306,16 +279,10 @@ class SurrealStorage(BaseVectorStorage):
306
279
  logger.info(
307
280
  "Adding %d records to table '%s'.", len(records), self.table
308
281
  )
309
- from surrealdb import Surreal # type: ignore[import-not-found]
310
-
311
282
  try:
312
- with Surreal(self.url) as db:
313
- db.signin({"username": self.user, "password": self.password})
314
- db.use(self.ns, self.db)
315
-
316
- validated_records = self._validate_and_convert_records(records)
317
- for record in validated_records:
318
- db.create(self.table, record)
283
+ validated_records = self._validate_and_convert_records(records)
284
+ for record in validated_records:
285
+ self._surreal_client.create(self.table, record)
319
286
 
320
287
  logger.info(
321
288
  "Successfully added %d records to table '%s'.",
@@ -340,32 +307,23 @@ class SurrealStorage(BaseVectorStorage):
340
307
  ids (Optional[List[str]]): List of record IDs to delete.
341
308
  if_all (bool): Whether to delete all records in the table.
342
309
  """
343
- from surrealdb import Surreal # type: ignore[import-not-found]
344
- from surrealdb.data.types.record_id import ( # type: ignore[import-not-found]
345
- RecordID,
346
- )
310
+ from surrealdb.data.types.record_id import RecordID
347
311
 
348
312
  try:
349
- with Surreal(self.url) as db:
350
- db.signin({"username": self.user, "password": self.password})
351
- db.use(self.ns, self.db)
352
-
353
- if if_all:
354
- db.delete(self.table, **kwargs)
355
- logger.info(
356
- f"Deleted all records from table '{self.table}'"
357
- )
358
- return
359
-
360
- if not ids:
361
- raise ValueError(
362
- "Either `ids` must be provided or `if_all=True`"
363
- )
364
-
365
- for id_str in ids:
366
- rec = RecordID(self.table, id_str)
367
- db.delete(rec, **kwargs)
368
- logger.info(f"Deleted record {rec}")
313
+ if if_all:
314
+ self._surreal_client.delete(self.table, **kwargs)
315
+ logger.info(f"Deleted all records from table '{self.table}'")
316
+ return
317
+
318
+ if not ids:
319
+ raise ValueError(
320
+ "Either `ids` must be provided or `if_all=True`"
321
+ )
322
+
323
+ for id_str in ids:
324
+ rec = RecordID(self.table, id_str)
325
+ self._surreal_client.delete(rec, **kwargs)
326
+ logger.info(f"Deleted record {rec}")
369
327
 
370
328
  except Exception as e:
371
329
  logger.exception("Error deleting records from SurrealDB")
@@ -404,12 +362,4 @@ class SurrealStorage(BaseVectorStorage):
404
362
  @property
405
363
  def client(self) -> "Surreal":
406
364
  r"""Provides access to the underlying SurrealDB client."""
407
- if self._surreal_client is None:
408
- from surrealdb import Surreal # type: ignore[import-not-found]
409
-
410
- self._surreal_client = Surreal(self.url)
411
- self._surreal_client.signin( # type: ignore[attr-defined]
412
- {"username": self.user, "password": self.password}
413
- )
414
- self._surreal_client.use(self.ns, self.db) # type: ignore[attr-defined]
415
365
  return self._surreal_client
camel/toolkits/base.py CHANGED
@@ -48,7 +48,9 @@ class BaseToolkit(metaclass=AgentOpsMeta):
48
48
  super().__init_subclass__(**kwargs)
49
49
  for attr_name, attr_value in cls.__dict__.items():
50
50
  if callable(attr_value) and not attr_name.startswith("__"):
51
- setattr(cls, attr_name, with_timeout(attr_value))
51
+ # Skip methods that have manual timeout management
52
+ if not getattr(attr_value, '_manual_timeout', False):
53
+ setattr(cls, attr_name, with_timeout(attr_value))
52
54
 
53
55
  def get_tools(self) -> List[FunctionTool]:
54
56
  r"""Returns a list of FunctionTool objects representing the