camel-ai 0.2.76a2__py3-none-any.whl → 0.2.76a3__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 CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  from camel.logger import disable_logging, enable_logging, set_log_level
16
16
 
17
- __version__ = '0.2.76a2'
17
+ __version__ = '0.2.76a3'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
@@ -18,6 +18,8 @@ import warnings
18
18
  from contextlib import AsyncExitStack
19
19
  from typing import Any, Dict, List, Optional
20
20
 
21
+ from typing_extensions import TypeGuard
22
+
21
23
  from camel.logger import get_logger
22
24
  from camel.toolkits import BaseToolkit, FunctionTool
23
25
  from camel.utils.commons import run_async
@@ -43,6 +45,187 @@ class MCPToolError(Exception):
43
45
  pass
44
46
 
45
47
 
48
+ _EMPTY_SCHEMA = {
49
+ "additionalProperties": False,
50
+ "type": "object",
51
+ "properties": {},
52
+ "required": [],
53
+ }
54
+
55
+
56
+ def ensure_strict_json_schema(schema: dict[str, Any]) -> dict[str, Any]:
57
+ r"""Mutates the given JSON schema to ensure it conforms to the
58
+ `strict` standard that the OpenAI API expects.
59
+ """
60
+ if schema == {}:
61
+ return _EMPTY_SCHEMA
62
+ return _ensure_strict_json_schema(schema, path=(), root=schema)
63
+
64
+
65
+ def _ensure_strict_json_schema(
66
+ json_schema: object,
67
+ *,
68
+ path: tuple[str, ...],
69
+ root: dict[str, object],
70
+ ) -> dict[str, Any]:
71
+ if not is_dict(json_schema):
72
+ raise TypeError(
73
+ f"Expected {json_schema} to be a dictionary; path={path}"
74
+ )
75
+
76
+ defs = json_schema.get("$defs")
77
+ if is_dict(defs):
78
+ for def_name, def_schema in defs.items():
79
+ _ensure_strict_json_schema(
80
+ def_schema, path=(*path, "$defs", def_name), root=root
81
+ )
82
+
83
+ definitions = json_schema.get("definitions")
84
+ if is_dict(definitions):
85
+ for definition_name, definition_schema in definitions.items():
86
+ _ensure_strict_json_schema(
87
+ definition_schema,
88
+ path=(*path, "definitions", definition_name),
89
+ root=root,
90
+ )
91
+
92
+ typ = json_schema.get("type")
93
+ if typ == "object" and "additionalProperties" not in json_schema:
94
+ json_schema["additionalProperties"] = False
95
+ elif (
96
+ typ == "object"
97
+ and "additionalProperties" in json_schema
98
+ and json_schema["additionalProperties"]
99
+ ):
100
+ raise ValueError(
101
+ "additionalProperties should not be set for object types. This "
102
+ "could be because you're using an older version of Pydantic, or "
103
+ "because you configured additional properties to be allowed. If "
104
+ "you really need this, update the function or output tool "
105
+ "to not use a strict schema."
106
+ )
107
+
108
+ # object types
109
+ # { 'type': 'object', 'properties': { 'a': {...} } }
110
+ properties = json_schema.get("properties")
111
+ if is_dict(properties):
112
+ json_schema["required"] = list(properties.keys())
113
+ json_schema["properties"] = {
114
+ key: _ensure_strict_json_schema(
115
+ prop_schema, path=(*path, "properties", key), root=root
116
+ )
117
+ for key, prop_schema in properties.items()
118
+ }
119
+
120
+ # arrays
121
+ # { 'type': 'array', 'items': {...} }
122
+ items = json_schema.get("items")
123
+ if is_dict(items):
124
+ json_schema["items"] = _ensure_strict_json_schema(
125
+ items, path=(*path, "items"), root=root
126
+ )
127
+
128
+ # unions
129
+ any_of = json_schema.get("anyOf")
130
+ if is_list(any_of):
131
+ json_schema["anyOf"] = [
132
+ _ensure_strict_json_schema(
133
+ variant, path=(*path, "anyOf", str(i)), root=root
134
+ )
135
+ for i, variant in enumerate(any_of)
136
+ ]
137
+
138
+ # intersections
139
+ all_of = json_schema.get("allOf")
140
+ if is_list(all_of):
141
+ if len(all_of) == 1:
142
+ json_schema.update(
143
+ _ensure_strict_json_schema(
144
+ all_of[0], path=(*path, "allOf", "0"), root=root
145
+ )
146
+ )
147
+ json_schema.pop("allOf")
148
+ else:
149
+ json_schema["allOf"] = [
150
+ _ensure_strict_json_schema(
151
+ entry, path=(*path, "allOf", str(i)), root=root
152
+ )
153
+ for i, entry in enumerate(all_of)
154
+ ]
155
+
156
+ # strip `None` defaults as there's no meaningful distinction here
157
+ # the schema will still be `nullable` and the model will default
158
+ # to using `None` anyway
159
+ if json_schema.get("default", None) is None:
160
+ json_schema.pop("default", None)
161
+
162
+ # we can't use `$ref`s if there are also other properties defined, e.g.
163
+ # `{"$ref": "...", "description": "my description"}`
164
+ #
165
+ # so we unravel the ref
166
+ # `{"type": "string", "description": "my description"}`
167
+ ref = json_schema.get("$ref")
168
+ if ref and has_more_than_n_keys(json_schema, 1):
169
+ assert isinstance(ref, str), f"Received non-string $ref - {ref}"
170
+
171
+ resolved = resolve_ref(root=root, ref=ref)
172
+ if not is_dict(resolved):
173
+ raise ValueError(
174
+ f"Expected `$ref: {ref}` to resolved to a dictionary but got "
175
+ f"{resolved}"
176
+ )
177
+
178
+ # properties from the json schema take priority
179
+ # over the ones on the `$ref`
180
+ json_schema.update({**resolved, **json_schema})
181
+ json_schema.pop("$ref")
182
+ # Since the schema expanded from `$ref` might not
183
+ # have `additionalProperties: false` applied
184
+ # we call `_ensure_strict_json_schema` again to fix the inlined
185
+ # schema and ensure it's valid
186
+ return _ensure_strict_json_schema(json_schema, path=path, root=root)
187
+
188
+ return json_schema
189
+
190
+
191
+ def resolve_ref(*, root: dict[str, object], ref: str) -> object:
192
+ if not ref.startswith("#/"):
193
+ raise ValueError(
194
+ f"Unexpected $ref format {ref!r}; Does not start with #/"
195
+ )
196
+
197
+ path = ref[2:].split("/")
198
+ resolved = root
199
+ for key in path:
200
+ value = resolved[key]
201
+ assert is_dict(value), (
202
+ f"encountered non-dictionary entry while resolving {ref} - "
203
+ f"{resolved}"
204
+ )
205
+ resolved = value
206
+
207
+ return resolved
208
+
209
+
210
+ def is_dict(obj: object) -> TypeGuard[dict[str, object]]:
211
+ # just pretend that we know there are only `str` keys
212
+ # as that check is not worth the performance cost
213
+ return isinstance(obj, dict)
214
+
215
+
216
+ def is_list(obj: object) -> TypeGuard[list[object]]:
217
+ return isinstance(obj, list)
218
+
219
+
220
+ def has_more_than_n_keys(obj: dict[str, object], n: int) -> bool:
221
+ i = 0
222
+ for _ in obj.keys():
223
+ i += 1
224
+ if i > n:
225
+ return True
226
+ return False
227
+
228
+
46
229
  class MCPToolkit(BaseToolkit):
