camel-ai 0.2.75a6__py3-none-any.whl → 0.2.76__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 (97) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +1001 -205
  3. camel/agents/mcp_agent.py +30 -27
  4. camel/configs/__init__.py +6 -0
  5. camel/configs/amd_config.py +70 -0
  6. camel/configs/cometapi_config.py +104 -0
  7. camel/data_collectors/alpaca_collector.py +15 -6
  8. camel/environments/tic_tac_toe.py +1 -1
  9. camel/interpreters/__init__.py +2 -0
  10. camel/interpreters/docker/Dockerfile +3 -12
  11. camel/interpreters/microsandbox_interpreter.py +395 -0
  12. camel/loaders/__init__.py +11 -2
  13. camel/loaders/chunkr_reader.py +9 -0
  14. camel/memories/__init__.py +2 -1
  15. camel/memories/agent_memories.py +3 -1
  16. camel/memories/blocks/chat_history_block.py +21 -3
  17. camel/memories/records.py +88 -8
  18. camel/messages/base.py +127 -34
  19. camel/models/__init__.py +4 -0
  20. camel/models/amd_model.py +101 -0
  21. camel/models/azure_openai_model.py +0 -6
  22. camel/models/base_model.py +30 -0
  23. camel/models/cometapi_model.py +83 -0
  24. camel/models/model_factory.py +4 -0
  25. camel/models/openai_compatible_model.py +0 -6
  26. camel/models/openai_model.py +0 -6
  27. camel/models/zhipuai_model.py +61 -2
  28. camel/parsers/__init__.py +18 -0
  29. camel/parsers/mcp_tool_call_parser.py +176 -0
  30. camel/retrievers/auto_retriever.py +1 -0
  31. camel/runtimes/daytona_runtime.py +11 -12
  32. camel/societies/workforce/prompts.py +131 -50
  33. camel/societies/workforce/single_agent_worker.py +434 -49
  34. camel/societies/workforce/structured_output_handler.py +30 -18
  35. camel/societies/workforce/task_channel.py +43 -0
  36. camel/societies/workforce/utils.py +105 -12
  37. camel/societies/workforce/workforce.py +1322 -311
  38. camel/societies/workforce/workforce_logger.py +24 -5
  39. camel/storages/key_value_storages/json.py +15 -2
  40. camel/storages/object_storages/google_cloud.py +1 -1
  41. camel/storages/vectordb_storages/oceanbase.py +10 -11
  42. camel/storages/vectordb_storages/tidb.py +8 -6
  43. camel/tasks/task.py +4 -3
  44. camel/toolkits/__init__.py +18 -5
  45. camel/toolkits/aci_toolkit.py +45 -0
  46. camel/toolkits/code_execution.py +28 -1
  47. camel/toolkits/context_summarizer_toolkit.py +684 -0
  48. camel/toolkits/dingtalk.py +1135 -0
  49. camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
  50. camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +194 -34
  51. camel/toolkits/function_tool.py +6 -1
  52. camel/toolkits/google_drive_mcp_toolkit.py +12 -31
  53. camel/toolkits/hybrid_browser_toolkit/config_loader.py +12 -0
  54. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +79 -2
  55. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +95 -59
  56. camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
  57. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +5 -612
  58. camel/toolkits/hybrid_browser_toolkit/ts/package.json +0 -1
  59. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +619 -95
  60. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +7 -2
  61. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +115 -219
  62. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  63. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
  64. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  65. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +1 -0
  66. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +39 -6
  67. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +405 -131
  68. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +9 -5
  69. camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +98 -31
  70. camel/toolkits/markitdown_toolkit.py +27 -1
  71. camel/toolkits/mcp_toolkit.py +348 -348
  72. camel/toolkits/message_integration.py +3 -0
  73. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  74. camel/toolkits/note_taking_toolkit.py +18 -8
  75. camel/toolkits/notion_mcp_toolkit.py +16 -26
  76. camel/toolkits/origene_mcp_toolkit.py +8 -49
  77. camel/toolkits/playwright_mcp_toolkit.py +12 -31
  78. camel/toolkits/resend_toolkit.py +168 -0
  79. camel/toolkits/slack_toolkit.py +50 -1
  80. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  81. camel/toolkits/terminal_toolkit/terminal_toolkit.py +924 -0
  82. camel/toolkits/terminal_toolkit/utils.py +532 -0
  83. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  84. camel/toolkits/video_analysis_toolkit.py +17 -11
  85. camel/toolkits/wechat_official_toolkit.py +483 -0
  86. camel/types/enums.py +124 -1
  87. camel/types/unified_model_type.py +5 -0
  88. camel/utils/commons.py +17 -0
  89. camel/utils/context_utils.py +804 -0
  90. camel/utils/mcp.py +136 -2
  91. camel/utils/token_counting.py +25 -17
  92. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/METADATA +158 -59
  93. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/RECORD +95 -76
  94. camel/loaders/pandas_reader.py +0 -368
  95. camel/toolkits/terminal_toolkit.py +0 -1788
  96. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/WHEEL +0 -0
  97. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/licenses/LICENSE +0 -0
