camel-ai 0.2.62__py3-none-any.whl → 0.2.64__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.

Files changed (46) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +76 -17
  3. camel/agents/mcp_agent.py +5 -1
  4. camel/configs/__init__.py +3 -0
  5. camel/configs/crynux_config.py +94 -0
  6. camel/interpreters/base.py +14 -1
  7. camel/interpreters/docker/Dockerfile +63 -7
  8. camel/interpreters/docker_interpreter.py +65 -7
  9. camel/interpreters/e2b_interpreter.py +23 -8
  10. camel/interpreters/internal_python_interpreter.py +30 -2
  11. camel/interpreters/ipython_interpreter.py +21 -3
  12. camel/interpreters/subprocess_interpreter.py +34 -2
  13. camel/memories/records.py +5 -3
  14. camel/models/__init__.py +2 -0
  15. camel/models/azure_openai_model.py +101 -25
  16. camel/models/cohere_model.py +65 -0
  17. camel/models/crynux_model.py +94 -0
  18. camel/models/deepseek_model.py +43 -1
  19. camel/models/gemini_model.py +50 -4
  20. camel/models/litellm_model.py +38 -0
  21. camel/models/mistral_model.py +66 -0
  22. camel/models/model_factory.py +10 -1
  23. camel/models/openai_compatible_model.py +81 -17
  24. camel/models/openai_model.py +86 -16
  25. camel/models/reka_model.py +69 -0
  26. camel/models/samba_model.py +69 -2
  27. camel/models/sglang_model.py +74 -2
  28. camel/models/watsonx_model.py +62 -0
  29. camel/societies/workforce/role_playing_worker.py +2 -2
  30. camel/societies/workforce/single_agent_worker.py +23 -0
  31. camel/societies/workforce/workforce.py +409 -7
  32. camel/storages/__init__.py +2 -0
  33. camel/storages/vectordb_storages/__init__.py +2 -0
  34. camel/storages/vectordb_storages/weaviate.py +714 -0
  35. camel/tasks/task.py +19 -10
  36. camel/toolkits/code_execution.py +37 -8
  37. camel/toolkits/mcp_toolkit.py +13 -2
  38. camel/types/enums.py +56 -1
  39. camel/types/unified_model_type.py +5 -0
  40. camel/utils/__init__.py +16 -0
  41. camel/utils/langfuse.py +258 -0
  42. camel/utils/mcp_client.py +84 -17
  43. {camel_ai-0.2.62.dist-info → camel_ai-0.2.64.dist-info}/METADATA +6 -1
  44. {camel_ai-0.2.62.dist-info → camel_ai-0.2.64.dist-info}/RECORD +46 -42
  45. {camel_ai-0.2.62.dist-info → camel_ai-0.2.64.dist-info}/WHEEL +0 -0
  46. {camel_ai-0.2.62.dist-info → camel_ai-0.2.64.dist-info}/licenses/LICENSE +0 -0
@@ -15,13 +15,13 @@ from __future__ import annotations
15
15
 
16
16
  import asyncio
17
17
  import json
18
+ import uuid
18
19
  from collections import deque
19
20
  from typing import Deque, Dict, List, Optional
20
21
 
21
22
  from colorama import Fore
22
23
 
23
24
  from camel.agents import ChatAgent
24
- from camel.configs import ChatGPTConfig
25
25
  from camel.logger import get_logger
26
26
  from camel.messages.base import BaseMessage
27
27
  from camel.models import ModelFactory
@@ -43,6 +43,7 @@ from camel.societies.workforce.worker import Worker
43
43
  from camel.tasks.task import Task, TaskState
44
44
  from camel.toolkits import CodeExecutionToolkit, SearchToolkit, ThinkingToolkit
45
45
  from camel.types import ModelPlatformType, ModelType
46
+ from camel.utils import dependencies_required
46
47
 
47
48
  logger = get_logger(__name__)
48
49
 
@@ -85,6 +86,10 @@ class Workforce(BaseNode):
85
86
  available parameters.
