camel-ai 0.2.65__py3-none-any.whl → 0.2.66__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of camel-ai might be problematic. Click here for more details.
- camel/__init__.py +1 -1
- camel/agents/mcp_agent.py +1 -5
- camel/societies/workforce/role_playing_worker.py +2 -2
- camel/societies/workforce/single_agent_worker.py +2 -2
- camel/societies/workforce/utils.py +0 -51
- camel/societies/workforce/workforce.py +10 -1
- camel/tasks/task.py +61 -1
- camel/toolkits/function_tool.py +79 -7
- camel/toolkits/mcp_toolkit.py +70 -19
- camel/utils/mcp_client.py +1 -35
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.66.dist-info}/METADATA +1 -1
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.66.dist-info}/RECORD +14 -14
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.66.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.65.dist-info → camel_ai-0.2.66.dist-info}/licenses/LICENSE +0 -0
camel/__init__.py
CHANGED
camel/agents/mcp_agent.py
CHANGED
|
@@ -125,7 +125,6 @@ class MCPAgent(ChatAgent):
|
|
|
125
125
|
local_config_path: Optional[str] = None,
|
|
126
126
|
tools: Optional[List[Union[FunctionTool, Callable]]] = None,
|
|
127
127
|
function_calling_available: bool = True,
|
|
128
|
-
strict: bool = False,
|
|
129
128
|
**kwargs,
|
|
130
129
|
):
|
|
131
130
|
if model is None:
|
|
@@ -145,7 +144,6 @@ class MCPAgent(ChatAgent):
|
|
|
145
144
|
|
|
146
145
|
self.local_config = local_config
|
|
147
146
|
self.function_calling_available = function_calling_available
|
|
148
|
-
self.strict = strict
|
|
149
147
|
|
|
150
148
|
if function_calling_available:
|
|
151
149
|
sys_msg_content = "You are a helpful assistant, and you prefer "
|
|
@@ -179,7 +177,7 @@ class MCPAgent(ChatAgent):
|
|
|
179
177
|
if self.local_config:
|
|
180
178
|
config_dict.update(self.local_config)
|
|
181
179
|
|
|
182
|
-
return MCPToolkit(config_dict=config_dict
|
|
180
|
+
return MCPToolkit(config_dict=config_dict)
|
|
183
181
|
|
|
184
182
|
def add_registry(self, registry_config: BaseMCPRegistryConfig) -> None:
|
|
185
183
|
r"""Add a new registry configuration to the agent.
|
|
@@ -213,7 +211,6 @@ class MCPAgent(ChatAgent):
|
|
|
213
211
|
] = None,
|
|
214
212
|
model: Optional[BaseModelBackend] = None,
|
|
215
213
|
function_calling_available: bool = False,
|
|
216
|
-
strict: bool = False,
|
|
217
214
|
**kwargs,
|
|
218
215
|
) -> "MCPAgent":
|
|
219
216
|
r"""Create and connect an MCPAgent instance.
|
|
@@ -279,7 +276,6 @@ class MCPAgent(ChatAgent):
|
|
|
279
276
|
registry_configs=final_registry_configs,
|
|
280
277
|
model=model,
|
|
281
278
|
function_calling_available=function_calling_available,
|
|
282
|
-
strict=strict,
|
|
283
279
|
**kwargs,
|
|
284
280
|
)
|
|
285
281
|
|
|
@@ -25,9 +25,9 @@ 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
|
|
29
29
|
from camel.societies.workforce.worker import Worker
|
|
30
|
-
from camel.tasks.task import Task, TaskState
|
|
30
|
+
from camel.tasks.task import Task, TaskState, validate_task_content
|
|
31
31
|
from camel.utils import print_text_animated
|
|
32
32
|
|
|
33
33
|
|
|
@@ -21,9 +21,9 @@ from colorama import Fore
|
|
|
21
21
|
|
|
22
22
|
from camel.agents import ChatAgent
|
|
23
23
|
from camel.societies.workforce.prompts import PROCESS_TASK_PROMPT
|
|
24
|
-
from camel.societies.workforce.utils import TaskResult
|
|
24
|
+
from camel.societies.workforce.utils import TaskResult
|
|
25
25
|
from camel.societies.workforce.worker import Worker
|
|
26
|
-
from camel.tasks.task import Task, TaskState
|
|
26
|
+
from camel.tasks.task import Task, TaskState, validate_task_content
|
|
27
27
|
from camel.utils import print_text_animated
|
|
28
28
|
|
|
29
29
|
|
|
@@ -16,10 +16,6 @@ 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
|
-
|
|
23
19
|
|
|
24
20
|
class WorkerConf(BaseModel):
|
|
25
21
|
r"""The configuration of a worker."""
|
|
@@ -75,50 +71,3 @@ def check_if_running(running: bool) -> Callable:
|
|
|
75
71
|
return wrapper
|
|
76
72
|
|
|
77
73
|
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
|
|
@@ -40,7 +40,7 @@ from camel.societies.workforce.utils import (
|
|
|
40
40
|
check_if_running,
|
|
41
41
|
)
|
|
42
42
|
from camel.societies.workforce.worker import Worker
|
|
43
|
-
from camel.tasks.task import Task, TaskState
|
|
43
|
+
from camel.tasks.task import Task, TaskState, validate_task_content
|
|
44
44
|
from camel.toolkits import CodeExecutionToolkit, SearchToolkit, ThinkingToolkit
|
|
45
45
|
from camel.types import ModelPlatformType, ModelType
|
|
46
46
|
from camel.utils import dependencies_required
|
|
@@ -211,6 +211,15 @@ class Workforce(BaseNode):
|
|
|
211
211
|
Returns:
|
|
212
212
|
Task: The updated task.
|
|
213
213
|
"""
|
|
214
|
+
if not validate_task_content(task.content, task.id):
|
|
215
|
+
task.state = TaskState.FAILED
|
|
216
|
+
task.result = "Task failed: Invalid or empty content provided"
|
|
217
|
+
logger.warning(
|
|
218
|
+
f"Task {task.id} rejected: Invalid or empty content. "
|
|
219
|
+
f"Content preview: '{task.content[:50]}...'"
|
|
220
|
+
)
|
|
221
|
+
return task
|
|
222
|
+
|
|
214
223
|
self.reset()
|
|
215
224
|
self._task = task
|
|
216
225
|
task.state = TaskState.FAILED
|
camel/tasks/task.py
CHANGED
|
@@ -19,15 +19,66 @@ from typing import Any, Callable, Dict, List, Literal, Optional, Union
|
|
|
19
19
|
from pydantic import BaseModel
|
|
20
20
|
|
|
21
21
|
from camel.agents import ChatAgent
|
|
22
|
+
from camel.logger import get_logger
|
|
22
23
|
from camel.messages import BaseMessage
|
|
23
24
|
from camel.prompts import TextPrompt
|
|
24
25
|
|
|
26
|
+
# Note: validate_task_content moved here to avoid circular imports
|
|
25
27
|
from .task_prompt import (
|
|
26
28
|
TASK_COMPOSE_PROMPT,
|
|
27
29
|
TASK_DECOMPOSE_PROMPT,
|
|
28
30
|
TASK_EVOLVE_PROMPT,
|
|
29
31
|
)
|
|
30
32
|
|
|
33
|
+
logger = get_logger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_task_content(
|
|
37
|
+
content: str, task_id: str = "unknown", min_length: int = 10
|
|
38
|
+
) -> bool:
|
|
39
|
+
r"""Validates task result content to avoid silent failures.
|
|
40
|
+
It performs basic checks to ensure the content meets minimum
|
|
41
|
+
quality standards.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
content (str): The task result content to validate.
|
|
45
|
+
task_id (str): Task ID for logging purposes.
|
|
46
|
+
(default: :obj:`"unknown"`)
|
|
47
|
+
min_length (int): Minimum content length after stripping whitespace.
|
|
48
|
+
(default: :obj:`10`)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
bool: True if content passes validation, False otherwise.
|
|
52
|
+
"""
|
|
53
|
+
# 1: Content must not be None
|
|
54
|
+
if content is None:
|
|
55
|
+
logger.warning(f"Task {task_id}: None content rejected")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
# 2: Content must not be empty after stripping whitespace
|
|
59
|
+
stripped_content = content.strip()
|
|
60
|
+
if not stripped_content:
|
|
61
|
+
logger.warning(
|
|
62
|
+
f"Task {task_id}: Empty or whitespace-only content rejected."
|
|
63
|
+
)
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
# 3: Content must meet minimum meaningful length
|
|
67
|
+
if len(stripped_content) < min_length:
|
|
68
|
+
logger.warning(
|
|
69
|
+
f"Task {task_id}: Content too short ({len(stripped_content)} "
|
|
70
|
+
f"chars < {min_length} minimum). Content preview: "
|
|
71
|
+
f"'{stripped_content[:50]}...'"
|
|
72
|
+
)
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
# All validation checks passed
|
|
76
|
+
logger.debug(
|
|
77
|
+
f"Task {task_id}: Content validation passed "
|
|
78
|
+
f"({len(stripped_content)} chars)"
|
|
79
|
+
)
|
|
80
|
+
return True
|
|
81
|
+
|
|
31
82
|
|
|
32
83
|
def parse_response(
|
|
33
84
|
response: str, task_id: Optional[str] = None
|
|
@@ -49,7 +100,16 @@ def parse_response(
|
|
|
49
100
|
if task_id is None:
|
|
50
101
|
task_id = "0"
|
|
51
102
|
for i, content in enumerate(tasks_content):
|
|
52
|
-
|
|
103
|
+
stripped_content = content.strip()
|
|
104
|
+
# validate subtask content before creating the task
|
|
105
|
+
if validate_task_content(stripped_content, f"{task_id}.{i}"):
|
|
106
|
+
tasks.append(Task(content=stripped_content, id=f"{task_id}.{i}"))
|
|
107
|
+
else:
|
|
108
|
+
logger.warning(
|
|
109
|
+
f"Skipping invalid subtask {task_id}.{i} "
|
|
110
|
+
f"during decomposition: "
|
|
111
|
+
f"Content '{stripped_content[:50]}...' failed validation"
|
|
112
|
+
)
|
|
53
113
|
return tasks
|
|
54
114
|
|
|
55
115
|
|
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/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,4 +1,4 @@
|
|
|
1
|
-
camel/__init__.py,sha256=
|
|
1
|
+
camel/__init__.py,sha256=ieSIdwh_pdP_S24iZL6locMLIdis19n6Xl6rhY33ZVg,899
|
|
2
2
|
camel/generators.py,sha256=JRqj9_m1PF4qT6UtybzTQ-KBT9MJQt18OAAYvQ_fr2o,13844
|
|
3
3
|
camel/human.py,sha256=Xg8x1cS5KK4bQ1SDByiHZnzsRpvRP-KZViNvmu38xo4,5475
|
|
4
4
|
camel/logger.py,sha256=rZVeOVYuQ9RYJ5Tqyv0usqy0g4zaVEq4qSfZ9nd2640,5755
|
|
@@ -12,7 +12,7 @@ camel/agents/critic_agent.py,sha256=L6cTbYjyZB0DCa51tQ6LZLA6my8kHLC4nktHySH78H4,
|
|
|
12
12
|
camel/agents/deductive_reasoner_agent.py,sha256=6BZGaq1hR6hKJuQtOfoYQnk_AkZpw_Mr7mUy2MspQgs,13540
|
|
13
13
|
camel/agents/embodied_agent.py,sha256=XBxBu5ZMmSJ4B2U3Z7SMwvLlgp6yNpaBe8HNQmY9CZA,7536
|
|
14
14
|
camel/agents/knowledge_graph_agent.py,sha256=7Tchhyvm1s8tQ3at7iGKZt70xWZllRXu2vwUFR37p10,9681
|
|
15
|
-
camel/agents/mcp_agent.py,sha256=
|
|
15
|
+
camel/agents/mcp_agent.py,sha256=ZMkOBQnjx9q4smJtrpZyMz5L-rX7yZOZl0ctnu2zQbQ,16455
|
|
16
16
|
camel/agents/multi_hop_generator_agent.py,sha256=aYsZNsEFHxIq8_wDN8lZRkvRbfhlOYGBKezWr87y8Bs,4325
|
|
17
17
|
camel/agents/programmed_agent_instruction.py,sha256=99fLe41che3X6wPpNPJXRwl4If6EoQqQVWIoT3DKE1s,7124
|
|
18
18
|
camel/agents/repo_agent.py,sha256=ZzZ9zvh0vgdJv1KNY3GEaEPDdDxxXrjUZxjjHjVEWtE,22187
|
|
@@ -269,12 +269,12 @@ camel/societies/role_playing.py,sha256=1TsQbGYmN91BeQ0DGM5PpSJ9TMbn1F3maJNuv4fQ5
|
|
|
269
269
|
camel/societies/workforce/__init__.py,sha256=bkTI-PE-MSK9AQ2V2gR6cR2WY-R7Jqy_NmXRtAoqo8o,920
|
|
270
270
|
camel/societies/workforce/base.py,sha256=z2DmbTP5LL5-aCAAqglznQqCLfPmnyM5zD3w6jjtsb8,2175
|
|
271
271
|
camel/societies/workforce/prompts.py,sha256=4OGp-1-XFYIZ8ZERubSsG-hwXti8fhjtwc3n5-WEgsA,7821
|
|
272
|
-
camel/societies/workforce/role_playing_worker.py,sha256
|
|
273
|
-
camel/societies/workforce/single_agent_worker.py,sha256=
|
|
272
|
+
camel/societies/workforce/role_playing_worker.py,sha256=-o1--PZh2tUYQN9UIJdguIJ09p72ED88G8JQYV7s-TE,7802
|
|
273
|
+
camel/societies/workforce/single_agent_worker.py,sha256=X23KE7flH_xBeMQm4qguqY0iBjJd8NlYMkrdxxNVjUc,4758
|
|
274
274
|
camel/societies/workforce/task_channel.py,sha256=9t5hoinfGYnbRavX4kx34Jk1FOy05SnJZYbNzb5M-CQ,7140
|
|
275
|
-
camel/societies/workforce/utils.py,sha256=
|
|
275
|
+
camel/societies/workforce/utils.py,sha256=yPbcLx8lNZStl15C4UXRBl5qsTrGA-hiIGynnGi53WQ,2384
|
|
276
276
|
camel/societies/workforce/worker.py,sha256=xdUWBW8Qifhhrh4jdK5S_q4LiSEL4NxKuDZN1fgQESA,4135
|
|
277
|
-
camel/societies/workforce/workforce.py,sha256=
|
|
277
|
+
camel/societies/workforce/workforce.py,sha256=8hvZXYe77xzhzVDqQ5BRIu-SGoDwxpKZZKdrsDF2Q1I,41539
|
|
278
278
|
camel/storages/__init__.py,sha256=bFpvvAS2QyZoIr-tnwhMWsZRL411kIRq6IMUHcI7KHs,1989
|
|
279
279
|
camel/storages/graph_storages/__init__.py,sha256=G29BNn651C0WTOpjCl4QnVM-4B9tcNh8DdmsCiONH8Y,948
|
|
280
280
|
camel/storages/graph_storages/base.py,sha256=uSe9jWuLudfm5jtfo6E-L_kNzITwK1_Ef-6L4HWw-JM,2852
|
|
@@ -301,7 +301,7 @@ camel/storages/vectordb_storages/qdrant.py,sha256=a_cT0buSCHQ2CPZy852-mdvMDwy5zo
|
|
|
301
301
|
camel/storages/vectordb_storages/tidb.py,sha256=w83bxgKgso43MtHqlpf2EMSpn1_Nz6ZZtY4fPw_-vgs,11192
|
|
302
302
|
camel/storages/vectordb_storages/weaviate.py,sha256=wDUE4KvfmOl3DqHFU4uF0VKbHu-q9vKhZDe8FZ6QXsk,27888
|
|
303
303
|
camel/tasks/__init__.py,sha256=MuHwkw5GRQc8NOCzj8tjtBrr2Xg9KrcKp-ed_-2ZGIM,906
|
|
304
|
-
camel/tasks/task.py,sha256=
|
|
304
|
+
camel/tasks/task.py,sha256=DWEXf0coDKNc3k2--svb6D3v7ExAuMml4NKRTU6icYI,16054
|
|
305
305
|
camel/tasks/task_prompt.py,sha256=3KZmKYKUPcTKe8EAZOZBN3G05JHRVt7oHY9ORzLVu1g,2150
|
|
306
306
|
camel/terminators/__init__.py,sha256=t8uqrkUnXEOYMXQDgaBkMFJ0EXFKI0kmx4cUimli3Ls,991
|
|
307
307
|
camel/terminators/base.py,sha256=xmJzERX7GdSXcxZjAHHODa0rOxRChMSRboDCNHWSscs,1511
|
|
@@ -323,7 +323,7 @@ camel/toolkits/dappier_toolkit.py,sha256=ewhXeeUj7e4DiTzuWDA-gHGhrLdyoZ4l9pbijvF
|
|
|
323
323
|
camel/toolkits/data_commons_toolkit.py,sha256=aHZUSL1ACpnYGaf1rE2csVKTmXTmN8lMGRUBYhZ_YEk,14168
|
|
324
324
|
camel/toolkits/excel_toolkit.py,sha256=DSjBXl24_LrJubGFFmB_vqliKzzWTbT1TH309YQVUO8,6653
|
|
325
325
|
camel/toolkits/file_write_toolkit.py,sha256=3JF_6fFXXQUlZv3VjkXVXDOodyGHHev0GJgSyZpkEH8,16483
|
|
326
|
-
camel/toolkits/function_tool.py,sha256=
|
|
326
|
+
camel/toolkits/function_tool.py,sha256=045I_vyqvB6PorcoVaEbxYjGe-fD-FBKtO4lfpZ2eVw,33535
|
|
327
327
|
camel/toolkits/github_toolkit.py,sha256=Xq4KynmvIW_2924BJBS98I9TaF0ugmN62Y74kcNv_us,13102
|
|
328
328
|
camel/toolkits/google_calendar_toolkit.py,sha256=E-sdgdbtNBs_CXbhht9t1dsVr4DsTr5NguHkx4fvSmc,15410
|
|
329
329
|
camel/toolkits/google_maps_toolkit.py,sha256=WTnkURpGri9KcY5OwV7AJJHOzmpu5RNmYE1QCVqvwWM,12023
|
|
@@ -334,7 +334,7 @@ camel/toolkits/jina_reranker_toolkit.py,sha256=0OWUlSqRNYYmD5EQZW7OX87lfmzLRjjDm
|
|
|
334
334
|
camel/toolkits/klavis_toolkit.py,sha256=ZKerhgz5e-AV-iv0ftf07HgWikknIHjB3EOQswfuR80,9864
|
|
335
335
|
camel/toolkits/linkedin_toolkit.py,sha256=wn4eXwYYlVA7doTna7k7WYhUqTBF83W79S-UJs_IQr0,8065
|
|
336
336
|
camel/toolkits/math_toolkit.py,sha256=KdI8AJ9Dbq5cfWboAYJUYgSkmADMCO5eZ6yqYkWuiIQ,3686
|
|
337
|
-
camel/toolkits/mcp_toolkit.py,sha256=
|
|
337
|
+
camel/toolkits/mcp_toolkit.py,sha256=czGYgZ-Vd9NdAcPe_hoA7ZYxSV2QKhsxuJNZNg3Rof4,25459
|
|
338
338
|
camel/toolkits/memory_toolkit.py,sha256=TeKYd5UMwgjVpuS2orb-ocFL13eUNKujvrFOruDCpm8,4436
|
|
339
339
|
camel/toolkits/meshy_toolkit.py,sha256=NbgdOBD3FYLtZf-AfonIv6-Q8-8DW129jsaP1PqI2rs,7126
|
|
340
340
|
camel/toolkits/mineru_toolkit.py,sha256=vRX9LholLNkpbJ6axfEN4pTG85aWb0PDmlVy3rAAXhg,6868
|
|
@@ -407,7 +407,7 @@ camel/utils/deduplication.py,sha256=UHikAtOW1TTDunf2t_wa2kFbmkrXWf7HfOKwLvwCxzo,
|
|
|
407
407
|
camel/utils/filename.py,sha256=HYNc1wbSCgNR1CN21cwHxdAhpnsf5ySJ6jUDfeqOK20,2532
|
|
408
408
|
camel/utils/langfuse.py,sha256=OowR6A790XG-b0UHiTYduYvS18PvSGFdmqki2Poogo0,8578
|
|
409
409
|
camel/utils/mcp.py,sha256=iuthL8VuUXIRU34Nvx8guq7frfglpZoxewUKuAg3e1s,5077
|
|
410
|
-
camel/utils/mcp_client.py,sha256=
|
|
410
|
+
camel/utils/mcp_client.py,sha256=1581sSQKNFxZFq-MvLXRq8jU1HIo3-X3xTfz6hJkKtE,36066
|
|
411
411
|
camel/utils/response_format.py,sha256=xZcx6xBxeg3A0e7R0JCMJdNm2oQ1-diqVLs0JsiCkZU,5319
|
|
412
412
|
camel/utils/token_counting.py,sha256=apkERzNoVc4sgvJvWVosvepX3KH8pVypVjrL4AA7RB4,17521
|
|
413
413
|
camel/utils/chunker/__init__.py,sha256=6iN6HL6sblIjDuJTILk-9qKcHBZ97t8b6tZCWPZ0OYI,899
|
|
@@ -420,7 +420,7 @@ camel/verifiers/math_verifier.py,sha256=tA1D4S0sm8nsWISevxSN0hvSVtIUpqmJhzqfbuMo
|
|
|
420
420
|
camel/verifiers/models.py,sha256=GdxYPr7UxNrR1577yW4kyroRcLGfd-H1GXgv8potDWU,2471
|
|
421
421
|
camel/verifiers/physics_verifier.py,sha256=c1grrRddcrVN7szkxhv2QirwY9viIRSITWeWFF5HmLs,30187
|
|
422
422
|
camel/verifiers/python_verifier.py,sha256=ogTz77wODfEcDN4tMVtiSkRQyoiZbHPY2fKybn59lHw,20558
|
|
423
|
-
camel_ai-0.2.
|
|
424
|
-
camel_ai-0.2.
|
|
425
|
-
camel_ai-0.2.
|
|
426
|
-
camel_ai-0.2.
|
|
423
|
+
camel_ai-0.2.66.dist-info/METADATA,sha256=yUOJzkC_rZSyxTCZ31wVoWTDOTZGS4S-uBBUbq8Pilg,44783
|
|
424
|
+
camel_ai-0.2.66.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
425
|
+
camel_ai-0.2.66.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
|
|
426
|
+
camel_ai-0.2.66.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|