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.
- camel/__init__.py +1 -1
- camel/agents/mcp_agent.py +1 -5
- camel/configs/__init__.py +3 -0
- camel/configs/qianfan_config.py +85 -0
- camel/models/__init__.py +2 -0
- camel/models/aiml_model.py +8 -0
- camel/models/anthropic_model.py +8 -0
- camel/models/aws_bedrock_model.py +8 -0
- camel/models/azure_openai_model.py +14 -5
- camel/models/base_model.py +4 -0
- camel/models/cohere_model.py +9 -2
- camel/models/crynux_model.py +8 -0
- camel/models/deepseek_model.py +8 -0
- camel/models/gemini_model.py +8 -0
- camel/models/groq_model.py +8 -0
- camel/models/internlm_model.py +8 -0
- camel/models/litellm_model.py +5 -0
- camel/models/lmstudio_model.py +14 -1
- camel/models/mistral_model.py +15 -1
- camel/models/model_factory.py +6 -0
- camel/models/modelscope_model.py +8 -0
- camel/models/moonshot_model.py +8 -0
- camel/models/nemotron_model.py +17 -2
- camel/models/netmind_model.py +8 -0
- camel/models/novita_model.py +8 -0
- camel/models/nvidia_model.py +8 -0
- camel/models/ollama_model.py +8 -0
- camel/models/openai_compatible_model.py +23 -5
- camel/models/openai_model.py +21 -4
- camel/models/openrouter_model.py +8 -0
- camel/models/ppio_model.py +8 -0
- camel/models/qianfan_model.py +104 -0
- camel/models/qwen_model.py +8 -0
- camel/models/reka_model.py +18 -3
- camel/models/samba_model.py +17 -3
- camel/models/sglang_model.py +20 -5
- camel/models/siliconflow_model.py +8 -0
- camel/models/stub_model.py +8 -1
- camel/models/togetherai_model.py +8 -0
- camel/models/vllm_model.py +7 -0
- camel/models/volcano_model.py +14 -1
- camel/models/watsonx_model.py +4 -1
- camel/models/yi_model.py +8 -0
- camel/models/zhipuai_model.py +8 -0
- camel/societies/workforce/prompts.py +33 -17
- camel/societies/workforce/role_playing_worker.py +5 -10
- camel/societies/workforce/single_agent_worker.py +3 -5
- camel/societies/workforce/task_channel.py +16 -18
- camel/societies/workforce/utils.py +104 -65
- camel/societies/workforce/workforce.py +1263 -100
- camel/societies/workforce/workforce_logger.py +613 -0
- camel/tasks/task.py +77 -6
- camel/toolkits/__init__.py +2 -0
- camel/toolkits/code_execution.py +1 -1
- camel/toolkits/function_tool.py +79 -7
- camel/toolkits/mcp_toolkit.py +70 -19
- camel/toolkits/playwright_mcp_toolkit.py +2 -1
- camel/toolkits/pptx_toolkit.py +4 -4
- camel/types/enums.py +32 -0
- camel/types/unified_model_type.py +5 -0
- camel/utils/mcp_client.py +1 -35
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.67.dist-info}/METADATA +3 -3
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.67.dist-info}/RECORD +65 -62
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.67.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
camel/toolkits/__init__.py
CHANGED
|
@@ -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
|
]
|
camel/toolkits/code_execution.py
CHANGED
|
@@ -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.
|
camel/toolkits/function_tool.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
#
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
#
|
|
217
|
-
parameters
|
|
288
|
+
# Recursively add additionalProperties: false to all objects
|
|
289
|
+
_add_additional_properties_false(parameters)
|
|
218
290
|
|
|
219
291
|
return parameters_dict
|
|
220
292
|
|
camel/toolkits/mcp_toolkit.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
468
|
-
clients are connected or if no tools are
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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:
|
camel/toolkits/pptx_toolkit.py
CHANGED
|
@@ -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
|
|
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 =
|
|
273
|
+
presentation = Presentation()
|
|
274
274
|
else:
|
|
275
|
-
presentation =
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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'
|