camel-ai 0.2.65__py3-none-any.whl → 0.2.67__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.
Files changed (65) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/mcp_agent.py +1 -5
  3. camel/configs/__init__.py +3 -0
  4. camel/configs/qianfan_config.py +85 -0
  5. camel/models/__init__.py +2 -0
  6. camel/models/aiml_model.py +8 -0
  7. camel/models/anthropic_model.py +8 -0
  8. camel/models/aws_bedrock_model.py +8 -0
  9. camel/models/azure_openai_model.py +14 -5
  10. camel/models/base_model.py +4 -0
  11. camel/models/cohere_model.py +9 -2
  12. camel/models/crynux_model.py +8 -0
  13. camel/models/deepseek_model.py +8 -0
  14. camel/models/gemini_model.py +8 -0
  15. camel/models/groq_model.py +8 -0
  16. camel/models/internlm_model.py +8 -0
  17. camel/models/litellm_model.py +5 -0
  18. camel/models/lmstudio_model.py +14 -1
  19. camel/models/mistral_model.py +15 -1
  20. camel/models/model_factory.py +6 -0
  21. camel/models/modelscope_model.py +8 -0
  22. camel/models/moonshot_model.py +8 -0
  23. camel/models/nemotron_model.py +17 -2
  24. camel/models/netmind_model.py +8 -0
  25. camel/models/novita_model.py +8 -0
  26. camel/models/nvidia_model.py +8 -0
  27. camel/models/ollama_model.py +8 -0
  28. camel/models/openai_compatible_model.py +23 -5
  29. camel/models/openai_model.py +21 -4
  30. camel/models/openrouter_model.py +8 -0
  31. camel/models/ppio_model.py +8 -0
  32. camel/models/qianfan_model.py +104 -0
  33. camel/models/qwen_model.py +8 -0
  34. camel/models/reka_model.py +18 -3
  35. camel/models/samba_model.py +17 -3
  36. camel/models/sglang_model.py +20 -5
  37. camel/models/siliconflow_model.py +8 -0
  38. camel/models/stub_model.py +8 -1
  39. camel/models/togetherai_model.py +8 -0
  40. camel/models/vllm_model.py +7 -0
  41. camel/models/volcano_model.py +14 -1
  42. camel/models/watsonx_model.py +4 -1
  43. camel/models/yi_model.py +8 -0
  44. camel/models/zhipuai_model.py +8 -0
  45. camel/societies/workforce/prompts.py +33 -17
  46. camel/societies/workforce/role_playing_worker.py +5 -10
  47. camel/societies/workforce/single_agent_worker.py +3 -5
  48. camel/societies/workforce/task_channel.py +16 -18
  49. camel/societies/workforce/utils.py +104 -65
  50. camel/societies/workforce/workforce.py +1263 -100
  51. camel/societies/workforce/workforce_logger.py +613 -0
  52. camel/tasks/task.py +77 -6
  53. camel/toolkits/__init__.py +2 -0
  54. camel/toolkits/code_execution.py +1 -1
  55. camel/toolkits/function_tool.py +79 -7
  56. camel/toolkits/mcp_toolkit.py +70 -19
  57. camel/toolkits/playwright_mcp_toolkit.py +2 -1
  58. camel/toolkits/pptx_toolkit.py +4 -4
  59. camel/types/enums.py +32 -0
  60. camel/types/unified_model_type.py +5 -0
  61. camel/utils/mcp_client.py +1 -35
  62. {camel_ai-0.2.65.dist-info → camel_ai-0.2.67.dist-info}/METADATA +3 -3
  63. {camel_ai-0.2.65.dist-info → camel_ai-0.2.67.dist-info}/RECORD +65 -62
  64. {camel_ai-0.2.65.dist-info → camel_ai-0.2.67.dist-info}/WHEEL +0 -0
  65. {camel_ai-0.2.65.dist-info → camel_ai-0.2.67.dist-info}/licenses/LICENSE +0 -0
camel/tasks/task.py CHANGED
@@ -14,20 +14,81 @@
14
14
 
15
15
  import re
16
16
  from enum import Enum
17
- from typing import Any, Callable, Dict, List, Literal, Optional, Union
17
+ from typing import (
18
+ TYPE_CHECKING,
19
+ Any,
20
+ Callable,
21
+ Dict,
22
+ List,
23
+ Literal,
24
+ Optional,
25
+ Union,
26
+ )
18
27
 
