camel-ai 0.2.22__py3-none-any.whl → 0.2.23__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/agents/_types.py +41 -0
- camel/agents/_utils.py +188 -0
- camel/agents/chat_agent.py +570 -965
- camel/agents/knowledge_graph_agent.py +7 -1
- camel/agents/multi_hop_generator_agent.py +1 -1
- camel/configs/base_config.py +10 -13
- camel/configs/deepseek_config.py +4 -30
- camel/configs/gemini_config.py +5 -31
- camel/configs/openai_config.py +14 -32
- camel/configs/qwen_config.py +36 -36
- camel/datagen/self_improving_cot.py +81 -3
- camel/datagen/self_instruct/filter/instruction_filter.py +19 -3
- camel/datagen/self_instruct/self_instruct.py +52 -3
- camel/datasets/__init__.py +28 -0
- camel/datasets/base.py +969 -0
- camel/environments/__init__.py +16 -0
- camel/environments/base.py +503 -0
- camel/extractors/__init__.py +16 -0
- camel/extractors/base.py +263 -0
- camel/memories/agent_memories.py +16 -1
- camel/memories/blocks/chat_history_block.py +10 -2
- camel/memories/blocks/vectordb_block.py +1 -0
- camel/memories/context_creators/score_based.py +20 -3
- camel/memories/records.py +10 -0
- camel/messages/base.py +8 -8
- camel/models/__init__.py +2 -0
- camel/models/_utils.py +57 -0
- camel/models/aiml_model.py +48 -17
- camel/models/anthropic_model.py +41 -3
- camel/models/azure_openai_model.py +39 -3
- camel/models/base_audio_model.py +92 -0
- camel/models/base_model.py +88 -13
- camel/models/cohere_model.py +88 -11
- camel/models/deepseek_model.py +107 -45
- camel/models/fish_audio_model.py +18 -8
- camel/models/gemini_model.py +133 -15
- camel/models/groq_model.py +72 -10
- camel/models/internlm_model.py +14 -3
- camel/models/litellm_model.py +9 -2
- camel/models/mistral_model.py +42 -5
- camel/models/model_manager.py +57 -3
- camel/models/moonshot_model.py +33 -4
- camel/models/nemotron_model.py +32 -3
- camel/models/nvidia_model.py +43 -3
- camel/models/ollama_model.py +139 -17
- camel/models/openai_audio_models.py +87 -2
- camel/models/openai_compatible_model.py +37 -3
- camel/models/openai_model.py +158 -46
- camel/models/qwen_model.py +61 -4
- camel/models/reka_model.py +53 -3
- camel/models/samba_model.py +209 -4
- camel/models/sglang_model.py +153 -14
- camel/models/siliconflow_model.py +16 -3
- camel/models/stub_model.py +46 -4
- camel/models/togetherai_model.py +38 -3
- camel/models/vllm_model.py +37 -3
- camel/models/yi_model.py +36 -3
- camel/models/zhipuai_model.py +38 -3
- camel/retrievers/__init__.py +3 -0
- camel/retrievers/hybrid_retrival.py +237 -0
- camel/toolkits/__init__.py +15 -1
- camel/toolkits/arxiv_toolkit.py +2 -1
- camel/toolkits/ask_news_toolkit.py +4 -2
- camel/toolkits/audio_analysis_toolkit.py +238 -0
- camel/toolkits/base.py +22 -3
- camel/toolkits/code_execution.py +2 -0
- camel/toolkits/dappier_toolkit.py +2 -1
- camel/toolkits/data_commons_toolkit.py +38 -12
- camel/toolkits/excel_toolkit.py +172 -0
- camel/toolkits/function_tool.py +13 -0
- camel/toolkits/github_toolkit.py +5 -1
- camel/toolkits/google_maps_toolkit.py +2 -1
- camel/toolkits/google_scholar_toolkit.py +2 -0
- camel/toolkits/human_toolkit.py +0 -3
- camel/toolkits/image_analysis_toolkit.py +202 -0
- camel/toolkits/linkedin_toolkit.py +3 -2
- camel/toolkits/meshy_toolkit.py +3 -2
- camel/toolkits/mineru_toolkit.py +2 -2
- camel/toolkits/networkx_toolkit.py +240 -0
- camel/toolkits/notion_toolkit.py +2 -0
- camel/toolkits/openbb_toolkit.py +3 -2
- camel/toolkits/page_script.js +376 -0
- camel/toolkits/reddit_toolkit.py +11 -3
- camel/toolkits/retrieval_toolkit.py +6 -1
- camel/toolkits/semantic_scholar_toolkit.py +2 -1
- camel/toolkits/stripe_toolkit.py +8 -2
- camel/toolkits/sympy_toolkit.py +6 -1
- camel/toolkits/video_analysis_toolkit.py +407 -0
- camel/toolkits/{video_toolkit.py → video_download_toolkit.py} +21 -25
- camel/toolkits/web_toolkit.py +1307 -0
- camel/toolkits/whatsapp_toolkit.py +3 -2
- camel/toolkits/zapier_toolkit.py +191 -0
- camel/types/__init__.py +2 -2
- camel/types/agents/__init__.py +16 -0
- camel/types/agents/tool_calling_record.py +52 -0
- camel/types/enums.py +3 -0
- camel/types/openai_types.py +16 -14
- camel/utils/__init__.py +2 -1
- camel/utils/async_func.py +2 -2
- camel/utils/commons.py +114 -1
- camel/verifiers/__init__.py +23 -0
- camel/verifiers/base.py +340 -0
- camel/verifiers/models.py +82 -0
- camel/verifiers/python_verifier.py +202 -0
- camel_ai-0.2.23.dist-info/METADATA +671 -0
- {camel_ai-0.2.22.dist-info → camel_ai-0.2.23.dist-info}/RECORD +122 -97
- {camel_ai-0.2.22.dist-info → camel_ai-0.2.23.dist-info}/WHEEL +1 -1
- camel_ai-0.2.22.dist-info/METADATA +0 -527
- {camel_ai-0.2.22.dist-info → camel_ai-0.2.23.dist-info/licenses}/LICENSE +0 -0
camel/models/yi_model.py
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
|
|
15
15
|
import os
|
|
16
|
-
from typing import Any, Dict, List, Optional, Union
|
|
16
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
17
17
|
|
|
18
|
-
from openai import OpenAI, Stream
|
|
18
|
+
from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
|
|
19
|
+
from pydantic import BaseModel
|
|
19
20
|
|
|
20
21
|
from camel.configs import YI_API_PARAMS, YiConfig
|
|
21
22
|
from camel.messages import OpenAIMessage
|
|
@@ -80,10 +81,42 @@ class YiModel(BaseModelBackend):
|
|
|
80
81
|
api_key=self._api_key,
|
|
81
82
|
base_url=self._url,
|
|
82
83
|
)
|
|
84
|
+
self._async_client = AsyncOpenAI(
|
|
85
|
+
timeout=180,
|
|
86
|
+
max_retries=3,
|
|
87
|
+
api_key=self._api_key,
|
|
88
|
+
base_url=self._url,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async def _arun(
|
|
92
|
+
self,
|
|
93
|
+
messages: List[OpenAIMessage],
|
|
94
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
95
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
96
|
+
) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
97
|
+
r"""Runs inference of Yi chat completion.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
messages (List[OpenAIMessage]): Message list with the chat history
|
|
101
|
+
in OpenAI API format.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
105
|
+
`ChatCompletion` in the non-stream mode, or
|
|
106
|
+
`AsyncStream[ChatCompletionChunk]` in the stream mode.
|
|
107
|
+
"""
|
|
108
|
+
response = await self._async_client.chat.completions.create(
|
|
109
|
+
messages=messages,
|
|
110
|
+
model=self.model_type,
|
|
111
|
+
**self.model_config_dict,
|
|
112
|
+
)
|
|
113
|
+
return response
|
|
83
114
|
|
|
84
|
-
def
|
|
115
|
+
def _run(
|
|
85
116
|
self,
|
|
86
117
|
messages: List[OpenAIMessage],
|
|
118
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
119
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
87
120
|
) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
|
|
88
121
|
r"""Runs inference of Yi chat completion.
|
|
89
122
|
|
camel/models/zhipuai_model.py
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
|
|
15
15
|
import os
|
|
16
|
-
from typing import Any, Dict, List, Optional, Union
|
|
16
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
17
17
|
|
|
18
|
-
from openai import OpenAI, Stream
|
|
18
|
+
from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
|
|
19
|
+
from pydantic import BaseModel
|
|
19
20
|
|
|
20
21
|
from camel.configs import ZHIPUAI_API_PARAMS, ZhipuAIConfig
|
|
21
22
|
from camel.messages import OpenAIMessage
|
|
@@ -80,10 +81,44 @@ class ZhipuAIModel(BaseModelBackend):
|
|
|
80
81
|
api_key=self._api_key,
|
|
81
82
|
base_url=self._url,
|
|
82
83
|
)
|
|
84
|
+
self._async_client = AsyncOpenAI(
|
|
85
|
+
timeout=180,
|
|
86
|
+
max_retries=3,
|
|
87
|
+
api_key=self._api_key,
|
|
88
|
+
base_url=self._url,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async def _arun(
|
|
92
|
+
self,
|
|
93
|
+
messages: List[OpenAIMessage],
|
|
94
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
95
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
96
|
+
) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
97
|
+
r"""Runs inference of OpenAI chat completion.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
messages (List[OpenAIMessage]): Message list with the chat history
|
|
101
|
+
in OpenAI API format.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
105
|
+
`ChatCompletion` in the non-stream mode, or
|
|
106
|
+
`AsyncStream[ChatCompletionChunk]` in the stream mode.
|
|
107
|
+
"""
|
|
108
|
+
# Use OpenAI cilent as interface call ZhipuAI
|
|
109
|
+
# Reference: https://open.bigmodel.cn/dev/api#openai_sdk
|
|
110
|
+
response = await self._async_client.chat.completions.create(
|
|
111
|
+
messages=messages,
|
|
112
|
+
model=self.model_type,
|
|
113
|
+
**self.model_config_dict,
|
|
114
|
+
)
|
|
115
|
+
return response
|
|
83
116
|
|
|
84
|
-
def
|
|
117
|
+
def _run(
|
|
85
118
|
self,
|
|
86
119
|
messages: List[OpenAIMessage],
|
|
120
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
121
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
87
122
|
) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
|
|
88
123
|
r"""Runs inference of OpenAI chat completion.
|
|
89
124
|
|
camel/retrievers/__init__.py
CHANGED
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
# See the License for the specific language governing permissions and
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
# ruff: noqa: I001
|
|
14
15
|
from .auto_retriever import AutoRetriever
|
|
15
16
|
from .base import BaseRetriever
|
|
16
17
|
from .bm25_retriever import BM25Retriever
|
|
17
18
|
from .cohere_rerank_retriever import CohereRerankRetriever
|
|
18
19
|
from .vector_retriever import VectorRetriever
|
|
20
|
+
from .hybrid_retrival import HybridRetriever
|
|
19
21
|
|
|
20
22
|
__all__ = [
|
|
21
23
|
'BaseRetriever',
|
|
@@ -23,4 +25,5 @@ __all__ = [
|
|
|
23
25
|
'AutoRetriever',
|
|
24
26
|
'BM25Retriever',
|
|
25
27
|
'CohereRerankRetriever',
|
|
28
|
+
'HybridRetriever',
|
|
26
29
|
]
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
from typing import Any, Collection, Dict, List, Optional, Sequence, Union
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
from camel.embeddings import BaseEmbedding
|
|
19
|
+
from camel.retrievers import BaseRetriever, BM25Retriever, VectorRetriever
|
|
20
|
+
from camel.storages import BaseVectorStorage
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HybridRetriever(BaseRetriever):
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
embedding_model: Optional[BaseEmbedding] = None,
|
|
27
|
+
vector_storage: Optional[BaseVectorStorage] = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
r"""Initializes the HybridRetriever with optional embedding model and
|
|
30
|
+
vector storage.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
embedding_model (Optional[BaseEmbedding]): An optional embedding
|
|
34
|
+
model used by the VectorRetriever. Defaults to None.
|
|
35
|
+
vector_storage (Optional[BaseVectorStorage]): An optional vector
|
|
36
|
+
storage used by the VectorRetriever. Defaults to None.
|
|
37
|
+
"""
|
|
38
|
+
self.vr = VectorRetriever(embedding_model, vector_storage)
|
|
39
|
+
self.bm25 = BM25Retriever()
|
|
40
|
+
|
|
41
|
+
def process(self, content_input_path: str) -> None:
|
|
42
|
+
r"""Processes the content input path for both vector and BM25
|
|
43
|
+
retrievers.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
content_input_path (str): File path or URL of the content to be
|
|
47
|
+
processed.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: If the content_input_path is empty.
|
|
51
|
+
"""
|
|
52
|
+
if not content_input_path:
|
|
53
|
+
raise ValueError("content_input_path cannot be empty.")
|
|
54
|
+
|
|
55
|
+
self.content_input_path = content_input_path
|
|
56
|
+
self.vr.process(content=self.content_input_path)
|
|
57
|
+
self.bm25.process(content_input_path=self.content_input_path)
|
|
58
|
+
|
|
59
|
+
def _sort_rrf_scores(
|
|
60
|
+
self,
|
|
61
|
+
vector_retriever_results: List[Dict[str, Any]],
|
|
62
|
+
bm25_retriever_results: List[Dict[str, Any]],
|
|
63
|
+
top_k: int,
|
|
64
|
+
vector_weight: float,
|
|
65
|
+
bm25_weight: float,
|
|
66
|
+
rank_smoothing_factor: float,
|
|
67
|
+
) -> List[Dict[str, Union[str, float]]]:
|
|
68
|
+
r"""Sorts and combines results from vector and BM25 retrievers using
|
|
69
|
+
Reciprocal Rank Fusion (RRF).
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
vector_retriever_results: A list of dictionaries containing the
|
|
73
|
+
results from the vector retriever, where each dictionary
|
|
74
|
+
contains a 'text' entry.
|
|
75
|
+
bm25_retriever_results: A list of dictionaries containing the
|
|
76
|
+
results from the BM25 retriever, where each dictionary
|
|
77
|
+
contains a 'text' entry.
|
|
78
|
+
top_k: The number of top results to return after sorting by RRF
|
|
79
|
+
score.
|
|
80
|
+
vector_weight: The weight to assign to the vector retriever
|
|
81
|
+
results in the RRF calculation.
|
|
82
|
+
bm25_weight: The weight to assign to the BM25 retriever results in
|
|
83
|
+
the RRF calculation.
|
|
84
|
+
rank_smoothing_factor: A hyperparameter for the RRF calculation
|
|
85
|
+
that helps smooth the rank positions.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
List[Dict[str, Union[str, float]]]: A list of dictionaries
|
|
89
|
+
representing the sorted results. Each dictionary contains the
|
|
90
|
+
'text'from the retrieved items and their corresponding 'rrf_score'.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If any of the input weights are negative.
|
|
94
|
+
|
|
95
|
+
References:
|
|
96
|
+
https://medium.com/@devalshah1619/mathematical-intuition-behind-reciprocal-rank-fusion-rrf-explained-in-2-mins-002df0cc5e2a
|
|
97
|
+
https://colab.research.google.com/drive/1iwVJrN96fiyycxN1pBqWlEr_4EPiGdGy#scrollTo=0qh83qGV2dY8
|
|
98
|
+
"""
|
|
99
|
+
text_to_id = {}
|
|
100
|
+
id_to_info = {}
|
|
101
|
+
current_id = 1
|
|
102
|
+
|
|
103
|
+
# Iterate over vector_retriever_results
|
|
104
|
+
for rank, result in enumerate(vector_retriever_results, start=1):
|
|
105
|
+
text = result.get('text', None) # type: ignore[attr-defined]
|
|
106
|
+
if text is None:
|
|
107
|
+
raise KeyError("Each result must contain a 'text' key")
|
|
108
|
+
|
|
109
|
+
if text not in text_to_id:
|
|
110
|
+
text_to_id[text] = current_id
|
|
111
|
+
id_to_info[current_id] = {'text': text, 'vector_rank': rank}
|
|
112
|
+
current_id += 1
|
|
113
|
+
else:
|
|
114
|
+
id_to_info[text_to_id[text]]['vector_rank'] = rank
|
|
115
|
+
|
|
116
|
+
# Iterate over bm25_retriever_results
|
|
117
|
+
for rank, result in enumerate(bm25_retriever_results, start=1):
|
|
118
|
+
text = result['text']
|
|
119
|
+
if text not in text_to_id:
|
|
120
|
+
text_to_id[text] = current_id
|
|
121
|
+
id_to_info[current_id] = {'text': text, 'bm25_rank': rank}
|
|
122
|
+
current_id += 1
|
|
123
|
+
else:
|
|
124
|
+
id_to_info[text_to_id[text]].setdefault('bm25_rank', rank)
|
|
125
|
+
|
|
126
|
+
vector_ranks = np.array(
|
|
127
|
+
[
|
|
128
|
+
info.get('vector_rank', float('inf'))
|
|
129
|
+
for info in id_to_info.values()
|
|
130
|
+
]
|
|
131
|
+
)
|
|
132
|
+
bm25_ranks = np.array(
|
|
133
|
+
[
|
|
134
|
+
info.get('bm25_rank', float('inf'))
|
|
135
|
+
for info in id_to_info.values()
|
|
136
|
+
]
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Calculate RRF scores
|
|
140
|
+
vector_rrf_scores = vector_weight / (
|
|
141
|
+
rank_smoothing_factor + vector_ranks
|
|
142
|
+
)
|
|
143
|
+
bm25_rrf_scores = bm25_weight / (rank_smoothing_factor + bm25_ranks)
|
|
144
|
+
rrf_scores = vector_rrf_scores + bm25_rrf_scores
|
|
145
|
+
|
|
146
|
+
for idx, (_, info) in enumerate(id_to_info.items()):
|
|
147
|
+
info['rrf_score'] = rrf_scores[idx]
|
|
148
|
+
sorted_results = sorted(
|
|
149
|
+
id_to_info.values(), key=lambda x: x['rrf_score'], reverse=True
|
|
150
|
+
)
|
|
151
|
+
return sorted_results[:top_k]
|
|
152
|
+
|
|
153
|
+
def query(
|
|
154
|
+
self,
|
|
155
|
+
query: str,
|
|
156
|
+
top_k: int = 20,
|
|
157
|
+
vector_weight: float = 0.8,
|
|
158
|
+
bm25_weight: float = 0.2,
|
|
159
|
+
rank_smoothing_factor: int = 60,
|
|
160
|
+
vector_retriever_top_k: int = 50,
|
|
161
|
+
vector_retriever_similarity_threshold: float = 0.5,
|
|
162
|
+
bm25_retriever_top_k: int = 50,
|
|
163
|
+
return_detailed_info: bool = False,
|
|
164
|
+
) -> Union[
|
|
165
|
+
dict[str, Sequence[Collection[str]]],
|
|
166
|
+
dict[str, Sequence[Union[str, float]]],
|
|
167
|
+
]:
|
|
168
|
+
r"""Executes a hybrid retrieval query using both vector and BM25
|
|
169
|
+
retrievers.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
query (str): The search query.
|
|
173
|
+
top_k (int): Number of top results to return (default 20).
|
|
174
|
+
vector_weight (float): Weight for vector retriever results in RRF.
|
|
175
|
+
bm25_weight (float): Weight for BM25 retriever results in RRF.
|
|
176
|
+
rank_smoothing_factor (int): RRF hyperparameter for rank smoothing.
|
|
177
|
+
vector_retriever_top_k (int): Top results from vector retriever.
|
|
178
|
+
vector_retriever_similarity_threshold (float): Similarity
|
|
179
|
+
threshold for vector retriever.
|
|
180
|
+
bm25_retriever_top_k (int): Top results from BM25 retriever.
|
|
181
|
+
return_detailed_info (bool): Return detailed info if True.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Union[
|
|
185
|
+
dict[str, Sequence[Collection[str]]],
|
|
186
|
+
dict[str, Sequence[Union[str, float]]]
|
|
187
|
+
]: By default, returns only the text information. If
|
|
188
|
+
`return_detailed_info` is `True`, return detailed information
|
|
189
|
+
including rrf scores.
|
|
190
|
+
"""
|
|
191
|
+
if top_k > max(vector_retriever_top_k, bm25_retriever_top_k):
|
|
192
|
+
raise ValueError(
|
|
193
|
+
"top_k needs to be less than or equal to the "
|
|
194
|
+
"maximum value among vector_retriever_top_k and "
|
|
195
|
+
"bm25_retriever_top_k."
|
|
196
|
+
)
|
|
197
|
+
if vector_weight < 0 or bm25_weight < 0:
|
|
198
|
+
raise ValueError(
|
|
199
|
+
"Neither `vector_weight` nor `bm25_weight` can be negative."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
vr_raw_results: List[Dict[str, Any]] = self.vr.query(
|
|
203
|
+
query=query,
|
|
204
|
+
top_k=vector_retriever_top_k,
|
|
205
|
+
similarity_threshold=vector_retriever_similarity_threshold,
|
|
206
|
+
)
|
|
207
|
+
# if the number of results is less than top_k, return all results
|
|
208
|
+
with_score = [
|
|
209
|
+
info for info in vr_raw_results if 'similarity score' in info
|
|
210
|
+
]
|
|
211
|
+
vector_retriever_results = sorted(
|
|
212
|
+
with_score, key=lambda x: x['similarity score'], reverse=True
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
bm25_retriever_results = self.bm25.query(
|
|
216
|
+
query=query,
|
|
217
|
+
top_k=bm25_retriever_top_k,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
all_retrieved_info = self._sort_rrf_scores(
|
|
221
|
+
vector_retriever_results,
|
|
222
|
+
bm25_retriever_results,
|
|
223
|
+
top_k,
|
|
224
|
+
vector_weight,
|
|
225
|
+
bm25_weight,
|
|
226
|
+
rank_smoothing_factor,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
retrieved_info = {
|
|
230
|
+
"Original Query": query,
|
|
231
|
+
"Retrieved Context": (
|
|
232
|
+
all_retrieved_info
|
|
233
|
+
if return_detailed_info
|
|
234
|
+
else [item['text'] for item in all_retrieved_info] # type: ignore[misc]
|
|
235
|
+
),
|
|
236
|
+
}
|
|
237
|
+
return retrieved_info
|
camel/toolkits/__init__.py
CHANGED
|
@@ -43,11 +43,18 @@ from .retrieval_toolkit import RetrievalToolkit
|
|
|
43
43
|
from .notion_toolkit import NotionToolkit
|
|
44
44
|
from .human_toolkit import HumanToolkit
|
|
45
45
|
from .stripe_toolkit import StripeToolkit
|
|
46
|
-
from .
|
|
46
|
+
from .video_download_toolkit import VideoDownloaderToolkit
|
|
47
47
|
from .dappier_toolkit import DappierToolkit
|
|
48
|
+
from .networkx_toolkit import NetworkXToolkit
|
|
48
49
|
from .semantic_scholar_toolkit import SemanticScholarToolkit
|
|
50
|
+
from .zapier_toolkit import ZapierToolkit
|
|
49
51
|
from .sympy_toolkit import SymPyToolkit
|
|
50
52
|
from .mineru_toolkit import MinerUToolkit
|
|
53
|
+
from .audio_analysis_toolkit import AudioAnalysisToolkit
|
|
54
|
+
from .excel_toolkit import ExcelToolkit
|
|
55
|
+
from .video_analysis_toolkit import VideoAnalysisToolkit
|
|
56
|
+
from .image_analysis_toolkit import ImageAnalysisToolkit
|
|
57
|
+
from .web_toolkit import WebToolkit
|
|
51
58
|
|
|
52
59
|
|
|
53
60
|
__all__ = [
|
|
@@ -81,7 +88,14 @@ __all__ = [
|
|
|
81
88
|
'MeshyToolkit',
|
|
82
89
|
'OpenBBToolkit',
|
|
83
90
|
'DappierToolkit',
|
|
91
|
+
'NetworkXToolkit',
|
|
84
92
|
'SemanticScholarToolkit',
|
|
93
|
+
'ZapierToolkit',
|
|
85
94
|
'SymPyToolkit',
|
|
86
95
|
'MinerUToolkit',
|
|
96
|
+
'AudioAnalysisToolkit',
|
|
97
|
+
'ExcelToolkit',
|
|
98
|
+
'VideoAnalysisToolkit',
|
|
99
|
+
'ImageAnalysisToolkit',
|
|
100
|
+
'WebToolkit',
|
|
87
101
|
]
|
camel/toolkits/arxiv_toolkit.py
CHANGED
|
@@ -28,8 +28,9 @@ class ArxivToolkit(BaseToolkit):
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
@dependencies_required('arxiv')
|
|
31
|
-
def __init__(self) -> None:
|
|
31
|
+
def __init__(self, timeout: Optional[float] = None) -> None:
|
|
32
32
|
r"""Initializes the ArxivToolkit and sets up the arXiv client."""
|
|
33
|
+
super().__init__(timeout=timeout)
|
|
33
34
|
import arxiv
|
|
34
35
|
|
|
35
36
|
self.client = arxiv.Client()
|
|
@@ -62,11 +62,13 @@ class AskNewsToolkit(BaseToolkit):
|
|
|
62
62
|
based on user queries using the AskNews API.
|
|
63
63
|
"""
|
|
64
64
|
|
|
65
|
-
def __init__(self):
|
|
65
|
+
def __init__(self, timeout: Optional[float] = None):
|
|
66
66
|
r"""Initialize the AskNewsToolkit with API clients.The API keys and
|
|
67
67
|
credentials are retrieved from environment variables.
|
|
68
68
|
"""
|
|
69
|
-
|
|
69
|
+
super().__init__(timeout=timeout)
|
|
70
|
+
|
|
71
|
+
from asknews_sdk import AskNewsSDK # type: ignore[import-not-found]
|
|
70
72
|
|
|
71
73
|
client_id = os.environ.get("ASKNEWS_CLIENT_ID")
|
|
72
74
|
client_secret = os.environ.get("ASKNEWS_CLIENT_SECRET")
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
import os
|
|
15
|
+
import uuid
|
|
16
|
+
from typing import List, Optional
|
|
17
|
+
from urllib.parse import urlparse
|
|
18
|
+
|
|
19
|
+
import requests
|
|
20
|
+
|
|
21
|
+
from camel.logger import get_logger
|
|
22
|
+
from camel.messages import BaseMessage
|
|
23
|
+
from camel.models import BaseAudioModel, BaseModelBackend, OpenAIAudioModels
|
|
24
|
+
from camel.toolkits.base import BaseToolkit
|
|
25
|
+
from camel.toolkits.function_tool import FunctionTool
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def download_file(url: str, cache_dir: str) -> str:
|
|
31
|
+
r"""Download a file from a URL to a local cache directory.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
url (str): The URL of the file to download.
|
|
35
|
+
cache_dir (str): The directory to save the downloaded file.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
str: The path to the downloaded file.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
Exception: If the download fails.
|
|
42
|
+
"""
|
|
43
|
+
# Create cache directory if it doesn't exist
|
|
44
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
# Extract filename from URL or generate a unique one
|
|
47
|
+
parsed_url = urlparse(url)
|
|
48
|
+
filename = os.path.basename(parsed_url.path)
|
|
49
|
+
if not filename:
|
|
50
|
+
# Generate a unique filename if none is provided in the URL
|
|
51
|
+
file_ext = ".mp3" # Default extension
|
|
52
|
+
content_type = None
|
|
53
|
+
|
|
54
|
+
# Try to get the file extension from the content type
|
|
55
|
+
try:
|
|
56
|
+
response = requests.head(url)
|
|
57
|
+
content_type = response.headers.get('Content-Type', '')
|
|
58
|
+
if 'audio/wav' in content_type:
|
|
59
|
+
file_ext = '.wav'
|
|
60
|
+
elif 'audio/mpeg' in content_type:
|
|
61
|
+
file_ext = '.mp3'
|
|
62
|
+
elif 'audio/ogg' in content_type:
|
|
63
|
+
file_ext = '.ogg'
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
filename = f"{uuid.uuid4()}{file_ext}"
|
|
68
|
+
|
|
69
|
+
local_path = os.path.join(cache_dir, filename)
|
|
70
|
+
|
|
71
|
+
# Download the file
|
|
72
|
+
response = requests.get(url, stream=True)
|
|
73
|
+
response.raise_for_status()
|
|
74
|
+
|
|
75
|
+
with open(local_path, 'wb') as f:
|
|
76
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
77
|
+
f.write(chunk)
|
|
78
|
+
|
|
79
|
+
logger.debug(f"Downloaded file from {url} to {local_path}")
|
|
80
|
+
return local_path
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AudioAnalysisToolkit(BaseToolkit):
|
|
84
|
+
r"""A toolkit for audio processing and analysis.
|
|
85
|
+
|
|
86
|
+
This class provides methods for processing, transcribing, and extracting
|
|
87
|
+
information from audio data, including direct question answering about
|
|
88
|
+
audio content.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
cache_dir (Optional[str]): Directory path for caching downloaded audio
|
|
92
|
+
files. If not provided, 'tmp/' will be used. (default: :obj:`None`)
|
|
93
|
+
transcribe_model (Optional[BaseAudioModel]): Model used for audio
|
|
94
|
+
transcription. If not provided, OpenAIAudioModels will be used.
|
|
95
|
+
(default: :obj:`None`)
|
|
96
|
+
audio_reasoning_model (Optional[BaseModelBackend]): Model used for
|
|
97
|
+
audio reasoning and question answering. If not provided, uses the
|
|
98
|
+
default model from ChatAgent. (default: :obj:`None`)
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
cache_dir: Optional[str] = None,
|
|
104
|
+
transcribe_model: Optional[BaseAudioModel] = None,
|
|
105
|
+
audio_reasoning_model: Optional[BaseModelBackend] = None,
|
|
106
|
+
):
|
|
107
|
+
self.cache_dir = 'tmp/'
|
|
108
|
+
if cache_dir:
|
|
109
|
+
self.cache_dir = cache_dir
|
|
110
|
+
|
|
111
|
+
if transcribe_model:
|
|
112
|
+
self.transcribe_model = transcribe_model
|
|
113
|
+
else:
|
|
114
|
+
self.transcribe_model = OpenAIAudioModels()
|
|
115
|
+
logger.warning(
|
|
116
|
+
"No audio transcription model provided. "
|
|
117
|
+
"Using OpenAIAudioModels."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
from camel.agents import ChatAgent
|
|
121
|
+
|
|
122
|
+
if audio_reasoning_model:
|
|
123
|
+
self.audio_agent = ChatAgent(model=audio_reasoning_model)
|
|
124
|
+
else:
|
|
125
|
+
self.audio_agent = ChatAgent()
|
|
126
|
+
logger.warning(
|
|
127
|
+
"No audio reasoning model provided. Using default model in"
|
|
128
|
+
" ChatAgent."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def audio2text(self, audio_path: str) -> str:
|
|
132
|
+
r"""Transcribe audio to text.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
audio_path (str): The path to the audio file or URL.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
str: The transcribed text.
|
|
139
|
+
"""
|
|
140
|
+
logger.debug(
|
|
141
|
+
f"Calling transcribe_audio method for audio file `{audio_path}`."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
audio_transcript = self.transcribe_model.speech_to_text(audio_path)
|
|
146
|
+
if not audio_transcript:
|
|
147
|
+
logger.warning("Audio transcription returned empty result")
|
|
148
|
+
return "No audio transcription available."
|
|
149
|
+
return audio_transcript
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"Audio transcription failed: {e}")
|
|
152
|
+
return "Audio transcription failed."
|
|
153
|
+
|
|
154
|
+
def ask_question_about_audio(self, audio_path: str, question: str) -> str:
|
|
155
|
+
r"""Ask any question about the audio and get the answer using
|
|
156
|
+
multimodal model.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
audio_path (str): The path to the audio file.
|
|
160
|
+
question (str): The question to ask about the audio.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
str: The answer to the question.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
logger.debug(
|
|
167
|
+
f"Calling ask_question_about_audio method for audio file \
|
|
168
|
+
`{audio_path}` and question `{question}`."
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
parsed_url = urlparse(audio_path)
|
|
172
|
+
is_url = all([parsed_url.scheme, parsed_url.netloc])
|
|
173
|
+
local_audio_path = audio_path
|
|
174
|
+
|
|
175
|
+
# If the audio is a URL, download it first
|
|
176
|
+
if is_url:
|
|
177
|
+
try:
|
|
178
|
+
local_audio_path = download_file(audio_path, self.cache_dir)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(f"Failed to download audio file: {e}")
|
|
181
|
+
return f"Failed to download audio file: {e!s}"
|
|
182
|
+
|
|
183
|
+
# Try direct audio question answering first
|
|
184
|
+
try:
|
|
185
|
+
# Check if the transcribe_model supports audio_question_answering
|
|
186
|
+
if hasattr(self.transcribe_model, 'audio_question_answering'):
|
|
187
|
+
logger.debug("Using direct audio question answering")
|
|
188
|
+
response = self.transcribe_model.audio_question_answering(
|
|
189
|
+
local_audio_path, question
|
|
190
|
+
)
|
|
191
|
+
return response
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.warning(
|
|
194
|
+
f"Direct audio question answering failed: {e}. "
|
|
195
|
+
"Falling back to transcription-based approach."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Fallback to transcription-based approach
|
|
199
|
+
try:
|
|
200
|
+
transcript = self.audio2text(local_audio_path)
|
|
201
|
+
reasoning_prompt = f"""
|
|
202
|
+
<speech_transcription_result>{transcript}</
|
|
203
|
+
speech_transcription_result>
|
|
204
|
+
|
|
205
|
+
Please answer the following question based on the speech
|
|
206
|
+
transcription result above:
|
|
207
|
+
<question>{question}</question>
|
|
208
|
+
"""
|
|
209
|
+
msg = BaseMessage.make_user_message(
|
|
210
|
+
role_name="User", content=reasoning_prompt
|
|
211
|
+
)
|
|
212
|
+
response = self.audio_agent.step(msg)
|
|
213
|
+
|
|
214
|
+
if not response or not response.msgs:
|
|
215
|
+
logger.error("Model returned empty response")
|
|
216
|
+
return (
|
|
217
|
+
"Failed to generate an answer. "
|
|
218
|
+
"The model returned an empty response."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
answer = response.msgs[0].content
|
|
222
|
+
return answer
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(f"Audio question answering failed: {e}")
|
|
225
|
+
return f"Failed to answer question about audio: {e!s}"
|
|
226
|
+
|
|
227
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
228
|
+
r"""Returns a list of FunctionTool objects representing the functions
|
|
229
|
+
in the toolkit.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
List[FunctionTool]: A list of FunctionTool objects representing the
|
|
233
|
+
functions in the toolkit.
|
|
234
|
+
"""
|
|
235
|
+
return [
|
|
236
|
+
FunctionTool(self.ask_question_about_audio),
|
|
237
|
+
FunctionTool(self.audio2text),
|
|
238
|
+
]
|