camel-ai 0.2.5__py3-none-any.whl → 0.2.7__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/chat_agent.py +114 -23
- camel/configs/__init__.py +6 -4
- camel/configs/base_config.py +21 -0
- camel/configs/gemini_config.py +17 -9
- camel/configs/qwen_config.py +91 -0
- camel/configs/samba_config.py +1 -38
- camel/configs/yi_config.py +58 -0
- camel/generators.py +93 -0
- camel/interpreters/docker_interpreter.py +5 -0
- camel/interpreters/ipython_interpreter.py +2 -1
- camel/loaders/__init__.py +2 -0
- camel/loaders/apify_reader.py +223 -0
- camel/memories/agent_memories.py +24 -1
- camel/messages/base.py +38 -0
- camel/models/__init__.py +4 -0
- camel/models/model_factory.py +6 -0
- camel/models/qwen_model.py +139 -0
- camel/models/samba_model.py +1 -1
- camel/models/yi_model.py +138 -0
- camel/prompts/image_craft.py +8 -0
- camel/prompts/video_description_prompt.py +8 -0
- camel/retrievers/vector_retriever.py +5 -1
- camel/societies/role_playing.py +29 -18
- camel/societies/workforce/base.py +7 -1
- camel/societies/workforce/task_channel.py +10 -0
- camel/societies/workforce/utils.py +6 -0
- camel/societies/workforce/worker.py +2 -0
- camel/storages/vectordb_storages/qdrant.py +147 -24
- camel/tasks/task.py +15 -0
- camel/terminators/base.py +4 -0
- camel/terminators/response_terminator.py +1 -0
- camel/terminators/token_limit_terminator.py +1 -0
- camel/toolkits/__init__.py +4 -1
- camel/toolkits/base.py +9 -0
- camel/toolkits/data_commons_toolkit.py +360 -0
- camel/toolkits/function_tool.py +174 -7
- camel/toolkits/github_toolkit.py +175 -176
- camel/toolkits/google_scholar_toolkit.py +36 -7
- camel/toolkits/notion_toolkit.py +279 -0
- camel/toolkits/search_toolkit.py +164 -36
- camel/types/enums.py +88 -0
- camel/types/unified_model_type.py +10 -0
- camel/utils/commons.py +2 -1
- camel/utils/constants.py +2 -0
- {camel_ai-0.2.5.dist-info → camel_ai-0.2.7.dist-info}/METADATA +129 -79
- {camel_ai-0.2.5.dist-info → camel_ai-0.2.7.dist-info}/RECORD +49 -42
- {camel_ai-0.2.5.dist-info → camel_ai-0.2.7.dist-info}/LICENSE +0 -0
- {camel_ai-0.2.5.dist-info → camel_ai-0.2.7.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ 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 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
+
import os
|
|
15
|
+
from typing import List, Optional, cast
|
|
16
|
+
|
|
17
|
+
from camel.toolkits import FunctionTool
|
|
18
|
+
from camel.toolkits.base import BaseToolkit
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_plain_text_from_rich_text(rich_text: List[dict]) -> str:
|
|
22
|
+
r"""Extracts plain text from a list of rich text elements.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
rich_text: A list of dictionaries representing rich text elements.
|
|
26
|
+
Each dictionary should contain a key named "plain_text" with
|
|
27
|
+
the plain text content.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: A string containing the combined plain text from all elements,
|
|
31
|
+
joined together.
|
|
32
|
+
"""
|
|
33
|
+
plain_texts = [element.get("plain_text", "") for element in rich_text]
|
|
34
|
+
return "".join(plain_texts)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_media_source_text(block: dict) -> str:
|
|
38
|
+
r"""Extracts the source URL and optional caption from a
|
|
39
|
+
Notion media block.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
block: A dictionary representing a Notion media block.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A string containing the source URL and caption (if available),
|
|
46
|
+
separated by a colon.
|
|
47
|
+
"""
|
|
48
|
+
block_type = block.get("type", "Unknown Type")
|
|
49
|
+
block_content = block.get(block_type, {})
|
|
50
|
+
|
|
51
|
+
# Extract source URL based on available types
|
|
52
|
+
source = (
|
|
53
|
+
block_content.get("external", {}).get("url")
|
|
54
|
+
or block_content.get("file", {}).get("url")
|
|
55
|
+
or block_content.get(
|
|
56
|
+
"url", "[Missing case for media block types]: " + block_type
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Extract caption if available
|
|
61
|
+
caption_elements = block_content.get("caption", [])
|
|
62
|
+
if caption_elements:
|
|
63
|
+
caption = get_plain_text_from_rich_text(caption_elements)
|
|
64
|
+
return f"{caption}: {source}"
|
|
65
|
+
|
|
66
|
+
return source
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class NotionToolkit(BaseToolkit):
|
|
70
|
+
r"""A toolkit for retrieving information from the user's notion pages.
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
notion_token (Optional[str], optional): The notion_token used to
|
|
74
|
+
interact with notion APIs.(default: :obj:`None`)
|
|
75
|
+
notion_client (module): The notion module for interacting with
|
|
76
|
+
the notion APIs.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
notion_token: Optional[str] = None,
|
|
82
|
+
) -> None:
|
|
83
|
+
r"""Initializes the NotionToolkit.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
notion_token (Optional[str], optional): The optional notion_token
|
|
87
|
+
used to interact with notion APIs.(default: :obj:`None`)
|
|
88
|
+
"""
|
|
89
|
+
from notion_client import Client
|
|
90
|
+
|
|
91
|
+
self.notion_token = notion_token or os.environ.get("NOTION_TOKEN")
|
|
92
|
+
self.notion_client = Client(auth=self.notion_token)
|
|
93
|
+
|
|
94
|
+
def list_all_users(self) -> List[dict]:
|
|
95
|
+
r"""Lists all users via the Notion integration.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List[dict]: A list of user objects with type, name, and workspace.
|
|
99
|
+
"""
|
|
100
|
+
all_users_info: List[dict] = []
|
|
101
|
+
cursor = None
|
|
102
|
+
|
|
103
|
+
while True:
|
|
104
|
+
response = cast(
|
|
105
|
+
dict,
|
|
106
|
+
self.notion_client.users.list(start_cursor=cursor),
|
|
107
|
+
)
|
|
108
|
+
all_users_info.extend(response["results"])
|
|
109
|
+
|
|
110
|
+
if not response["has_more"]:
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
cursor = response["next_cursor"]
|
|
114
|
+
|
|
115
|
+
formatted_users = [
|
|
116
|
+
{
|
|
117
|
+
"type": user["type"],
|
|
118
|
+
"name": user["name"],
|
|
119
|
+
"workspace": user.get(user.get("type"), {}).get(
|
|
120
|
+
"workspace_name", ""
|
|
121
|
+
),
|
|
122
|
+
}
|
|
123
|
+
for user in all_users_info
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
return formatted_users
|
|
127
|
+
|
|
128
|
+
def list_all_pages(self) -> List[dict]:
|
|
129
|
+
r"""Lists all pages in the Notion workspace.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
List[dict]: A list of page objects with title and id.
|
|
133
|
+
"""
|
|
134
|
+
all_pages_info: List[dict] = []
|
|
135
|
+
cursor = None
|
|
136
|
+
|
|
137
|
+
while True:
|
|
138
|
+
response = cast(
|
|
139
|
+
dict,
|
|
140
|
+
self.notion_client.search(
|
|
141
|
+
filter={"property": "object", "value": "page"},
|
|
142
|
+
start_cursor=cursor,
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
all_pages_info.extend(response["results"])
|
|
146
|
+
|
|
147
|
+
if not response["has_more"]:
|
|
148
|
+
break
|
|
149
|
+
|
|
150
|
+
cursor = response["next_cursor"]
|
|
151
|
+
|
|
152
|
+
formatted_pages = [
|
|
153
|
+
{
|
|
154
|
+
"id": page.get("id"),
|
|
155
|
+
"title": next(
|
|
156
|
+
(
|
|
157
|
+
title.get("text", {}).get("content")
|
|
158
|
+
for title in page["properties"]
|
|
159
|
+
.get("title", {})
|
|
160
|
+
.get("title", [])
|
|
161
|
+
if title["type"] == "text"
|
|
162
|
+
),
|
|
163
|
+
None,
|
|
164
|
+
),
|
|
165
|
+
}
|
|
166
|
+
for page in all_pages_info
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
return formatted_pages
|
|
170
|
+
|
|
171
|
+
def get_notion_block_text_content(self, block_id: str) -> str:
|
|
172
|
+
r"""Retrieves the text content of a Notion block.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
block_id (str): The ID of the Notion block to retrieve.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
str: The text content of a Notion block, containing all
|
|
179
|
+
the sub blocks.
|
|
180
|
+
"""
|
|
181
|
+
blocks: List[dict] = []
|
|
182
|
+
cursor = None
|
|
183
|
+
|
|
184
|
+
while True:
|
|
185
|
+
response = cast(
|
|
186
|
+
dict,
|
|
187
|
+
self.notion_client.blocks.children.list(
|
|
188
|
+
block_id=block_id, start_cursor=cursor
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
blocks.extend(response["results"])
|
|
192
|
+
|
|
193
|
+
if not response["has_more"]:
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
cursor = response["next_cursor"]
|
|
197
|
+
|
|
198
|
+
block_text_content = " ".join(
|
|
199
|
+
[self.get_text_from_block(sub_block) for sub_block in blocks]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return block_text_content
|
|
203
|
+
|
|
204
|
+
def get_text_from_block(self, block: dict) -> str:
|
|
205
|
+
r"""Extracts plain text from a Notion block based on its type.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
block (dict): A dictionary representing a Notion block.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
str: A string containing the extracted plain text and block type.
|
|
212
|
+
"""
|
|
213
|
+
# Get rich text for supported block types
|
|
214
|
+
if block.get(block.get("type"), {}).get("rich_text"):
|
|
215
|
+
# Empty string if it's an empty line
|
|
216
|
+
text = get_plain_text_from_rich_text(
|
|
217
|
+
block[block["type"]]["rich_text"]
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
# Handle block types by case
|
|
221
|
+
block_type = block.get("type")
|
|
222
|
+
if block_type == "unsupported":
|
|
223
|
+
text = "[Unsupported block type]"
|
|
224
|
+
elif block_type == "bookmark":
|
|
225
|
+
text = block["bookmark"]["url"]
|
|
226
|
+
elif block_type == "child_database":
|
|
227
|
+
text = block["child_database"]["title"]
|
|
228
|
+
# Use other API endpoints for full database data
|
|
229
|
+
elif block_type == "child_page":
|
|
230
|
+
text = block["child_page"]["title"]
|
|
231
|
+
elif block_type in ("embed", "video", "file", "image", "pdf"):
|
|
232
|
+
text = get_media_source_text(block)
|
|
233
|
+
elif block_type == "equation":
|
|
234
|
+
text = block["equation"]["expression"]
|
|
235
|
+
elif block_type == "link_preview":
|
|
236
|
+
text = block["link_preview"]["url"]
|
|
237
|
+
elif block_type == "synced_block":
|
|
238
|
+
if block["synced_block"].get("synced_from"):
|
|
239
|
+
text = (
|
|
240
|
+
f"This block is synced with a block with ID: "
|
|
241
|
+
f"""
|
|
242
|
+
{block['synced_block']['synced_from']
|
|
243
|
+
[block['synced_block']['synced_from']['type']]}
|
|
244
|
+
"""
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
text = (
|
|
248
|
+
"Source sync block that another"
|
|
249
|
+
+ "blocked is synced with."
|
|
250
|
+
)
|
|
251
|
+
elif block_type == "table":
|
|
252
|
+
text = f"Table width: {block['table']['table_width']}"
|
|
253
|
+
# Fetch children for full table data
|
|
254
|
+
elif block_type == "table_of_contents":
|
|
255
|
+
text = f"ToC color: {block['table_of_contents']['color']}"
|
|
256
|
+
elif block_type in ("breadcrumb", "column_list", "divider"):
|
|
257
|
+
text = "No text available"
|
|
258
|
+
else:
|
|
259
|
+
text = "[Needs case added]"
|
|
260
|
+
|
|
261
|
+
# Query children for blocks with children
|
|
262
|
+
if block.get("has_children"):
|
|
263
|
+
text += self.get_notion_block_text_content(block["id"])
|
|
264
|
+
|
|
265
|
+
return text
|
|
266
|
+
|
|
267
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
268
|
+
r"""Returns a list of FunctionTool objects representing the
|
|
269
|
+
functions in the toolkit.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
List[FunctionTool]: A list of FunctionTool objects
|
|
273
|
+
representing the functions in the toolkit.
|
|
274
|
+
"""
|
|
275
|
+
return [
|
|
276
|
+
FunctionTool(self.list_all_pages),
|
|
277
|
+
FunctionTool(self.list_all_users),
|
|
278
|
+
FunctionTool(self.get_notion_block_text_content),
|
|
279
|
+
]
|
camel/toolkits/search_toolkit.py
CHANGED
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
14
|
import os
|
|
15
|
-
|
|
15
|
+
import xml.etree.ElementTree as ET
|
|
16
|
+
from typing import Any, Dict, List, Union
|
|
17
|
+
|
|
18
|
+
import requests
|
|
16
19
|
|
|
17
20
|
from camel.toolkits.base import BaseToolkit
|
|
18
21
|
from camel.toolkits.function_tool import FunctionTool
|
|
22
|
+
from camel.utils import api_keys_required, dependencies_required
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
class SearchToolkit(BaseToolkit):
|
|
@@ -25,6 +29,7 @@ class SearchToolkit(BaseToolkit):
|
|
|
25
29
|
search engines like Google, DuckDuckGo, Wikipedia and Wolfram Alpha.
|
|
26
30
|
"""
|
|
27
31
|
|
|
32
|
+
@dependencies_required("wikipedia")
|
|
28
33
|
def search_wiki(self, entity: str) -> str:
|
|
29
34
|
r"""Search the entity in WikiPedia and return the summary of the
|
|
30
35
|
required page, containing factual information about
|
|
@@ -37,13 +42,7 @@ class SearchToolkit(BaseToolkit):
|
|
|
37
42
|
str: The search result. If the page corresponding to the entity
|
|
38
43
|
exists, return the summary of this entity in a string.
|
|
39
44
|
"""
|
|
40
|
-
|
|
41
|
-
import wikipedia
|
|
42
|
-
except ImportError:
|
|
43
|
-
raise ImportError(
|
|
44
|
-
"Please install `wikipedia` first. You can install it "
|
|
45
|
-
"by running `pip install wikipedia`."
|
|
46
|
-
)
|
|
45
|
+
import wikipedia
|
|
47
46
|
|
|
48
47
|
result: str
|
|
49
48
|
|
|
@@ -64,6 +63,7 @@ class SearchToolkit(BaseToolkit):
|
|
|
64
63
|
|
|
65
64
|
return result
|
|
66
65
|
|
|
66
|
+
@dependencies_required("duckduckgo_search")
|
|
67
67
|
def search_duckduckgo(
|
|
68
68
|
self, query: str, source: str = "text", max_results: int = 5
|
|
69
69
|
) -> List[Dict[str, Any]]:
|
|
@@ -151,6 +151,7 @@ class SearchToolkit(BaseToolkit):
|
|
|
151
151
|
# If no answer found, return an empty list
|
|
152
152
|
return responses
|
|
153
153
|
|
|
154
|
+
@api_keys_required("GOOGLE_API_KEY", "SEARCH_ENGINE_ID")
|
|
154
155
|
def search_google(
|
|
155
156
|
self, query: str, num_result_pages: int = 5
|
|
156
157
|
) -> List[Dict[str, Any]]:
|
|
@@ -251,7 +252,10 @@ class SearchToolkit(BaseToolkit):
|
|
|
251
252
|
# If no answer found, return an empty list
|
|
252
253
|
return responses
|
|
253
254
|
|
|
254
|
-
|
|
255
|
+
@dependencies_required("wolframalpha")
|
|
256
|
+
def query_wolfram_alpha(
|
|
257
|
+
self, query: str, is_detailed: bool = False
|
|
258
|
+
) -> Union[str, Dict[str, Any]]:
|
|
255
259
|
r"""Queries Wolfram|Alpha and returns the result. Wolfram|Alpha is an
|
|
256
260
|
answer engine developed by Wolfram Research. It is offered as an online
|
|
257
261
|
service that answers factual queries by computing answers from
|
|
@@ -259,19 +263,16 @@ class SearchToolkit(BaseToolkit):
|
|
|
259
263
|
|
|
260
264
|
Args:
|
|
261
265
|
query (str): The query to send to Wolfram Alpha.
|
|
262
|
-
is_detailed (bool): Whether to include additional details
|
|
263
|
-
result.
|
|
266
|
+
is_detailed (bool): Whether to include additional details
|
|
267
|
+
including step by step information in the result.
|
|
268
|
+
(default::obj:`False`)
|
|
264
269
|
|
|
265
270
|
Returns:
|
|
266
|
-
str: The result from Wolfram Alpha
|
|
271
|
+
Union[str, Dict[str, Any]]: The result from Wolfram Alpha.
|
|
272
|
+
Returns a string if `is_detailed` is False, otherwise returns
|
|
273
|
+
a dictionary with detailed information.
|
|
267
274
|
"""
|
|
268
|
-
|
|
269
|
-
import wolframalpha
|
|
270
|
-
except ImportError:
|
|
271
|
-
raise ImportError(
|
|
272
|
-
"Please install `wolframalpha` first. You can install it by"
|
|
273
|
-
" running `pip install wolframalpha`."
|
|
274
|
-
)
|
|
275
|
+
import wolframalpha
|
|
275
276
|
|
|
276
277
|
WOLFRAMALPHA_APP_ID = os.environ.get('WOLFRAMALPHA_APP_ID')
|
|
277
278
|
if not WOLFRAMALPHA_APP_ID:
|
|
@@ -284,28 +285,154 @@ class SearchToolkit(BaseToolkit):
|
|
|
284
285
|
try:
|
|
285
286
|
client = wolframalpha.Client(WOLFRAMALPHA_APP_ID)
|
|
286
287
|
res = client.query(query)
|
|
287
|
-
|
|
288
|
-
answer = next(res.results).text or "No answer found."
|
|
288
|
+
|
|
289
289
|
except Exception as e:
|
|
290
|
-
|
|
291
|
-
return "Wolfram Alpha wasn't able to answer it"
|
|
292
|
-
else:
|
|
293
|
-
error_message = (
|
|
294
|
-
f"Wolfram Alpha wasn't able to answer it" f"{e!s}."
|
|
295
|
-
)
|
|
296
|
-
return error_message
|
|
290
|
+
return f"Wolfram Alpha wasn't able to answer it. Error: {e}"
|
|
297
291
|
|
|
298
|
-
|
|
292
|
+
pased_result = self._parse_wolfram_result(res)
|
|
299
293
|
|
|
300
|
-
# Add additional details in the result
|
|
301
294
|
if is_detailed:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
295
|
+
step_info = self._get_wolframalpha_step_by_step_solution(
|
|
296
|
+
WOLFRAMALPHA_APP_ID, query
|
|
297
|
+
)
|
|
298
|
+
pased_result["steps"] = step_info
|
|
299
|
+
return pased_result
|
|
300
|
+
|
|
301
|
+
return pased_result["final_answer"]
|
|
302
|
+
|
|
303
|
+
def _parse_wolfram_result(self, result) -> Dict[str, Any]:
|
|
304
|
+
r"""Parses a Wolfram Alpha API result into a structured dictionary
|
|
305
|
+
format.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
result: The API result returned from a Wolfram Alpha
|
|
309
|
+
query, structured with multiple pods, each containing specific
|
|
310
|
+
information related to the query.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
dict: A structured dictionary with the original query and the
|
|
314
|
+
final answer.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
# Extract the original query
|
|
318
|
+
query = result.get('@inputstring', '')
|
|
319
|
+
|
|
320
|
+
# Initialize a dictionary to hold structured output
|
|
321
|
+
output = {"query": query, "pod_info": [], "final_answer": None}
|
|
322
|
+
|
|
323
|
+
# Loop through each pod to extract the details
|
|
324
|
+
for pod in result.get('pod', []):
|
|
325
|
+
pod_info = {
|
|
326
|
+
"title": pod.get('@title', ''),
|
|
327
|
+
"description": pod.get('subpod', {}).get('plaintext', ''),
|
|
328
|
+
"image_url": pod.get('subpod', {})
|
|
329
|
+
.get('img', {})
|
|
330
|
+
.get('@src', ''),
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Add to steps list
|
|
334
|
+
output["pod_info"].append(pod_info)
|
|
335
|
+
|
|
336
|
+
# Get final answer
|
|
337
|
+
if pod.get('@primary', False):
|
|
338
|
+
output["final_answer"] = pod_info["description"]
|
|
307
339
|
|
|
308
|
-
return
|
|
340
|
+
return output
|
|
341
|
+
|
|
342
|
+
def _get_wolframalpha_step_by_step_solution(
|
|
343
|
+
self, app_id: str, query: str
|
|
344
|
+
) -> dict:
|
|
345
|
+
r"""Retrieve a step-by-step solution from the Wolfram Alpha API for a
|
|
346
|
+
given query.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
app_id (str): Your Wolfram Alpha API application ID.
|
|
350
|
+
query (str): The mathematical or computational query to solve.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
dict: The step-by-step solution response text from the Wolfram
|
|
354
|
+
Alpha API.
|
|
355
|
+
"""
|
|
356
|
+
# Define the base URL
|
|
357
|
+
url = "https://api.wolframalpha.com/v2/query"
|
|
358
|
+
|
|
359
|
+
# Set up the query parameters
|
|
360
|
+
params = {
|
|
361
|
+
'appid': app_id,
|
|
362
|
+
'input': query,
|
|
363
|
+
'podstate': ['Result__Step-by-step solution', 'Show all steps'],
|
|
364
|
+
'format': 'plaintext',
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# Send the request
|
|
368
|
+
response = requests.get(url, params=params)
|
|
369
|
+
root = ET.fromstring(response.text)
|
|
370
|
+
|
|
371
|
+
# Extracting step-by-step hints and removing 'Hint: |'
|
|
372
|
+
steps = []
|
|
373
|
+
for subpod in root.findall(
|
|
374
|
+
".//pod[@title='Results']//subpod[stepbystepcontenttype='SBSHintStep']//plaintext"
|
|
375
|
+
):
|
|
376
|
+
if subpod.text:
|
|
377
|
+
step_text = subpod.text.strip()
|
|
378
|
+
cleaned_step = step_text.replace('Hint: |', '').strip()
|
|
379
|
+
steps.append(cleaned_step)
|
|
380
|
+
|
|
381
|
+
# Structuring the steps into a dictionary
|
|
382
|
+
structured_steps = {}
|
|
383
|
+
for i, step in enumerate(steps, start=1):
|
|
384
|
+
structured_steps[f"step{i}"] = step
|
|
385
|
+
|
|
386
|
+
return structured_steps
|
|
387
|
+
|
|
388
|
+
def tavily_search(
|
|
389
|
+
self, query: str, num_results: int = 5, **kwargs
|
|
390
|
+
) -> List[Dict[str, Any]]:
|
|
391
|
+
r"""Use Tavily Search API to search information for the given query.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
query (str): The query to be searched.
|
|
395
|
+
num_results (int): The number of search results to retrieve
|
|
396
|
+
(default is `5`).
|
|
397
|
+
**kwargs: Additional optional parameters supported by Tavily's API:
|
|
398
|
+
- search_depth (str): "basic" or "advanced" search depth.
|
|
399
|
+
- topic (str): The search category, e.g., "general" or "news."
|
|
400
|
+
- days (int): Time frame in days for news-related searches.
|
|
401
|
+
- max_results (int): Max number of results to return
|
|
402
|
+
(overrides `num_results`).
|
|
403
|
+
See https://docs.tavily.com/docs/python-sdk/tavily-search/
|
|
404
|
+
api-reference for details.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
List[Dict[str, Any]]: A list of dictionaries representing search
|
|
408
|
+
results. Each dictionary contains:
|
|
409
|
+
- 'result_id' (int): The result's index.
|
|
410
|
+
- 'title' (str): The title of the result.
|
|
411
|
+
- 'description' (str): A brief description of the result.
|
|
412
|
+
- 'long_description' (str): Detailed information, if available.
|
|
413
|
+
- 'url' (str): The URL of the result.
|
|
414
|
+
- 'content' (str): Relevant content from the search result.
|
|
415
|
+
- 'images' (list): A list of related images (if
|
|
416
|
+
`include_images` is True).
|
|
417
|
+
- 'published_date' (str): Publication date for news topics
|
|
418
|
+
(if available).
|
|
419
|
+
"""
|
|
420
|
+
from tavily import TavilyClient # type: ignore[import-untyped]
|
|
421
|
+
|
|
422
|
+
Tavily_API_KEY = os.getenv("TAVILY_API_KEY")
|
|
423
|
+
if not Tavily_API_KEY:
|
|
424
|
+
raise ValueError(
|
|
425
|
+
"`TAVILY_API_KEY` not found in environment variables. "
|
|
426
|
+
"Get `TAVILY_API_KEY` here: `https://www.tavily.com/api/`."
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
client = TavilyClient(Tavily_API_KEY)
|
|
430
|
+
|
|
431
|
+
try:
|
|
432
|
+
results = client.search(query, max_results=num_results, **kwargs)
|
|
433
|
+
return results
|
|
434
|
+
except Exception as e:
|
|
435
|
+
return [{"error": f"An unexpected error occurred: {e!s}"}]
|
|
309
436
|
|
|
310
437
|
def get_tools(self) -> List[FunctionTool]:
|
|
311
438
|
r"""Returns a list of FunctionTool objects representing the
|
|
@@ -320,6 +447,7 @@ class SearchToolkit(BaseToolkit):
|
|
|
320
447
|
FunctionTool(self.search_google),
|
|
321
448
|
FunctionTool(self.search_duckduckgo),
|
|
322
449
|
FunctionTool(self.query_wolfram_alpha),
|
|
450
|
+
FunctionTool(self.tavily_search),
|
|
323
451
|
]
|
|
324
452
|
|
|
325
453
|
|