19
28
  from pydantic import BaseModel
20
29
 
21
- from camel.agents import ChatAgent
30
+ if TYPE_CHECKING:
31
+ from camel.agents import ChatAgent
32
+ from camel.logger import get_logger
22
33
  from camel.messages import BaseMessage
23
34
  from camel.prompts import TextPrompt
24
35
 
36
+ # Note: validate_task_content moved here to avoid circular imports
25
37
  from .task_prompt import (
26
38
  TASK_COMPOSE_PROMPT,
27
39
  TASK_DECOMPOSE_PROMPT,
28
40
  TASK_EVOLVE_PROMPT,
29
41
  )
30
42
 
43
+ logger = get_logger(__name__)
44
+
45
+
46
+ def validate_task_content(
47
+ content: str, task_id: str = "unknown", min_length: int = 10
48
+ ) -> bool:
49
+ r"""Validates task result content to avoid silent failures.
50
+ It performs basic checks to ensure the content meets minimum
51
+ quality standards.
52
+
53
+ Args:
54
+ content (str): The task result content to validate.
55
+ task_id (str): Task ID for logging purposes.
56
+ (default: :obj:`"unknown"`)
57
+ min_length (int): Minimum content length after stripping whitespace.
58
+ (default: :obj:`10`)
59
+
60
+ Returns:
61
+ bool: True if content passes validation, False otherwise.
62
+ """
63
+ # 1: Content must not be None
64
+ if content is None:
65
+ logger.warning(f"Task {task_id}: None content rejected")
66
+ return False
67
+
68
+ # 2: Content must not be empty after stripping whitespace
69
+ stripped_content = content.strip()
70
+ if not stripped_content:
71
+ logger.warning(
72
+ f"Task {task_id}: Empty or whitespace-only content rejected."
73
+ )
74
+ return False
75
+
76
+ # 3: Content must meet minimum meaningful length
77
+ if len(stripped_content) < min_length:
78
+ logger.warning(
79
+ f"Task {task_id}: Content too short ({len(stripped_content)} "
80
+ f"chars < {min_length} minimum). Content preview: "
81
+ f"'{stripped_content[:50]}...'"
82
+ )
83
+ return False
84
+
85
+ # All validation checks passed
86
+ logger.debug(
87
+ f"Task {task_id}: Content validation passed "
88
+ f"({len(stripped_content)} chars)"
89
+ )
90
+ return True
91
+
31
92
 