@@ -19,8 +19,8 @@ from pydantic import BaseModel, ValidationError
19
19
 
20
20
  from camel.logger import get_logger
21
21
  from camel.societies.workforce.utils import (
22
- RecoveryDecision,
23
22
  RecoveryStrategy,
23
+ TaskAnalysisResult,
24
24
  TaskAssignResult,
25
25
  WorkerConf,
26
26
  )
@@ -65,9 +65,9 @@ class StructuredOutputHandler:
65
65
  r'description.*?:\s*"([^"]+)"'
66
66
  ),
67
67
  ],
68
- 'RecoveryDecision': [
69
- r'"strategy"\s*:\s*"([^"]+)".*?"reasoning"\s*:\s*"([^"]+)"',
70
- r'strategy.*?:\s*"([^"]+)".*?reasoning.*?:\s*"([^"]+)"',
68
+ 'TaskAnalysisResult': [
69
+ r'"recovery_strategy"\s*:\s*"([^"]+)".*?"reasoning"\s*:\s*"([^"]+)"',
70
+ r'recovery_strategy.*?:\s*"([^"]+)".*?reasoning.*?:\s*"([^"]+)"',
71
71
  ],
72
72
  }
73
73
 
@@ -239,12 +239,12 @@ Ensure the JSON is valid and properly formatted.
239
239
  except (IndexError, AttributeError):
240
240
  continue
241
241
 
242
- elif schema_name == 'RecoveryDecision':
242
+ elif schema_name == 'TaskAnalysisResult':
243
243
  for pattern in patterns:
244
244
  match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
245
245
  if match:
246
246
  try:
247
- strategy = match.group(1)
247
+ recovery_strategy = match.group(1)
248
248
  reasoning = match.group(2)
249
249
  # Look for modified_task_content
250
250
  content_match = re.search(
@@ -252,14 +252,25 @@ Ensure the JSON is valid and properly formatted.
252
252
  text,
253
253
  re.IGNORECASE,
254
254
  )
255
+ # Look for quality_score (for quality evaluation)
256
+ score_match = re.search(
257
+ r'"quality_score"\s*:\s*(\d+)',
258
+ text,
259
+ re.IGNORECASE,
260
+ )
255
261
  return {
256
- 'strategy': strategy,
262
+ 'recovery_strategy': recovery_strategy,
257
263
  'reasoning': reasoning,
258
264
  'modified_task_content': (
259
265
  content_match.group(1)
260
266
  if content_match
261
267
  else None
262
268
  ),
269
+ 'quality_score': (
270
+ int(score_match.group(1))
271
+ if score_match
272
+ else None
273
+ ),
263
274
  }
264
275
  except (IndexError, AttributeError):
265
276
  continue
@@ -370,21 +381,22 @@ Ensure the JSON is valid and properly formatted.
370
381
  else:
371
382
  assignment['dependencies'] = []
372
383
 