86
87
  (default: :obj:`None` - creates workers with SearchToolkit,
87
88
  CodeExecutionToolkit, and ThinkingToolkit)
89
+ graceful_shutdown_timeout (float, optional): The timeout in seconds
90
+ for graceful shutdown when a task fails 3 times. During this
91
+ period, the workforce remains active for debugging.
92
+ Set to 0 for immediate shutdown. (default: :obj:`15.0`)
88
93
 
89
94
  Example:
90
95
  >>> # Configure with custom model
@@ -109,11 +114,13 @@ class Workforce(BaseNode):
109
114
  coordinator_agent_kwargs: Optional[Dict] = None,
110
115
  task_agent_kwargs: Optional[Dict] = None,
111
116
  new_worker_agent_kwargs: Optional[Dict] = None,
117
+ graceful_shutdown_timeout: float = 15.0,
112
118
  ) -> None:
113
119
  super().__init__(description)
114
120
  self._child_listening_tasks: Deque[asyncio.Task] = deque()
115
121
  self._children = children or []
116
122
  self.new_worker_agent_kwargs = new_worker_agent_kwargs
123
+ self.graceful_shutdown_timeout = graceful_shutdown_timeout
117
124
 
118
125
  # Warning messages for default model usage
119
126
  if coordinator_agent_kwargs is None:
@@ -421,15 +428,10 @@ class Workforce(BaseNode):
421
428
  *ThinkingToolkit().get_tools(),
422
429
  ]
423
430
 
424
- model_config_dict = ChatGPTConfig(
425
- tools=function_list,
426
- temperature=0.0,
427
- ).as_dict()
428
-
429
431
  model = ModelFactory.create(
430
432
  model_platform=ModelPlatformType.DEFAULT,
431
433
  model_type=ModelType.DEFAULT,
432
- model_config_dict=model_config_dict,
434
+ model_config_dict={"temperature": 0},
433
435
  )
434
436
 
435
437
  return ChatAgent(worker_sys_msg, model=model, tools=function_list) # type: ignore[arg-type]
@@ -494,6 +496,26 @@ class Workforce(BaseNode):
494
496
  await self._channel.archive_task(task.id)
495
497
  await self._post_ready_tasks()
496
498
 
499
+ async def _graceful_shutdown(self, failed_task: Task) -> None:
500
+ r"""Handle graceful shutdown with configurable timeout. This is used to
501
+ keep the workforce running for a while to debug the failed task.
502
+
503
+ Args:
504
+ failed_task (Task): The task that failed and triggered shutdown.
505
+ """
506
+ if self.graceful_shutdown_timeout <= 0:
507
+ # Immediate shutdown if timeout is 0 or negative
508
+ return
509
+
510
+ logger.warning(
511
+ f"Workforce will shutdown in {self.graceful_shutdown_timeout} "
512
+ f"seconds due to failure. You can use this time to inspect the "
513
+ f"current state of the workforce."
514
+ )
515
+
516
+ # Wait for the full timeout period
517
+ await asyncio.sleep(self.graceful_shutdown_timeout)
518
+
497
519
  @check_if_running(False)
498
520
  async def _listen_to_channel(self) -> None:
