camel-ai 0.2.62__py3-none-any.whl → 0.2.65__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.

Files changed (59) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +95 -24
  3. camel/agents/mcp_agent.py +5 -1
  4. camel/benchmarks/mock_website/README.md +96 -0
  5. camel/benchmarks/mock_website/mock_web.py +299 -0
  6. camel/benchmarks/mock_website/requirements.txt +3 -0
  7. camel/benchmarks/mock_website/shopping_mall/app.py +465 -0
  8. camel/benchmarks/mock_website/task.json +104 -0
  9. camel/configs/__init__.py +3 -0
  10. camel/configs/crynux_config.py +94 -0
  11. camel/datasets/models.py +1 -1
  12. camel/datasets/static_dataset.py +6 -0
  13. camel/interpreters/base.py +14 -1
  14. camel/interpreters/docker/Dockerfile +63 -7
  15. camel/interpreters/docker_interpreter.py +65 -7
  16. camel/interpreters/e2b_interpreter.py +23 -8
  17. camel/interpreters/internal_python_interpreter.py +30 -2
  18. camel/interpreters/ipython_interpreter.py +21 -3
  19. camel/interpreters/subprocess_interpreter.py +34 -2
  20. camel/memories/records.py +5 -3
  21. camel/models/__init__.py +2 -0
  22. camel/models/azure_openai_model.py +101 -25
  23. camel/models/cohere_model.py +65 -0
  24. camel/models/crynux_model.py +94 -0
  25. camel/models/deepseek_model.py +43 -1
  26. camel/models/gemini_model.py +50 -4
  27. camel/models/litellm_model.py +38 -0
  28. camel/models/mistral_model.py +66 -0
  29. camel/models/model_factory.py +10 -1
  30. camel/models/openai_compatible_model.py +81 -17
  31. camel/models/openai_model.py +87 -16
  32. camel/models/reka_model.py +69 -0
  33. camel/models/samba_model.py +69 -2
  34. camel/models/sglang_model.py +74 -2
  35. camel/models/watsonx_model.py +62 -0
  36. camel/societies/workforce/role_playing_worker.py +11 -3
  37. camel/societies/workforce/single_agent_worker.py +31 -1
  38. camel/societies/workforce/utils.py +51 -0
  39. camel/societies/workforce/workforce.py +409 -7
  40. camel/storages/__init__.py +2 -0
  41. camel/storages/vectordb_storages/__init__.py +2 -0
  42. camel/storages/vectordb_storages/weaviate.py +714 -0
  43. camel/tasks/task.py +27 -10
  44. camel/toolkits/async_browser_toolkit.py +97 -54
  45. camel/toolkits/browser_toolkit.py +65 -18
  46. camel/toolkits/code_execution.py +37 -8
  47. camel/toolkits/function_tool.py +2 -2
  48. camel/toolkits/mcp_toolkit.py +13 -2
  49. camel/toolkits/playwright_mcp_toolkit.py +16 -3
  50. camel/toolkits/task_planning_toolkit.py +134 -0
  51. camel/types/enums.py +61 -2
  52. camel/types/unified_model_type.py +5 -0
  53. camel/utils/__init__.py +16 -0
  54. camel/utils/langfuse.py +258 -0
  55. camel/utils/mcp_client.py +84 -17
  56. {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/METADATA +9 -12
  57. {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/RECORD +59 -49
  58. {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/WHEEL +0 -0
  59. {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/licenses/LICENSE +0 -0
@@ -38,6 +38,9 @@ from camel.utils import (
38
38
  BaseTokenCounter,
39
39
  OpenAITokenCounter,
40
40
  api_keys_required,
41
+ get_current_agent_session_id,
42
+ update_current_observation,
43
+ update_langfuse_trace,
41
44
  )
42
45
 
43
46
  try:
@@ -48,6 +51,14 @@ try:
48
51
  except (ImportError, AttributeError):
49
52
  LLMEvent = None
50
53
 
54
+ if os.environ.get("LANGFUSE_ENABLED", "False").lower() == "true":
55
+ try:
56
+ from langfuse.decorators import observe
57
+ except ImportError:
58
+ from camel.utils import observe
59
+ else:
60
+ from camel.utils import observe
61
+
51
62
 
52
63
  class SambaModel(BaseModelBackend):
53
64
  r"""SambaNova service interface.
@@ -161,6 +172,7 @@ class SambaModel(BaseModelBackend):
161
172
  " SambaNova service"
162
173
  )
163
174
 
175
+ @observe(as_type="generation")
164
176
  async def _arun( # type: ignore[misc]
165
177
  self,
166
178
  messages: List[OpenAIMessage],
@@ -178,13 +190,42 @@ class SambaModel(BaseModelBackend):
178
190
  `ChatCompletion` in the non-stream mode, or
179
191
  `AsyncStream[ChatCompletionChunk]` in the stream mode.
180
192
  """
193
+
194
+ update_current_observation(
195
+ input={
196
+ "messages": messages,
197
+ "tools": tools,
198
+ },
199
+ model=str(self.model_type),
200
+ model_parameters=self.model_config_dict,
201
+ )
202
+
203
+ # Update Langfuse trace with current agent session and metadata
204
+ agent_session_id = get_current_agent_session_id()
205
+ if agent_session_id:
206
+ update_langfuse_trace(
207
+ session_id=agent_session_id,
208
+ metadata={
209
+ "source": "camel",
210
+ "agent_id": agent_session_id,
211
+ "agent_type": "camel_chat_agent",
212
+ "model_type": str(self.model_type),
213
+ },
214
+ tags=["CAMEL-AI", str(self.model_type)],
215
+ )
216
+
181
217
  if "tools" in self.model_config_dict:
182
218
  del self.model_config_dict["tools"]
183
219
  if self.model_config_dict.get("stream") is True:
184
220
  return await self._arun_streaming(messages)
185
221
  else:
186
- return await self._arun_non_streaming(messages)
222
+ response = await self._arun_non_streaming(messages)
223
+ update_current_observation(
224
+ usage=response.usage,
225
+ )
226
+ return response
187
227
 
228
+ @observe(as_type="generation")
188
229
  def _run( # type: ignore[misc]
189
230
  self,
190
231
  messages: List[OpenAIMessage],
@@ -202,12 +243,38 @@ class SambaModel(BaseModelBackend):
202
243
  `ChatCompletion` in the non-stream mode, or
203
244
  `Stream[ChatCompletionChunk]` in the stream mode.
204
245
  """
246
+ update_current_observation(
247
+ input={
248
+ "messages": messages,
249
+ "tools": tools,
250
+ },
251
+ model=str(self.model_type),
252
+ model_parameters=self.model_config_dict,
253
+ )
254
+ # Update Langfuse trace with current agent session and metadata
255
+ agent_session_id = get_current_agent_session_id()
256
+ if agent_session_id:
257
+ update_langfuse_trace(
258
+ session_id=agent_session_id,
259
+ metadata={
260
+ "source": "camel",
261
+ "agent_id": agent_session_id,
262
+ "agent_type": "camel_chat_agent",
263
+ "model_type": str(self.model_type),
264
+ },
265
+ tags=["CAMEL-AI", str(self.model_type)],
266
+ )
267
+
205
268
  if "tools" in self.model_config_dict:
206
269
  del self.model_config_dict["tools"]
207
270
  if self.model_config_dict.get("stream") is True:
208
271
  return self._run_streaming(messages)
209
272
  else:
210
- return self._run_non_streaming(messages)
273
+ response = self._run_non_streaming(messages)
274
+ update_current_observation(
275
+ usage=response.usage,
276
+ )
277
+ return response
211
278
 
212
279
  def _run_streaming(
213
280
  self, messages: List[OpenAIMessage]
@@ -29,7 +29,21 @@ from camel.types import (
29
29
  ChatCompletionChunk,
30
30
  ModelType,
31
31
  )
32
- from camel.utils import BaseTokenCounter, OpenAITokenCounter
32
+ from camel.utils import (
33
+ BaseTokenCounter,
34
+ OpenAITokenCounter,
35
+ get_current_agent_session_id,
36
+ update_current_observation,
37
+ update_langfuse_trace,
38
+ )
39
+
40
+ if os.environ.get("LANGFUSE_ENABLED", "False").lower() == "true":
41
+ try:
42
+ from langfuse.decorators import observe
43
+ except ImportError:
44
+ from camel.utils import observe
45
+ else:
46
+ from camel.utils import observe
33
47
 
34
48
 
35
49
  class SGLangModel(BaseModelBackend):
@@ -195,6 +209,7 @@ class SGLangModel(BaseModelBackend):
195
209
  "input into SGLang model backend."
196
210
  )
197
211
 
212
+ @observe(as_type='generation')
198
213
  async def _arun(
199
214
  self,
200
215
  messages: List[OpenAIMessage],
@@ -213,6 +228,28 @@ class SGLangModel(BaseModelBackend):
213
228
  `AsyncStream[ChatCompletionChunk]` in the stream mode.
214
229
  """
215
230
 
231
+ update_current_observation(
232
+ input={
233
+ "messages": messages,
234
+ "tools": tools,
235
+ },
236
+ model=str(self.model_type),
237
+ model_parameters=self.model_config_dict,
238
+ )
239
+ # Update Langfuse trace with current agent session and metadata
240
+ agent_session_id = get_current_agent_session_id()
241
+ if agent_session_id:
242
+ update_langfuse_trace(
243
+ session_id=agent_session_id,
244
+ metadata={
245
+ "source": "camel",
246
+ "agent_id": agent_session_id,
247
+ "agent_type": "camel_chat_agent",
248
+ "model_type": str(self.model_type),
249
+ },
250
+ tags=["CAMEL-AI", str(self.model_type)],
251
+ )
252
+
216
253
  # Ensure server is running
217
254
  self._ensure_server_running()
218
255
 
@@ -230,9 +267,16 @@ class SGLangModel(BaseModelBackend):
230
267
  model=self.model_type,
231
268
  **self.model_config_dict,
232
269
  )
233
-
270
+ update_current_observation(
271
+ usage_details={
272
+ "prompt_tokens": response.usage.prompt_tokens,
273
+ "completion_tokens": response.usage.completion_tokens,
274
+ "total_tokens": response.usage.total_tokens,
275
+ },
276
+ )
234
277
  return response
235
278
 
279
+ @observe(as_type='generation')
236
280
  def _run(
237
281
  self,
238
282
  messages: List[OpenAIMessage],
@@ -250,6 +294,27 @@ class SGLangModel(BaseModelBackend):
250
294
  `ChatCompletion` in the non-stream mode, or
251
295
  `Stream[ChatCompletionChunk]` in the stream mode.
252
296
  """
297
+ update_current_observation(
298
+ input={
299
+ "messages": messages,
300
+ "tools": tools,
301
+ },
302
+ model=str(self.model_type),
303
+ model_parameters=self.model_config_dict,
304
+ )
305
+ # Update Langfuse trace with current agent session and metadata
306
+ agent_session_id = get_current_agent_session_id()
307
+ if agent_session_id:
308
+ update_langfuse_trace(
309
+ session_id=agent_session_id,
310
+ metadata={
311
+ "source": "camel",
312
+ "agent_id": agent_session_id,
313
+ "agent_type": "camel_chat_agent",
314
+ "model_type": str(self.model_type),
315
+ },
316
+ tags=["CAMEL-AI", str(self.model_type)],
317
+ )
253
318
 
254
319
  # Ensure server is running
255
320
  self._ensure_server_running()
@@ -268,6 +333,13 @@ class SGLangModel(BaseModelBackend):
268
333
  model=self.model_type,
269
334
  **self.model_config_dict,
270
335
  )
336
+ update_current_observation(
337
+ usage_details={
338
+ "prompt_tokens": response.usage.prompt_tokens,
339
+ "completion_tokens": response.usage.completion_tokens,
340
+ "total_tokens": response.usage.total_tokens,
341
+ },
342
+ )
271
343
 
272
344
  return response
273
345
 
@@ -26,8 +26,19 @@ from camel.utils import (
26
26
  BaseTokenCounter,
27
27
  OpenAITokenCounter,
28
28
  api_keys_required,
29
+ get_current_agent_session_id,
30
+ update_current_observation,
31
+ update_langfuse_trace,
29
32
  )
30
33
 
34
+ if os.environ.get("LANGFUSE_ENABLED", "False").lower() == "true":
35
+ try:
36
+ from langfuse.decorators import observe
37
+ except ImportError:
38
+ from camel.utils import observe
39
+ else:
40
+ from camel.utils import observe
41
+
31
42
  logger = get_logger(__name__)
32
43
 
33
44
 
@@ -151,6 +162,7 @@ class WatsonXModel(BaseModelBackend):
151
162
 
152
163
  return request_config
153
164
 
165
+ @observe(as_type='generation')
154
166
  def _run(
155
167
  self,
156
168
  messages: List[OpenAIMessage],
@@ -170,6 +182,27 @@ class WatsonXModel(BaseModelBackend):
170
182
  Returns:
171
183
  ChatCompletion.
172
184
  """
185
+ update_current_observation(
186
+ input={
187
+ "messages": messages,
188
+ "tools": tools,
189
+ },
190
+ model=str(self.model_type),
191
+ model_parameters=self.model_config_dict,
192
+ )
193
+ # Update Langfuse trace with current agent session and metadata
194
+ agent_session_id = get_current_agent_session_id()
195
+ if agent_session_id:
196
+ update_langfuse_trace(
197
+ session_id=agent_session_id,
198
+ metadata={
199
+ "source": "camel",
200
+ "agent_id": agent_session_id,
201
+ "agent_type": "camel_chat_agent",
202
+ "model_type": str(self.model_type),
203
+ },
204
+ tags=["CAMEL-AI", str(self.model_type)],
205
+ )
173
206
  try:
174
207
  request_config = self._prepare_request(
175
208
  messages, response_format, tools
@@ -183,12 +216,16 @@ class WatsonXModel(BaseModelBackend):
183
216
  )
184
217
 
185
218
  openai_response = self._to_openai_response(response)
219
+ update_current_observation(
220
+ usage=openai_response.usage,
221
+ )
186
222
  return openai_response
187
223
 
188
224
  except Exception as e:
189
225
  logger.error(f"Unexpected error when calling WatsonX API: {e!s}")
190
226
  raise
191
227
 
228
+ @observe(as_type='generation')
192
229
  async def _arun(
193
230
  self,
194
231
  messages: List[OpenAIMessage],
@@ -208,6 +245,28 @@ class WatsonXModel(BaseModelBackend):
208
245
  Returns:
209
246
  ChatCompletion.
210
247
  """
248
+ update_current_observation(
249
+ input={
250
+ "messages": messages,
251
+ "tools": tools,
252
+ },
253
+ model=str(self.model_type),
254
+ model_parameters=self.model_config_dict,
255
+ )
256
+ # Update Langfuse trace with current agent session and metadata
257
+ agent_session_id = get_current_agent_session_id()
258
+ if agent_session_id:
259
+ update_langfuse_trace(
260
+ session_id=agent_session_id,
261
+ metadata={
262
+ "source": "camel",
263
+ "agent_id": agent_session_id,
264
+ "agent_type": "camel_chat_agent",
265
+ "model_type": str(self.model_type),
266
+ },
267
+ tags=["CAMEL-AI", str(self.model_type)],
268
+ )
269
+
211
270
  try:
212
271
  request_config = self._prepare_request(
213
272
  messages, response_format, tools
@@ -221,6 +280,9 @@ class WatsonXModel(BaseModelBackend):
221
280
  )
222
281
 
223
282
  openai_response = self._to_openai_response(response)
283
+ update_current_observation(
284
+ usage=openai_response.usage,
285
+ )
224
286
  return openai_response
225
287
 
226
288
  except Exception as e:
@@ -25,7 +25,7 @@ from camel.societies.workforce.prompts import (
25
25
  ROLEPLAY_PROCESS_TASK_PROMPT,
26
26
  ROLEPLAY_SUMMARIZE_PROMPT,
27
27
  )
28
- from camel.societies.workforce.utils import TaskResult
28
+ from camel.societies.workforce.utils import TaskResult, validate_task_content
29
29
  from camel.societies.workforce.worker import Worker
30
30
  from camel.tasks.task import Task, TaskState
31
31
  from camel.utils import print_text_animated
@@ -48,7 +48,7 @@ class RolePlayingWorker(Worker):
48
48
  initialize the summarize agent, like the model name, etc.
49
49
  (default: :obj:`None`)
50
50
  chat_turn_limit (int): The maximum number of chat turns in the role
51
- playing. (default: :obj:`3`)
51
+ playing. (default: :obj:`20`)
52
52
  """
53
53
 
54
54
  def __init__(
@@ -59,7 +59,7 @@ class RolePlayingWorker(Worker):
59
59
  assistant_agent_kwargs: Optional[Dict] = None,
60
60
  user_agent_kwargs: Optional[Dict] = None,
61
61
  summarize_agent_kwargs: Optional[Dict] = None,
62
- chat_turn_limit: int = 3,
62
+ chat_turn_limit: int = 20,
63
63
  ) -> None:
64
64
  super().__init__(description)
65
65
  self.summarize_agent_kwargs = summarize_agent_kwargs
@@ -182,6 +182,14 @@ class RolePlayingWorker(Worker):
182
182
  )
183
183
  result_dict = json.loads(response.msg.content)
184
184
  task_result = TaskResult(**result_dict)
185
+
186
+ if not validate_task_content(task_result.content, task.id):
187
+ print(
188
+ f"{Fore.RED}Task {task.id}: Content validation failed - "
189
+ f"task marked as failed{Fore.RESET}"
190
+ )
191
+ return TaskState.FAILED
192
+
185
193
  task.result = task_result.content
186
194
 
187
195
  print(f"Task result: {task.result}\n")
@@ -13,6 +13,7 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  from __future__ import annotations
15
15
 
16
+ import datetime
16
17
  import json
17
18
  from typing import Any, List
18
19
 
@@ -20,7 +21,7 @@ from colorama import Fore
20
21
 
21
22
  from camel.agents import ChatAgent
22
23
  from camel.societies.workforce.prompts import PROCESS_TASK_PROMPT
23
- from camel.societies.workforce.utils import TaskResult
24
+ from camel.societies.workforce.utils import TaskResult, validate_task_content
24
25
  from camel.societies.workforce.worker import Worker
25
26
  from camel.tasks.task import Task, TaskState
26
27
  from camel.utils import print_text_animated
@@ -83,6 +84,28 @@ class SingleAgentWorker(Worker):
83
84
  )
84
85
  return TaskState.FAILED
85
86
 
87
+ # Populate additional_info with worker attempt details
88
+ if task.additional_info is None:
89
+ task.additional_info = {}
90
+
91
+ # Create worker attempt details with descriptive keys
92
+ worker_attempt_details = {
93
+ "agent_id": getattr(
94
+ self.worker, "agent_id", self.worker.role_name
95
+ ),
96
+ "timestamp": str(datetime.datetime.now()),
97
+ "description": f"Attempt by "
98
+ f"{getattr(self.worker, 'agent_id', self.worker.role_name)} "
99
+ f"to process task {task.content}",
100
+ "response_content": response.msg.content,
101
+ "tool_calls": response.info["tool_calls"],
102
+ }
103
+
104
+ # Store the worker attempt in additional_info
105
+ if "worker_attempts" not in task.additional_info:
106
+ task.additional_info["worker_attempts"] = []
107
+ task.additional_info["worker_attempts"].append(worker_attempt_details)
108
+
86
109
  print(f"======\n{Fore.GREEN}Reply from {self}:{Fore.RESET}")
87
110
 
88
111
  result_dict = json.loads(response.msg.content)
@@ -97,5 +120,12 @@ class SingleAgentWorker(Worker):
97
120
  if task_result.failed:
98
121
  return TaskState.FAILED
99
122
 
123
+ if not validate_task_content(task_result.content, task.id):
124
+ print(
125
+ f"{Fore.RED}Task {task.id}: Content validation failed - "
126
+ f"task marked as failed{Fore.RESET}"
127
+ )
128
+ return TaskState.FAILED
129
+
100
130
  task.result = task_result.content
101
131
  return TaskState.DONE
@@ -16,6 +16,10 @@ from typing import Callable
16
16
 
17
17
  from pydantic import BaseModel, Field
18
18
 
19
+ from camel.logger import get_logger
20
+
21
+ logger = get_logger(__name__)
22
+
19
23
 
20
24
  class WorkerConf(BaseModel):
21
25
  r"""The configuration of a worker."""
@@ -71,3 +75,50 @@ def check_if_running(running: bool) -> Callable:
71
75
  return wrapper
72
76
 
73
77
  return decorator
78
+
79
+
80
+ def validate_task_content(
81
+ content: str, task_id: str = "unknown", min_length: int = 10
82
+ ) -> bool:
83
+ r"""Validates task result content to avoid silent failures.
84
+ It performs basic checks to ensure the content meets minimum
85
+ quality standards.
86
+
87
+ Args:
88
+ content (str): The task result content to validate.
89
+ task_id (str): Task ID for logging purposes.
90
+ (default: :obj:`"unknown"`)
91
+ min_length (int): Minimum content length after stripping whitespace.
92
+ (default: :obj:`10`)
93
+
94
+ Returns:
95
+ bool: True if content passes validation, False otherwise.
96
+ """
97
+ # 1: Content must not be None
98
+ if content is None:
99
+ logger.warning(f"Task {task_id}: None content rejected")
100
+ return False
101
+
102
+ # 2: Content must not be empty after stripping whitespace
103
+ stripped_content = content.strip()
104
+ if not stripped_content:
105
+ logger.warning(
106
+ f"Task {task_id}: Empty or whitespace-only content rejected."
107
+ )
108
+ return False
109
+
110
+ # 3: Content must meet minimum meaningful length
111
+ if len(stripped_content) < min_length:
112
+ logger.warning(
113
+ f"Task {task_id}: Content too short ({len(stripped_content)} "
114
+ f"chars < {min_length} minimum). Content preview: "
115
+ f"'{stripped_content[:50]}...'"
116
+ )
117
+ return False
118
+
119
+ # All validation checks passed
120
+ logger.debug(
121
+ f"Task {task_id}: Content validation passed "
122
+ f"({len(stripped_content)} chars)"
123
+ )
124
+ return True