373
- elif schema_name == 'RecoveryDecision':
374
- # Ensure strategy is valid
375
- if 'strategy' in fixed_data:
376
- strategy = fixed_data['strategy'].lower()
384
+ elif schema_name == 'TaskAnalysisResult':
385
+ # Ensure recovery_strategy is valid
386
+ if 'recovery_strategy' in fixed_data:
387
+ strategy = fixed_data['recovery_strategy'].lower()
377
388
  valid_strategies = [
378
389
  'retry',
379
390
  'replan',
380
391
  'decompose',
381
392
  'create_worker',
393
+ 'reassign',
382
394
  ]
383
395
  if strategy not in valid_strategies:
384
396
  # Try to match partial
385
397
  for valid in valid_strategies:
386
398
  if valid.startswith(strategy) or strategy in valid:
387
- fixed_data['strategy'] = valid
399
+ fixed_data['recovery_strategy'] = valid
388
400
  break
389
401
 
390
402
  return fixed_data
@@ -410,10 +422,10 @@ Ensure the JSON is valid and properly formatted.
410
422
  sys_msg="You are a helpful assistant.",
411
423
  description="A general-purpose worker",
412
424
  )
413
- elif schema_name == 'RecoveryDecision':
414
- return RecoveryDecision(
415
- strategy=RecoveryStrategy.RETRY,
425
+ elif schema_name == 'TaskAnalysisResult':
426
+ return TaskAnalysisResult(
416
427
  reasoning="Unable to parse response, defaulting to retry",
428
+ recovery_strategy=RecoveryStrategy.RETRY,
417
429
  modified_task_content=None,
418
430
  )
419
431
  else:
@@ -482,11 +494,11 @@ Ensure the JSON is valid and properly formatted.
482
494
  description=f"Fallback worker for task: {task_content}...",
483
495
  )
484
496
 
485
- elif schema_name == 'RecoveryDecision':
497
+ elif schema_name == 'TaskAnalysisResult':
486
498
  # Default to retry strategy
