camel-ai 0.2.60__py3-none-any.whl → 0.2.62__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 (53) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +159 -8
  3. camel/agents/mcp_agent.py +5 -5
  4. camel/configs/anthropic_config.py +6 -5
  5. camel/{data_collector → data_collectors}/alpaca_collector.py +1 -1
  6. camel/{data_collector → data_collectors}/sharegpt_collector.py +1 -1
  7. camel/datagen/evol_instruct/scorer.py +22 -23
  8. camel/datagen/evol_instruct/templates.py +46 -46
  9. camel/datasets/static_dataset.py +144 -0
  10. camel/loaders/__init__.py +5 -2
  11. camel/loaders/chunkr_reader.py +117 -91
  12. camel/loaders/mistral_reader.py +148 -0
  13. camel/memories/blocks/chat_history_block.py +1 -2
  14. camel/models/model_manager.py +7 -3
  15. camel/retrievers/auto_retriever.py +20 -1
  16. camel/{runtime → runtimes}/daytona_runtime.py +1 -1
  17. camel/{runtime → runtimes}/docker_runtime.py +1 -1
  18. camel/{runtime → runtimes}/llm_guard_runtime.py +2 -2
  19. camel/{runtime → runtimes}/remote_http_runtime.py +1 -1
  20. camel/{runtime → runtimes}/ubuntu_docker_runtime.py +1 -1
  21. camel/societies/workforce/base.py +7 -3
  22. camel/societies/workforce/single_agent_worker.py +2 -1
  23. camel/societies/workforce/worker.py +5 -3
  24. camel/societies/workforce/workforce.py +65 -24
  25. camel/storages/__init__.py +2 -0
  26. camel/storages/vectordb_storages/__init__.py +2 -0
  27. camel/storages/vectordb_storages/faiss.py +712 -0
  28. camel/toolkits/__init__.py +4 -0
  29. camel/toolkits/async_browser_toolkit.py +75 -523
  30. camel/toolkits/bohrium_toolkit.py +318 -0
  31. camel/toolkits/browser_toolkit.py +215 -538
  32. camel/toolkits/browser_toolkit_commons.py +568 -0
  33. camel/toolkits/file_write_toolkit.py +80 -31
  34. camel/toolkits/mcp_toolkit.py +477 -665
  35. camel/toolkits/pptx_toolkit.py +777 -0
  36. camel/toolkits/wolfram_alpha_toolkit.py +5 -1
  37. camel/types/enums.py +13 -1
  38. camel/utils/__init__.py +2 -0
  39. camel/utils/commons.py +27 -0
  40. camel/utils/mcp_client.py +979 -0
  41. {camel_ai-0.2.60.dist-info → camel_ai-0.2.62.dist-info}/METADATA +14 -1
  42. {camel_ai-0.2.60.dist-info → camel_ai-0.2.62.dist-info}/RECORD +53 -47
  43. /camel/{data_collector → data_collectors}/__init__.py +0 -0
  44. /camel/{data_collector → data_collectors}/base.py +0 -0
  45. /camel/{runtime → runtimes}/__init__.py +0 -0
  46. /camel/{runtime → runtimes}/api.py +0 -0
  47. /camel/{runtime → runtimes}/base.py +0 -0
  48. /camel/{runtime → runtimes}/configs.py +0 -0
  49. /camel/{runtime → runtimes}/utils/__init__.py +0 -0
  50. /camel/{runtime → runtimes}/utils/function_risk_toolkit.py +0 -0
  51. /camel/{runtime → runtimes}/utils/ignore_risk_toolkit.py +0 -0
  52. {camel_ai-0.2.60.dist-info → camel_ai-0.2.62.dist-info}/WHEEL +0 -0
  53. {camel_ai-0.2.60.dist-info → camel_ai-0.2.62.dist-info}/licenses/LICENSE +0 -0
@@ -13,16 +13,38 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
 