47
230
  r"""MCPToolkit provides a unified interface for managing multiple
48
231
  MCP server connections and their tools.
@@ -476,367 +659,149 @@ class MCPToolkit(BaseToolkit):
476
659
  raise ValueError(error_msg) from e
477
660
 
478
661
  def _ensure_strict_tool_schema(self, tool: FunctionTool) -> FunctionTool:
479
- r"""Ensure a tool has a strict schema compatible with OpenAI's
480
- requirements according to the structured outputs specification.
481
-
482
- Args:
483
- tool (FunctionTool): The tool to check and update if necessary.
484
-
485
- Returns:
486
- FunctionTool: The tool with a strict schema.
662
+ r"""Ensure a tool has a strict schema compatible with
663
+ OpenAI's requirements.
664
+
665
+ Strategy:
666
+ - Ensure parameters exist with at least an empty properties object
667
+ (OpenAI requirement).
668
+ - Try converting parameters to strict using ensure_strict_json_schema.
669
+ - If conversion fails, mark function.strict = False and
670
+ keep best-effort parameters.
487
671
  """
488
672
  try:
489
673
  schema = tool.get_openai_tool_schema()
490
674
 
491
- # Helper functions for validation and transformation
492
- def _validate_and_fix_schema(obj, path="", in_root=True):
493
- r"""Recursively validate and fix schema to meet strict
494
- requirements.
495
- """
496
- if isinstance(obj, dict):
497
- # Check if this is the root object
498
- if in_root and path == "":
499
- # Root must be an object, not anyOf
500
- if "anyOf" in obj and "type" not in obj:
501
- raise ValueError(
502
- "Root object must not be anyOf and must "
503
- "be an object"
504
- )
505
- if obj.get("type") and obj["type"] != "object":
506
- raise ValueError(
507
- "Root object must have type 'object'"
508
- )
509
-
510
- # Handle object types
511
- if obj.get("type") == "object":
512
- # Ensure additionalProperties is false
513
- obj["additionalProperties"] = False
514
-
515
- # Process properties
516
- if "properties" in obj:
517
- props = obj["properties"]
518
- # Only set required if it doesn't exist or needs
519
- # updating
520
- if "required" not in obj:
521
- # If no required field exists, make all fields
522
- # required
523
- obj["required"] = list(props.keys())
524
- else:
525
- # Ensure required field only contains valid
526
- # property names
527
- existing_required = obj.get("required", [])
528
- valid_required = [
529
- req
530
- for req in existing_required
531
- if req in props
532
- ]
533
- # Add any missing properties to required
534
- for prop_name in props:
535
- if prop_name not in valid_required:
536
- valid_required.append(prop_name)
537
- obj["required"] = valid_required
538
-
539
- # Recursively process each property
540
- for prop_name, prop_schema in props.items():
541
- _validate_and_fix_schema(
542
- prop_schema, f"{path}.{prop_name}", False
543
- )
544
-
545
- # Handle arrays
546
- elif obj.get("type") == "array":
547
- if "items" in obj:
548
- _validate_and_fix_schema(
549
- obj["items"], f"{path}.items", False
550
- )
675
+ def _has_strict_mode_incompatible_features(json_schema):
676
+ r"""Check if schema has features incompatible
677
+ with OpenAI strict mode."""
551
678
 