32
93
  def parse_response(
33
94
  response: str, task_id: Optional[str] = None
@@ -49,7 +110,16 @@ def parse_response(
49
110
  if task_id is None:
50
111
  task_id = "0"
51
112
  for i, content in enumerate(tasks_content):
52
- tasks.append(Task(content=content.strip(), id=f"{task_id}.{i}"))
113
+ stripped_content = content.strip()
114
+ # validate subtask content before creating the task
115
+ if validate_task_content(stripped_content, f"{task_id}.{i}"):
116
+ tasks.append(Task(content=stripped_content, id=f"{task_id}.{i}"))
117
+ else:
118
+ logger.warning(
119
+ f"Skipping invalid subtask {task_id}.{i} "
120
+ f"during decomposition: "
121
+ f"Content '{stripped_content[:50]}...' failed validation"
122
+ )
53
123
  return tasks
54
124
 
55
125
 
@@ -228,7 +298,7 @@ class Task(BaseModel):
228
298
 
229
299
  def decompose(
230
300
  self,
231
- agent: ChatAgent,
301
+ agent: "ChatAgent",
232
302
  prompt: Optional[str] = None,
233
303
  task_parser: Callable[[str, str], List["Task"]] = parse_response,
234
304
  ) -> List["Task"]:
@@ -263,7 +333,7 @@ class Task(BaseModel):
263
333
 
264
334
  def compose(
265
335
  self,
266
- agent: ChatAgent,
336
+ agent: "ChatAgent",
267
337
  template: TextPrompt = TASK_COMPOSE_PROMPT,
268
338
  result_parser: Optional[Callable[[str], str]] = None,
269
339
  ):
@@ -412,12 +482,13 @@ class TaskManager:
412
482
  def evolve(
413
483
  self,
414
484
  task: Task,
415
- agent: ChatAgent,
485
+ agent: "ChatAgent",
416
486
  template: Optional[TextPrompt] = None,
417
487
  task_parser: Optional[Callable[[str, str], List[Task]]] = None,
418
488
  ) -> Optional[Task]:
419
489
  r"""Evolve a task to a new task.
420
490
  Evolve is only used for data generation.
491
+
421
492
  Args:
422
493
  task (Task): A given task.
423
494
  agent (ChatAgent): An agent that used to evolve the task.
@@ -76,6 +76,7 @@ from .klavis_toolkit import KlavisToolkit
76
76
  from .aci_toolkit import ACIToolkit
77
77
  from .playwright_mcp_toolkit import PlaywrightMCPToolkit
78
78
  from .wolfram_alpha_toolkit import WolframAlphaToolkit
79
+ from .task_planning_toolkit import TaskPlanningToolkit
79
80
 
80
81
 
81
82
  __all__ = [
@@ -140,4 +141,5 @@ __all__ = [
140
141
  'PlaywrightMCPToolkit',
141
142
  'WolframAlphaToolkit',
142
143
  'BohriumToolkit',
144
+ 'TaskPlanningToolkit',
143
145
  ]
@@ -122,7 +122,7 @@ class CodeExecutionToolkit(BaseToolkit):
122
122
 
123
123
  def execute_command(self, command: str) -> Union[str, tuple[str, str]]:
124
124
  r"""Execute a command can be used to resolve the dependency of the
125
- code.
125
+ code. Useful if there's dependency issues when you try to execute code.
126
126
 
127
127
  Args:
128
128
  command (str): The command to execute.
@@ -190,7 +190,9 @@ def sanitize_and_enforce_required(parameters_dict):
190
190
  r"""Cleans and updates the function schema to conform with OpenAI's
191
191
  requirements:
192
192
  - Removes invalid 'default' fields from the parameters schema.
193
- - Ensures all fields or function parameters are marked as required.
193
+ - Ensures all fields are marked as required or have null type for optional
194
+ fields.
195
+ - Recursively adds additionalProperties: false to all nested objects.
194
196
 
195
197
  Args:
196
198
  parameters_dict (dict): The dictionary representing the function
@@ -198,8 +200,38 @@ def sanitize_and_enforce_required(parameters_dict):
198
200
 
199
201
  Returns:
200
202
  dict: The updated dictionary with invalid defaults removed and all
201
- fields set as required.
203
+ fields properly configured for strict mode.
202
204
  """
205
+
206
+ def _add_additional_properties_false(obj):
207
+ r"""Recursively add additionalProperties: false to all objects."""
208
+ if isinstance(obj, dict):
209
+ if (
210
+ obj.get("type") == "object"
211
+ and "additionalProperties" not in obj
212
+ ):
213
+ obj["additionalProperties"] = False
214
+
215
+ # Process nested structures
216
+ for key, value in obj.items():
217
+ if key == "properties" and isinstance(value, dict):
218
+ for prop_value in value.values():
219
+ _add_additional_properties_false(prop_value)
220
+ elif key in [
221
+ "items",
222
+ "allOf",
223
+ "oneOf",
224
+ "anyOf",
225
+ ] and isinstance(value, (dict, list)):
226
+ if isinstance(value, dict):
227
+ _add_additional_properties_false(value)
228
+ elif isinstance(value, list):
229
+ for item in value:
230
+ _add_additional_properties_false(item)
231
+ elif key == "$defs" and isinstance(value, dict):
232
+ for def_value in value.values():
233
+ _add_additional_properties_false(def_value)
234
+
203
235
  # Check if 'function' and 'parameters' exist
204
236
  if (
205
237
  'function' in parameters_dict
@@ -209,12 +241,52 @@ def sanitize_and_enforce_required(parameters_dict):
209
241
  parameters = parameters_dict['function']['parameters']
210
242
  properties = parameters.get('properties', {})
211
243
 
212
- # Remove 'default' key from each property
213
- for field in properties.values():
214
- field.pop('default', None)
244
+ # Track which fields should be required vs optional
245
+ required_fields = []
246
+
247
+ # Process each property
248
+ for field_name, field_schema in properties.items():
249
+ # Check if this field had a default value (making it optional)
250
+ had_default = 'default' in field_schema
251
+
252
+ # Remove 'default' key from field schema as required by OpenAI
253
+ field_schema.pop('default', None)
254
+
255
+ if had_default:
256
+ # This field is optional - add null to its type
257
+ current_type = field_schema.get('type')
258
+ has_ref = '$ref' in field_schema
259
+
260
+ if has_ref:
261
+ # Fields with $ref shouldn't have additional type field
262
+ # The $ref itself defines the type structure
263
+ pass
264
+ elif current_type:
265
+ if isinstance(current_type, str):
266
+ # Single type - convert to array with null
267
+ field_schema['type'] = [current_type, 'null']
268
+ elif (
269
+ isinstance(current_type, list)
270
+ and 'null' not in current_type
271
+ ):
272
+ # Array of types - add null if not present
273
+ field_schema['type'] = [*current_type, 'null']
274
+ else:
275
+ # No type specified, add null type
276
+ field_schema['type'] = ['null']
277
+
278
+ # Optional fields are still marked as required in strict mode
279
+ # but with null type to indicate they can be omitted
280
+ required_fields.append(field_name)
281
+ else:
282
+ # This field is required
283
+ required_fields.append(field_name)
284
+
285
+ # Set all fields as required (strict mode requirement)
286
+ parameters['required'] = required_fields
215
287
 
216
- # Mark all keys in 'properties' as required
217
- parameters['required'] = list(properties.keys())
288
+ # Recursively add additionalProperties: false to all objects
289
+ _add_additional_properties_false(parameters)
218
290
 
219
291
  return parameters_dict
220
292
 
@@ -98,8 +98,6 @@ class MCPToolkit(BaseToolkit):
98
98
  timeout (Optional[float], optional): Timeout for connection attempts
99
99
  in seconds. This timeout applies to individual client connections.
100
100
  (default: :obj:`None`)
101
- strict (Optional[bool], optional): Flag to indicate strict mode.
102
- (default: :obj:`False`)
103
101
 
104
102
  Note:
105
103
  At least one of :obj:`clients`, :obj:`config_path`, or
@@ -148,7 +146,6 @@ class MCPToolkit(BaseToolkit):
148
146
  config_path: Optional[str] = None,
149
147
  config_dict: Optional[Dict[str, Any]] = None,
150
148
  timeout: Optional[float] = None,
151
- strict: Optional[bool] = False,
152
149
  ):
153
150
  # Call parent constructor first
154
151
  super().__init__(timeout=timeout)
@@ -165,7 +162,6 @@ class MCPToolkit(BaseToolkit):
165
162
  raise ValueError(error_msg)
166
163
 
167
164
  self.clients: List[MCPClient] = clients or []
168
- self.strict = strict # Store strict parameter
169
165
  self._is_connected = False
170
166
  self._exit_stack: Optional[AsyncExitStack] = None
171
167
 
@@ -313,7 +309,6 @@ class MCPToolkit(BaseToolkit):
313
309
  config_path: Optional[str] = None,
314
310
  config_dict: Optional[Dict[str, Any]] = None,
315
311
  timeout: Optional[float] = None,
316
- strict: Optional[bool] = False,
317
312
  ) -> "MCPToolkit":
318
313
  r"""Factory method that creates and connects to all MCP servers.
319
314
 
@@ -331,8 +326,6 @@ class MCPToolkit(BaseToolkit):
331
326
  config file. (default: :obj:`None`)
332
327
  timeout (Optional[float], optional): Timeout for connection
333
328
  attempts in seconds. (default: :obj:`None`)
334
- strict (Optional[bool], optional): Flag to indicate strict mode.
335
- (default: :obj:`False`)
336
329
 
337
330
  Returns:
338
331
  MCPToolkit: A fully initialized and connected :obj:`MCPToolkit`
@@ -361,7 +354,6 @@ class MCPToolkit(BaseToolkit):
361
354
  config_path=config_path,
362
355
  config_dict=config_dict,
363
356
  timeout=timeout,
364
- strict=strict,
365
357
  )
366
358
  try:
367
359
  await toolkit.connect()
@@ -381,11 +373,10 @@ class MCPToolkit(BaseToolkit):
381
373
  config_path: Optional[str] = None,
382
374
  config_dict: Optional[Dict[str, Any]] = None,
383
375
  timeout: Optional[float] = None,
384
- strict: Optional[bool] = False,
385
376
  ) -> "MCPToolkit":
386
377
  r"""Synchronously create and connect to all MCP servers."""
387
378
  return run_async(cls.create)(
388
- clients, config_path, config_dict, timeout, strict
379
+ clients, config_path, config_dict, timeout
389
380
  )
390
381
 
391
382
  def _load_clients_from_config(self, config_path: str) -> List[MCPClient]:
@@ -442,12 +433,10 @@ class MCPToolkit(BaseToolkit):
442
433
 
443
434
  try:
444
435
  # Use the new mcp_client factory function
445
- # Pass timeout and strict from toolkit if available
436
+ # Pass timeout from toolkit if available
446
437
  kwargs = {}
447
438
  if hasattr(self, "timeout") and self.timeout is not None:
448
439
  kwargs["timeout"] = self.timeout
449
- if hasattr(self, "strict") and self.strict is not None:
450
- kwargs["strict"] = self.strict
451
440
 
452
441
  client = create_mcp_client(cfg, **kwargs)
453
442
  return client
@@ -455,17 +444,68 @@ class MCPToolkit(BaseToolkit):
455
444
  error_msg = f"Failed to create client for server '{name}': {e}"
456
445
  raise ValueError(error_msg) from e
457
446
 
447
+ def _ensure_strict_tool_schema(self, tool: FunctionTool) -> FunctionTool:
448
+ r"""Ensure a tool has a strict schema compatible with OpenAI's
449
+ requirements.
450
+
451
+ Args:
452
+ tool (FunctionTool): The tool to check and update if necessary.
453
+
454
+ Returns:
455
+ FunctionTool: The tool with a strict schema.
456
+ """
457
+ try:
458
+ schema = tool.get_openai_tool_schema()
459
+
460
+ # Check if the tool already has strict mode enabled
461
+ if schema.get("function", {}).get("strict") is True:
462
+ return tool
463
+
464
+ # Update the schema to be strict
465
+ if "function" in schema:
466
+ schema["function"]["strict"] = True
467
+
468
+ # Ensure parameters have proper strict mode configuration
469
+ parameters = schema["function"].get("parameters", {})
470
+ if parameters:
471
+ # Ensure additionalProperties is false
472
+ parameters["additionalProperties"] = False
473
+
474
+ # Process properties to handle optional fields
475
+ properties = parameters.get("properties", {})
476
+ parameters["required"] = list(properties.keys())
477
+
478
+ # Apply the sanitization function from function_tool
479
+ from camel.toolkits.function_tool import (
480
+ sanitize_and_enforce_required,
481
+ )
482
+
483
+ schema = sanitize_and_enforce_required(schema)
484
+
485
+ tool.set_openai_tool_schema(schema)
486
+ logger.debug(
487
+ f"Updated tool '{tool.get_function_name()}' to strict mode"
488
+ )
489
+
490
+ except Exception as e:
491
+ logger.warning(f"Failed to ensure strict schema for tool: {e}")
492
+
493
+ return tool
494
+
458
495
  def get_tools(self) -> List[FunctionTool]:
459
496
  r"""Aggregates all tools from the managed MCP client instances.
460
497
 
461
498
  Collects and combines tools from all connected MCP clients into a
462
499
  single unified list. Each tool is converted to a CAMEL-compatible
463
- :obj:`FunctionTool` that can be used with CAMEL agents.
500
+ :obj:`FunctionTool` that can be used with CAMEL agents. All tools
501
+ are ensured to have strict schemas compatible with OpenAI's
502
+ requirements.
464
503
 
465
504
  Returns:
466
505
  List[FunctionTool]: Combined list of all available function tools
467
- from all connected MCP servers. Returns an empty list if no
468
- clients are connected or if no tools are available.
506
+ from all connected MCP servers with strict schemas. Returns an
507
+ empty list if no clients are connected or if no tools are
508
+ available.
469
509
 
470
510
  Note:
471
511
  This method can be called even when the toolkit is not connected,
@@ -492,14 +532,25 @@ class MCPToolkit(BaseToolkit):
492
532
  for i, client in enumerate(self.clients):
493
533
  try:
494
534
  client_tools = client.get_tools()
495
- all_tools.extend(client_tools)
535
+
536
+ # Ensure all tools have strict schemas
537
+ strict_tools = []
538
+ for tool in client_tools:
539
+ strict_tool = self._ensure_strict_tool_schema(tool)
540
+ strict_tools.append(strict_tool)
541
+
542
+ all_tools.extend(strict_tools)
496
543
  logger.debug(
497
- f"Client {i+1} contributed {len(client_tools)} tools"
544
+ f"Client {i+1} contributed {len(strict_tools)} "
545
+ f"tools (strict mode enabled)"
498
546
  )
499
547
  except Exception as e:
500
548
  logger.error(f"Failed to get tools from client {i+1}: {e}")
501
549
 
502
- logger.info(f"Total tools available: {len(all_tools)}")
550
+ logger.info(
551
+ f"Total tools available: {len(all_tools)} (all with strict "
552
+ f"schemas)"
553
+ )
503
554
  return all_tools
504
555
 
505
556
  def get_text_tools(self) -> str:
@@ -62,7 +62,8 @@ class PlaywrightMCPToolkit(BaseToolkit):
62
62
  + (additional_args or []),
63
63
  }