15
15
  import json
16
- import logging
17
16
  import os
18
- import time
19
- from typing import IO, Any, Optional, Union
17
+ from typing import TYPE_CHECKING, Optional
20
18
 
21
- import requests
19
+ if TYPE_CHECKING:
20
+ from chunkr_ai.models import Configuration
22
21
 
22
+ from camel.logger import get_logger
23
23
  from camel.utils import api_keys_required
24
24
 
25
- logger = logging.getLogger(__name__)
25
+ logger = get_logger(__name__)
26
+
27
+
28
+ class ChunkrReaderConfig:
29
+ r"""Defines the parameters for configuring the task.
30
+
31
+ Args:
32
+ chunk_processing (int, optional): The target chunk length.
33
+ (default: :obj:`512`)
34
+ high_resolution (bool, optional): Whether to use high resolution OCR.
35
+ (default: :obj:`True`)
36
+ ocr_strategy (str, optional): The OCR strategy. Defaults to 'Auto'.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ chunk_processing: int = 512,
42
+ high_resolution: bool = True,
43
+ ocr_strategy: str = "Auto",
44
+ ):
45
+ self.chunk_processing = chunk_processing
46
+ self.high_resolution = high_resolution
47
+ self.ocr_strategy = ocr_strategy
26
48
 
27
49
 
28
50
  class ChunkrReader:
@@ -35,8 +57,6 @@ class ChunkrReader:
35
57
  `CHUNKR_API_KEY`. (default: :obj:`None`)
36
58
  url (Optional[str], optional): The url to the Chunkr service.
37
59
  (default: :obj:`https://api.chunkr.ai/api/v1/task`)
38
- timeout (int, optional): The maximum time in seconds to wait for the
39
- API responses. (default: :obj:`30`)
40
60
  **kwargs (Any): Additional keyword arguments for request headers.
41
61
  """
42
62
 
@@ -49,111 +69,80 @@ class ChunkrReader:
49
69
  self,
50
70
  api_key: Optional[str] = None,
51
71
  url: Optional[str] = "https://api.chunkr.ai/api/v1/task",
52
- timeout: int = 30,
53
- **kwargs: Any,
54
72
  ) -> None:
73
+ from chunkr_ai import Chunkr
74
+
55
75
  self._api_key = api_key or os.getenv('CHUNKR_API_KEY')