552
- # Handle anyOf
553
- elif "anyOf" in obj:
554
- # Validate anyOf schemas
555
- for i, schema in enumerate(obj["anyOf"]):
556
- _validate_and_fix_schema(
557
- schema, f"{path}.anyOf[{i}]", False
558
- )
559
-
560
- # Handle string format validation
561
- elif obj.get("type") == "string":
562
- if "format" in obj:
563
- allowed_formats = [
564
- "date-time",
565
- "time",
566
- "date",
567
- "duration",
568
- "email",
569
- "hostname",
570
- "ipv4",
571
- "ipv6",
572
- "uuid",
573
- ]
574
- if obj["format"] not in allowed_formats:
575
- del obj["format"] # Remove unsupported format
576
-
577
- # Handle number/integer validation
578
- elif obj.get("type") in ["number", "integer"]:
579
- # These properties are supported
580
- supported_props = [
581
- "multipleOf",
582
- "maximum",
583
- "exclusiveMaximum",
584
- "minimum",
585
- "exclusiveMinimum",
586
- ]
587
- # Remove any unsupported properties
588
- for key in list(obj.keys()):
589
- if key not in [
590
- *supported_props,
591
- "type",
592
- "description",
593
- "default",
594
- ]:
595
- del obj[key]
596
-
597
- # Process nested structures
598
- for key in ["allOf", "oneOf", "$defs", "definitions"]:
599
- if key in obj:
600
- if isinstance(obj[key], list):
601
- for i, item in enumerate(obj[key]):
602
- _validate_and_fix_schema(
603
- item, f"{path}.{key}[{i}]", False
604
- )
605
- elif isinstance(obj[key], dict):
606
- for def_name, def_schema in obj[key].items():
607
- _validate_and_fix_schema(
608
- def_schema,
609
- f"{path}.{key}.{def_name}",
610
- False,
611
- )
612
-
613
- elif isinstance(obj, list):
614
- for i, item in enumerate(obj):
615
- _validate_and_fix_schema(item, f"{path}[{i}]", False)
616
-
617
- def _check_schema_limits(obj, counts=None):
618
- r"""Check if schema exceeds OpenAI limits."""
619
- if counts is None:
620
- counts = {
621
- "properties": 0,
622
- "depth": 0,
623
- "enums": 0,
624
- "string_length": 0,
625
- }
679
+ def _check_incompatible(obj, path=""):
680
+ if not isinstance(obj, dict):
681
+ return False
626
682
 
627
- def _count_properties(o, depth=0):
628
- if isinstance(o, dict):
629
- if depth > 5:
630
- raise ValueError(
631
- "Schema exceeds maximum nesting depth of 5"
683
+ # Check for allOf in array items (known to cause issues)
684
+ if "items" in obj and isinstance(obj["items"], dict):
685
+ items_schema = obj["items"]
686
+ if "allOf" in items_schema:
687
+ logger.debug(
688
+ f"Found allOf in array items at {path}"
632
689
  )
690
+ return True
691
+ # Recursively check items schema
692
+ if _check_incompatible(items_schema, f"{path}.items"):
693
+ return True
694
+
695
+ # Check for other potentially problematic patterns
696
+ # anyOf/oneOf in certain contexts can also cause issues
697
+ if (
698
+ "anyOf" in obj and len(obj["anyOf"]) > 10
699
+ ): # Large unions can be problematic
700
+ return True
701
+
702
+ # Recursively check nested objects
703
+ for key in [
704
+ "properties",
705
+ "additionalProperties",
706
+ "patternProperties",
707
+ ]:
708
+ if key in obj and isinstance(obj[key], dict):
709
+ if key == "properties":
710
+ for prop_name, prop_schema in obj[key].items():
711
+ if isinstance(
712
+ prop_schema, dict
713
+ ) and _check_incompatible(
714
+ prop_schema,
715
+ f"{path}.{key}.{prop_name}",
716
+ ):
717
+ return True
718
+ elif _check_incompatible(
719
+ obj[key], f"{path}.{key}"
720
+ ):
721
+ return True
633
722
 
634
- if o.get("type") == "object" and "properties" in o:
635
- counts["properties"] += len(o["properties"])
636
- for prop in o["properties"].values():
637
- _count_properties(prop, depth + 1)
638
-
639
- if "enum" in o:
640
- counts["enums"] += len(o["enum"])
641
- if isinstance(o["enum"], list):
642
- for val in o["enum"]:
643
- if isinstance(val, str):
644
- counts["string_length"] += len(val)
645
-
646
- # Count property names
647
- if "properties" in o:
648
- for name in o["properties"].keys():
649
- counts["string_length"] += len(name)
650
-
651
- # Process nested structures
652
- for key in ["items", "allOf", "oneOf", "anyOf"]:
653
- if key in o:
654
- if isinstance(o[key], dict):
655
- _count_properties(o[key], depth)
656
- elif isinstance(o[key], list):
657
- for item in o[key]:
658
- _count_properties(item, depth)
659
-
660
- _count_properties(obj)
661
-
662
- # Check limits, reference: https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#objects-have-limitations-on-nesting-depth-and-size # noqa: E501
663
- if counts["properties"] > 5000:
664
- raise ValueError(
665
- "Schema exceeds maximum of 5000 properties"
666
- )
667
- if counts["enums"] > 1000:
668
- raise ValueError(
669
- "Schema exceeds maximum of 1000 enum values"
670
- )
671
- if counts["string_length"] > 120000:
672
- raise ValueError(
673
- "Schema exceeds maximum total string length of 120000"
674
- )
675
-
676
- return True
723
+ # Check arrays and unions
724
+ for key in ["allOf", "anyOf", "oneOf"]:
725
+ if key in obj and isinstance(obj[key], list):
726
+ for i, item in enumerate(obj[key]):
727
+ if isinstance(
728
+ item, dict
729
+ ) and _check_incompatible(
730
+ item, f"{path}.{key}[{i}]"
731
+ ):
732
+ return True
677
733
 
678
- # Check if schema has any issues that prevent strict mode
679
- def _has_strict_mode_issues(obj):
680
- r"""Check for any issues that would prevent strict mode."""
681
- issues = []
734
+ return False
682
735
 
683
- def _check_issues(o, path=""):
684
- if isinstance(o, dict):
685
- # Check for additionalProperties: true
686
- if o.get("additionalProperties") is True:
687
- issues.append(
688
- f"additionalProperties: true at {path}"
689
- )
736
+ return _check_incompatible(json_schema)
690
737
 
691
- # Check for unsupported keywords
692
- unsupported = [
693
- "not",
694
- "dependentRequired",
695
- "dependentSchemas",
696
- "if",
697
- "then",
698
- "else",
699
- "patternProperties",
700
- ]
701
- for keyword in unsupported:
702
- if keyword in o:
703
- issues.append(
704
- f"Unsupported keyword '{keyword}' "
705
- f"at {path}"
706
- )
707
-
708
- # Recursively check
709
- for key, value in o.items():
710
- if isinstance(value, (dict, list)):
711
- _check_issues(value, f"{path}.{key}")
712
-
713
- elif isinstance(o, list):
714
- for i, item in enumerate(o):
715
- _check_issues(item, f"{path}[{i}]")
716
-
717
- _check_issues(obj)
718
- return issues
719
-
720
- # Check if already strict and compliant
721
- if schema.get("function", {}).get("strict") is True:
722
- # Validate it's actually compliant
723
- try:
724
- params = schema["function"].get("parameters", {})
725
- if params:
726
- _validate_and_fix_schema(params)
727
- _check_schema_limits(params)
728
- return tool
729
- except Exception:
730
- # Not actually compliant, continue to fix it
731
- pass
732
-
733
- # Apply sanitization first to handle optional fields properly
738
+ # Apply sanitization if available
734
739
  if "function" in schema:
735
- # Apply the sanitization function first
736
- from camel.toolkits.function_tool import (
737
- sanitize_and_enforce_required,
738
- )
740
+ try:
741
+ from camel.toolkits.function_tool import (
742
+ sanitize_and_enforce_required,
743
+ )
739
744
 
740
- schema = sanitize_and_enforce_required(schema)
741
-
742
- # Special handling for schemas with additionalProperties that
743
- # aren't false These can't use strict mode
744
- def _has_open_props(obj, path=""):
745
- """Check if any object has additionalProperties that
746
- isn't false."""
747
- if isinstance(obj, dict):
748
- if (
749
- obj.get("type") == "object"
750
- and "additionalProperties" in obj
751
- ):
752
- if obj["additionalProperties"] is not False:
753
- return True
745
+ schema = sanitize_and_enforce_required(schema)
746
+ except ImportError:
747
+ logger.debug("sanitize_and_enforce_required not available")
754
748
 
755
- # Recurse through the schema
756
- for key, value in obj.items():
757
- if key in [
758
- "properties",
759
- "items",
760
- "allOf",
761
- "oneOf",
762
- "anyOf",
763
- ]:
764
- if isinstance(value, dict):
765
- if _has_open_props(value, f"{path}.{key}"):
766
- return True
767
- elif isinstance(value, list):
768
- for i, item in enumerate(value):
769
- if _has_open_props(
770
- item,
771
- f"{path}.{key}[{i}]",
772
- ):
773
- return True
774
- elif isinstance(value, dict) and key not in [
775
- "description",
776
- "type",
777
- "enum",
778
- ]:
779
- if _has_open_props(value, f"{path}.{key}"):
780
- return True
781
- return False
749
+ parameters = schema["function"].get("parameters", {})
750
+ if not parameters:
751
+ # Empty parameters - use minimal valid schema
752
+ parameters = {
753
+ "type": "object",
754
+ "properties": {},
755
+ "additionalProperties": False,
756
+ }
757
+ schema["function"]["parameters"] = parameters
782
758
 
783
- # Check if schema has dynamic additionalProperties
784
- if _has_open_props(schema["function"].get("parameters", {})):
785
- # Can't use strict mode with dynamic additionalProperties
786
- schema["function"]["strict"] = False
787
- tool.set_openai_tool_schema(schema)
788
- logger.warning(
789
- f"Tool '{tool.get_function_name()}' has "
790
- f"dynamic additionalProperties and cannot use "
791
- f"strict mode"
792
- )
793
- return tool
759
+ # MCP spec doesn't require 'properties', but OpenAI spec does
760
+ if (
761
+ parameters.get("type") == "object"
762
+ and "properties" not in parameters
763
+ ):
764
+ parameters["properties"] = {}
794
765
 
795
- # Now check for blocking issues after sanitization
796
- issues = _has_strict_mode_issues(schema)
797
- if issues:
798
- # Can't use strict mode
766
+ try:
767
+ # _check_schema_limits(parameters)
768
+
769
+ # Check for OpenAI strict mode incompatible features
770
+ if _has_strict_mode_incompatible_features(parameters):
771
+ raise ValueError(
772
+ "Schema contains features "
773
+ "incompatible with strict mode"
774
+ )
775
+
776
+ strict_params = ensure_strict_json_schema(parameters)
777
+ schema["function"]["parameters"] = strict_params
778
+ schema["function"]["strict"] = True
779
+ except Exception as e:
780
+ # Fallback to non-strict mode on any failure
799
781
  schema["function"]["strict"] = False
800
- tool.set_openai_tool_schema(schema)
801
782
  logger.warning(
802
- f"Tool '{tool.get_function_name()}' has "
803
- f"issues preventing strict mode: "
804
- f"{'; '.join(issues[:3])}{'...' if len(issues) > 3 else ''}" # noqa: E501
783
+ f"Tool '{tool.get_function_name()}' "
784
+ f"cannot use strict mode: {e}"
805
785
  )
806
- return tool
807
-
808
- # Enable strict mode
809
- schema["function"]["strict"] = True
810
-
811
- parameters = schema["function"].get("parameters", {})
812
- if parameters:
813
- # Validate and fix the parameters schema
814
- _validate_and_fix_schema(parameters)
815
-
816
- # Check schema limits
817
- _check_schema_limits(parameters)
818
786
 
819
787
  tool.set_openai_tool_schema(schema)
820
- logger.debug(
821
- f"Updated tool '{tool.get_function_name()}' to strict mode"
822
- )
823
788
 
824
789
  except Exception as e:
825
- # If we can't make it strict, disable strict mode
790
+ # Final fallback - ensure tool still works
826
791
  try:
827
- if "function" in schema:
828
- schema["function"]["strict"] = False
829
- tool.set_openai_tool_schema(schema)
792
+ current_schema = tool.get_openai_tool_schema()
793
+ if "function" in current_schema:
794
+ current_schema["function"]["strict"] = False
795
+ tool.set_openai_tool_schema(current_schema)
830
796
  logger.warning(
831
- f"Failed to ensure strict schema for "
832
- f"tool '{tool.get_function_name()}': {str(e)[:100]}. "
833
- f"Setting strict=False."
797
+ f"Error processing schema for tool "
798
+ f"'{tool.get_function_name()}': {str(e)[:100]}. "
799
+ f"Using non-strict mode."
834
800
  )
835
801
  except Exception as inner_e:
836
- # If even setting strict=False fails, log the error
837
802
  logger.error(
838
- f"Critical error processing "
839
- f"tool '{tool.get_function_name()}': {inner_e}. "
803
+ f"Critical error processing tool "
804
+ f"'{tool.get_function_name()}': {inner_e}. "
840
805
  f"Tool may not function correctly."
841
806
  )
842
807
 
@@ -879,6 +844,7 @@ class MCPToolkit(BaseToolkit):
879
844
  )
880
845
 
881
846
  all_tools = []
847
+ seen_names: set[str] = set()
882
848
  for i, client in enumerate(self.clients):
883
849
  try:
884
850
  client_tools = client.get_tools()
@@ -887,6 +853,14 @@ class MCPToolkit(BaseToolkit):
887
853
  strict_tools = []
888
854
  for tool in client_tools:
889
855
  strict_tool = self._ensure_strict_tool_schema(tool)
856
+ name = strict_tool.get_function_name()
857
+ if name in seen_names:
858
+ logger.warning(
859
+ f"Duplicate tool name detected and "
860
+ f"skipped: '{name}' from client {i+1}"
861
+ )
862
+ continue
863
+ seen_names.add(name)
890
864
  strict_tools.append(strict_tool)
891
865
 
892
866
  all_tools.extend(strict_tools)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camel-ai
3
- Version: 0.2.76a2
3
+ Version: 0.2.76a3
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
@@ -1,4 +1,4 @@
1
- camel/__init__.py,sha256=kLbq9fj3ffO20PS_HeFWEBQFjG_e0tk2Grxj_hGCu58,901
1
+ camel/__init__.py,sha256=9DLMrXKU5X35VRsz3WSLMoC2MnzWuYKWD0SHeoLtyIc,901
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=WgEwael_eT6D-lVAKHpKIpwXSTjvLbny5jbV1Ab8lnA,5760
@@ -354,7 +354,7 @@ camel/toolkits/klavis_toolkit.py,sha256=ZKerhgz5e-AV-iv0ftf07HgWikknIHjB3EOQswfu
354
354
  camel/toolkits/linkedin_toolkit.py,sha256=wn4eXwYYlVA7doTna7k7WYhUqTBF83W79S-UJs_IQr0,8065
355
355
  camel/toolkits/markitdown_toolkit.py,sha256=lwN6qQY8TLZkNWOqzeKZG3Fku-HMpGFrdRwhtPaJSlw,3844
356
356
  camel/toolkits/math_toolkit.py,sha256=SJbzT6akHRlmqo1QwCj1f7-6pEv0sNKJbcYvYAylHQw,5439
357
- camel/toolkits/mcp_toolkit.py,sha256=FbBKyz7f6y2hA-lBVsh3K-_gyz2rVDwe3uvaK8OsuRc,41442
357
+ camel/toolkits/mcp_toolkit.py,sha256=Y6bwq_Wy1FeBi7uVjiR6SrN1GQeJJDqgDt6Hcl26gOw,37530
358
358
  camel/toolkits/memory_toolkit.py,sha256=TeKYd5UMwgjVpuS2orb-ocFL13eUNKujvrFOruDCpm8,4436
359
359
  camel/toolkits/meshy_toolkit.py,sha256=NbgdOBD3FYLtZf-AfonIv6-Q8-8DW129jsaP1PqI2rs,7126
360
360
  camel/toolkits/message_agent_toolkit.py,sha256=yWvAaxoxAvDEtD7NH7IkkHIyfWIYK47WZhn5E_RaxKo,22661
@@ -479,7 +479,7 @@ camel/verifiers/math_verifier.py,sha256=tA1D4S0sm8nsWISevxSN0hvSVtIUpqmJhzqfbuMo
479
479
  camel/verifiers/models.py,sha256=GdxYPr7UxNrR1577yW4kyroRcLGfd-H1GXgv8potDWU,2471
480
480
  camel/verifiers/physics_verifier.py,sha256=c1grrRddcrVN7szkxhv2QirwY9viIRSITWeWFF5HmLs,30187
481
481
  camel/verifiers/python_verifier.py,sha256=ogTz77wODfEcDN4tMVtiSkRQyoiZbHPY2fKybn59lHw,20558
482
- camel_ai-0.2.76a2.dist-info/METADATA,sha256=BC0_IwTj6y7726aIiEeEwakNxBmS0gvjZPkaj4POw0s,54897
483
- camel_ai-0.2.76a2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
484
- camel_ai-0.2.76a2.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
485
- camel_ai-0.2.76a2.dist-info/RECORD,,
482
+ camel_ai-0.2.76a3.dist-info/METADATA,sha256=rib4FdlfEFWDE_QAGVNdTIJg4SxJH03pxIhP0GqgNvU,54897
483
+ camel_ai-0.2.76a3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
484
+ camel_ai-0.2.76a3.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
485
+ camel_ai-0.2.76a3.dist-info/RECORD,,