64
64
  }
65
- }
65
+ },
66
+ timeout=timeout,
66
67
  )
67
68
 
68
69
  async def connect(self):
@@ -260,7 +260,7 @@ class PPTXToolkit(BaseToolkit):
260
260
  provided, the default template will be used. (default: :obj:
261
261
  `None`)
262
262
  """
263
- import pptx
263
+ from pptx import Presentation
264
264
 
265
265
  # Use template if provided, otherwise create new presentation
266
266
  if template is not None:
@@ -270,9 +270,9 @@ class PPTXToolkit(BaseToolkit):
270
270
  f"Template file not found: {template_path}, using "
271
271
  "default template"
272
272
  )
273
- presentation = pptx.Presentation()
273
+ presentation = Presentation()
274
274
  else:
275
- presentation = pptx.Presentation(str(template_path))
275
+ presentation = Presentation(str(template_path))
276
276
  # Clear all existing slides by removing them from the slide
277
277
  # list
278
278
  while len(presentation.slides) > 0:
@@ -280,7 +280,7 @@ class PPTXToolkit(BaseToolkit):
280
280
  presentation.part.drop_rel(rId)
281
281
  del presentation.slides._sldIdLst[-1]
282
282
  else:
283
- presentation = pptx.Presentation()
283
+ presentation = Presentation()
284
284
 
285
285
  slide_width_inch, slide_height_inch = (
286
286
  self._get_slide_width_height_inches(presentation)
camel/types/enums.py CHANGED
@@ -386,6 +386,16 @@ class ModelType(UnifiedModelType, Enum):
386
386
  )
387
387
  WATSONX_MISTRAL_LARGE = "mistralai/mistral-large"
388
388
 
389
+ # Qianfan models
390
+ QIANFAN_ERNIE_X1_TURBO_32K = "ernie-x1-turbo-32k"
391
+ QIANFAN_ERNIE_X1_32K = "ernie-x1-32k"
392
+ QIANFAN_ERNIE_X1_32K_PREVIEW = "ernie-x1-32k-preview"
393
+ QIANFAN_ERNIE_4_5_TURBO_128K = "ernie-4.5-turbo-128k"
394
+ QIANFAN_ERNIE_4_5_TURBO_32K = "ernie-4.5-turbo-32k"
395
+ QIANFAN_DEEPSEEK_V3 = "deepseek-v3"
396
+ QIANFAN_DEEPSEEK_R1 = "deepseek-r1"
397
+ QIANFAN_QWEN3_235B_A22B = "qwen3-235b-a22b"
398
+
389
399
  # Crynux models
390
400
  CRYNUX_DEEPSEEK_R1_DISTILL_QWEN_1_5B = (
391
401
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
@@ -867,6 +877,19 @@ class ModelType(UnifiedModelType, Enum):
867
877
  ModelType.WATSONX_MISTRAL_LARGE,
868
878
  }
869
879
 
880
+ @property
881
+ def is_qianfan(self) -> bool:
882
+ return self in {
883
+ ModelType.QIANFAN_ERNIE_X1_TURBO_32K,
884
+ ModelType.QIANFAN_ERNIE_X1_32K,
885
+ ModelType.QIANFAN_ERNIE_X1_32K_PREVIEW,
886
+ ModelType.QIANFAN_ERNIE_4_5_TURBO_128K,
887
+ ModelType.QIANFAN_ERNIE_4_5_TURBO_32K,
888
+ ModelType.QIANFAN_DEEPSEEK_V3,
889
+ ModelType.QIANFAN_DEEPSEEK_R1,
890
+ ModelType.QIANFAN_QWEN3_235B_A22B,
891
+ }
892
+
870
893
  @property
871
894
  def is_novita(self) -> bool:
872
895
  return self in {
@@ -1038,6 +1061,11 @@ class ModelType(UnifiedModelType, Enum):
1038
1061
  ModelType.CRYNUX_QWEN_2_5_7B_INSTRUCT,
1039
1062
  ModelType.CRYNUX_NOUS_HERMES_3_LLAMA_3_1_8B,
1040
1063
  ModelType.CRYNUX_NOUS_HERMES_3_LLAMA_3_2_3B,
1064
+ ModelType.QIANFAN_ERNIE_X1_TURBO_32K,
1065
+ ModelType.QIANFAN_ERNIE_X1_32K,
1066
+ ModelType.QIANFAN_ERNIE_X1_32K_PREVIEW,
1067
+ ModelType.QIANFAN_ERNIE_4_5_TURBO_32K,
1068
+ ModelType.QIANFAN_QWEN3_235B_A22B,
1041
1069
  }:
1042
1070
  return 32_000
1043
1071
  elif self in {
@@ -1118,6 +1146,7 @@ class ModelType(UnifiedModelType, Enum):
1118
1146
  return 65_535
1119
1147
  elif self in {
1120
1148
  ModelType.NOVITA_QWEN_2_5_V1_72B,
1149
+ ModelType.QIANFAN_DEEPSEEK_R1,
1121
1150
  }:
1122
1151
  return 96_000
1123
1152
  elif self in {
@@ -1171,6 +1200,8 @@ class ModelType(UnifiedModelType, Enum):
1171
1200
  ModelType.NETMIND_DEEPSEEK_V3,
1172
1201
  ModelType.NOVITA_DEEPSEEK_V3_0324,
1173
1202
  ModelType.MISTRAL_MEDIUM_3,
1203
+ ModelType.QIANFAN_ERNIE_4_5_TURBO_128K,
1204
+ ModelType.QIANFAN_DEEPSEEK_V3,
1174
1205
  }:
1175
1206
  return 128_000
1176
1207
  elif self in {
@@ -1502,6 +1533,7 @@ class ModelPlatformType(Enum):
1502
1533
  NETMIND = "netmind"
1503
1534
  NOVITA = "novita"
1504
1535
  WATSONX = "watsonx"
1536
+ QIANFAN = "qianfan"
1505
1537
  CRYNUX = "crynux"
1506
1538
 
1507
1539
  @classmethod
@@ -163,6 +163,11 @@ class UnifiedModelType(str):
163
163
  r"""Returns whether the model is a WatsonX served model."""
164
164
  return True
165
165
 
166
+ @property
167
+ def is_qianfan(self) -> bool:
168
+ r"""Returns whether the model is a Qianfan served model."""
169
+ return True
170
+
166
171
  @property
167
172
  def is_crynux(self) -> bool:
168
173
  r"""Returns whether the model is a Crynux served model."""
camel/utils/mcp_client.py CHANGED
@@ -173,8 +173,6 @@ class MCPClient:
173
173
  initialization. (default: :obj:`None`)
174
174
  timeout (Optional[float], optional): Timeout for waiting for messages
175
175
  from the server in seconds. (default: :obj:`10.0`)
176
- strict (Optional[bool], optional): Strict mode for generating
177
- FunctionTool objects. (default: :obj:`False`)
178
176
 
179
177
  Examples:
180
178
  STDIO server:
@@ -209,20 +207,6 @@ class MCPClient:
209
207
  async with MCPClient({"url": "ws://localhost:8080/mcp"}) as client:
210
208
  tools = client.get_tools()
211
209
 
212
- With strict mode enabled:
213
-
214
- .. code-block:: python
215
-
216
- async with MCPClient({
217
- "command": "npx",
218
- "args": [
219
- "-y",
220
- "@modelcontextprotocol/server-filesystem",
221
- "/path"
222
- ]
223
- }, strict=True) as client:
224
- tools = client.get_tools()
225
-
226
210
  Attributes:
227
211
  config (ServerConfig): The server configuration object.
228
212
  client_info (Optional[types.Implementation]): Client implementation
@@ -235,14 +219,12 @@ class MCPClient:
235
219
  config: Union[ServerConfig, Dict[str, Any]],
236
220
  client_info: Optional[types.Implementation] = None,
237
221
  timeout: Optional[float] = 10.0,
238
- strict: Optional[bool] = False,
239
222
  ):
240
223
  # Convert dict config to ServerConfig if needed
241
224
  if isinstance(config, dict):
242
225
  config = ServerConfig(**config)
243
226
 
244
227
  self.config = config
245
- self.strict = strict
246
228
 
247
229
  # Validate transport type early (this will raise ValueError if invalid)
248
230
  _ = self.config.transport_type
@@ -766,7 +748,6 @@ class MCPClient:
766
748
  "name": mcp_tool.name,
767
749
  "description": mcp_tool.description
768
750
  or "No description provided.",
769
- "strict": self.strict,
770
751
  "parameters": parameters,
771
752
  },
772
753
  }
@@ -932,8 +913,7 @@ def create_mcp_client(
932
913
  dictionary is provided, it will be automatically converted to
933
914
  a :obj:`ServerConfig`.
934
915
  **kwargs: Additional keyword arguments passed to the :obj:`MCPClient`
935
- constructor, such as :obj:`client_info`, :obj:`timeout`, and
936
- :obj:`strict`.
916
+ constructor, such as :obj:`client_info`, :obj:`timeout`.
937
917
 
938
918
  Returns:
939
919
  MCPClient: A configured :obj:`MCPClient` instance ready for use as
@@ -972,20 +952,6 @@ def create_mcp_client(
972
952
  "url": "ws://localhost:8080/mcp"
973
953
  }) as client:
974
954
  tools = client.get_tools()
975
-
976
- With strict mode enabled:
977
-
978
- .. code-block:: python
979
-
980
- async with create_mcp_client({
981
- "command": "npx",
982
- "args": [
983
- "-y",
984
- "@modelcontextprotocol/server-filesystem",
985
- "/path",
986
- ],
987
- }, strict=True) as client:
988
- tools = client.get_tools()
989
955
  """
