camel-ai 0.2.16__py3-none-any.whl → 0.2.18__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 +30 -6
- camel/agents/multi_hop_generator_agent.py +85 -0
- camel/agents/programmed_agent_instruction.py +148 -0
- camel/benchmarks/__init__.py +2 -0
- camel/benchmarks/apibank.py +5 -0
- camel/benchmarks/apibench.py +8 -4
- camel/benchmarks/gaia.py +2 -2
- camel/benchmarks/ragbench.py +333 -0
- camel/bots/__init__.py +1 -1
- camel/bots/discord/__init__.py +26 -0
- camel/bots/discord/discord_app.py +384 -0
- camel/bots/discord/discord_installation.py +64 -0
- camel/bots/discord/discord_store.py +160 -0
- camel/configs/__init__.py +3 -0
- camel/configs/anthropic_config.py +17 -15
- camel/configs/deepseek_config.py +2 -2
- camel/configs/internlm_config.py +60 -0
- camel/data_collector/base.py +5 -5
- camel/data_collector/sharegpt_collector.py +2 -2
- camel/datagen/self_instruct/self_instruct.py +4 -1
- camel/datagen/self_instruct/templates.py +12 -14
- camel/interpreters/internal_python_interpreter.py +24 -7
- camel/loaders/__init__.py +2 -0
- camel/loaders/panda_reader.py +337 -0
- camel/messages/__init__.py +10 -4
- camel/messages/func_message.py +30 -22
- camel/models/__init__.py +2 -0
- camel/models/anthropic_model.py +1 -22
- camel/models/cohere_model.py +8 -0
- camel/models/deepseek_model.py +67 -0
- camel/models/gemini_model.py +10 -1
- camel/models/internlm_model.py +143 -0
- camel/models/mistral_model.py +14 -7
- camel/models/model_factory.py +3 -0
- camel/models/reward/__init__.py +2 -0
- camel/models/reward/skywork_model.py +88 -0
- camel/synthetic_datagen/source2synth/data_processor.py +373 -0
- camel/synthetic_datagen/source2synth/models.py +68 -0
- camel/synthetic_datagen/source2synth/user_data_processor_config.py +73 -0
- camel/toolkits/google_scholar_toolkit.py +9 -0
- camel/types/__init__.py +4 -2
- camel/types/enums.py +81 -1
- camel/types/openai_types.py +6 -4
- camel/types/unified_model_type.py +5 -0
- camel/utils/token_counting.py +3 -3
- {camel_ai-0.2.16.dist-info → camel_ai-0.2.18.dist-info}/METADATA +158 -187
- {camel_ai-0.2.16.dist-info → camel_ai-0.2.18.dist-info}/RECORD +50 -37
- {camel_ai-0.2.16.dist-info → camel_ai-0.2.18.dist-info}/WHEEL +1 -1
- camel/bots/discord_app.py +0 -138
- {camel_ai-0.2.16.dist-info → camel_ai-0.2.18.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,384 @@
|
|
|
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
|
+
from datetime import datetime, timedelta
|
|
16
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
17
|
+
|
|
18
|
+
import discord
|
|
19
|
+
import httpx
|
|
20
|
+
from fastapi import FastAPI
|
|
21
|
+
|
|
22
|
+
from camel.bots.discord.discord_installation import DiscordInstallation
|
|
23
|
+
from camel.logger import get_logger
|
|
24
|
+
from camel.utils import api_keys_required, dependencies_required
|
|
25
|
+
|
|
26
|
+
from .discord_store import DiscordBaseInstallationStore
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from discord import Message
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
TOKEN_URL = "https://discord.com/api/oauth2/token"
|
|
34
|
+
USER_URL = "https://discord.com/api/users/@me"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DiscordApp:
|
|
38
|
+
r"""A class representing a Discord app that uses the `discord.py` library
|
|
39
|
+
to interact with Discord servers.
|
|
40
|
+
|
|
41
|
+
This bot can respond to messages in specific channels and only reacts to
|
|
42
|
+
messages that mention the bot.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
channel_ids (Optional[List[int]]): A list of allowed channel IDs. If
|
|
46
|
+
provided, the bot will only respond to messages in these channels.
|
|
47
|
+
token (Optional[str]): The Discord bot token used for authentication.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@dependencies_required('discord')
|
|
51
|
+
@api_keys_required(
|
|
52
|
+
[
|
|
53
|
+
("token", "DISCORD_BOT_TOKEN"),
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
channel_ids: Optional[List[int]] = None,
|
|
59
|
+
token: Optional[str] = None,
|
|
60
|
+
client_id: Optional[str] = None,
|
|
61
|
+
client_secret: Optional[str] = None,
|
|
62
|
+
redirect_uri: Optional[str] = None,
|
|
63
|
+
installation_store: Optional[DiscordBaseInstallationStore] = None,
|
|
64
|
+
intents: Optional[discord.Intents] = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
r"""Initialize the DiscordApp instance by setting up the Discord client
|
|
67
|
+
and event handlers.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
channel_ids (Optional[List[int]]): A list of allowed channel IDs.
|
|
71
|
+
The bot will only respond to messages in these channels if
|
|
72
|
+
provided. (default: :obj:`None`)
|
|
73
|
+
token (Optional[str]): The Discord bot token for authentication.
|
|
74
|
+
If not provided, the token will be retrieved from the
|
|
75
|
+
environment variable `DISCORD_TOKEN`. (default: :obj:`None`)
|
|
76
|
+
client_id (str, optional): The client ID for Discord OAuth.
|
|
77
|
+
(default: :obj:`None`)
|
|
78
|
+
client_secret (Optional[str]): The client secret for Discord OAuth.
|
|
79
|
+
(default: :obj:`None`)
|
|
80
|
+
redirect_uri (str): The redirect URI for OAuth callbacks.
|
|
81
|
+
(default: :obj:`None`)
|
|
82
|
+
installation_store (DiscordAsyncInstallationStore): The database
|
|
83
|
+
stores all information of all installations.
|
|
84
|
+
(default: :obj:`None`)
|
|
85
|
+
intents (discord.Intents): The Discord intents of this app.
|
|
86
|
+
(default: :obj:`None`)
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If the `DISCORD_BOT_TOKEN` is not found in environment
|
|
90
|
+
variables.
|
|
91
|
+
"""
|
|
92
|
+
self.token = token or os.getenv("DISCORD_BOT_TOKEN")
|
|
93
|
+
self.channel_ids = channel_ids
|
|
94
|
+
self.installation_store = installation_store
|
|
95
|
+
|
|
96
|
+
if not intents:
|
|
97
|
+
intents = discord.Intents.all()
|
|
98
|
+
intents.message_content = True
|
|
99
|
+
intents.guilds = True
|
|
100
|
+
|
|
101
|
+
self._client = discord.Client(intents=intents)
|
|
102
|
+
|
|
103
|
+
# Register event handlers
|
|
104
|
+
self._client.event(self.on_ready)
|
|
105
|
+
self._client.event(self.on_message)
|
|
106
|
+
|
|
107
|
+
# OAuth flow
|
|
108
|
+
self.client_id = client_id or os.getenv("DISCORD_CLIENT_ID")
|
|
109
|
+
self.client_secret = client_secret or os.getenv(
|
|
110
|
+
"DISCORD_CLIENT_SECRET"
|
|
111
|
+
)
|
|
112
|
+
self.redirect_uri = redirect_uri
|
|
113
|
+
|
|
114
|
+
self.oauth_flow = bool(
|
|
115
|
+
self.client_id
|
|
116
|
+
and self.client_secret
|
|
117
|
+
and self.redirect_uri
|
|
118
|
+
and self.installation_store
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
self.app = FastAPI()
|
|
122
|
+
|
|
123
|
+
async def start(self):
|
|
124
|
+
r"""Asynchronously start the Discord bot using its token.
|
|
125
|
+
|
|
126
|
+
This method starts the bot and logs into Discord asynchronously using
|
|
127
|
+
the provided token. It should be awaited when used in an async
|
|
128
|
+
environment.
|
|
129
|
+
"""
|
|
130
|
+
await self._client.start(self.token)
|
|
131
|
+
|
|
132
|
+
def run(self) -> None:
|
|
133
|
+
r"""Start the Discord bot using its token.
|
|
134
|
+
|
|
135
|
+
This method starts the bot and logs into Discord synchronously using
|
|
136
|
+
the provided token. It blocks execution and keeps the bot running.
|
|
137
|
+
"""
|
|
138
|
+
self._client.run(self.token) # type: ignore[arg-type]
|
|
139
|
+
|
|
140
|
+
async def exchange_code_for_token_response(
|
|
141
|
+
self, code: str
|
|
142
|
+
) -> Optional[str]:
|
|
143
|
+
r"""Exchange the authorization code for an access token.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
code (str): The authorization code received from Discord after
|
|
147
|
+
user authorization.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Optional[str]: The access token if successful, otherwise None.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
ValueError: If OAuth configuration is incomplete or invalid.
|
|
154
|
+
httpx.RequestError: If there is a network issue during the request.
|
|
155
|
+
"""
|
|
156
|
+
if not self.oauth_flow:
|
|
157
|
+
logger.warning(
|
|
158
|
+
"OAuth is not enabled. Missing client_id, "
|
|
159
|
+
"client_secret, or redirect_uri."
|
|
160
|
+
)
|
|
161
|
+
return None
|
|
162
|
+
data = {
|
|
163
|
+
"client_id": self.client_id,
|
|
164
|
+
"client_secret": self.client_secret,
|
|
165
|
+
"grant_type": "authorization_code",
|
|
166
|
+
"code": code,
|
|
167
|
+
"redirect_uri": self.redirect_uri,
|
|
168
|
+
}
|
|
169
|
+
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
|
170
|
+
try:
|
|
171
|
+
async with httpx.AsyncClient() as client:
|
|
172
|
+
response = await client.post(
|
|
173
|
+
TOKEN_URL, data=data, headers=headers
|
|
174
|
+
)
|
|
175
|
+
if response.status_code != 200:
|
|
176
|
+
logger.error(f"Failed to exchange code: {response.text}")
|
|
177
|
+
return None
|
|
178
|
+
response_data = response.json()
|
|
179
|
+
|
|
180
|
+
return response_data
|
|
181
|
+
except (httpx.RequestError, ValueError) as e:
|
|
182
|
+
logger.error(f"Error during token fetch: {e}")
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
async def get_user_info(self, access_token: str) -> Optional[dict]:
|
|
186
|
+
r"""Retrieve user information using the access token.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
access_token (str): The access token received from Discord.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
dict: The user information retrieved from Discord.
|
|
193
|
+
"""
|
|
194
|
+
if not self.oauth_flow:
|
|
195
|
+
logger.warning(
|
|
196
|
+
"OAuth is not enabled. Missing client_id, "
|
|
197
|
+
"client_secret, or redirect_uri."
|
|
198
|
+
)
|
|
199
|
+
return None
|
|
200
|
+
headers = {"Authorization": f"Bearer {access_token}"}
|
|
201
|
+
async with httpx.AsyncClient() as client:
|
|
202
|
+
user_response = await client.get(USER_URL, headers=headers)
|
|
203
|
+
return user_response.json()
|
|
204
|
+
|
|
205
|
+
async def refresh_access_token(self, refresh_token: str) -> Optional[str]:
|
|
206
|
+
r"""Refresh the access token using a refresh token.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
refresh_token (str): The refresh token issued by Discord that
|
|
210
|
+
can be used to obtain a new access token.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Optional[str]: The new access token if successful, otherwise None.
|
|
214
|
+
"""
|
|
215
|
+
if not self.oauth_flow:
|
|
216
|
+
logger.warning(
|
|
217
|
+
"OAuth is not enabled. Missing client_id, "
|
|
218
|
+
"client_secret, or redirect_uri."
|
|
219
|
+
)
|
|
220
|
+
return None
|
|
221
|
+
data = {
|
|
222
|
+
"client_id": self.client_id,
|
|
223
|
+
"client_secret": self.client_secret,
|
|
224
|
+
"grant_type": "refresh_token",
|
|
225
|
+
"refresh_token": refresh_token,
|
|
226
|
+
"redirect_uri": self.redirect_uri,
|
|
227
|
+
}
|
|
228
|
+
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
|
229
|
+
async with httpx.AsyncClient() as client:
|
|
230
|
+
response = await client.post(TOKEN_URL, data=data, headers=headers)
|
|
231
|
+
if response.status_code != 200:
|
|
232
|
+
logger.error(f"Failed to refresh token: {response.text}")
|
|
233
|
+
return None
|
|
234
|
+
response_data = response.json()
|
|
235
|
+
return response_data.get("access_token")
|
|
236
|
+
|
|
237
|
+
async def get_valid_access_token(self, guild_id: str) -> Optional[str]:
|
|
238
|
+
r"""Retrieve a valid access token for the specified guild.
|
|
239
|
+
|
|
240
|
+
This method attempts to retrieve an access token for a specific guild.
|
|
241
|
+
If the current access token is expired, it will refresh the token using
|
|
242
|
+
the refresh token.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
guild_id (str): The ID of the guild to retrieve the access
|
|
246
|
+
token for.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Optional[str]: The valid access token if successful,
|
|
250
|
+
otherwise None.
|
|
251
|
+
"""
|
|
252
|
+
if not self.oauth_flow:
|
|
253
|
+
logger.warning(
|
|
254
|
+
"OAuth is not enabled. Missing client_id, "
|
|
255
|
+
"client_secret, or redirect_uri."
|
|
256
|
+
)
|
|
257
|
+
return None
|
|
258
|
+
assert self.installation_store is not None
|
|
259
|
+
installation = await self.installation_store.find_by_guild(
|
|
260
|
+
guild_id=guild_id
|
|
261
|
+
)
|
|
262
|
+
if not installation:
|
|
263
|
+
logger.error(f"No installation found for guild: {guild_id}")
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
installation.token_expires_at
|
|
268
|
+
and datetime.now() >= installation.token_expires_at
|
|
269
|
+
):
|
|
270
|
+
logger.info(
|
|
271
|
+
f"Access token expired for guild: {guild_id}, "
|
|
272
|
+
f"refreshing token..."
|
|
273
|
+
)
|
|
274
|
+
new_access_token = await self.refresh_access_token(
|
|
275
|
+
installation.refresh_token
|
|
276
|
+
)
|
|
277
|
+
if new_access_token:
|
|
278
|
+
installation.access_token = new_access_token
|
|
279
|
+
installation.token_expires_at = datetime.now() + timedelta(
|
|
280
|
+
seconds=3600
|
|
281
|
+
)
|
|
282
|
+
await self.installation_store.save(installation)
|
|
283
|
+
return new_access_token
|
|
284
|
+
else:
|
|
285
|
+
logger.error(
|
|
286
|
+
f"Failed to refresh access token for guild: {guild_id}"
|
|
287
|
+
)
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
return installation.access_token
|
|
291
|
+
|
|
292
|
+
async def save_installation(
|
|
293
|
+
self,
|
|
294
|
+
guild_id: str,
|
|
295
|
+
access_token: str,
|
|
296
|
+
refresh_token: str,
|
|
297
|
+
expires_in: int,
|
|
298
|
+
):
|
|
299
|
+
r"""Save the installation information for a given guild.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
guild_id (str): The ID of the guild where the bot is installed.
|
|
303
|
+
access_token (str): The access token for the guild.
|
|
304
|
+
refresh_token (str): The refresh token for the guild.
|
|
305
|
+
expires_in: (int): The expiration time of the
|
|
306
|
+
access token.
|
|
307
|
+
"""
|
|
308
|
+
if not self.oauth_flow:
|
|
309
|
+
logger.warning(
|
|
310
|
+
"OAuth is not enabled. Missing client_id, "
|
|
311
|
+
"client_secret, or redirect_uri."
|
|
312
|
+
)
|
|
313
|
+
return None
|
|
314
|
+
assert self.installation_store is not None
|
|
315
|
+
expires_at = datetime.now() + timedelta(seconds=expires_in)
|
|
316
|
+
installation = DiscordInstallation(
|
|
317
|
+
guild_id=guild_id,
|
|
318
|
+
access_token=access_token,
|
|
319
|
+
refresh_token=refresh_token,
|
|
320
|
+
installed_at=datetime.now(),
|
|
321
|
+
token_expires_at=expires_at,
|
|
322
|
+
)
|
|
323
|
+
await self.installation_store.save(installation)
|
|
324
|
+
logger.info(f"Installation saved for guild: {guild_id}")
|
|
325
|
+
|
|
326
|
+
async def remove_installation(self, guild: discord.Guild):
|
|
327
|
+
r"""Remove the installation for a given guild.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
guild (discord.Guild): The guild from which the bot is
|
|
331
|
+
being removed.
|
|
332
|
+
"""
|
|
333
|
+
if not self.oauth_flow:
|
|
334
|
+
logger.warning(
|
|
335
|
+
"OAuth is not enabled. Missing client_id, "
|
|
336
|
+
"client_secret, or redirect_uri."
|
|
337
|
+
)
|
|
338
|
+
return None
|
|
339
|
+
assert self.installation_store is not None
|
|
340
|
+
await self.installation_store.delete(guild_id=str(guild.id))
|
|
341
|
+
print(f"Bot removed from guild: {guild.id}")
|
|
342
|
+
|
|
343
|
+
async def on_ready(self) -> None:
|
|
344
|
+
r"""Event handler that is called when the bot has successfully
|
|
345
|
+
connected to the Discord server.
|
|
346
|
+
|
|
347
|
+
When the bot is ready and logged into Discord, it prints a message
|
|
348
|
+
displaying the bot's username.
|
|
349
|
+
"""
|
|
350
|
+
logger.info(f'We have logged in as {self._client.user}')
|
|
351
|
+
|
|
352
|
+
async def on_message(self, message: 'Message') -> None:
|
|
353
|
+
r"""Event handler for processing incoming messages.
|
|
354
|
+
|
|
355
|
+
This method is called whenever a new message is received by the bot. It
|
|
356
|
+
will ignore messages sent by the bot itself, only respond to messages
|
|
357
|
+
in allowed channels (if specified), and only to messages that mention
|
|
358
|
+
the bot.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
message (discord.Message): The message object received from
|
|
362
|
+
Discord.
|
|
363
|
+
"""
|
|
364
|
+
# If the message author is the bot itself,
|
|
365
|
+
# do not respond to this message
|
|
366
|
+
if message.author == self._client.user:
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
# If allowed channel IDs are provided,
|
|
370
|
+
# only respond to messages in those channels
|
|
371
|
+
if self.channel_ids and message.channel.id not in self.channel_ids:
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
# Only respond to messages that mention the bot
|
|
375
|
+
if not self._client.user or not self._client.user.mentioned_in(
|
|
376
|
+
message
|
|
377
|
+
):
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
logger.info(f"Received message: {message.content}")
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def client(self):
|
|
384
|
+
return self._client
|
|
@@ -0,0 +1,64 @@
|
|
|
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 datetime import datetime
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DiscordInstallation:
|
|
19
|
+
r"""Represents an installation of a Discord application in a
|
|
20
|
+
specific guild (server).
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
guild_id (str): The unique identifier for the Discord guild (server)
|
|
24
|
+
where the application is installed.
|
|
25
|
+
access_token (str): The access token used to authenticate API requests
|
|
26
|
+
for the installed application.
|
|
27
|
+
refresh_token (str): The token used to refresh the access token when
|
|
28
|
+
it expires.
|
|
29
|
+
installed_at (datetime): The timestamp indicating when the application
|
|
30
|
+
was installed in the guild.
|
|
31
|
+
token_expires_at (Optional[datetime]): The optional timestamp
|
|
32
|
+
indicating when the access token will expire. Defaults to None
|
|
33
|
+
if the token does not have an expiration time.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
guild_id: str,
|
|
39
|
+
access_token: str,
|
|
40
|
+
refresh_token: str,
|
|
41
|
+
installed_at: datetime,
|
|
42
|
+
token_expires_at: Optional[datetime] = None,
|
|
43
|
+
):
|
|
44
|
+
r"""Initialize the DiscordInstallation.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
guild_id (str): The unique identifier for the Discord guild
|
|
48
|
+
(server) where the application is installed.
|
|
49
|
+
access_token (str): The access token used to authenticate API
|
|
50
|
+
requests for the installed application.
|
|
51
|
+
refresh_token (str): The token used to refresh the access token
|
|
52
|
+
when it expires.
|
|
53
|
+
installed_at (datetime): The timestamp indicating when the
|
|
54
|
+
application was installed in the guild.
|
|
55
|
+
token_expires_at (Optional[datetime]): The optional timestamp
|
|
56
|
+
indicating when the access token will expire. Defaults to None
|
|
57
|
+
if the token does not have an expiration time.
|
|
58
|
+
(default: :obj:`None`)
|
|
59
|
+
"""
|
|
60
|
+
self.guild_id = guild_id
|
|
61
|
+
self.access_token = access_token
|
|
62
|
+
self.refresh_token = refresh_token
|
|
63
|
+
self.installed_at = installed_at
|
|
64
|
+
self.token_expires_at = token_expires_at
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from .discord_installation import DiscordInstallation
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DiscordBaseInstallationStore:
|
|
21
|
+
r"""Abstract base class for managing Discord installations.
|
|
22
|
+
|
|
23
|
+
This class defines the interface for database operations related to storing
|
|
24
|
+
and retrieving Discord installation data. Subclasses must implement these
|
|
25
|
+
methods to handle database-specific logic.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
async def init(self):
|
|
29
|
+
r"""Initializes the database connection or structure."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
async def save(self, installation: DiscordInstallation):
|
|
33
|
+
r"""Saves or updates a Discord installation record."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
async def find_by_guild(
|
|
37
|
+
self, guild_id: str
|
|
38
|
+
) -> Optional[DiscordInstallation]:
|
|
39
|
+
r"""Finds an installation record by guild ID."""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
async def delete(self, guild_id: str):
|
|
43
|
+
r"""Deletes an installation record by guild ID."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DiscordSQLiteInstallationStore(DiscordBaseInstallationStore):
|
|
48
|
+
r"""SQLite-based implementation for managing Discord installations.
|
|
49
|
+
|
|
50
|
+
This class provides methods for initializing the database, saving,
|
|
51
|
+
retrieving, and deleting installation records using SQLite.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
database (str): Path to the SQLite database file.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, database: str):
|
|
58
|
+
r"""Initializes the SQLite installation store.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
database (str): Path to the SQLite database file.
|
|
62
|
+
"""
|
|
63
|
+
self.database = database
|
|
64
|
+
|
|
65
|
+
async def init(self):
|
|
66
|
+
r"""Initializes the database by creating the required table if it
|
|
67
|
+
does not exist."""
|
|
68
|
+
import aiosqlite
|
|
69
|
+
|
|
70
|
+
async with aiosqlite.connect(self.database) as db:
|
|
71
|
+
await db.execute(
|
|
72
|
+
"""
|
|
73
|
+
CREATE TABLE IF NOT EXISTS discord_installations (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
guild_id TEXT NOT NULL UNIQUE,
|
|
76
|
+
access_token TEXT NOT NULL,
|
|
77
|
+
refresh_token TEXT NOT NULL,
|
|
78
|
+
installed_at DATETIME NOT NULL,
|
|
79
|
+
token_expires_at DATETIME
|
|
80
|
+
);
|
|
81
|
+
"""
|
|
82
|
+
)
|
|
83
|
+
await db.commit()
|
|
84
|
+
|
|
85
|
+
async def save(self, installation: DiscordInstallation):
|
|
86
|
+
r"""Saves a new installation record or updates an existing one.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
installation (DiscordInstallation): The installation data to save.
|
|
90
|
+
"""
|
|
91
|
+
import aiosqlite
|
|
92
|
+
|
|
93
|
+
async with aiosqlite.connect(self.database) as db:
|
|
94
|
+
await db.execute(
|
|
95
|
+
"""
|
|
96
|
+
INSERT INTO discord_installations (
|
|
97
|
+
guild_id, access_token, refresh_token,
|
|
98
|
+
installed_at, token_expires_at
|
|
99
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
100
|
+
ON CONFLICT(guild_id) DO UPDATE SET
|
|
101
|
+
access_token = excluded.access_token,
|
|
102
|
+
refresh_token = excluded.refresh_token,
|
|
103
|
+
token_expires_at = excluded.token_expires_at;
|
|
104
|
+
""",
|
|
105
|
+
[
|
|
106
|
+
installation.guild_id,
|
|
107
|
+
installation.access_token,
|
|
108
|
+
installation.refresh_token,
|
|
109
|
+
installation.installed_at,
|
|
110
|
+
installation.token_expires_at,
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
await db.commit()
|
|
114
|
+
|
|
115
|
+
async def find_by_guild(
|
|
116
|
+
self, guild_id: str
|
|
117
|
+
) -> Optional[DiscordInstallation]:
|
|
118
|
+
r"""Finds an installation record by guild ID.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
guild_id (str): The guild ID to search for.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Optional[DiscordInstallation]: The installation record if found,
|
|
125
|
+
otherwise None.
|
|
126
|
+
"""
|
|
127
|
+
import aiosqlite
|
|
128
|
+
|
|
129
|
+
async with aiosqlite.connect(self.database) as db:
|
|
130
|
+
async with db.execute(
|
|
131
|
+
"SELECT guild_id, access_token, refresh_token, "
|
|
132
|
+
"installed_at, token_expires_at FROM discord_installations "
|
|
133
|
+
"WHERE guild_id = ?",
|
|
134
|
+
[guild_id],
|
|
135
|
+
) as cursor:
|
|
136
|
+
row = await cursor.fetchone()
|
|
137
|
+
if row:
|
|
138
|
+
return DiscordInstallation(
|
|
139
|
+
guild_id=row[0],
|
|
140
|
+
access_token=row[1],
|
|
141
|
+
refresh_token=row[2],
|
|
142
|
+
installed_at=row[3],
|
|
143
|
+
token_expires_at=row[4],
|
|
144
|
+
)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
async def delete(self, guild_id: str):
|
|
148
|
+
r"""Deletes an installation record by guild ID.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
guild_id (str): The guild ID of the record to delete.
|
|
152
|
+
"""
|
|
153
|
+
import aiosqlite
|
|
154
|
+
|
|
155
|
+
async with aiosqlite.connect(self.database) as db:
|
|
156
|
+
await db.execute(
|
|
157
|
+
"DELETE FROM discord_installations WHERE guild_id = ?",
|
|
158
|
+
[guild_id],
|
|
159
|
+
)
|
|
160
|
+
await db.commit()
|
camel/configs/__init__.py
CHANGED
|
@@ -17,6 +17,7 @@ from .cohere_config import COHERE_API_PARAMS, CohereConfig
|
|
|
17
17
|
from .deepseek_config import DEEPSEEK_API_PARAMS, DeepSeekConfig
|
|
18
18
|
from .gemini_config import Gemini_API_PARAMS, GeminiConfig
|
|
19
19
|
from .groq_config import GROQ_API_PARAMS, GroqConfig
|
|
20
|
+
from .internlm_config import INTERNLM_API_PARAMS, InternLMConfig
|
|
20
21
|
from .litellm_config import LITELLM_API_PARAMS, LiteLLMConfig
|
|
21
22
|
from .mistral_config import MISTRAL_API_PARAMS, MistralConfig
|
|
22
23
|
from .nvidia_config import NVIDIA_API_PARAMS, NvidiaConfig
|
|
@@ -76,4 +77,6 @@ __all__ = [
|
|
|
76
77
|
'QWEN_API_PARAMS',
|
|
77
78
|
'DeepSeekConfig',
|
|
78
79
|
'DEEPSEEK_API_PARAMS',
|
|
80
|
+
'InternLMConfig',
|
|
81
|
+
'INTERNLM_API_PARAMS',
|
|
79
82
|
]
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
from typing import List, Union
|
|
16
|
+
from typing import Any, ClassVar, List, Union
|
|
17
17
|
|
|
18
18
|
from camel.configs.base_config import BaseConfig
|
|
19
|
-
from camel.types import
|
|
19
|
+
from camel.types import NotGiven
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class AnthropicConfig(BaseConfig):
|
|
@@ -29,41 +29,43 @@ class AnthropicConfig(BaseConfig):
|
|
|
29
29
|
generate before stopping. Note that Anthropic models may stop
|
|
30
30
|
before reaching this maximum. This parameter only specifies the
|
|
31
31
|
absolute maximum number of tokens to generate.
|
|
32
|
-
(default: :obj:`
|
|
32
|
+
(default: :obj:`8192`)
|
|
33
33
|
stop_sequences (List[str], optional): Sequences that will cause the
|
|
34
34
|
model to stop generating completion text. Anthropic models stop
|
|
35
35
|
on "\n\nHuman:", and may include additional built-in stop sequences
|
|
36
36
|
in the future. By providing the stop_sequences parameter, you may
|
|
37
37
|
include additional strings that will cause the model to stop
|
|
38
|
-
generating.
|
|
38
|
+
generating. (default: :obj:`[]`)
|
|
39
39
|
temperature (float, optional): Amount of randomness injected into the
|
|
40
40
|
response. Defaults to 1. Ranges from 0 to 1. Use temp closer to 0
|
|
41
41
|
for analytical / multiple choice, and closer to 1 for creative
|
|
42
|
-
and generative tasks.
|
|
43
|
-
(default: :obj:`1`)
|
|
42
|
+
and generative tasks. (default: :obj:`1`)
|
|
44
43
|
top_p (float, optional): Use nucleus sampling. In nucleus sampling, we
|
|
45
44
|
compute the cumulative distribution over all the options for each
|
|
46
45
|
subsequent token in decreasing probability order and cut it off
|
|
47
46
|
once it reaches a particular probability specified by `top_p`.
|
|
48
47
|
You should either alter `temperature` or `top_p`,
|
|
49
|
-
but not both.
|
|
50
|
-
(default: :obj:`0.7`)
|
|
48
|
+
but not both. (default: :obj:`0.7`)
|
|
51
49
|
top_k (int, optional): Only sample from the top K options for each
|
|
52
50
|
subsequent token. Used to remove "long tail" low probability
|
|
53
|
-
responses.
|
|
54
|
-
(default: :obj:`5`)
|
|
51
|
+
responses. (default: :obj:`5`)
|
|
55
52
|
metadata: An object describing metadata about the request.
|
|
56
53
|
stream (bool, optional): Whether to incrementally stream the response
|
|
57
54
|
using server-sent events. (default: :obj:`False`)
|
|
58
55
|
"""
|
|
59
56
|
|
|
60
|
-
max_tokens: int =
|
|
61
|
-
stop_sequences: Union[List[str], NotGiven] =
|
|
57
|
+
max_tokens: int = 8192
|
|
58
|
+
stop_sequences: ClassVar[Union[List[str], NotGiven]] = []
|
|
62
59
|
temperature: float = 1
|
|
63
|
-
top_p: Union[float, NotGiven] =
|
|
64
|
-
top_k: Union[int, NotGiven] =
|
|
65
|
-
metadata: NotGiven = NOT_GIVEN
|
|
60
|
+
top_p: Union[float, NotGiven] = 0.7
|
|
61
|
+
top_k: Union[int, NotGiven] = 5
|
|
66
62
|
stream: bool = False
|
|
67
63
|
|
|
64
|
+
def as_dict(self) -> dict[str, Any]:
|
|
65
|
+
config_dict = super().as_dict()
|
|
66
|
+
if "tools" in config_dict:
|
|
67
|
+
del config_dict["tools"] # TODO: Support tool calling.
|
|
68
|
+
return config_dict
|
|
69
|
+
|
|
68
70
|
|
|
69
71
|
ANTHROPIC_API_PARAMS = {param for param in AnthropicConfig.model_fields.keys()}
|