487
- return RecoveryDecision(
488
- strategy=RecoveryStrategy.RETRY,
499
+ return TaskAnalysisResult(
489
500
  reasoning=f"Fallback decision due to: {error_message}",
501
+ recovery_strategy=RecoveryStrategy.RETRY,
490
502
  modified_task_content=None,
491
503
  )
492
504
 
@@ -16,8 +16,11 @@ from collections import defaultdict, deque
16
16
  from enum import Enum
17
17
  from typing import Dict, List, Optional, Set
18
18
 
19
+ from camel.logger import get_logger
19
20
  from camel.tasks import Task
20
21
 
22
+ logger = get_logger(__name__)
23
+
21
24
 
22
25
  class PacketStatus(Enum):
23
26
  r"""The status of a packet. The packet can be in one of the following
@@ -269,6 +272,46 @@ class TaskChannel:
269
272
  async with self._condition:
270
273
  return list(self._task_by_status[PacketStatus.ARCHIVED])
271
274
 
275
+ async def get_in_flight_tasks(self, publisher_id: str) -> List[Task]:
276
+ r"""Get all tasks that are currently in-flight (SENT, RETURNED
277
+ or PROCESSING) published by the given publisher.
278
+
279
+ Args:
280
+ publisher_id (str): The ID of the publisher whose
281
+ in-flight tasks to retrieve.
282
+
283
+ Returns:
284
+ List[Task]: List of tasks that are currently in-flight.
285
+ """
286
+ async with self._condition:
287
+ in_flight_tasks = []
288
+ seen_task_ids = set() # Track seen IDs for duplicate detection
289
+
290
+ # Get tasks with SENT, RETURNED or PROCESSING
291
+ # status published by this publisher
292
+ for status in [
293
+ PacketStatus.SENT,
294
+ PacketStatus.PROCESSING,
295
+ PacketStatus.RETURNED,
296
+ ]:
297
+ for task_id in self._task_by_status[status]:
298
+ if task_id in self._task_dict:
299
+ packet = self._task_dict[task_id]
300
+ if packet.publisher_id == publisher_id:
301
+ # Defensive check: detect if task appears in
302
+ # multiple status sets (should never happen)
303
+ if task_id in seen_task_ids:
304
+ logger.warning(
305
+ f"Task {task_id} found in multiple "
306
+ f"status sets. This indicates a bug in "
307
+ f"status management."
308
+ )
309
+ continue
310
+ in_flight_tasks.append(packet.task)
311
+ seen_task_ids.add(task_id)
312
+
313
+ return in_flight_tasks
314
+
272
315
  async def get_task_by_id(self, task_id: str) -> Task:
273
316
  r"""Get a task from the channel by its ID."""
274
317
  async with self._condition:
@@ -42,6 +42,36 @@ class TaskResult(BaseModel):
42
42
  )
43
43
 
44
44
 
45
+ class QualityEvaluation(BaseModel):
46
+ r"""Quality evaluation result for a completed task.
47
+
48
+ .. deprecated::
49
+ Use :class:`TaskAnalysisResult` instead. This class is kept for
50
+ backward compatibility.
51
+ """
52
+
53
+ quality_sufficient: bool = Field(
54
+ description="Whether the task result meets quality standards."
55
+ )
56
+ quality_score: int = Field(
57
+ description="Quality score from 0 to 100.", ge=0, le=100
58
+ )
59
+ issues: List[str] = Field(
60
+ default_factory=list,
61
+ description="List of quality issues found in the result.",
62
+ )
63
+ recovery_strategy: Optional[str] = Field(
64
+ default=None,
65
+ description="Recommended recovery strategy if quality is "
66
+ "insufficient: "
67
+ "'retry', 'reassign', 'replan', or 'decompose'.",
68
+ )
69
+ modified_task_content: Optional[str] = Field(
70
+ default=None,
71
+ description="Modified task content for replan strategy.",
72
+ )
73
+
74
+
45
75
  class TaskAssignment(BaseModel):
46
76
  r"""An individual task assignment within a batch."""
47
77
 
@@ -52,7 +82,8 @@ class TaskAssignment(BaseModel):
52
82
  dependencies: List[str] = Field(
53
83
  default_factory=list,
54
84
  description="List of task IDs that must complete before this task. "
55
- "This is critical for the task decomposition and execution.",
85
+ "This is critical for the task decomposition and "
86
+ "execution.",
56
87
  )
57
88
 
58
89
  # Allow LLMs to output dependencies as a comma-separated string or empty
@@ -60,7 +91,8 @@ class TaskAssignment(BaseModel):
60
91
  # downstream logic does not break with validation errors.
61
92
  @staticmethod
62
93
  def _split_and_strip(dep_str: str) -> List[str]:
63
- r"""Utility to split a comma separated string and strip whitespace."""
94
+ r"""Utility to split a comma separated string and strip
95
+ whitespace."""
64
96
  return [d.strip() for d in dep_str.split(',') if d.strip()]
65
97
 
66
98
  @field_validator("dependencies", mode="before")
@@ -74,7 +106,8 @@ class TaskAssignment(BaseModel):
74
106
 
75
107
 
76
108
  class TaskAssignResult(BaseModel):
77
- r"""The result of task assignment for both single and batch assignments."""
109
+ r"""The result of task assignment for both single and batch
110
+ assignments."""
78
111
 
79
112
  assignments: List[TaskAssignment] = Field(
80
113
  description="List of task assignments."
@@ -88,6 +121,7 @@ class RecoveryStrategy(str, Enum):
88
121
  REPLAN = "replan"
89
122
  DECOMPOSE = "decompose"
90
123
  CREATE_WORKER = "create_worker"
124
+ REASSIGN = "reassign"
91
125
 
92
126
  def __str__(self):
93
127
  return self.value
@@ -116,17 +150,75 @@ class FailureContext(BaseModel):
116
150
  )
117
151
 
118
152
 
119
- class RecoveryDecision(BaseModel):
120
- r"""Decision on how to recover from a task failure."""
153
+ class TaskAnalysisResult(BaseModel):
154
+ r"""Unified result for task failure analysis and quality evaluation.
155
+
156
+ This model combines both failure recovery decisions and quality evaluation
157
+ results into a single structure. For failure analysis, only the recovery
158
+ strategy and reasoning fields are populated. For quality evaluation, all
159
+ fields including quality_score and issues are populated.
160
+ """
161
+
162
+ # Common fields - always populated
163
+ reasoning: str = Field(
164
+ description="Explanation for the analysis result or recovery "
165
+ "decision"
166
+ )
121
167
 
122
- strategy: RecoveryStrategy = Field(
123
- description="The chosen recovery strategy"
168
+ recovery_strategy: Optional[RecoveryStrategy] = Field(
169
+ default=None,
170
+ description="Recommended recovery strategy: 'retry', 'replan', "
171
+ "'decompose', 'create_worker', or 'reassign'. None indicates no "
172
+ "recovery needed (quality sufficient).",
124
173
  )
125
- reasoning: str = Field(description="Explanation for the chosen strategy")
174
+
126
175
  modified_task_content: Optional[str] = Field(
127
- default=None, description="Modified task content if strategy is REPLAN"
176
+ default=None,
177
+ description="Modified task content if strategy requires replan",
128
178
  )
129
179
 
180
+ # Quality-specific fields - populated only for quality evaluation
181
+ quality_score: Optional[int] = Field(
182
+ default=None,
183
+ description="Quality score from 0 to 100 (only for quality "
184
+ "evaluation). "
185
+ "None indicates this is a failure analysis, "
186
+ "not quality evaluation.",
187
+ ge=0,
188
+ le=100,
189
+ )
190
+
191
+ issues: List[str] = Field(
192
+ default_factory=list,
193
+ description="List of issues found. For failures: error details. "
194
+ "For quality evaluation: quality issues.",
195
+ )
196
+
197
+ @property
198
+ def is_quality_evaluation(self) -> bool:
199
+ r"""Check if this is a quality evaluation result.
200
+
201
+ Returns:
202
+ bool: True if this is a quality evaluation (has quality_score),
203
+ False if this is a failure analysis.
204
+ """
205
+ return self.quality_score is not None
206
+
207
+ @property
208
+ def quality_sufficient(self) -> bool:
209
+ r"""For quality evaluations, check if quality meets standards.
210
+
211
+ Returns:
212
+ bool: True if quality is sufficient (score >= 70 and no recovery
213
+ strategy recommended), False otherwise. Always False for
214
+ failure analysis results.
215
+ """
216
+ return (
217
+ self.quality_score is not None
218
+ and self.quality_score >= 70
219
+ and self.recovery_strategy is None
220
+ )
221
+
130
222
 
131
223
  def check_if_running(
132
224
  running: bool,
@@ -178,7 +270,7 @@ def check_if_running(
178
270
  if retries < max_retries:
179
271
  logger.warning(
180
272
  f"{error_msg} Retrying in {retry_delay}s... "
181
- f"(Attempt {retries+1}/{max_retries})"
273
+ f"(Attempt {retries + 1}/{max_retries})"
182
274
  )
183
275
  time.sleep(retry_delay)
184
276
  retries += 1
@@ -200,7 +292,7 @@ def check_if_running(
200
292
  logger.warning(
201
293
  f"Exception in {func.__name__}: {e}. "
202
294
  f"Retrying in {retry_delay}s... "
203
- f"(Attempt {retries+1}/{max_retries})"
295
+ f"(Attempt {retries + 1}/{max_retries})"
204
296
  )
205
297
  time.sleep(retry_delay)
206
298
  retries += 1
@@ -218,7 +310,8 @@ def check_if_running(
218
310
  # This should not be reached, but just in case
219
311
  if handle_exceptions:
220
312
  logger.error(
221
- f"Unexpected failure in {func.__name__}: {last_exception}"
313
+ f"Unexpected failure in {func.__name__}: "
314
+ f"{last_exception}"
222
315
  )
223
316
  return None
224
317
  else: