camel-ai 0.2.73a6__py3-none-any.whl → 0.2.73a9__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.73a6'
17
+ __version__ = '0.2.73a9'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
@@ -13,8 +13,10 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  from .babyagi_playing import BabyAGI
15
15
  from .role_playing import RolePlaying
16
+ from .workforce import Workforce
16
17
 
17
18
  __all__ = [
18
19
  'RolePlaying',
19
20
  'BabyAGI',
21
+ 'Workforce',
20
22
  ]
@@ -103,34 +103,6 @@ class Mem0Storage(BaseKeyValueStorage):
103
103
  }
104
104
  return {k: v for k, v in options.items() if v is not None}
105
105
 
106
- def _prepare_filters(
107
- self,
108
- agent_id: Optional[str] = None,
109
- user_id: Optional[str] = None,
110
- filters: Optional[Dict[str, Any]] = None,
111
- ) -> Dict[str, Any]:
112
- r"""Helper method to prepare filters for Mem0 API calls.
113
-
114
- Args:
115
- agent_id (Optional[str], optional): Agent ID to filter by
116
- (default: :obj:`None`).
117
- user_id (Optional[str], optional): User ID to filter by (default:
118
- :obj:`None`).
119
- filters (Optional[Dict[str, Any]], optional): Additional filters
120
- (default: :obj:`None`).
121
-
122
- Returns:
123
- Dict[str, Any]: Prepared filters dictionary for API calls.
124
- """
125
- base_filters: Dict[str, Any] = {"AND": []}
126
- if filters:
127
- base_filters["AND"].append(filters)
128
- if agent_id or self.agent_id:
129
- base_filters["AND"].append({"agent_id": agent_id or self.agent_id})
130
- if user_id or self.user_id:
131
- base_filters["AND"].append({"user_id": user_id or self.user_id})
132
- return base_filters if base_filters["AND"] else {}
133
-
134
106
  def _prepare_messages(
135
107
  self,
136
108
  records: List[Dict[str, Any]],
@@ -168,7 +140,6 @@ class Mem0Storage(BaseKeyValueStorage):
168
140
  self.client.add(messages, **options)
169
141
  except Exception as e:
170
142
  logger.error(f"Error adding memory: {e}")
171
- logger.error(f"Error: {e}")
172
143
 
173
144
  def load(self) -> List[Dict[str, Any]]:
174
145
  r"""Loads all stored records from the Mem0 storage system.
@@ -178,11 +149,18 @@ class Mem0Storage(BaseKeyValueStorage):
178
149
  represents a stored record.
179
150
  """
180
151
  try:
181
- filters = self._prepare_filters(
182
- agent_id=self.agent_id,
183
- user_id=self.user_id,
184
- )
185
- results = self.client.get_all(version="v2", **filters)
152
+ # Build kwargs for get_all
153
+ kwargs = {}
154
+ if self.agent_id:
155
+ kwargs['agent_id'] = self.agent_id
156
+ if self.user_id:
157
+ kwargs['user_id'] = self.user_id
158
+
159
+ # If no filters available, return empty list
160
+ if not kwargs:
161
+ return []
162
+
163
+ results = self.client.get_all(**kwargs)
186
164
 
187
165
  # Transform results into MemoryRecord objects
188
166
  transformed_results = []
@@ -190,35 +168,58 @@ class Mem0Storage(BaseKeyValueStorage):
190
168
  memory_record = MemoryRecord(
191
169
  uuid=UUID(result["id"]),
192
170
  message=BaseMessage(
193
- role_name="user",
171
+ role_name="memory",
194
172
  role_type=RoleType.USER,
195
- meta_dict={},
173
+ meta_dict=result.get("metadata", {}),
196
174
  content=result["memory"],
197
175
  ),
198
176
  role_at_backend=OpenAIBackendRole.USER,
199
177
  extra_info=result.get("metadata", {}),
200
178
  timestamp=datetime.fromisoformat(
201
- result["created_at"]
179
+ result["created_at"].replace('Z', '+00:00')
202
180
  ).timestamp(),
203
- agent_id=result.get("agent_id", ""),
181
+ agent_id=result.get("agent_id", self.agent_id or ""),
204
182
  )
205
183
  transformed_results.append(memory_record.to_dict())
206
184
 
207
185
  return transformed_results
208
186
  except Exception as e:
209
- logger.error(f"Error searching memories: {e}")
187
+ logger.error(f"Error loading memories: {e}")
210
188
  return []
211
189
 
212
190
  def clear(
213
191
  self,
192
+ agent_id: Optional[str] = None,
193
+ user_id: Optional[str] = None,
214
194
  ) -> None:
215
- r"""Removes all records from the Mem0 storage system."""
195
+ r"""Removes all records from the Mem0 storage system.
196
+
197
+ Args:
198
+ agent_id (Optional[str]): Specific agent ID to clear memories for.
199
+ user_id (Optional[str]): Specific user ID to clear memories for.
200
+ """
216
201
  try:
217
- filters = self._prepare_filters(
218
- agent_id=self.agent_id,
219
- user_id=self.user_id,
220
- )
221
- self.client.delete_users(**filters)
202
+ # Use provided IDs or fall back to instance defaults
203
+ target_user_id = user_id or self.user_id
204
+ target_agent_id = agent_id or self.agent_id
205
+
206
+ # Build kwargs for delete_users method
207
+ kwargs = {}
208
+ if target_user_id:
209
+ kwargs['user_id'] = target_user_id
210
+ if target_agent_id:
211
+ kwargs['agent_id'] = target_agent_id
212
+
213
+ if kwargs:
214
+ # Use delete_users (plural) - this is the correct method name
215
+ self.client.delete_users(**kwargs)
216
+ logger.info(
217
+ f"Successfully cleared memories with filters: {kwargs}"
218
+ )
219
+ else:
220
+ logger.warning(
221
+ "No user_id or agent_id available for clearing memories"
222
+ )
223
+
222
224
  except Exception as e:
223
225
  logger.error(f"Error deleting memories: {e}")
224
- logger.error(f"Error: {e}")
@@ -840,6 +840,69 @@ class FileWriteToolkit(BaseToolkit):
840
840
 
841
841
  return text
842
842
 
843
+ def _ensure_html_utf8_meta(self, content: str) -> str:
844
+ r"""Ensure HTML content has UTF-8 meta tag.
845
+
846
+ Args:
847
+ content (str): The HTML content.
848
+
849
+ Returns:
850
+ str: HTML content with UTF-8 meta tag.
851
+ """
852
+ # Check if content already has a charset meta tag
853
+ has_charset = re.search(
854
+ r'<meta[^>]*charset[^>]*>', content, re.IGNORECASE
855
+ )
856
+
857
+ # UTF-8 meta tag
858
+ utf8_meta = '<meta charset="utf-8">'
859
+
860
+ if has_charset:
861
+ # Replace existing charset with UTF-8
862
+ content = re.sub(
863
+ r'<meta[^>]*charset[^>]*>',
864
+ utf8_meta,
865
+ content,
866
+ flags=re.IGNORECASE,
867
+ )
868
+ else:
869
+ # Add UTF-8 meta tag
870
+ # Try to find <head> tag
871
+ head_match = re.search(r'<head[^>]*>', content, re.IGNORECASE)
872
+ if head_match:
873
+ # Insert after <head> tag
874
+ insert_pos = head_match.end()
875
+ content = (
876
+ content[:insert_pos]
877
+ + '\n '
878
+ + utf8_meta
879
+ + content[insert_pos:]
880
+ )
881
+ else:
882
+ # No <head> tag found, check if there's <html> tag
883
+ html_match = re.search(r'<html[^>]*>', content, re.IGNORECASE)
884
+ if html_match:
885
+ # Insert <head> with meta tag after <html>
886
+ insert_pos = html_match.end()
887
+ content = (
888
+ content[:insert_pos]
889
+ + '\n<head>\n '
890
+ + utf8_meta
891
+ + '\n</head>'
892
+ + content[insert_pos:]
893
+ )
894
+ else:
895
+ # No proper HTML structure, wrap content
896
+ content = (
897
+ '<!DOCTYPE html>\n<html>\n<head>\n '
898
+ + utf8_meta
899
+ + '\n</head>\n<body>\n'
900
+ + content
901
+ + '\n</body>\n</html>'
902
+ )
903
+
904
+ return content
905
+
843
906
  def _write_csv_file(
844
907
  self,
845
908
  file_path: Path,
@@ -901,6 +964,10 @@ class FileWriteToolkit(BaseToolkit):
901
964
  content (str): The content to write.
902
965
  encoding (str): Character encoding to use. (default: :obj:`utf-8`)
903
966
  """
967
+ # For HTML files, ensure UTF-8 meta tag is present
968
+ if file_path.suffix.lower() in ['.html', '.htm']:
969
+ content = self._ensure_html_utf8_meta(content)
970
+
904
971
  with file_path.open("w", encoding=encoding) as f:
905
972
  f.write(content)
906
973
 
@@ -14,6 +14,7 @@
14
14
 
15
15
  import json
16
16
  import os
17
+ import warnings
17
18
  from contextlib import AsyncExitStack
18
19
  from typing import Any, Dict, List, Optional
19
20
 
@@ -24,6 +25,11 @@ from camel.utils.mcp_client import MCPClient, create_mcp_client
24
25
 
25
26
  logger = get_logger(__name__)
26
27
 
28
+ # Suppress parameter description warnings for MCP tools
29
+ warnings.filterwarnings(
30
+ "ignore", message="Parameter description is missing", category=UserWarning
31
+ )
32
+
27
33
 
28
34
  class MCPConnectionError(Exception):
29
35
  r"""Raised when MCP connection fails."""
@@ -446,7 +452,7 @@ class MCPToolkit(BaseToolkit):
446
452
 
447
453
  def _ensure_strict_tool_schema(self, tool: FunctionTool) -> FunctionTool:
448
454
  r"""Ensure a tool has a strict schema compatible with OpenAI's
449
- requirements.
455
+ requirements according to the structured outputs specification.
450
456
 
451
457
  Args:
452
458
  tool (FunctionTool): The tool to check and update if necessary.
@@ -457,60 +463,333 @@ class MCPToolkit(BaseToolkit):
457
463
  try:
458
464
  schema = tool.get_openai_tool_schema()
459
465
 
460
- # Check if any object in the schema has additionalProperties: true
461
- def _has_additional_properties_true(obj):
462
- r"""Recursively check if any object has additionalProperties: true""" # noqa: E501
466
+ # Helper functions for validation and transformation
467
+ def _validate_and_fix_schema(obj, path="", in_root=True):
468
+ r"""Recursively validate and fix schema to meet strict
469
+ requirements.
470
+ """
463
471
  if isinstance(obj, dict):
464
- if obj.get("additionalProperties") is True:
465
- return True
466
- for value in obj.values():
467
- if _has_additional_properties_true(value):
468
- return True
472
+ # Check if this is the root object
473
+ if in_root and path == "":
474
+ # Root must be an object, not anyOf
475
+ if "anyOf" in obj and "type" not in obj:
476
+ raise ValueError(
477
+ "Root object must not be anyOf and must "
478
+ "be an object"
479
+ )
480
+ if obj.get("type") and obj["type"] != "object":
481
+ raise ValueError(
482
+ "Root object must have type 'object'"
483
+ )
484
+
485
+ # Handle object types
486
+ if obj.get("type") == "object":
487
+ # Ensure additionalProperties is false
488
+ obj["additionalProperties"] = False
489
+
490
+ # Process properties
491
+ if "properties" in obj:
492
+ props = obj["properties"]
493
+ # Only set required if it doesn't exist or needs
494
+ # updating
495
+ if "required" not in obj:
496
+ # If no required field exists, make all fields
497
+ # required
498
+ obj["required"] = list(props.keys())
499
+ else:
500
+ # Ensure required field only contains valid
501
+ # property names
502
+ existing_required = obj.get("required", [])
503
+ valid_required = [
504
+ req
505
+ for req in existing_required
506
+ if req in props
507
+ ]
508
+ # Add any missing properties to required
509
+ for prop_name in props:
510
+ if prop_name not in valid_required:
511
+ valid_required.append(prop_name)
512
+ obj["required"] = valid_required
513
+
514
+ # Recursively process each property
515
+ for prop_name, prop_schema in props.items():
516
+ _validate_and_fix_schema(
517
+ prop_schema, f"{path}.{prop_name}", False
518
+ )
519
+
520
+ # Handle arrays
521
+ elif obj.get("type") == "array":
522
+ if "items" in obj:
523
+ _validate_and_fix_schema(
524
+ obj["items"], f"{path}.items", False
525
+ )
526
+
527
+ # Handle anyOf
528
+ elif "anyOf" in obj:
529
+ # Validate anyOf schemas
530
+ for i, schema in enumerate(obj["anyOf"]):
531
+ _validate_and_fix_schema(
532
+ schema, f"{path}.anyOf[{i}]", False
533
+ )
534
+
535
+ # Handle string format validation
536
+ elif obj.get("type") == "string":
537
+ if "format" in obj:
538
+ allowed_formats = [
539
+ "date-time",
540
+ "time",
541
+ "date",
542
+ "duration",
543
+ "email",
544
+ "hostname",
545
+ "ipv4",
546
+ "ipv6",
547
+ "uuid",
548
+ ]
549
+ if obj["format"] not in allowed_formats:
550
+ del obj["format"] # Remove unsupported format
551
+
552
+ # Handle number/integer validation
553
+ elif obj.get("type") in ["number", "integer"]:
554
+ # These properties are supported
555
+ supported_props = [
556
+ "multipleOf",
557
+ "maximum",
558
+ "exclusiveMaximum",
559
+ "minimum",
560
+ "exclusiveMinimum",
561
+ ]
562
+ # Remove any unsupported properties
563
+ for key in list(obj.keys()):
564
+ if key not in [
565
+ *supported_props,
566
+ "type",
567
+ "description",
568
+ "default",
569
+ ]:
570
+ del obj[key]
571
+
572
+ # Process nested structures
573
+ for key in ["allOf", "oneOf", "$defs", "definitions"]:
574
+ if key in obj:
575
+ if isinstance(obj[key], list):
576
+ for i, item in enumerate(obj[key]):
577
+ _validate_and_fix_schema(
578
+ item, f"{path}.{key}[{i}]", False
579
+ )
580
+ elif isinstance(obj[key], dict):
581
+ for def_name, def_schema in obj[key].items():
582
+ _validate_and_fix_schema(
583
+ def_schema,
584
+ f"{path}.{key}.{def_name}",
585
+ False,
586
+ )
587
+
469
588
  elif isinstance(obj, list):
470
- for item in obj:
471
- if _has_additional_properties_true(item):
472
- return True
473
- return False
474
-
475
- # FIRST: Check if the schema contains additionalProperties: true
476
- # This must be checked before strict mode validation
477
- if _has_additional_properties_true(schema):
478
- # Force strict mode to False and log warning
479
- if "function" in schema:
480
- schema["function"]["strict"] = False
481
- tool.set_openai_tool_schema(schema)
482
- logger.warning(
483
- f"Tool '{tool.get_function_name()}' contains "
484
- f"additionalProperties: true which is incompatible with "
485
- f"OpenAI strict mode. Setting strict=False for this tool."
486
- )
487
- return tool
589
+ for i, item in enumerate(obj):
590
+ _validate_and_fix_schema(item, f"{path}[{i}]", False)
591
+
592
+ def _check_schema_limits(obj, counts=None):
593
+ r"""Check if schema exceeds OpenAI limits."""
594
+ if counts is None:
595
+ counts = {
596
+ "properties": 0,
597
+ "depth": 0,
598
+ "enums": 0,
599
+ "string_length": 0,
600
+ }
601
+
602
+ def _count_properties(o, depth=0):
603
+ if isinstance(o, dict):
604
+ if depth > 5:
605
+ raise ValueError(
606
+ "Schema exceeds maximum nesting depth of 5"
607
+ )
608
+
609
+ if o.get("type") == "object" and "properties" in o:
610
+ counts["properties"] += len(o["properties"])
611
+ for prop in o["properties"].values():
612
+ _count_properties(prop, depth + 1)
613
+
614
+ if "enum" in o:
615
+ counts["enums"] += len(o["enum"])
616
+ if isinstance(o["enum"], list):
617
+ for val in o["enum"]:
618
+ if isinstance(val, str):
619
+ counts["string_length"] += len(val)
620
+
621
+ # Count property names
622
+ if "properties" in o:
623
+ for name in o["properties"].keys():
624
+ counts["string_length"] += len(name)
625
+
626
+ # Process nested structures
627
+ for key in ["items", "allOf", "oneOf", "anyOf"]:
628
+ if key in o:
629
+ if isinstance(o[key], dict):
630
+ _count_properties(o[key], depth)
631
+ elif isinstance(o[key], list):
632
+ for item in o[key]:
633
+ _count_properties(item, depth)
634
+
635
+ _count_properties(obj)
636
+
637
+ # Check limits, reference: https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#objects-have-limitations-on-nesting-depth-and-size # noqa: E501
638
+ if counts["properties"] > 5000:
639
+ raise ValueError(
640
+ "Schema exceeds maximum of 5000 properties"
641
+ )
642
+ if counts["enums"] > 1000:
643
+ raise ValueError(
644
+ "Schema exceeds maximum of 1000 enum values"
645
+ )
646
+ if counts["string_length"] > 120000:
647
+ raise ValueError(
648
+ "Schema exceeds maximum total string length of 120000"
649
+ )
488
650
 
489
- # SECOND: Check if the tool already has strict mode enabled
490
- # Only do this if there are no additionalProperties conflicts
651
+ return True
652
+
653
+ # Check if schema has any issues that prevent strict mode
654
+ def _has_strict_mode_issues(obj):
655
+ r"""Check for any issues that would prevent strict mode."""
656
+ issues = []
657
+
658
+ def _check_issues(o, path=""):
659
+ if isinstance(o, dict):
660
+ # Check for additionalProperties: true
661
+ if o.get("additionalProperties") is True:
662
+ issues.append(
663
+ f"additionalProperties: true at {path}"
664
+ )
665
+
666
+ # Check for unsupported keywords
667
+ unsupported = [
668
+ "not",
669
+ "dependentRequired",
670
+ "dependentSchemas",
671
+ "if",
672
+ "then",
673
+ "else",
674
+ "patternProperties",
675
+ ]
676
+ for keyword in unsupported:
677
+ if keyword in o:
678
+ issues.append(
679
+ f"Unsupported keyword '{keyword}' "
680
+ f"at {path}"
681
+ )
682
+
683
+ # Recursively check
684
+ for key, value in o.items():
685
+ if isinstance(value, (dict, list)):
686
+ _check_issues(value, f"{path}.{key}")
687
+
688
+ elif isinstance(o, list):
689
+ for i, item in enumerate(o):
690
+ _check_issues(item, f"{path}[{i}]")
691
+
692
+ _check_issues(obj)
693
+ return issues
694
+
695
+ # Check if already strict and compliant
491
696
  if schema.get("function", {}).get("strict") is True:
492
- return tool
493
-
494
- # Update the schema to be strict
697
+ # Validate it's actually compliant
698
+ try:
699
+ params = schema["function"].get("parameters", {})
700
+ if params:
701
+ _validate_and_fix_schema(params)
702
+ _check_schema_limits(params)
703
+ return tool
704
+ except Exception:
705
+ # Not actually compliant, continue to fix it
706
+ pass
707
+
708
+ # Apply sanitization first to handle optional fields properly
495
709
  if "function" in schema:
710
+ # Apply the sanitization function first
711
+ from camel.toolkits.function_tool import (
712
+ sanitize_and_enforce_required,
713
+ )
714
+
715
+ schema = sanitize_and_enforce_required(schema)
716
+
717
+ # Special handling for schemas with additionalProperties that
718
+ # aren't false These can't use strict mode
719
+ def _has_open_props(obj, path=""):
720
+ """Check if any object has additionalProperties that
721
+ isn't false."""
722
+ if isinstance(obj, dict):
723
+ if (
724
+ obj.get("type") == "object"
725
+ and "additionalProperties" in obj
726
+ ):
727
+ if obj["additionalProperties"] is not False:
728
+ return True
729
+
730
+ # Recurse through the schema
731
+ for key, value in obj.items():
732
+ if key in [
733
+ "properties",
734
+ "items",
735
+ "allOf",
736
+ "oneOf",
737
+ "anyOf",
738
+ ]:
739
+ if isinstance(value, dict):
740
+ if _has_open_props(value, f"{path}.{key}"):
741
+ return True
742
+ elif isinstance(value, list):
743
+ for i, item in enumerate(value):
744
+ if _has_open_props(
745
+ item,
746
+ f"{path}.{key}[{i}]",
747
+ ):
748
+ return True
749
+ elif isinstance(value, dict) and key not in [
750
+ "description",
751
+ "type",
752
+ "enum",
753
+ ]:
754
+ if _has_open_props(value, f"{path}.{key}"):
755
+ return True
756
+ return False
757
+
758
+ # Check if schema has dynamic additionalProperties
759
+ if _has_open_props(schema["function"].get("parameters", {})):
760
+ # Can't use strict mode with dynamic additionalProperties
761
+ schema["function"]["strict"] = False
762
+ tool.set_openai_tool_schema(schema)
763
+ logger.warning(
764
+ f"Tool '{tool.get_function_name()}' has "
765
+ f"dynamic additionalProperties and cannot use "
766
+ f"strict mode"
767
+ )
768
+ return tool
769
+
770
+ # Now check for blocking issues after sanitization
771
+ issues = _has_strict_mode_issues(schema)
772
+ if issues:
773
+ # Can't use strict mode
774
+ schema["function"]["strict"] = False
775
+ tool.set_openai_tool_schema(schema)
776
+ logger.warning(
777
+ f"Tool '{tool.get_function_name()}' has "
778
+ f"issues preventing strict mode: "
779
+ f"{'; '.join(issues[:3])}{'...' if len(issues) > 3 else ''}" # noqa: E501
780
+ )
781
+ return tool
782
+
783
+ # Enable strict mode
496
784
  schema["function"]["strict"] = True
497
785
 
498
- # Ensure parameters have proper strict mode configuration
499
786
  parameters = schema["function"].get("parameters", {})
500
787
  if parameters:
501
- # Ensure additionalProperties is false
502
- parameters["additionalProperties"] = False
503
-
504
- # Process properties to handle optional fields
505
- properties = parameters.get("properties", {})
506
- parameters["required"] = list(properties.keys())
507
-
508
- # Apply the sanitization function from function_tool
509
- from camel.toolkits.function_tool import (
510
- sanitize_and_enforce_required,
511
- )
788
+ # Validate and fix the parameters schema
789
+ _validate_and_fix_schema(parameters)
512
790
 
513
- schema = sanitize_and_enforce_required(schema)
791
+ # Check schema limits
792
+ _check_schema_limits(parameters)
514
793
 
515
794
  tool.set_openai_tool_schema(schema)
516
795
  logger.debug(
@@ -518,7 +797,23 @@ class MCPToolkit(BaseToolkit):
518
797
  )
519
798
 
520
799
  except Exception as e:
521
- logger.warning(f"Failed to ensure strict schema for tool: {e}")
800
+ # If we can't make it strict, disable strict mode
801
+ try:
802
+ if "function" in schema:
803
+ schema["function"]["strict"] = False
804
+ tool.set_openai_tool_schema(schema)
805
+ logger.warning(
806
+ f"Failed to ensure strict schema for "
807
+ f"tool '{tool.get_function_name()}': {str(e)[:100]}. "
808
+ f"Setting strict=False."
809
+ )
810
+ except Exception as inner_e:
811
+ # If even setting strict=False fails, log the error
812
+ logger.error(
813
+ f"Critical error processing "
814
+ f"tool '{tool.get_function_name()}': {inner_e}. "
815
+ f"Tool may not function correctly."
816
+ )
522
817
 
523
818
  return tool
524
819
 
@@ -98,6 +98,7 @@ class TerminalToolkit(BaseToolkit):
98
98
  self.is_macos = platform.system() == 'Darwin'
99
99
  self.initial_env_path: Optional[str] = None
100
100
  self.initial_env_prepared = False
101
+ self.uv_path: Optional[str] = None
101
102
 
102
103
  atexit.register(self.__del__)
103
104
 
@@ -200,37 +201,111 @@ class TerminalToolkit(BaseToolkit):
200
201
  f"Creating new Python environment at: {self.cloned_env_path}\n"
201
202
  )
202
203
 
203
- # Create virtual environment with pip
204
- venv.create(self.cloned_env_path, with_pip=True)
204
+ # Try to use uv if available
205
+ if self._ensure_uv_available():
206
+ # Use uv to create environment with current Python version
207
+ uv_command = self.uv_path if self.uv_path else "uv"
205
208
 
206
- # Ensure pip is properly available by upgrading it
207
- if self.os_type == 'Windows':
208
- python_path = os.path.join(
209
- self.cloned_env_path, "Scripts", "python.exe"
209
+ # Get current Python version
210
+ current_version = (
211
+ f"{sys.version_info.major}.{sys.version_info.minor}"
210
212
  )
211
- else:
212
- python_path = os.path.join(
213
- self.cloned_env_path, "bin", "python"
213
+
214
+ subprocess.run(
215
+ [
216
+ uv_command,
217
+ "venv",
218
+ "--python",
219
+ current_version,
220
+ self.cloned_env_path,
221
+ ],
222
+ check=True,
223
+ capture_output=True,
224
+ cwd=self.working_dir,
225
+ timeout=300,
214
226
  )
215
227
 
216
- # Verify python executable exists
217
- if os.path.exists(python_path):
218
- # Use python -m pip to ensure pip is available
228
+ # Get the python path from the new environment
229
+ if self.os_type == 'Windows':
230
+ python_path = os.path.join(
231
+ self.cloned_env_path, "Scripts", "python.exe"
232
+ )
233
+ else:
234
+ python_path = os.path.join(
235
+ self.cloned_env_path, "bin", "python"
236
+ )
237
+
238
+ # Install pip and setuptools using uv
219
239
  subprocess.run(
220
- [python_path, "-m", "pip", "install", "--upgrade", "pip"],
240
+ [
241
+ uv_command,
242
+ "pip",
243
+ "install",
244
+ "--python",
245
+ python_path,
246
+ "pip",
247
+ "setuptools",
248
+ "wheel",
249
+ ],
221
250
  check=True,
222
251
  capture_output=True,
223
252
  cwd=self.working_dir,
224
- timeout=60,
253
+ timeout=300,
225
254
  )
255
+
226
256
  self._update_terminal_output(
227
- "New Python environment created successfully with pip!\n"
257
+ "[UV] Cloned Python environment created successfully!\n"
228
258
  )
259
+
229
260
  else:
261
+ # Fallback to standard venv
230
262
  self._update_terminal_output(
231
- f"Warning: Python executable not found at {python_path}\n"
263
+ "Falling back to standard venv for cloning environment\n"
232
264
  )
233
265
 
266
+ # Create virtual environment with pip. On macOS, use
267
+ # symlinks=False to avoid dyld library loading issues
268
+ venv.create(
269
+ self.cloned_env_path, with_pip=True, symlinks=False
270
+ )
271
+
272
+ # Ensure pip is properly available by upgrading it
273
+ if self.os_type == 'Windows':
274
+ python_path = os.path.join(
275
+ self.cloned_env_path, "Scripts", "python.exe"
276
+ )
277
+ else:
278
+ python_path = os.path.join(
279
+ self.cloned_env_path, "bin", "python"
280
+ )
281
+
282
+ # Verify python executable exists
283
+ if os.path.exists(python_path):
284
+ # Use python -m pip to ensure pip is available
285
+ subprocess.run(
286
+ [
287
+ python_path,
288
+ "-m",
289
+ "pip",
290
+ "install",
291
+ "--upgrade",
292
+ "pip",
293
+ ],
294
+ check=True,
295
+ capture_output=True,
296
+ cwd=self.working_dir,
297
+ timeout=60,
298
+ )
299
+ self._update_terminal_output(
300
+ "New Python environment created successfully "
301
+ "with pip!\n"
302
+ )
303
+ else:
304
+ self._update_terminal_output(
305
+ f"Warning: Python executable not found "
306
+ f"at {python_path}\n"
307
+ )
308
+
234
309
  except subprocess.CalledProcessError as e:
235
310
  error_msg = e.stderr.decode() if e.stderr else str(e)
236
311
  self._update_terminal_output(
@@ -255,6 +330,97 @@ class TerminalToolkit(BaseToolkit):
255
330
  or shutil.which("uv") is not None
256
331
  )
257
332
 
333
+ def _ensure_uv_available(self) -> bool:
334
+ r"""Ensure uv is available, installing it if necessary.
335
+
336
+ Returns:
337
+ bool: True if uv is available (either already installed or
338
+ successfully installed), False otherwise.
339
+ """
340
+ # Check if uv is already available
341
+ existing_uv = shutil.which("uv")
342
+ if existing_uv is not None:
343
+ self.uv_path = existing_uv
344
+ self._update_terminal_output(
345
+ f"uv is already available at: {self.uv_path}\n"
346
+ )
347
+ return True
348
+
349
+ try:
350
+ self._update_terminal_output("uv not found, installing...\n")
351
+
352
+ # Install uv using the official installer script
353
+ if self.os_type in ['Darwin', 'Linux']:
354
+ # Use curl to download and execute the installer
355
+ install_cmd = "curl -LsSf https://astral.sh/uv/install.sh | sh"
356
+ result = subprocess.run(
357
+ install_cmd,
358
+ shell=True,
359
+ capture_output=True,
360
+ text=True,
361
+ timeout=60,
362
+ )
363
+
364
+ if result.returncode != 0:
365
+ self._update_terminal_output(
366
+ f"Failed to install uv: {result.stderr}\n"
367
+ )
368
+ return False
369
+
370
+ # Check if uv was installed in the expected location
371
+ home = os.path.expanduser("~")
372
+ uv_bin_path = os.path.join(home, ".cargo", "bin")
373
+ uv_executable = os.path.join(uv_bin_path, "uv")
374
+
375
+ if os.path.exists(uv_executable):
376
+ # Store the full path to uv instead of modifying PATH
377
+ self.uv_path = uv_executable
378
+ self._update_terminal_output(
379
+ f"uv installed successfully at: {self.uv_path}\n"
380
+ )
381
+ return True
382
+
383
+ elif self.os_type == 'Windows':
384
+ # Use PowerShell to install uv on Windows
385
+ install_cmd = (
386
+ "powershell -ExecutionPolicy Bypass -c "
387
+ "\"irm https://astral.sh/uv/install.ps1 | iex\""
388
+ )
389
+ result = subprocess.run(
390
+ install_cmd,
391
+ shell=True,
392
+ capture_output=True,
393
+ text=True,
394
+ timeout=60,
395
+ )
396
+
397
+ if result.returncode != 0:
398
+ self._update_terminal_output(
399
+ f"Failed to install uv: {result.stderr}\n"
400
+ )
401
+ return False
402
+
403
+ # Check if uv was installed in the expected location on Windows
404
+ home = os.path.expanduser("~")
405
+ uv_bin_path = os.path.join(home, ".cargo", "bin")
406
+ uv_executable = os.path.join(uv_bin_path, "uv.exe")
407
+
408
+ if os.path.exists(uv_executable):
409
+ # Store the full path to uv instead of modifying PATH
410
+ self.uv_path = uv_executable
411
+ self._update_terminal_output(
412
+ f"uv installed successfully at: {self.uv_path}\n"
413
+ )
414
+ return True
415
+
416
+ self._update_terminal_output("Failed to verify uv installation\n")
417
+ return False
418
+
419
+ except Exception as e:
420
+ self._update_terminal_output(f"Error installing uv: {e!s}\n")
421
+ logger.error(f"Failed to install uv: {e}")
422
+ return False
423
+
258
424
  def _prepare_initial_environment(self):
259
425
  r"""Prepare initial environment with Python 3.10, pip, and other
260
426
  essential tools.
@@ -279,10 +445,14 @@ class TerminalToolkit(BaseToolkit):
279
445
  # Create the initial environment directory
280
446
  os.makedirs(self.initial_env_path, exist_ok=True)
281
447
 
282
- # Check if we should use uv
283
- if self._is_uv_environment():
448
+ # Try to ensure uv is available and use it preferentially
449
+ if self._ensure_uv_available():
284
450
  self._setup_initial_env_with_uv()
285
451
  else:
452
+ # Fallback to venv if uv installation failed
453
+ self._update_terminal_output(
454
+ "Falling back to standard venv for environment setup\n"
455
+ )
286
456
  self._setup_initial_env_with_venv()
287
457
 
288
458
  self.initial_env_prepared = True
@@ -302,9 +472,18 @@ class TerminalToolkit(BaseToolkit):
302
472
  raise Exception("Initial environment path not set")
303
473
 
304
474
  try:
475
+ # Use the stored uv path if available, otherwise fall back to "uv"
476
+ uv_command = self.uv_path if self.uv_path else "uv"
477
+
305
478
  # Create virtual environment with Python 3.10 using uv
306
479
  subprocess.run(
307
- ["uv", "venv", "--python", "3.10", self.initial_env_path],
480
+ [
481
+ uv_command,
482
+ "venv",
483
+ "--python",
484
+ "3.10",
485
+ self.initial_env_path,
486
+ ],
308
487
  check=True,
309
488
  capture_output=True,
310
489
  cwd=self.working_dir,
@@ -332,7 +511,7 @@ class TerminalToolkit(BaseToolkit):
332
511
  ]
333
512
  subprocess.run(
334
513
  [
335
- "uv",
514
+ uv_command,
336
515
  "pip",
337
516
  "install",
338
517
  "--python",
@@ -366,10 +545,12 @@ class TerminalToolkit(BaseToolkit):
366
545
 
367
546
  try:
368
547
  # Create virtual environment with system Python
548
+ # On macOS, use symlinks=False to avoid dyld library loading issues
369
549
  venv.create(
370
550
  self.initial_env_path,
371
551
  with_pip=True,
372
552
  system_site_packages=False,
553
+ symlinks=False,
373
554
  )
374
555
 
375
556
  # Get pip path
camel/types/enums.py CHANGED
@@ -245,6 +245,7 @@ class ModelType(UnifiedModelType, Enum):
245
245
  QWEN_QWQ_32B = "qwq-32b-preview"
246
246
  QWEN_QVQ_72B = "qvq-72b-preview"
247
247
  QWEN_QWQ_PLUS = "qwq-plus"
248
+ QWEN_3_CODER_PLUS = "qwen3-coder-plus"
248
249
 
249
250
  # Yi models (01-ai)
250
251
  YI_LIGHTNING = "yi-lightning"
@@ -763,6 +764,7 @@ class ModelType(UnifiedModelType, Enum):
763
764
  ModelType.QWEN_PLUS_2025_04_28,
764
765
  ModelType.QWEN_TURBO_LATEST,
765
766
  ModelType.QWEN_TURBO_2025_04_28,
767
+ ModelType.QWEN_3_CODER_PLUS,
766
768
  }
767
769
 
768
770
  @property
@@ -1306,6 +1308,10 @@ class ModelType(UnifiedModelType, Enum):
1306
1308
  ModelType.NOVITA_LLAMA_4_MAVERICK_17B,
1307
1309
  }:
1308
1310
  return 1_048_576
1311
+ elif self in {
1312
+ ModelType.QWEN_3_CODER_PLUS,
1313
+ }:
1314
+ return 1_000_000
1309
1315
  elif self in {
1310
1316
  ModelType.QWEN_LONG,
1311
1317
  ModelType.TOGETHER_LLAMA_4_SCOUT,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camel-ai
3
- Version: 0.2.73a6
3
+ Version: 0.2.73a9
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=JP5f2mqGwiHtIMh6SJqVmdqUdZ1ig-Hw_q0RjVeoYbE,901
1
+ camel/__init__.py,sha256=5x8F3f70sOTYS-YwvaLAyGdigu-jW_3Tncrt3bj-R-s,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
@@ -268,7 +268,7 @@ camel/schemas/base.py,sha256=x0H0oIwbQR6UGdEvR5v-srI25MJ8uTrEw8nnygvLwjw,1604
268
268
  camel/schemas/openai_converter.py,sha256=SEnYsYcboZgVmjcC1YP5xke3c0MYPESPRmYQWsDGZ4Y,4362
269
269
  camel/schemas/outlines_converter.py,sha256=OYKPR1fNyrYs9eh5RiXEAccMbnRc9WTwSVJYbh9HkKE,8738
270
270
  camel/services/agent_openapi_server.py,sha256=NUCBLNZBvi4C-J1ESMyRHiRX1NDhPdPkXTMJTl0oUQo,14698
271
- camel/societies/__init__.py,sha256=NOHjtlsY-gV9UCF2xXgcbG-xXyuigmbwbpLpNsDgEJ4,826
271
+ camel/societies/__init__.py,sha256=8J_PBAGkFdL0692Mxx8dvcSRm57ibouXdIU_f1n9dJE,876
272
272
  camel/societies/babyagi_playing.py,sha256=KbTdpHfZ2V8AripVck0bNTOyF-RSaMPCRARz3DvzWfQ,11855
273
273
  camel/societies/role_playing.py,sha256=0XScr3WfxX1QOC71RhBLmrcS5y2c7DMQB_mAFOHU34M,31421
274
274
  camel/societies/workforce/__init__.py,sha256=bkTI-PE-MSK9AQ2V2gR6cR2WY-R7Jqy_NmXRtAoqo8o,920
@@ -292,7 +292,7 @@ camel/storages/key_value_storages/__init__.py,sha256=qBNx5QwKGa2wydavFYpM5MLVPkq
292
292
  camel/storages/key_value_storages/base.py,sha256=FSfxeLuG7SPvn-Mg-OQxtRKPtQBnRkB7lYeDaFOefpk,2177
293
293
  camel/storages/key_value_storages/in_memory.py,sha256=k04Nx53lYxD5MoqDtBEgZrQYkAQ-zIuU6tqnoNqiHws,1949
294
294
  camel/storages/key_value_storages/json.py,sha256=Jn7-fh2MjSMaVSCCMF_Hu-mAIj6JJ86iwKaSgI-5Uf0,3483
295
- camel/storages/key_value_storages/mem0_cloud.py,sha256=py8-n3in65K9cFnUJTxXKhzE4J0H1BSUWFDD4DT3rPg,8254
295
+ camel/storages/key_value_storages/mem0_cloud.py,sha256=kO6HL__8AwosPY0APv_JaiiOv1jf_Xa2VyZfDlzmOdo,8213
296
296
  camel/storages/key_value_storages/redis.py,sha256=Suo7wxxBXFc0fkJ8qSX2xQ26Ik_YhoKWfTOVQKUl5vA,5720
297
297
  camel/storages/object_storages/__init__.py,sha256=26yATVTD9yVH-p9KueD30JakstTGiDh89GcFtUNNe4U,915
298
298
  camel/storages/object_storages/amazon_s3.py,sha256=9Yvyyyb1LGHxr8REEza7oGopbVtLEfOyXWJc18ZwgqA,7418
@@ -333,7 +333,7 @@ camel/toolkits/dappier_toolkit.py,sha256=OEHOYXX_oXhgbVtWYAy13nO9uXf9i5qEXSwY4Pe
333
333
  camel/toolkits/data_commons_toolkit.py,sha256=aHZUSL1ACpnYGaf1rE2csVKTmXTmN8lMGRUBYhZ_YEk,14168
334
334
  camel/toolkits/edgeone_pages_mcp_toolkit.py,sha256=1TFpAGHUNLggFQeN1OEw7P5laijwnlrCkfxBtgxFuUY,2331
335
335
  camel/toolkits/excel_toolkit.py,sha256=tQaonygk0yDTPZHWWQKG5osTN-R_EawR0bJIKLsLg08,35768
336
- camel/toolkits/file_write_toolkit.py,sha256=d8N8FfmK1fS13sY58PPhJh6M0vq6yh-s1-ltCZQJObg,37044
336
+ camel/toolkits/file_write_toolkit.py,sha256=BIN4c_Tw_24iBu7vDFxVV401UTNqKZkP5p_eOOL_Hmk,39389
337
337
  camel/toolkits/function_tool.py,sha256=3_hE-Khqf556CeebchsPpjIDCynC6vKmUJLdh1EO_js,34295
338
338
  camel/toolkits/github_toolkit.py,sha256=iUyRrjWGAW_iljZVfNyfkm1Vi55wJxK6PsDAQs9pOag,13099
339
339
  camel/toolkits/google_calendar_toolkit.py,sha256=E-sdgdbtNBs_CXbhht9t1dsVr4DsTr5NguHkx4fvSmc,15410
@@ -347,7 +347,7 @@ camel/toolkits/klavis_toolkit.py,sha256=ZKerhgz5e-AV-iv0ftf07HgWikknIHjB3EOQswfu
347
347
  camel/toolkits/linkedin_toolkit.py,sha256=wn4eXwYYlVA7doTna7k7WYhUqTBF83W79S-UJs_IQr0,8065
348
348
  camel/toolkits/markitdown_toolkit.py,sha256=Cwga4sOTT1HO51CjmXubD6ieRMhumtqEN6jlsgL1nq0,2867
349
349
  camel/toolkits/math_toolkit.py,sha256=KdI8AJ9Dbq5cfWboAYJUYgSkmADMCO5eZ6yqYkWuiIQ,3686
350
- camel/toolkits/mcp_toolkit.py,sha256=da7QLwGKIKnKvMx5mOOiC56w0hKV1bvD1Z9PgrSHOtA,26999
350
+ camel/toolkits/mcp_toolkit.py,sha256=orWwKwCCL7jiPPkuoydGcM97iDRZG3udabY1VyoyPNo,40542
351
351
  camel/toolkits/memory_toolkit.py,sha256=TeKYd5UMwgjVpuS2orb-ocFL13eUNKujvrFOruDCpm8,4436
352
352
  camel/toolkits/meshy_toolkit.py,sha256=NbgdOBD3FYLtZf-AfonIv6-Q8-8DW129jsaP1PqI2rs,7126
353
353
  camel/toolkits/message_agent_toolkit.py,sha256=yWvAaxoxAvDEtD7NH7IkkHIyfWIYK47WZhn5E_RaxKo,22661
@@ -378,7 +378,7 @@ camel/toolkits/slack_toolkit.py,sha256=ZnK_fRdrQiaAUpjkgHSv1iXdv1HKEgXMlKevu3Qiv
378
378
  camel/toolkits/stripe_toolkit.py,sha256=07swo5znGTnorafC1uYLKB4NRcJIOPOx19J7tkpLYWk,10102
379
379
  camel/toolkits/sympy_toolkit.py,sha256=BAQnI8EFJydNUpKQWXBdleQ1Cm-srDBhFlqp9V9pbPQ,33757
380
380
  camel/toolkits/task_planning_toolkit.py,sha256=Ttw9fHae4omGC1SA-6uaeXVHJ1YkwiVloz_hO-fm1gw,4855
381
- camel/toolkits/terminal_toolkit.py,sha256=jRprx7Qlf-_ZgXhuFXObydwNaNLG7vqDSYNA5BTw-l4,61274
381
+ camel/toolkits/terminal_toolkit.py,sha256=7LdcG0hqZaPiaJPagXVXi87Ncm-MJIIbFUTZRtrIXes,68194
382
382
  camel/toolkits/thinking_toolkit.py,sha256=nZYLvKWIx2BM1DYu69I9B5EISAG7aYcLYXKv9663BVk,8000
383
383
  camel/toolkits/twitter_toolkit.py,sha256=Px4N8aUxUzy01LhGSWkdrC2JgwKkrY3cvxgMeJ2XYfU,15939
384
384
  camel/toolkits/video_analysis_toolkit.py,sha256=h6D7b1MAAzaHn222n_YKtwG-EGEGgMt7mBrNNVipYZc,23361
@@ -438,7 +438,7 @@ camel/toolkits/open_api_specs/web_scraper/openapi.yaml,sha256=u_WalQ01e8W1D27VnZ
438
438
  camel/toolkits/open_api_specs/web_scraper/paths/__init__.py,sha256=OKCZrQCDwaWtXIN_2rA9FSqEvgpQRieRoHh7Ek6N16A,702
439
439
  camel/toolkits/open_api_specs/web_scraper/paths/scraper.py,sha256=aWy1_ppV4NVVEZfnbN3tu9XA9yAPAC9bRStJ5JuXMRU,1117
440
440
  camel/types/__init__.py,sha256=pFTg3CWGSCfwFdoxPDTf4dKV8DdJS1x-bBPuEOmtdf0,2549
441
- camel/types/enums.py,sha256=Zf37-MvLlbZt6WLNRMDi5UWiVDN1Q2CeZvLPSbFcg_Q,63051
441
+ camel/types/enums.py,sha256=JolxBxgOfAGXAD-Z6DzMLiQ51I8H-6GrAO__1dEB_MQ,63239
442
442
  camel/types/mcp_registries.py,sha256=dl4LgYtSaUhsqAKpz28k_SA9La11qxqBvDLaEuyzrFE,4971
443
443
  camel/types/openai_types.py,sha256=8ZFzLe-zGmKNPfuVZFzxlxAX98lGf18gtrPhOgMmzus,2104
444
444
  camel/types/unified_model_type.py,sha256=U3NUZux7QuMIxPW2H0qDp9BOyDJFHAx6jUmDNw5_9KM,5912
@@ -467,7 +467,7 @@ camel/verifiers/math_verifier.py,sha256=tA1D4S0sm8nsWISevxSN0hvSVtIUpqmJhzqfbuMo
467
467
  camel/verifiers/models.py,sha256=GdxYPr7UxNrR1577yW4kyroRcLGfd-H1GXgv8potDWU,2471
468
468
  camel/verifiers/physics_verifier.py,sha256=c1grrRddcrVN7szkxhv2QirwY9viIRSITWeWFF5HmLs,30187
469
469
  camel/verifiers/python_verifier.py,sha256=ogTz77wODfEcDN4tMVtiSkRQyoiZbHPY2fKybn59lHw,20558
470
- camel_ai-0.2.73a6.dist-info/METADATA,sha256=CS0ZgtTR4f4JAwpvej1pHlG7S1LqF2K8k7D42GUSito,50434
471
- camel_ai-0.2.73a6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
472
- camel_ai-0.2.73a6.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
473
- camel_ai-0.2.73a6.dist-info/RECORD,,
470
+ camel_ai-0.2.73a9.dist-info/METADATA,sha256=DX7cvC3menqY-BKlqULVDdurx20QNDEuUDiMQtrxhM0,50434
471
+ camel_ai-0.2.73a9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
472
+ camel_ai-0.2.73a9.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
473
+ camel_ai-0.2.73a9.dist-info/RECORD,,