56
- self._url = os.getenv('CHUNKR_API_URL') or url
57
- self._headers = {
58
- "Authorization": f"{self._api_key}",
59
- **kwargs,
60
- }
61
- self.timeout = timeout
62
-
63
- def submit_task(
76
+ self._chunkr = Chunkr(api_key=self._api_key)
77
+
78
+ async def submit_task(
64
79
  self,
65
80
  file_path: str,
66
- model: str = "Fast",
67
- ocr_strategy: str = "Auto",
68
- target_chunk_length: str = "512",
81
+ chunkr_config: Optional[ChunkrReaderConfig] = None,
69
82
  ) -> str:
70
83
  r"""Submits a file to the Chunkr API and returns the task ID.
71
84
 
72
85
  Args:
73
86
  file_path (str): The path to the file to be uploaded.
74
- model (str, optional): The model to be used for the task.
75
- (default: :obj:`Fast`)
76
- ocr_strategy (str, optional): The OCR strategy. Defaults to 'Auto'.
77
- target_chunk_length (str, optional): The target chunk length.
78
- (default: :obj:`512`)
87
+ chunkr_config (ChunkrReaderConfig, optional): The configuration
88
+ for the Chunkr API. Defaults to None.
79
89
 
80
90
  Returns:
81
91
  str: The task ID.
82
92
  """
83
- with open(file_path, 'rb') as file:
84
- files: dict[
85
- str, Union[tuple[None, IO[bytes]], tuple[None, str]]
86
- ] = {
87
- 'file': (
88
- None,
89
- file,
90
- ), # Properly pass the file as a binary stream
91
- 'model': (None, model),
92
- 'ocr_strategy': (None, ocr_strategy),
93
- 'target_chunk_length': (None, target_chunk_length),
94
- }
95
- try:
96
- response = requests.post(
97
- self._url, # type: ignore[arg-type]
98
- headers=self._headers,
99
- files=files,
100
- timeout=self.timeout,
101
- )
102
- response.raise_for_status()
103
- task_id = response.json().get('task_id')
104
- if not task_id:
105
- raise ValueError("Task ID not returned in the response.")
106
- logger.info(f"Task submitted successfully. Task ID: {task_id}")
107
- return task_id
108
- except Exception as e:
109
- logger.error(f"Failed to submit task: {e}")
110
- raise ValueError(f"Failed to submit task: {e}") from e
111
-
112
- def get_task_output(self, task_id: str, max_retries: int = 5) -> str:
93
+ chunkr_config = self._to_chunkr_configuration(
94
+ chunkr_config or ChunkrReaderConfig()
95
+ )
96
+
97
+ try:
98
+ task = await self._chunkr.create_task(
99
+ file=file_path, config=chunkr_config
100
+ )
101
+ logger.info(
102
+ f"Task submitted successfully. Task ID: {task.task_id}"
103
+ )
104
+ return task.task_id
105
+ except Exception as e:
106
+ logger.error(f"Failed to submit task: {e}")
107
+ raise ValueError(f"Failed to submit task: {e}") from e
108
+
109
+ async def get_task_output(self, task_id: str) -> str | None:
113
110
  r"""Polls the Chunkr API to check the task status and returns the task
114
111
  result.
115
112
 
116
113
  Args:
117
114
  task_id (str): The task ID to check the status for.
118
- max_retries (int, optional): Maximum number of retry attempts.
119
- (default: :obj:`5`)
120
115
 
121
116
  Returns:
122
- str: The formatted task result in JSON format.
123
-
124
- Raises:
125
- ValueError: If the task status cannot be retrieved.
126
- RuntimeError: If the maximum number of retries is reached without
127
- a successful task completion.
117
+ Optional[str]: The formatted task result in JSON format, or `None`
118
+ if the task fails or is canceld.
128
119
  """
129
- url_get = f"{self._url}/{task_id}"
130
- attempts = 0
131
-
132
- while attempts < max_retries:
133
- try:
134
- response = requests.get(
135
- url_get, headers=self._headers, timeout=self.timeout
120
+ from chunkr_ai.models import Status
121
+
122
+ try:
123
+ task = await self._chunkr.get_task(task_id)
124
+ except Exception as e:
125
+ logger.error(f"Failed to get task by task id: {task_id}: {e}")
126
+ raise ValueError(
127
+ f"Failed to get task by task id: {task_id}: {e}"
128
+ ) from e
129
+
130
+ try:
131
+ await task.poll()
132
+ if task.status == Status.SUCCEEDED:
133
+ logger.info(f"Task {task_id} completed successfully.")
134
+ return self._pretty_print_response(task.json())
135
+ elif task.status == Status.FAILED:
136
+ logger.warning(
137
+ f"Task {task_id} encountered an error: {task.message}"
136
138
  )
137
- response.raise_for_status()
138
- task_status = response.json().get('status')
139
-
140
- if task_status == "Succeeded":
141
- logger.info(f"Task {task_id} completed successfully.")
142
- return self._pretty_print_response(response.json())
143
- else:
144
- logger.info(
145
- f"Task {task_id} is still {task_status}. Retrying "
146
- "in 5 seconds..."
147
- )
148
- except Exception as e:
149
- logger.error(f"Failed to retrieve task status: {e}")
150
- raise ValueError(f"Failed to retrieve task status: {e}") from e
151
-
152
- attempts += 1
153
- time.sleep(5)
154
-
155
- logger.error(f"Max retries reached for task {task_id}.")
156
- raise RuntimeError(f"Max retries reached for task {task_id}.")
139
+ return None
140
+ else:
141
+ logger.warning(f"Task {task_id} was manually cancelled.")
142
+ return None
143
+ except Exception as e:
144
+ logger.error(f"Failed to retrieve task status: {e}")
145
+ raise ValueError(f"Failed to retrieve task status: {e}") from e
157
146
 
158
147
  def _pretty_print_response(self, response_json: dict) -> str:
159
148
  r"""Pretty prints the JSON response.
@@ -164,4 +153,41 @@ class ChunkrReader:
164
153
  Returns:
165
154
  str: Formatted JSON as a string.
166
155
  """
167
- return json.dumps(response_json, indent=4, ensure_ascii=False)
156
+ from datetime import datetime
157
+
158
+ return json.dumps(
159
+ response_json,
160
+ default=lambda o: o.isoformat()
161
+ if isinstance(o, datetime)
162
+ else None,
163
+ indent=4,
164
+ )
165
+
166
+ def _to_chunkr_configuration(
167
+ self, chunkr_config: ChunkrReaderConfig
168
+ ) -> "Configuration":
169
+ r"""Converts the ChunkrReaderConfig to Chunkr Configuration.
170
+
171
+ Args:
172
+ chunkr_config (ChunkrReaderConfig):
173
+ The ChunkrReaderConfig to convert.
174
+
175
+ Returns:
176
+ Configuration: Chunkr SDK configuration.
177
+ """
178
+ from chunkr_ai.models import (
179
+ ChunkProcessing,
180
+ Configuration,
181
+ OcrStrategy,
182
+ )
183
+
184
+ return Configuration(
185
+ chunk_processing=ChunkProcessing(
186
+ target_length=chunkr_config.chunk_processing
187
+ ),
188
+ high_resolution=chunkr_config.high_resolution,
189
+ ocr_strategy={
190
+ "Auto": OcrStrategy.AUTO,
191
+ "All": OcrStrategy.ALL,
192
+ }.get(chunkr_config.ocr_strategy, OcrStrategy.ALL),
193
+ )
@@ -0,0 +1,148 @@
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import os
15
+ from typing import TYPE_CHECKING, List, Optional
16
+
17
+ if TYPE_CHECKING:
18
+ from mistralai.models import OCRResponse
19
+
20
+ from camel.logger import get_logger
21
+ from camel.utils import api_keys_required
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ class MistralReader:
27
+ r"""Mistral Document Loader."""
28
+
29
+ @api_keys_required(
30
+ [
31
+ ("api_key", "MISTRAL_API_KEY"),
32
+ ]
33
+ )
34
+ def __init__(
35
+ self,
36
+ api_key: Optional[str] = None,
37
+ model: Optional[str] = "mistral-ocr-latest",
38
+ ) -> None:
39
+ r"""Initialize the MistralReader.
40
+
41
+ Args:
42
+ api_key (Optional[str]): The API key for the Mistral API.
43
+ (default: :obj:`None`)
44
+ model (Optional[str]): The model to use for OCR.
45
+ (default: :obj:`"mistral-ocr-latest"`)
46
+ """
47
+ from mistralai import Mistral
48
+
49
+ self._api_key = api_key or os.environ.get("MISTRAL_API_KEY")
50
+ self.client = Mistral(api_key=self._api_key)
51
+ self.model = model
52
+
53
+ def _encode_file(self, file_path: str) -> str:
54
+ r"""Encode the pdf to base64.
55
+
56
+ Args:
57
+ file_path (str): Path to the input file.
58
+
59
+ Returns:
60
+ str: base64 version of the file.
61
+ """
62
+
63
+ import base64
64
+
65
+ try:
66
+ with open(file_path, "rb") as pdf_file:
67
+ return base64.b64encode(pdf_file.read()).decode('utf-8')
68
+ except FileNotFoundError:
69
+ logger.error(f"Error: The file {file_path} was not found.")
70
+ return ""
71
+ except Exception as e:
72
+ logger.error(f"Error: {e}")
73
+ return ""
74
+
75
+ def extract_text(
76
+ self,
77
+ file_path: str,
78
+ is_image: bool = False,
79
+ pages: Optional[List[int]] = None,
80
+ include_image_base64: Optional[bool] = None,
81
+ ) -> "OCRResponse":
82
+ r"""Converts the given file to Markdown format.
83
+
84
+ Args:
85
+ file_path (str): Path to the input file or a remote URL.
86
+ is_image (bool): Whether the file or URL is an image. If True,
87
+ uses image_url type instead of document_url.
88
+ (default: :obj:`False`)
89
+ pages (Optional[List[int]]): Specific pages user wants to process
90
+ in various formats: single number, range, or list of both.
91
+ Starts from 0. (default: :obj:`None`)
92
+ include_image_base64 (Optional[bool]): Whether to include image
93
+ URLs in response. (default: :obj:`None`)
94
+
95
+ Returns:
96
+ OCRResponse: page wise extractions.
97
+
98
+ Raises:
99
+ FileNotFoundError: If the specified local file does not exist.
100
+ ValueError: If the file format is not supported.
101
+ Exception: For other errors during conversion.
102
+ """
103
+ # Check if the input is a URL (starts with http:// or https://)
104
+ is_url = file_path.startswith(('http://', 'https://'))
105
+
106
+ if not is_url and not os.path.isfile(file_path):
107
+ logger.error(f"File not found: {file_path}")
108
+ raise FileNotFoundError(f"File not found: {file_path}")
109
+ try:
110
+ if is_url:
111
+ logger.info(f"Processing URL: {file_path}")
112
+ if is_image:
113
+ document_config = {
114
+ "type": "image_url",
115
+ "image_url": file_path,
116
+ }
117
+ else:
118
+ document_config = {
119
+ "type": "document_url",
120
+ "document_url": file_path,
121
+ }
122
+ else:
123
+ logger.info(f"Converting local file: {file_path}")
124
+ base64_file = self._encode_file(file_path)
125
+ if is_image:
126
+ document_config = {
127
+ "type": "image_url",
128
+ "image_url": f"data:image/jpeg;base64,{base64_file}",
129
+ }
130
+ else:
131
+ document_config = {
132
+ "type": "document_url",
133
+ "document_url": f"data:application/"
134
+ f"pdf;base64,{base64_file}",
135
+ }
136
+
137
+ ocr_response = self.client.ocr.process(
138
+ model=self.model,
139
+ document=document_config, # type: ignore[arg-type]
140
+ pages=None if is_image else pages,
141
+ include_image_base64=include_image_base64,
142
+ )
143
+
144
+ logger.info(f"Processing completed successfully for: {file_path}")
145
+ return ocr_response
146
+ except Exception as e:
147
+ logger.error(f"Error processing '{file_path}': {e}")
148
+ raise ValueError(f"Error processing '{file_path}': {e}")
@@ -73,7 +73,6 @@ class ChatHistoryBlock(MemoryBlock):
73
73
  warnings.warn("The `ChatHistoryMemory` is empty.")
74
74
  return list()
75
75
 
76
- chat_records: List[MemoryRecord] = []
77
76
  if window_size is not None and window_size >= 0:
78
77
  # Initial preserved index: Keep first message
79
78
  # if it's SYSTEM/DEVELOPER (index 0)
@@ -117,7 +116,7 @@ class ChatHistoryBlock(MemoryBlock):
117
116
  # Return full records when no window restriction
118
117
  final_records = record_dicts
119
118
 
120
- chat_records = [
119
+ chat_records: List[MemoryRecord] = [
121
120
  MemoryRecord.from_dict(record) for record in final_records
122
121
  ]
123
122
 
@@ -12,6 +12,7 @@
12
12
  # limitations under the License.
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
 
15
+ import asyncio
15
16
  import logging
16
17
  from itertools import cycle
17
18
  from random import choice
@@ -69,6 +70,7 @@ class ModelManager:
69
70
  self.models = [models]
70
71
  self.models_cycle = cycle(self.models)
71
72
  self.current_model = self.models[0]
73
+ self.lock = asyncio.Lock()
72
74
 
73
75
  # Set the scheduling strategy; default is round-robin
74
76
  try:
@@ -246,7 +248,8 @@ class ModelManager:
246
248
  `ChatCompletion` in the non-stream mode, or
247
249
  `AsyncStream[ChatCompletionChunk]` in the stream mode.
248
250
  """
249
- self.current_model = self.scheduling_strategy()
251
+ async with self.lock:
252
+ self.current_model = self.scheduling_strategy()
250
253
 
251
254
  # Pass all messages to the selected model and get the response
252
255
  try:
@@ -260,7 +263,8 @@ class ModelManager:
260
263
  logger.warning(
261
264
  "The scheduling strategy has been changed to 'round_robin'"
262
265
  )
263
- # Skip already used one
264
- self.current_model = self.scheduling_strategy()
266
+ async with self.lock:
267
+ # Skip already used one
268
+ self.current_model = self.scheduling_strategy()
265
269
  raise exc
266
270
  return response
@@ -128,12 +128,31 @@ class AutoRetriever:
128
128
  Returns:
129
129
  str: A sanitized, valid collection name suitable for use.
130
130
  """
131
+ import hashlib
132
+ import os
133
+
131
134
  from unstructured.documents.elements import Element
132
135
 
133
136
  if isinstance(content, Element):
134
137
  content = content.metadata.file_directory or str(uuid.uuid4())
135
138
 
136
- collection_name = re.sub(r'[^a-zA-Z0-9]', '', content)[:20]
139
+ # For file paths, use a combination of directory hash and filename
140
+ if os.path.isfile(content):
141
+ # Get directory and filename
142
+ directory = os.path.dirname(content)
143
+ filename = os.path.basename(content)
144
+ # Create a short hash of the directory path
145
+ dir_hash = hashlib.md5(directory.encode()).hexdigest()[:6]
146
+ # Get filename without extension and remove special chars
147
+ base_name = os.path.splitext(filename)[0]
148
+ clean_name = re.sub(r'[^a-zA-Z0-9]', '', base_name)[:10]
149
+ # Combine for a unique name
150
+ collection_name = f"{clean_name}_{dir_hash}"
151
+ else:
152
+ # For URL content
153
+ content_hash = hashlib.md5(content.encode()).hexdigest()[:6]
154
+ clean_content = re.sub(r'[^a-zA-Z0-9]', '', content)[-10:]
155
+ collection_name = f"{clean_content}_{content_hash}"
137
156
 
138
157
  # Ensure the first character is either an underscore or a letter for
139
158
  # Milvus
@@ -21,7 +21,7 @@ from typing import Any, Dict, List, Optional, Union
21
21
  from pydantic import BaseModel
22
22
 
23
23
  from camel.logger import get_logger
24
- from camel.runtime import BaseRuntime
24
+ from camel.runtimes import BaseRuntime
25
25
  from camel.toolkits.function_tool import FunctionTool
26
26
 
27
27
  logger = get_logger(__name__)
@@ -26,7 +26,7 @@ import requests
26
26
  from pydantic import BaseModel
27
27
  from tqdm import tqdm
28
28
 
29
- from camel.runtime import BaseRuntime, TaskConfig
29
+ from camel.runtimes import BaseRuntime, TaskConfig
30
30
  from camel.toolkits import FunctionTool
31
31
 
32
32
  if TYPE_CHECKING:
@@ -19,8 +19,8 @@ from typing import List, Optional, Union
19
19
  from camel.agents import ChatAgent
20
20
  from camel.configs import ChatGPTConfig
21
21
  from camel.models import BaseModelBackend, ModelFactory
22
- from camel.runtime import BaseRuntime
23
- from camel.runtime.utils import FunctionRiskToolkit, IgnoreRiskToolkit
22
+ from camel.runtimes import BaseRuntime
23
+ from camel.runtimes.utils import FunctionRiskToolkit, IgnoreRiskToolkit
24
24
  from camel.toolkits import FunctionTool
25
25
  from camel.types import ModelPlatformType, ModelType
26
26
 
@@ -24,7 +24,7 @@ from typing import Any, Dict, List, Optional, Union
24
24
  import requests
25
25
  from pydantic import BaseModel
26
26
 
27
- from camel.runtime import BaseRuntime
27
+ from camel.runtimes import BaseRuntime
28
28
  from camel.toolkits.function_tool import FunctionTool
29
29
 
30
30
  logger = logging.getLogger(__name__)
@@ -18,7 +18,7 @@ import time
18
18
  from pathlib import Path
19
19
  from typing import Callable, List, Optional, Union
20
20
 
21
- from camel.runtime.docker_runtime import DockerRuntime
21
+ from camel.runtimes.docker_runtime import DockerRuntime
22
22
  from camel.toolkits import FunctionTool
23
23
 
24
24
  logger = logging.getLogger(__name__)
@@ -12,7 +12,7 @@
12
12
  # limitations under the License.
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  from abc import ABC, abstractmethod
15
- from typing import Any
15
+ from typing import Any, Optional
16
16
 
17
17
  from camel.societies.workforce.task_channel import TaskChannel
18
18
  from camel.societies.workforce.utils import check_if_running
@@ -23,10 +23,14 @@ class BaseNode(ABC):
23
23
 
24
24
  Args:
25
25
  description (str): Description of the node.
26
+ node_id (Optional[str]): ID of the node. If not provided, it will
27
+ be generated automatically. (default: :obj:`None`)
26
28
  """
27
29
 
28
- def __init__(self, description: str) -> None:
29
- self.node_id = str(id(self))
30
+ def __init__(
31
+ self, description: str, node_id: Optional[str] = None
32
+ ) -> None:
33
+ self.node_id = node_id if node_id is not None else str(id(self))
30
34
  self.description = description
31
35
  self._channel: TaskChannel = TaskChannel()
32
36
  self._running = False
@@ -39,7 +39,8 @@ class SingleAgentWorker(Worker):
39
39
  description: str,
40
40
  worker: ChatAgent,
41
41
  ) -> None:
42
- super().__init__(description)
42
+ node_id = worker.agent_id
43
+ super().__init__(description, node_id=node_id)
43
44
  self.worker = worker
44
45
 
45
46
  def reset(self) -> Any:
@@ -15,7 +15,7 @@ from __future__ import annotations
15
15
 
16
16
  import logging
17
17
  from abc import ABC, abstractmethod
18
- from typing import List
18
+ from typing import List, Optional
19
19
 
20
20
  from colorama import Fore
21
21
 
@@ -33,14 +33,16 @@ class Worker(BaseNode, ABC):
33
33
 
34
34
  Args:
35
35
  description (str): Description of the node.
36
-
36
+ node_id (Optional[str]): ID of the node. If not provided, it will
37
+ be generated automatically. (default: :obj:`None`)
37
38
  """
38
39
 
39
40
  def __init__(
40
41
  self,
41
42
  description: str,
43
+ node_id: Optional[str] = None,
42
44
  ) -> None:
43
- super().__init__(description)
45
+ super().__init__(description, node_id=node_id)
44
46
 
45
47
  def __repr__(self):
46
48
  return f"Worker node {self.node_id} ({self.description})"