camel-ai 0.2.15a0__py3-none-any.whl → 0.2.16__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 +1 -1
- camel/benchmarks/__init__.py +11 -1
- camel/benchmarks/apibank.py +560 -0
- camel/benchmarks/apibench.py +496 -0
- camel/benchmarks/gaia.py +2 -2
- camel/benchmarks/nexus.py +518 -0
- camel/datagen/__init__.py +6 -2
- camel/datagen/{o1datagen.py → cotdatagen.py} +19 -6
- camel/datagen/self_instruct/__init__.py +36 -0
- camel/datagen/self_instruct/filter/__init__.py +34 -0
- camel/datagen/self_instruct/filter/filter_function.py +216 -0
- camel/datagen/self_instruct/filter/filter_registry.py +56 -0
- camel/datagen/self_instruct/filter/instruction_filter.py +81 -0
- camel/datagen/self_instruct/self_instruct.py +393 -0
- camel/datagen/self_instruct/templates.py +384 -0
- camel/datahubs/huggingface.py +12 -2
- camel/datahubs/models.py +2 -3
- camel/embeddings/mistral_embedding.py +5 -1
- camel/embeddings/openai_compatible_embedding.py +6 -1
- camel/embeddings/openai_embedding.py +5 -1
- camel/interpreters/e2b_interpreter.py +5 -1
- camel/loaders/apify_reader.py +5 -1
- camel/loaders/chunkr_reader.py +5 -1
- camel/loaders/firecrawl_reader.py +0 -30
- camel/logger.py +11 -5
- camel/models/anthropic_model.py +5 -1
- camel/models/azure_openai_model.py +1 -2
- camel/models/cohere_model.py +5 -1
- camel/models/deepseek_model.py +5 -1
- camel/models/gemini_model.py +5 -1
- camel/models/groq_model.py +5 -1
- camel/models/mistral_model.py +5 -1
- camel/models/nemotron_model.py +5 -1
- camel/models/nvidia_model.py +5 -1
- camel/models/openai_model.py +5 -1
- camel/models/qwen_model.py +5 -1
- camel/models/reka_model.py +5 -1
- camel/models/reward/nemotron_model.py +5 -1
- camel/models/samba_model.py +5 -1
- camel/models/togetherai_model.py +5 -1
- camel/models/yi_model.py +5 -1
- camel/models/zhipuai_model.py +5 -1
- camel/schemas/openai_converter.py +5 -1
- camel/storages/graph_storages/nebula_graph.py +89 -20
- camel/storages/graph_storages/neo4j_graph.py +138 -0
- camel/toolkits/__init__.py +4 -0
- camel/toolkits/arxiv_toolkit.py +20 -3
- camel/toolkits/dappier_toolkit.py +196 -0
- camel/toolkits/function_tool.py +61 -61
- camel/toolkits/meshy_toolkit.py +5 -1
- camel/toolkits/notion_toolkit.py +1 -1
- camel/toolkits/openbb_toolkit.py +869 -0
- camel/toolkits/search_toolkit.py +91 -5
- camel/toolkits/stripe_toolkit.py +5 -1
- camel/toolkits/twitter_toolkit.py +24 -16
- camel/utils/__init__.py +2 -0
- camel/utils/commons.py +104 -19
- {camel_ai-0.2.15a0.dist-info → camel_ai-0.2.16.dist-info}/METADATA +16 -4
- {camel_ai-0.2.15a0.dist-info → camel_ai-0.2.16.dist-info}/RECORD +61 -49
- {camel_ai-0.2.15a0.dist-info → camel_ai-0.2.16.dist-info}/LICENSE +0 -0
- {camel_ai-0.2.15a0.dist-info → camel_ai-0.2.16.dist-info}/WHEEL +0 -0
camel/models/deepseek_model.py
CHANGED
|
@@ -50,6 +50,11 @@ class DeepSeekModel(BaseModelBackend):
|
|
|
50
50
|
https://api-docs.deepseek.com/
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
|
+
@api_keys_required(
|
|
54
|
+
[
|
|
55
|
+
("api_key", "DEEPSEEK_API_KEY"),
|
|
56
|
+
]
|
|
57
|
+
)
|
|
53
58
|
def __init__(
|
|
54
59
|
self,
|
|
55
60
|
model_type: Union[ModelType, str],
|
|
@@ -90,7 +95,6 @@ class DeepSeekModel(BaseModelBackend):
|
|
|
90
95
|
)
|
|
91
96
|
return self._token_counter
|
|
92
97
|
|
|
93
|
-
@api_keys_required("DEEPSEEK_API_KEY")
|
|
94
98
|
def run(
|
|
95
99
|
self,
|
|
96
100
|
messages: List[OpenAIMessage],
|
camel/models/gemini_model.py
CHANGED
|
@@ -52,6 +52,11 @@ class GeminiModel(BaseModelBackend):
|
|
|
52
52
|
(default: :obj:`None`)
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
+
@api_keys_required(
|
|
56
|
+
[
|
|
57
|
+
("api_key", 'GEMINI_API_KEY'),
|
|
58
|
+
]
|
|
59
|
+
)
|
|
55
60
|
def __init__(
|
|
56
61
|
self,
|
|
57
62
|
model_type: Union[ModelType, str],
|
|
@@ -77,7 +82,6 @@ class GeminiModel(BaseModelBackend):
|
|
|
77
82
|
base_url=self._url,
|
|
78
83
|
)
|
|
79
84
|
|
|
80
|
-
@api_keys_required("GEMINI_API_KEY")
|
|
81
85
|
def run(
|
|
82
86
|
self,
|
|
83
87
|
messages: List[OpenAIMessage],
|
camel/models/groq_model.py
CHANGED
|
@@ -51,6 +51,11 @@ class GroqModel(BaseModelBackend):
|
|
|
51
51
|
(default: :obj:`None`)
|
|
52
52
|
"""
|
|
53
53
|
|
|
54
|
+
@api_keys_required(
|
|
55
|
+
[
|
|
56
|
+
("api_key", "GROQ_API_KEY"),
|
|
57
|
+
]
|
|
58
|
+
)
|
|
54
59
|
def __init__(
|
|
55
60
|
self,
|
|
56
61
|
model_type: Union[ModelType, str],
|
|
@@ -89,7 +94,6 @@ class GroqModel(BaseModelBackend):
|
|
|
89
94
|
self._token_counter = OpenAITokenCounter(ModelType.GPT_4O_MINI)
|
|
90
95
|
return self._token_counter
|
|
91
96
|
|
|
92
|
-
@api_keys_required("GROQ_API_KEY")
|
|
93
97
|
def run(
|
|
94
98
|
self,
|
|
95
99
|
messages: List[OpenAIMessage],
|
camel/models/mistral_model.py
CHANGED
|
@@ -59,6 +59,11 @@ class MistralModel(BaseModelBackend):
|
|
|
59
59
|
be used. (default: :obj:`None`)
|
|
60
60
|
"""
|
|
61
61
|
|
|
62
|
+
@api_keys_required(
|
|
63
|
+
[
|
|
64
|
+
("api_key", "MISTRAL_API_KEY"),
|
|
65
|
+
]
|
|
66
|
+
)
|
|
62
67
|
@dependencies_required('mistralai')
|
|
63
68
|
def __init__(
|
|
64
69
|
self,
|
|
@@ -200,7 +205,6 @@ class MistralModel(BaseModelBackend):
|
|
|
200
205
|
)
|
|
201
206
|
return self._token_counter
|
|
202
207
|
|
|
203
|
-
@api_keys_required("MISTRAL_API_KEY")
|
|
204
208
|
def run(
|
|
205
209
|
self,
|
|
206
210
|
messages: List[OpenAIMessage],
|
camel/models/nemotron_model.py
CHANGED
|
@@ -40,6 +40,11 @@ class NemotronModel(BaseModelBackend):
|
|
|
40
40
|
Nemotron model doesn't support additional model config like OpenAI.
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
|
+
@api_keys_required(
|
|
44
|
+
[
|
|
45
|
+
("api_key", "NVIDIA_API_KEY"),
|
|
46
|
+
]
|
|
47
|
+
)
|
|
43
48
|
def __init__(
|
|
44
49
|
self,
|
|
45
50
|
model_type: Union[ModelType, str],
|
|
@@ -58,7 +63,6 @@ class NemotronModel(BaseModelBackend):
|
|
|
58
63
|
api_key=self._api_key,
|
|
59
64
|
)
|
|
60
65
|
|
|
61
|
-
@api_keys_required("NVIDIA_API_KEY")
|
|
62
66
|
def run(
|
|
63
67
|
self,
|
|
64
68
|
messages: List[OpenAIMessage],
|
camel/models/nvidia_model.py
CHANGED
|
@@ -48,6 +48,11 @@ class NvidiaModel(BaseModelBackend):
|
|
|
48
48
|
(default: :obj:`None`)
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
|
+
@api_keys_required(
|
|
52
|
+
[
|
|
53
|
+
("api_key", "NVIDIA_API_KEY"),
|
|
54
|
+
]
|
|
55
|
+
)
|
|
51
56
|
def __init__(
|
|
52
57
|
self,
|
|
53
58
|
model_type: Union[ModelType, str],
|
|
@@ -72,7 +77,6 @@ class NvidiaModel(BaseModelBackend):
|
|
|
72
77
|
base_url=self._url,
|
|
73
78
|
)
|
|
74
79
|
|
|
75
|
-
@api_keys_required("NVIDIA_API_KEY")
|
|
76
80
|
def run(
|
|
77
81
|
self,
|
|
78
82
|
messages: List[OpenAIMessage],
|
camel/models/openai_model.py
CHANGED
|
@@ -52,6 +52,11 @@ class OpenAIModel(BaseModelBackend):
|
|
|
52
52
|
be used. (default: :obj:`None`)
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
+
@api_keys_required(
|
|
56
|
+
[
|
|
57
|
+
("api_key", "OPENAI_API_KEY"),
|
|
58
|
+
]
|
|
59
|
+
)
|
|
55
60
|
def __init__(
|
|
56
61
|
self,
|
|
57
62
|
model_type: Union[ModelType, str],
|
|
@@ -86,7 +91,6 @@ class OpenAIModel(BaseModelBackend):
|
|
|
86
91
|
self._token_counter = OpenAITokenCounter(self.model_type)
|
|
87
92
|
return self._token_counter
|
|
88
93
|
|
|
89
|
-
@api_keys_required("OPENAI_API_KEY")
|
|
90
94
|
def run(
|
|
91
95
|
self,
|
|
92
96
|
messages: List[OpenAIMessage],
|
camel/models/qwen_model.py
CHANGED
|
@@ -52,6 +52,11 @@ class QwenModel(BaseModelBackend):
|
|
|
52
52
|
(default: :obj:`None`)
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
+
@api_keys_required(
|
|
56
|
+
[
|
|
57
|
+
("api_key", "QWEN_API_KEY"),
|
|
58
|
+
]
|
|
59
|
+
)
|
|
55
60
|
def __init__(
|
|
56
61
|
self,
|
|
57
62
|
model_type: Union[ModelType, str],
|
|
@@ -77,7 +82,6 @@ class QwenModel(BaseModelBackend):
|
|
|
77
82
|
base_url=self._url,
|
|
78
83
|
)
|
|
79
84
|
|
|
80
|
-
@api_keys_required("QWEN_API_KEY")
|
|
81
85
|
def run(
|
|
82
86
|
self,
|
|
83
87
|
messages: List[OpenAIMessage],
|
camel/models/reka_model.py
CHANGED
|
@@ -56,6 +56,11 @@ class RekaModel(BaseModelBackend):
|
|
|
56
56
|
be used. (default: :obj:`None`)
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
|
+
@api_keys_required(
|
|
60
|
+
[
|
|
61
|
+
("api_key", "REKA_API_KEY"),
|
|
62
|
+
]
|
|
63
|
+
)
|
|
59
64
|
@dependencies_required('reka')
|
|
60
65
|
def __init__(
|
|
61
66
|
self,
|
|
@@ -168,7 +173,6 @@ class RekaModel(BaseModelBackend):
|
|
|
168
173
|
)
|
|
169
174
|
return self._token_counter
|
|
170
175
|
|
|
171
|
-
@api_keys_required("REKA_API_KEY")
|
|
172
176
|
def run(
|
|
173
177
|
self,
|
|
174
178
|
messages: List[OpenAIMessage],
|
|
@@ -53,7 +53,11 @@ class NemotronRewardModel(BaseRewardModel):
|
|
|
53
53
|
api_key=self.api_key,
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
-
@api_keys_required(
|
|
56
|
+
@api_keys_required(
|
|
57
|
+
[
|
|
58
|
+
(None, "NVIDIA_API_KEY"),
|
|
59
|
+
]
|
|
60
|
+
)
|
|
57
61
|
def evaluate(self, messages: List[Dict[str, str]]) -> Dict[str, float]:
|
|
58
62
|
r"""Evaluate the messages using the Nemotron model.
|
|
59
63
|
|
camel/models/samba_model.py
CHANGED
|
@@ -74,6 +74,11 @@ class SambaModel(BaseModelBackend):
|
|
|
74
74
|
ModelType.GPT_4O_MINI)` will be used.
|
|
75
75
|
"""
|
|
76
76
|
|
|
77
|
+
@api_keys_required(
|
|
78
|
+
[
|
|
79
|
+
("api_key", 'SAMBA_API_KEY'),
|
|
80
|
+
]
|
|
81
|
+
)
|
|
77
82
|
def __init__(
|
|
78
83
|
self,
|
|
79
84
|
model_type: Union[ModelType, str],
|
|
@@ -143,7 +148,6 @@ class SambaModel(BaseModelBackend):
|
|
|
143
148
|
" SambaNova service"
|
|
144
149
|
)
|
|
145
150
|
|
|
146
|
-
@api_keys_required("SAMBA_API_KEY")
|
|
147
151
|
def run( # type: ignore[misc]
|
|
148
152
|
self, messages: List[OpenAIMessage]
|
|
149
153
|
) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
|
camel/models/togetherai_model.py
CHANGED
|
@@ -53,6 +53,11 @@ class TogetherAIModel(BaseModelBackend):
|
|
|
53
53
|
ModelType.GPT_4O_MINI)` will be used.
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
|
+
@api_keys_required(
|
|
57
|
+
[
|
|
58
|
+
("api_key", 'TOGETHER_API_KEY'),
|
|
59
|
+
]
|
|
60
|
+
)
|
|
56
61
|
def __init__(
|
|
57
62
|
self,
|
|
58
63
|
model_type: Union[ModelType, str],
|
|
@@ -78,7 +83,6 @@ class TogetherAIModel(BaseModelBackend):
|
|
|
78
83
|
base_url=self._url,
|
|
79
84
|
)
|
|
80
85
|
|
|
81
|
-
@api_keys_required("TOGETHER_API_KEY")
|
|
82
86
|
def run(
|
|
83
87
|
self,
|
|
84
88
|
messages: List[OpenAIMessage],
|
camel/models/yi_model.py
CHANGED
|
@@ -52,6 +52,11 @@ class YiModel(BaseModelBackend):
|
|
|
52
52
|
(default: :obj:`None`)
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
+
@api_keys_required(
|
|
56
|
+
[
|
|
57
|
+
("api_key", 'YI_API_KEY'),
|
|
58
|
+
]
|
|
59
|
+
)
|
|
55
60
|
def __init__(
|
|
56
61
|
self,
|
|
57
62
|
model_type: Union[ModelType, str],
|
|
@@ -76,7 +81,6 @@ class YiModel(BaseModelBackend):
|
|
|
76
81
|
base_url=self._url,
|
|
77
82
|
)
|
|
78
83
|
|
|
79
|
-
@api_keys_required("YI_API_KEY")
|
|
80
84
|
def run(
|
|
81
85
|
self,
|
|
82
86
|
messages: List[OpenAIMessage],
|
camel/models/zhipuai_model.py
CHANGED
|
@@ -52,6 +52,11 @@ class ZhipuAIModel(BaseModelBackend):
|
|
|
52
52
|
(default: :obj:`None`)
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
+
@api_keys_required(
|
|
56
|
+
[
|
|
57
|
+
("api_key", 'ZHIPUAI_API_KEY'),
|
|
58
|
+
]
|
|
59
|
+
)
|
|
55
60
|
def __init__(
|
|
56
61
|
self,
|
|
57
62
|
model_type: Union[ModelType, str],
|
|
@@ -76,7 +81,6 @@ class ZhipuAIModel(BaseModelBackend):
|
|
|
76
81
|
base_url=self._url,
|
|
77
82
|
)
|
|
78
83
|
|
|
79
|
-
@api_keys_required("ZHIPUAI_API_KEY")
|
|
80
84
|
def run(
|
|
81
85
|
self,
|
|
82
86
|
messages: List[OpenAIMessage],
|
|
@@ -53,6 +53,11 @@ class OpenAISchemaConverter(BaseConverter):
|
|
|
53
53
|
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
|
+
@api_keys_required(
|
|
57
|
+
[
|
|
58
|
+
("api_key", "OPENAI_API_KEY"),
|
|
59
|
+
]
|
|
60
|
+
)
|
|
56
61
|
def __init__(
|
|
57
62
|
self,
|
|
58
63
|
model_type: ModelType = ModelType.GPT_4O_MINI,
|
|
@@ -69,7 +74,6 @@ class OpenAISchemaConverter(BaseConverter):
|
|
|
69
74
|
)._client
|
|
70
75
|
super().__init__()
|
|
71
76
|
|
|
72
|
-
@api_keys_required("OPENAI_API_KEY")
|
|
73
77
|
def convert( # type: ignore[override]
|
|
74
78
|
self,
|
|
75
79
|
content: str,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import logging
|
|
16
16
|
import re
|
|
17
17
|
import time
|
|
18
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Tuple
|
|
18
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
|
19
19
|
|
|
20
20
|
from camel.storages.graph_storages.base import BaseGraphStorage
|
|
21
21
|
from camel.storages.graph_storages.graph_element import (
|
|
@@ -203,46 +203,62 @@ class NebulaGraph(BaseGraphStorage):
|
|
|
203
203
|
def ensure_edge_type_exists(
|
|
204
204
|
self,
|
|
205
205
|
edge_type: str,
|
|
206
|
+
time_label: Optional[str] = None,
|
|
206
207
|
) -> None:
|
|
207
208
|
r"""Ensures that a specified edge type exists in the NebulaGraph
|
|
208
209
|
database. If the edge type already exists, this method does nothing.
|
|
209
210
|
|
|
210
211
|
Args:
|
|
211
212
|
edge_type (str): The name of the edge type to be created.
|
|
213
|
+
time_label (str, optional): A specific timestamp to set as the
|
|
214
|
+
default value for the time label property. If not
|
|
215
|
+
provided, no timestamp will be added. (default: :obj:`None`)
|
|
212
216
|
|
|
213
217
|
Raises:
|
|
214
218
|
Exception: If the edge type creation fails after multiple retry
|
|
215
219
|
attempts, an exception is raised with the error message.
|
|
216
220
|
"""
|
|
217
|
-
create_edge_stmt = f
|
|
221
|
+
create_edge_stmt = f"CREATE EDGE IF NOT EXISTS {edge_type} ()"
|
|
222
|
+
if time_label is not None:
|
|
223
|
+
time_label = self._validate_time_label(time_label)
|
|
224
|
+
create_edge_stmt = f"""CREATE EDGE IF NOT EXISTS {edge_type}
|
|
225
|
+
(time_label DATETIME DEFAULT {time_label})"""
|
|
218
226
|
|
|
219
227
|
for attempt in range(MAX_RETRIES):
|
|
220
228
|
res = self.query(create_edge_stmt)
|
|
221
229
|
if res.is_succeeded():
|
|
222
|
-
return #
|
|
230
|
+
return # Edge type creation succeeded
|
|
223
231
|
|
|
224
232
|
if attempt < MAX_RETRIES - 1:
|
|
225
233
|
time.sleep(RETRY_DELAY)
|
|
226
234
|
else:
|
|
227
235
|
# Final attempt failed, raise an exception
|
|
228
236
|
raise Exception(
|
|
229
|
-
f"Failed to create
|
|
237
|
+
f"Failed to create edge type `{edge_type}` after "
|
|
230
238
|
f"{MAX_RETRIES} attempts: {res.error_msg()}"
|
|
231
239
|
)
|
|
232
240
|
|
|
233
|
-
def ensure_tag_exists(
|
|
241
|
+
def ensure_tag_exists(
|
|
242
|
+
self, tag_name: str, time_label: Optional[str] = None
|
|
243
|
+
) -> None:
|
|
234
244
|
r"""Ensures a tag is created in the NebulaGraph database. If the tag
|
|
235
245
|
already exists, it does nothing.
|
|
236
246
|
|
|
237
247
|
Args:
|
|
238
248
|
tag_name (str): The name of the tag to be created.
|
|
249
|
+
time_label (str, optional): A specific timestamp to set as the
|
|
250
|
+
default value for the time label property. If not provided,
|
|
251
|
+
no timestamp will be added. (default: :obj:`None`)
|
|
239
252
|
|
|
240
253
|
Raises:
|
|
241
254
|
Exception: If the tag creation fails after retries, an exception
|
|
242
255
|
is raised with the error message.
|
|
243
256
|
"""
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
create_tag_stmt = f"CREATE TAG IF NOT EXISTS {tag_name} ()"
|
|
258
|
+
if time_label is not None:
|
|
259
|
+
time_label = self._validate_time_label(time_label)
|
|
260
|
+
create_tag_stmt = f"""CREATE TAG IF NOT EXISTS {tag_name}
|
|
261
|
+
(time_label DATETIME DEFAULT {time_label})"""
|
|
246
262
|
|
|
247
263
|
for attempt in range(MAX_RETRIES):
|
|
248
264
|
res = self.query(create_tag_stmt)
|
|
@@ -262,27 +278,39 @@ class NebulaGraph(BaseGraphStorage):
|
|
|
262
278
|
self,
|
|
263
279
|
node_id: str,
|
|
264
280
|
tag_name: str,
|
|
281
|
+
time_label: Optional[str] = None,
|
|
265
282
|
) -> None:
|
|
266
283
|
r"""Add a node with the specified tag and properties.
|
|
267
284
|
|
|
268
285
|
Args:
|
|
269
286
|
node_id (str): The ID of the node.
|
|
270
287
|
tag_name (str): The tag name of the node.
|
|
288
|
+
time_label (str, optional): A specific timestamp to set for
|
|
289
|
+
the node's time label property. If not provided, no timestamp
|
|
290
|
+
will be added. (default: :obj:`None`)
|
|
271
291
|
"""
|
|
272
292
|
node_id = re.sub(r'[^a-zA-Z0-9\u4e00-\u9fa5]', '', node_id)
|
|
273
293
|
tag_name = re.sub(r'[^a-zA-Z0-9\u4e00-\u9fa5]', '', tag_name)
|
|
274
294
|
|
|
275
|
-
self.ensure_tag_exists(tag_name)
|
|
295
|
+
self.ensure_tag_exists(tag_name, time_label)
|
|
276
296
|
|
|
277
|
-
# Insert node without
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
297
|
+
# Insert node with or without time_label property
|
|
298
|
+
if time_label is not None:
|
|
299
|
+
time_label = self._validate_time_label(time_label)
|
|
300
|
+
insert_stmt = (
|
|
301
|
+
f'INSERT VERTEX IF NOT EXISTS {tag_name}(time_label) VALUES '
|
|
302
|
+
f'"{node_id}":("{time_label}")'
|
|
303
|
+
)
|
|
304
|
+
else:
|
|
305
|
+
insert_stmt = (
|
|
306
|
+
f'INSERT VERTEX IF NOT EXISTS {tag_name}() VALUES '
|
|
307
|
+
f'"{node_id}":()'
|
|
308
|
+
)
|
|
281
309
|
|
|
282
310
|
for attempt in range(MAX_RETRIES):
|
|
283
311
|
res = self.query(insert_stmt)
|
|
284
312
|
if res.is_succeeded():
|
|
285
|
-
return #
|
|
313
|
+
return # Node creation succeeded, exit the method
|
|
286
314
|
|
|
287
315
|
if attempt < MAX_RETRIES - 1:
|
|
288
316
|
time.sleep(RETRY_DELAY)
|
|
@@ -348,7 +376,7 @@ class NebulaGraph(BaseGraphStorage):
|
|
|
348
376
|
@property
|
|
349
377
|
def get_structured_schema(self) -> Dict[str, Any]:
|
|
350
378
|
r"""Generates a structured schema consisting of node and relationship
|
|
351
|
-
properties, relationships, and metadata.
|
|
379
|
+
properties, relationships, and metadata, including timestamps.
|
|
352
380
|
|
|
353
381
|
Returns:
|
|
354
382
|
Dict[str, Any]: A dictionary representing the structured schema.
|
|
@@ -419,6 +447,7 @@ class NebulaGraph(BaseGraphStorage):
|
|
|
419
447
|
subj: str,
|
|
420
448
|
obj: str,
|
|
421
449
|
rel: str,
|
|
450
|
+
time_label: Optional[str] = None,
|
|
422
451
|
) -> None:
|
|
423
452
|
r"""Adds a relationship (triplet) between two entities in the Nebula
|
|
424
453
|
Graph database.
|
|
@@ -427,6 +456,13 @@ class NebulaGraph(BaseGraphStorage):
|
|
|
427
456
|
subj (str): The identifier for the subject entity.
|
|
428
457
|
obj (str): The identifier for the object entity.
|
|
429
458
|
rel (str): The relationship between the subject and object.
|
|
459
|
+
time_label (str, optional): A specific timestamp to set for the
|
|
460
|
+
time label property of the relationship. If not provided,
|
|
461
|
+
no timestamp will be added. (default: :obj:`None`)
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
ValueError: If the time_label format is invalid.
|
|
465
|
+
Exception: If creating the relationship fails.
|
|
430
466
|
"""
|
|
431
467
|
subj = re.sub(r'[^a-zA-Z0-9\u4e00-\u9fa5]', '', subj)
|
|
432
468
|
obj = re.sub(r'[^a-zA-Z0-9\u4e00-\u9fa5]', '', obj)
|
|
@@ -434,21 +470,30 @@ class NebulaGraph(BaseGraphStorage):
|
|
|
434
470
|
|
|
435
471
|
self.ensure_tag_exists(subj)
|
|
436
472
|
self.ensure_tag_exists(obj)
|
|
437
|
-
self.ensure_edge_type_exists(rel)
|
|
473
|
+
self.ensure_edge_type_exists(rel, time_label)
|
|
438
474
|
self.add_node(node_id=subj, tag_name=subj)
|
|
439
475
|
self.add_node(node_id=obj, tag_name=obj)
|
|
440
476
|
|
|
441
|
-
# Avoid
|
|
477
|
+
# Avoid latency
|
|
442
478
|
time.sleep(1)
|
|
443
479
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
480
|
+
# Create edge with or without time_label property
|
|
481
|
+
if time_label is not None:
|
|
482
|
+
time_label = self._validate_time_label(time_label)
|
|
483
|
+
insert_stmt = (
|
|
484
|
+
f'INSERT EDGE IF NOT EXISTS {rel}(time_label) VALUES '
|
|
485
|
+
f'"{subj}"->"{obj}":("{time_label}")'
|
|
486
|
+
)
|
|
487
|
+
else:
|
|
488
|
+
insert_stmt = (
|
|
489
|
+
f'INSERT EDGE IF NOT EXISTS {rel}() VALUES '
|
|
490
|
+
f'"{subj}"->"{obj}":()'
|
|
491
|
+
)
|
|
447
492
|
|
|
448
493
|
res = self.query(insert_stmt)
|
|
449
494
|
if not res.is_succeeded():
|
|
450
495
|
raise Exception(
|
|
451
|
-
f'create relationship `
|
|
496
|
+
f'create relationship `{subj}` -> `{obj}`'
|
|
452
497
|
+ f'failed: {res.error_msg()}'
|
|
453
498
|
)
|
|
454
499
|
|
|
@@ -568,3 +613,27 @@ class NebulaGraph(BaseGraphStorage):
|
|
|
568
613
|
)
|
|
569
614
|
|
|
570
615
|
return rel_schema_props, rel_structure_props
|
|
616
|
+
|
|
617
|
+
def _validate_time_label(self, time_label: str) -> str:
|
|
618
|
+
r"""Validates the format of a time label string.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
time_label (str): The time label string to validate.
|
|
622
|
+
Should be in format 'YYYY-MM-DDThh:mm:ss'.
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
str: The validated time label.
|
|
626
|
+
|
|
627
|
+
Raises:
|
|
628
|
+
ValueError: If the time label format is invalid.
|
|
629
|
+
"""
|
|
630
|
+
try:
|
|
631
|
+
# Check if the format matches YYYY-MM-DDThh:mm:ss
|
|
632
|
+
pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$'
|
|
633
|
+
if not re.match(pattern, time_label):
|
|
634
|
+
raise ValueError(
|
|
635
|
+
"Time label must be in format 'YYYY-MM-DDThh:mm:ss'"
|
|
636
|
+
)
|
|
637
|
+
return time_label
|
|
638
|
+
except Exception as e:
|
|
639
|
+
raise ValueError(f"Invalid time label format: {e!s}")
|
|
@@ -583,3 +583,141 @@ class Neo4jGraph(BaseGraphStorage):
|
|
|
583
583
|
]
|
|
584
584
|
},
|
|
585
585
|
)
|
|
586
|
+
|
|
587
|
+
def random_walk_with_restarts(
|
|
588
|
+
self,
|
|
589
|
+
graph_name: str,
|
|
590
|
+
sampling_ratio: float,
|
|
591
|
+
start_node_ids: List[int],
|
|
592
|
+
restart_probability: float = 0.1,
|
|
593
|
+
node_label_stratification: bool = False,
|
|
594
|
+
relationship_weight_property: Optional[str] = None,
|
|
595
|
+
) -> Dict[str, Any]:
|
|
596
|
+
r"""Runs the Random Walk with Restarts (RWR) sampling algorithm.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
graph_name (str): The name of the original graph in the graph
|
|
600
|
+
catalog.
|
|
601
|
+
sampling_ratio (float): The fraction of nodes in the original
|
|
602
|
+
graph to be sampled.
|
|
603
|
+
start_node_ids (List[int]): IDs of the initial set of nodes of the
|
|
604
|
+
original graph from which the sampling random walks will start.
|
|
605
|
+
restart_probability (float, optional): The probability that a
|
|
606
|
+
sampling random walk restarts from one of the start nodes.
|
|
607
|
+
Defaults to `0.1`.
|
|
608
|
+
node_label_stratification (bool, optional): If true, preserves the
|
|
609
|
+
node label distribution of the original graph. Defaults to
|
|
610
|
+
`False`.
|
|
611
|
+
relationship_weight_property (Optional[str], optional): Name of
|
|
612
|
+
the relationship property to use as weights. If unspecified,
|
|
613
|
+
the algorithm runs unweighted. Defaults to `None`.
|
|
614
|
+
|
|
615
|
+
Returns:
|
|
616
|
+
Dict[str, Any]: A dictionary with the results of the RWR sampling.
|
|
617
|
+
"""
|
|
618
|
+
from neo4j.exceptions import ClientError, CypherSyntaxError
|
|
619
|
+
|
|
620
|
+
try:
|
|
621
|
+
self.query(query="CALL gds.version() YIELD version RETURN version")
|
|
622
|
+
except ClientError:
|
|
623
|
+
raise ValueError(
|
|
624
|
+
"Graph Data Science (GDS) library is not installed or not"
|
|
625
|
+
" available. Reference: https://neo4j.com/docs/graph-data-science/current/installation/"
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
query = """
|
|
629
|
+
CALL gds.graph.sample.rwr($graphName, $fromGraphName, {
|
|
630
|
+
samplingRatio: $samplingRatio,
|
|
631
|
+
startNodes: $startNodes,
|
|
632
|
+
restartProbability: $restartProbability,
|
|
633
|
+
nodeLabelStratification: $nodeLabelStratification,
|
|
634
|
+
relationshipWeightProperty: $relationshipWeightProperty
|
|
635
|
+
})
|
|
636
|
+
YIELD graphName, fromGraphName, nodeCount,
|
|
637
|
+
relationshipCount, startNodeCount, projectMillis
|
|
638
|
+
RETURN graphName, fromGraphName, nodeCount,
|
|
639
|
+
relationshipCount, startNodeCount, projectMillis
|
|
640
|
+
"""
|
|
641
|
+
|
|
642
|
+
params = {
|
|
643
|
+
"graphName": f"{graph_name}_sampled",
|
|
644
|
+
"fromGraphName": graph_name,
|
|
645
|
+
"samplingRatio": sampling_ratio,
|
|
646
|
+
"startNodes": start_node_ids,
|
|
647
|
+
"restartProbability": restart_probability,
|
|
648
|
+
"nodeLabelStratification": node_label_stratification,
|
|
649
|
+
"relationshipWeightProperty": relationship_weight_property,
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
try:
|
|
653
|
+
result = self.query(query, params)
|
|
654
|
+
return result[0] if result else {}
|
|
655
|
+
except CypherSyntaxError as e:
|
|
656
|
+
raise ValueError(f"Generated Cypher Statement is not valid\n{e}")
|
|
657
|
+
|
|
658
|
+
def common_neighbour_aware_random_walk(
|
|
659
|
+
self,
|
|
660
|
+
graph_name: str,
|
|
661
|
+
sampling_ratio: float,
|
|
662
|
+
start_node_ids: List[int],
|
|
663
|
+
node_label_stratification: bool = False,
|
|
664
|
+
relationship_weight_property: Optional[str] = None,
|
|
665
|
+
) -> Dict[str, Any]:
|
|
666
|
+
r"""Runs the Common Neighbour Aware Random Walk (CNARW) sampling
|
|
667
|
+
algorithm.
|
|
668
|
+
|
|
669
|
+
Args:
|
|
670
|
+
graph_name (str): The name of the original graph in the graph
|
|
671
|
+
catalog.
|
|
672
|
+
sampling_ratio (float): The fraction of nodes in the original
|
|
673
|
+
graph to be sampled.
|
|
674
|
+
start_node_ids (List[int]): IDs of the initial set of nodes of the
|
|
675
|
+
original graph from which the sampling random walks will start.
|
|
676
|
+
node_label_stratification (bool, optional): If true, preserves the
|
|
677
|
+
node label distribution of the original graph. Defaults to
|
|
678
|
+
`False`.
|
|
679
|
+
relationship_weight_property (Optional[str], optional): Name of
|
|
680
|
+
the relationship property to use as weights. If unspecified,
|
|
681
|
+
the algorithm runs unweighted. Defaults to `None`.
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
Dict[str, Any]: A dictionary with the results of the CNARW
|
|
685
|
+
sampling.
|
|
686
|
+
"""
|
|
687
|
+
from neo4j.exceptions import ClientError, CypherSyntaxError
|
|
688
|
+
|
|
689
|
+
try:
|
|
690
|
+
self.query(query="CALL gds.version() YIELD version RETURN version")
|
|
691
|
+
except ClientError:
|
|
692
|
+
raise ValueError(
|
|
693
|
+
"Graph Data Science (GDS) library is not installed or not"
|
|
694
|
+
" available. Reference: https://neo4j.com/docs/graph-data-science/current/installation/"
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
query = """
|
|
698
|
+
CALL gds.graph.sample.cnarw($graphName, $fromGraphName, {
|
|
699
|
+
samplingRatio: $samplingRatio,
|
|
700
|
+
startNodes: $startNodes,
|
|
701
|
+
nodeLabelStratification: $nodeLabelStratification,
|
|
702
|
+
relationshipWeightProperty: $relationshipWeightProperty
|
|
703
|
+
})
|
|
704
|
+
YIELD graphName, fromGraphName, nodeCount,
|
|
705
|
+
relationshipCount, startNodeCount, projectMillis
|
|
706
|
+
RETURN graphName, fromGraphName, nodeCount,
|
|
707
|
+
relationshipCount, startNodeCount, projectMillis
|
|
708
|
+
"""
|
|
709
|
+
|
|
710
|
+
params = {
|
|
711
|
+
"graphName": f"{graph_name}_sampled_cnarw",
|
|
712
|
+
"fromGraphName": graph_name,
|
|
713
|
+
"samplingRatio": sampling_ratio,
|
|
714
|
+
"startNodes": start_node_ids,
|
|
715
|
+
"nodeLabelStratification": node_label_stratification,
|
|
716
|
+
"relationshipWeightProperty": relationship_weight_property,
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
try:
|
|
720
|
+
result = self.query(query, params)
|
|
721
|
+
return result[0] if result else {}
|
|
722
|
+
except CypherSyntaxError as e:
|
|
723
|
+
raise ValueError(f"Generated Cypher Statement is not valid\n{e}")
|
camel/toolkits/__init__.py
CHANGED
|
@@ -28,6 +28,7 @@ from .ask_news_toolkit import AskNewsToolkit, AsyncAskNewsToolkit
|
|
|
28
28
|
from .linkedin_toolkit import LinkedInToolkit
|
|
29
29
|
from .reddit_toolkit import RedditToolkit
|
|
30
30
|
from .meshy_toolkit import MeshyToolkit
|
|
31
|
+
from .openbb_toolkit import OpenBBToolkit
|
|
31
32
|
|
|
32
33
|
from .base import BaseToolkit
|
|
33
34
|
from .google_maps_toolkit import GoogleMapsToolkit
|
|
@@ -43,6 +44,7 @@ from .notion_toolkit import NotionToolkit
|
|
|
43
44
|
from .human_toolkit import HumanToolkit
|
|
44
45
|
from .stripe_toolkit import StripeToolkit
|
|
45
46
|
from .video_toolkit import VideoDownloaderToolkit
|
|
47
|
+
from .dappier_toolkit import DappierToolkit
|
|
46
48
|
|
|
47
49
|
__all__ = [
|
|
48
50
|
'BaseToolkit',
|
|
@@ -73,4 +75,6 @@ __all__ = [
|
|
|
73
75
|
'VideoDownloaderToolkit',
|
|
74
76
|
'StripeToolkit',
|
|
75
77
|
'MeshyToolkit',
|
|
78
|
+
'OpenBBToolkit',
|
|
79
|
+
'DappierToolkit',
|
|
76
80
|
]
|