990
956
  return MCPClient(config, **kwargs)
991
957
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camel-ai
3
- Version: 0.2.65
3
+ Version: 0.2.67
4
4
  Summary: Communicative Agents for AI Society Study
5
5
  Project-URL: Homepage, https://www.camel-ai.org/
6
6
  Project-URL: Repository, https://github.com/camel-ai/camel
@@ -38,7 +38,7 @@ Requires-Dist: dappier<0.4,>=0.3.3; extra == 'all'
38
38
  Requires-Dist: datacommons-pandas<0.0.4,>=0.0.3; extra == 'all'
39
39
  Requires-Dist: datacommons<2,>=1.4.3; extra == 'all'
40
40
  Requires-Dist: datasets<4,>=3; extra == 'all'
41
- Requires-Dist: daytona-sdk==0.14.0; extra == 'all'
41
+ Requires-Dist: daytona-sdk>=0.20.0; extra == 'all'
42
42
  Requires-Dist: diffusers<0.26,>=0.25.0; extra == 'all'
43
43
  Requires-Dist: discord-py<3,>=2.3.2; extra == 'all'
44
44
  Requires-Dist: docker<8,>=7.1.0; extra == 'all'
@@ -178,7 +178,7 @@ Requires-Dist: uv==0.6.5; extra == 'dev'
178
178
  Provides-Extra: dev-tools
179
179
  Requires-Dist: aci-sdk>=1.0.0b1; extra == 'dev-tools'
180
180
  Requires-Dist: agentops<0.4,>=0.3.21; extra == 'dev-tools'
181
- Requires-Dist: daytona-sdk==0.14.0; extra == 'dev-tools'
181
+ Requires-Dist: daytona-sdk>=0.20.0; extra == 'dev-tools'
182
182
  Requires-Dist: docker<8,>=7.1.0; extra == 'dev-tools'
183
183
  Requires-Dist: e2b-code-interpreter<2,>=1.0.3; extra == 'dev-tools'
184
184
  Requires-Dist: ipykernel<7,>=6.0.0; extra == 'dev-tools'