499
521
  r"""Continuously listen to the channel, post task to the channel and
@@ -517,6 +539,8 @@ class Workforce(BaseNode):
517
539
  f"{Fore.RED}Task {returned_task.id} has failed "
518
540
  f"for 3 times, halting the workforce.{Fore.RESET}"
519
541
  )
542
+ # Graceful shutdown instead of immediate break
543
+ await self._graceful_shutdown(returned_task)
520
544
  break
521
545
  elif returned_task.state == TaskState.OPEN:
522
546
  # TODO: multi-layer workforce
@@ -568,6 +592,7 @@ class Workforce(BaseNode):
568
592
  coordinator_agent_kwargs={},
569
593
  task_agent_kwargs={},
570
594
  new_worker_agent_kwargs=self.new_worker_agent_kwargs,
595
+ graceful_shutdown_timeout=self.graceful_shutdown_timeout,
571
596
  )
572
597
 
573
598
  new_instance.task_agent = self.task_agent.clone(with_memory)
@@ -598,3 +623,380 @@ class Workforce(BaseNode):
598
623
  continue
599
624
 
600
625
  return new_instance
626
+
627
+ @dependencies_required("mcp")
628
+ def to_mcp(
629
+ self,
630
+ name: str = "CAMEL-Workforce",
631
+ description: str = (
632
+ "A workforce system using the CAMEL AI framework for "
633
+ "multi-agent collaboration."
634
+ ),
635
+ dependencies: Optional[List[str]] = None,
636
+ host: str = "localhost",
637
+ port: int = 8001,
638
+ ):
639
+ r"""Expose this Workforce as an MCP server.
640
+
641
+ Args:
642
+ name (str): Name of the MCP server.
643
+ (default: :obj:`CAMEL-Workforce`)
644
+ description (str): Description of the workforce. If
645
+ None, a generic description is used. (default: :obj:`A
646
+ workforce system using the CAMEL AI framework for
647
+ multi-agent collaboration.`)
648
+ dependencies (Optional[List[str]]): Additional
649
+ dependencies for the MCP server. (default: :obj:`None`)
650
+ host (str): Host to bind to for HTTP transport.
651
+ (default: :obj:`localhost`)
652
+ port (int): Port to bind to for HTTP transport.
653
+ (default: :obj:`8001`)
654
+
655
+ Returns:
656
+ FastMCP: An MCP server instance that can be run.
657
+ """
658
+ from mcp.server.fastmcp import FastMCP
659
+
660
+ # Combine dependencies
661
+ all_dependencies = ["camel-ai[all]"]
662
+ if dependencies:
663
+ all_dependencies.extend(dependencies)
664
+
665
+ mcp_server = FastMCP(
666
+ name,
667
+ dependencies=all_dependencies,
668
+ host=host,
669
+ port=port,
670
+ )
671
+
672
+ # Store workforce reference
673
+ workforce_instance = self
674
+
675
+ # Define functions first
676
+ def process_task(task_content, task_id=None, additional_info=None):
677
+ r"""Process a task using the workforce.
678
+
679
+ Args:
680
+ task_content (str): The content of the task to be processed.
681
+ task_id (str, optional): Unique identifier for the task. If
682
+ None, a UUID will be automatically generated.
683
+ (default: :obj:`None`)
684
+ additional_info (str, optional): Additional information or
685
+ context for the task. (default: :obj:`None`)
686
+
687
+ Returns:
688
+ Dict[str, Any]: A dictionary containing the processing result
689
+ with the following keys:
690
+ - status (str): "success" or "error"
691
+ - task_id (str): The ID of the processed task
692
+ - state (str): Final state of the task
693
+ - result (str): Task result content
694
+ - subtasks (List[Dict]): List of subtask information
695
+ - message (str): Error message if status is "error"
696
+
697
+ Example:
698
+ >>> result = process_task("Analyze market trends", "task_001")
699
+ >>> print(result["status"]) # "success" or "error"
700
+ """
701
+ task = Task(
702
+ content=task_content,
703
+ id=task_id or str(uuid.uuid4()),
704
+ additional_info=additional_info or "",
705
+ )
706
+
707
+ try:
708
+ result_task = workforce_instance.process_task(task)
709
+ return {
710
+ "status": "success",
711
+ "task_id": result_task.id,
712
+ "state": str(result_task.state),
713
+ "result": result_task.result or "",
714
+ "subtasks": [
715
+ {
716
+ "id": subtask.id,
717
+ "content": subtask.content,
718
+ "state": str(subtask.state),
719
+ "result": subtask.result or "",
720
+ }
721
+ for subtask in (result_task.subtasks or [])
722
+ ],
723
+ }
724
+ except Exception as e:
725
+ return {
726
+ "status": "error",
727
+ "message": str(e),
728
+ "task_id": task.id,
729
+ }
730
+
731
+ # Reset tool
732
+ def reset():
733
+ r"""Reset the workforce to its initial state.
734
+
735
+ Clears all pending tasks, resets all child nodes, and returns
736
+ the workforce to a clean state ready for new task processing.
737
+
738
+ Returns:
739
+ Dict[str, str]: A dictionary containing the reset result with:
740
+ - status (str): "success" or "error"
741
+ - message (str): Descriptive message about the operation
742
+
743
+ Example:
744
+ >>> result = reset()
745
+ >>> print(result["message"]) # "Workforce reset successfully"
746
+ """
747
+ try:
748
+ workforce_instance.reset()
749
+ return {
750
+ "status": "success",
751
+ "message": "Workforce reset successfully",
752
+ }
753
+ except Exception as e:
754
+ return {"status": "error", "message": str(e)}
755
+
756
+ # Workforce info resource and tool
757
+ def get_workforce_info():
758
+ r"""Get comprehensive information about the workforce.
759
+
760
+ Retrieves the current state and configuration of the workforce
761
+ including its ID, description, running status, and task queue
762
+ information.
763
+
764
+ Returns:
765
+ Dict[str, Any]: A dictionary containing workforce information:
766
+ - node_id (str): Unique identifier of the workforce
767
+ - description (str): Workforce description
768
+ - mcp_description (str): MCP server description
769
+ - children_count (int): Number of child workers
770
+ - is_running (bool): Whether the workforce is active
771
+ - pending_tasks_count (int): Number of queued tasks
772
+ - current_task_id (str or None): ID of the active task
773
+
774
+ Example:
775
+ >>> info = get_workforce_info()
776
+ >>> print(f"Running: {info['is_running']}")
777
+ >>> print(f"Children: {info['children_count']}")
778
+ """
779
+ info = {
780
+ "node_id": workforce_instance.node_id,
781
+ "description": workforce_instance.description,
782
+ "mcp_description": description,
783
+ "children_count": len(workforce_instance._children),
784
+ "is_running": workforce_instance._running,
785
+ "pending_tasks_count": len(workforce_instance._pending_tasks),
786
+ "current_task_id": (
787
+ workforce_instance._task.id
788
+ if workforce_instance._task
789
+ else None
790
+ ),
791
+ }
792
+ return info
793
+
794
+ # Children info resource and tool
795
+ def get_children_info():
796
+ r"""Get information about all child nodes in the workforce.
797
+
798
+ Retrieves comprehensive information about each child worker
799
+ including their type, capabilities, and configuration details.
800
+
801
+ Returns:
802
+ List[Dict[str, Any]]: A list of dictionaries, each containing
803
+ child node information with common keys:
804
+ - node_id (str): Unique identifier of the child
805
+ - description (str): Child node description
806
+ - type (str): Type of worker (e.g., "SingleAgentWorker")
807
+
808
+ Additional keys depend on worker type:
809
+
810
+ For SingleAgentWorker:
811
+ - tools (List[str]): Available tool names
812
+ - role_name (str): Agent's role name
813
+
814
+ For RolePlayingWorker:
815
+ - assistant_role (str): Assistant agent role
816
+ - user_role (str): User agent role
817
+ - chat_turn_limit (int): Maximum conversation turns
818
+
819
+ For Workforce:
820
+ - children_count (int): Number of nested children
821
+ - is_running (bool): Whether the nested workforce is active
822
+
823
+ Example:
824
+ >>> children = get_children_info()
825
+ >>> for child in children:
826
+ ... print(f"{child['type']}: {child['description']}")
827
+ """
828
+ children_info = []
829
+ for child in workforce_instance._children:
830
+ child_info = {
831
+ "node_id": child.node_id,
832
+ "description": child.description,
833
+ "type": type(child).__name__,
834
+ }
835
+
836
+ if isinstance(child, SingleAgentWorker):
837
+ child_info["tools"] = list(child.worker.tool_dict.keys())
838
+ child_info["role_name"] = child.worker.role_name
839
+ elif isinstance(child, RolePlayingWorker):
840
+ child_info["assistant_role"] = child.assistant_role_name
841
+ child_info["user_role"] = child.user_role_name
842
+ child_info["chat_turn_limit"] = child.chat_turn_limit
843
+ elif isinstance(child, Workforce):
844
+ child_info["children_count"] = len(child._children)
845
+ child_info["is_running"] = child._running
846
+
847
+ children_info.append(child_info)
848
+
849
+ return children_info
850
+
851
+ # Add single agent worker
852
+ def add_single_agent_worker(
853
+ description,
854
+ system_message=None,
855
+ role_name="Assistant",
856
+ agent_kwargs=None,
857
+ ):
858
+ r"""Add a single agent worker to the workforce.
859
+
860
+ Creates and adds a new SingleAgentWorker to the workforce with
861
+ the specified configuration. The worker cannot be added while
862
+ the workforce is currently running.
863
+
864
+ Args:
865
+ description (str): Description of the worker's role and
866
+ capabilities.
867
+ system_message (str, optional): Custom system message for the
868
+ agent. If None, a default message based on role_name is
869
+ used. (default: :obj:`None`)
870
+ role_name (str, optional): Name of the agent's role.
871
+ (default: :obj:`"Assistant"`)
872
+ agent_kwargs (Dict, optional): Additional keyword arguments
873
+ to pass to the ChatAgent constructor, such as model
874
+ configuration, tools, etc. (default: :obj:`None`)
875
+
876
+ Returns:
877
+ Dict[str, str]: A dictionary containing the operation result:
878
+ - status (str): "success" or "error"
879
+ - message (str): Descriptive message about the operation
880
+ - worker_id (str): ID of the created worker (on success)
881
+
882
+ Example:
883
+ >>> result = add_single_agent_worker(
884
+ ... "Data Analyst",
885
+ ... "You are a data analysis expert.",
886
+ ... "Analyst"
887
+ ... )
888
+ >>> print(result["status"]) # "success" or "error"
889
+ """
890
+ try:
891
+ if workforce_instance._running:
892
+ return {
893
+ "status": "error",
894
+ "message": "Cannot add workers while workforce is running", # noqa: E501
895
+ }
896
+
897
+ # Create agent with provided configuration
898
+ sys_msg = BaseMessage.make_assistant_message(
899
+ role_name=role_name,
900
+ content=system_message or f"You are a {role_name}.",
901
+ )
902
+
903
+ agent = ChatAgent(sys_msg, **(agent_kwargs or {}))
904
+ workforce_instance.add_single_agent_worker(description, agent)
905
+
906
+ return {
907
+ "status": "success",
908
+ "message": f"Single agent worker '{description}' added",
909
+ "worker_id": workforce_instance._children[-1].node_id,
910
+ }
911
+ except Exception as e:
912
+ return {"status": "error", "message": str(e)}
913
+
914
+ # Add role playing worker
915
+ def add_role_playing_worker(
916
+ description,
917
+ assistant_role_name,
918
+ user_role_name,
919
+ chat_turn_limit=20,
920
+ assistant_agent_kwargs=None,
921
+ user_agent_kwargs=None,
922
+ summarize_agent_kwargs=None,
923
+ ):
924
+ r"""Add a role playing worker to the workforce.
925
+
926
+ Creates and adds a new RolePlayingWorker to the workforce that
927
+ uses two agents in a conversational role-playing setup. The
928
+ worker cannot be added while the workforce is currently running.
929
+
930
+ Args:
931
+ description (str): Description of the role playing worker's
932
+ purpose and capabilities.
933
+ assistant_role_name (str): Name/role of the assistant agent
934
+ in the role playing scenario.
935
+ user_role_name (str): Name/role of the user agent in the
936
+ role playing scenario.
937
+ chat_turn_limit (int, optional): Maximum number of
938
+ conversation turns between the two agents.
939
+ (default: :obj:`20`)
940
+ assistant_agent_kwargs (Dict, optional): Keyword arguments
941
+ for configuring the assistant ChatAgent, such as model
942
+ type, tools, etc. (default: :obj:`None`)
943
+ user_agent_kwargs (Dict, optional): Keyword arguments for
944
+ configuring the user ChatAgent, such as model type,
945
+ tools, etc. (default: :obj:`None`)
946
+ summarize_agent_kwargs (Dict, optional): Keyword arguments
947
+ for configuring the summarization agent used to process
948
+ the conversation results. (default: :obj:`None`)
949
+
950
+ Returns:
951
+ Dict[str, str]: A dictionary containing the operation result:
952
+ - status (str): "success" or "error"
953
+ - message (str): Descriptive message about the operation
954
+ - worker_id (str): ID of the created worker (on success)
955
+
956
+ Example:
957
+ >>> result = add_role_playing_worker(
958
+ ... "Design Review Team",
959
+ ... "Design Critic",
960
+ ... "Design Presenter",
961
+ ... chat_turn_limit=5
962
+ ... )
963
+ >>> print(result["status"]) # "success" or "error"
964
+ """
965
+ try:
966
+ if workforce_instance._running:
967
+ return {
968
+ "status": "error",
969
+ "message": "Cannot add workers while workforce is running", # noqa: E501
970
+ }
971
+
972
+ workforce_instance.add_role_playing_worker(
973
+ description=description,
974
+ assistant_role_name=assistant_role_name,
975
+ user_role_name=user_role_name,
976
+ chat_turn_limit=chat_turn_limit,
977
+ assistant_agent_kwargs=assistant_agent_kwargs,
978
+ user_agent_kwargs=user_agent_kwargs,
979
+ summarize_agent_kwargs=summarize_agent_kwargs,
980
+ )
981
+
982
+ return {
983
+ "status": "success",
984
+ "message": f"Role playing worker '{description}' added",
985
+ "worker_id": workforce_instance._children[-1].node_id,
986
+ }
987
+ except Exception as e:
988
+ return {"status": "error", "message": str(e)}
989
+
990
+ # Now register everything using decorators
991
+ mcp_server.tool()(process_task)
992
+ mcp_server.tool()(reset)
993
+ mcp_server.tool()(add_single_agent_worker)
994
+ mcp_server.tool()(add_role_playing_worker)
995
+
996
+ mcp_server.resource("workforce://")(get_workforce_info)
997
+ mcp_server.tool()(get_workforce_info)
998
+
999
+ mcp_server.resource("children://")(get_children_info)
1000
+ mcp_server.tool()(get_children_info)
1001
+
1002
+ return mcp_server
@@ -31,6 +31,7 @@ from .vectordb_storages.milvus import MilvusStorage
31
31
  from .vectordb_storages.oceanbase import OceanBaseStorage
32
32
  from .vectordb_storages.qdrant import QdrantStorage
33
33
  from .vectordb_storages.tidb import TiDBStorage
34
+ from .vectordb_storages.weaviate import WeaviateStorage
34
35
 
35
36
  __all__ = [
36
37
  'BaseKeyValueStorage',
@@ -50,4 +51,5 @@ __all__ = [
50
51
  'NebulaGraph',
51
52
  'Mem0Storage',
52
53
  'OceanBaseStorage',
54
+ 'WeaviateStorage',
53
55
  ]
@@ -24,6 +24,7 @@ from .milvus import MilvusStorage
24
24
  from .oceanbase import OceanBaseStorage
25
25
  from .qdrant import QdrantStorage
26
26
  from .tidb import TiDBStorage
27
+ from .weaviate import WeaviateStorage
27
28
 
28
29
  __all__ = [
29
30
  'BaseVectorStorage',
@@ -34,6 +35,7 @@ __all__ = [
34
35
  "TiDBStorage",
35
36
  'FaissStorage',
36
37
  'OceanBaseStorage',
38
+ 'WeaviateStorage',
37
39
  'VectorRecord',
38
40
  'VectorDBStatus',
39
41
  ]