camel-ai 0.2.70__py3-none-any.whl → 0.2.71a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of camel-ai might be problematic. Click here for more details.
- camel/__init__.py +1 -1
- camel/agents/chat_agent.py +61 -3
- camel/messages/func_message.py +32 -5
- camel/societies/workforce/role_playing_worker.py +4 -4
- camel/societies/workforce/single_agent_worker.py +5 -9
- camel/societies/workforce/workforce.py +304 -49
- camel/societies/workforce/workforce_logger.py +0 -1
- camel/tasks/task.py +83 -7
- camel/toolkits/craw4ai_toolkit.py +27 -7
- camel/toolkits/file_write_toolkit.py +110 -31
- camel/toolkits/human_toolkit.py +29 -9
- camel/toolkits/jina_reranker_toolkit.py +3 -4
- camel/toolkits/non_visual_browser_toolkit/browser_non_visual_toolkit.py +23 -2
- camel/toolkits/non_visual_browser_toolkit/nv_browser_session.py +53 -11
- camel/toolkits/non_visual_browser_toolkit/snapshot.js +211 -131
- camel/toolkits/non_visual_browser_toolkit/snapshot.py +9 -8
- camel/toolkits/terminal_toolkit.py +206 -64
- camel/toolkits/video_download_toolkit.py +6 -3
- camel/utils/message_summarizer.py +148 -0
- {camel_ai-0.2.70.dist-info → camel_ai-0.2.71a2.dist-info}/METADATA +4 -4
- {camel_ai-0.2.70.dist-info → camel_ai-0.2.71a2.dist-info}/RECORD +23 -22
- {camel_ai-0.2.70.dist-info → camel_ai-0.2.71a2.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.70.dist-info → camel_ai-0.2.71a2.dist-info}/licenses/LICENSE +0 -0
|
@@ -81,9 +81,10 @@ class TerminalToolkit(BaseToolkit):
|
|
|
81
81
|
self.terminal_ready = threading.Event()
|
|
82
82
|
self.gui_thread = None
|
|
83
83
|
self.safe_mode = safe_mode
|
|
84
|
-
|
|
84
|
+
self._file_initialized = False
|
|
85
85
|
self.cloned_env_path = None
|
|
86
86
|
self.use_shell_mode = use_shell_mode
|
|
87
|
+
self._human_takeover_active = False
|
|
87
88
|
|
|
88
89
|
self.python_executable = sys.executable
|
|
89
90
|
self.is_macos = platform.system() == 'Darwin'
|
|
@@ -129,25 +130,20 @@ class TerminalToolkit(BaseToolkit):
|
|
|
129
130
|
|
|
130
131
|
self.log_file = os.path.join(os.getcwd(), "camel_terminal.txt")
|
|
131
132
|
|
|
132
|
-
if os.path.exists(self.log_file):
|
|
133
|
-
with open(self.log_file, "w") as f:
|
|
134
|
-
f.truncate(0)
|
|
135
|
-
f.write("CAMEL Terminal Session\n")
|
|
136
|
-
f.write("=" * 50 + "\n")
|
|
137
|
-
f.write(f"Working Directory: {os.getcwd()}\n")
|
|
138
|
-
f.write("=" * 50 + "\n\n")
|
|
139
|
-
else:
|
|
140
|
-
with open(self.log_file, "w") as f:
|
|
141
|
-
f.write("CAMEL Terminal Session\n")
|
|
142
|
-
f.write("=" * 50 + "\n")
|
|
143
|
-
f.write(f"Working Directory: {os.getcwd()}\n")
|
|
144
|
-
f.write("=" * 50 + "\n\n")
|
|
145
|
-
|
|
146
133
|
# Inform the user
|
|
147
|
-
logger.info(f"Terminal output redirected to: {self.log_file}")
|
|
134
|
+
logger.info(f"Terminal output will be redirected to: {self.log_file}")
|
|
148
135
|
|
|
149
136
|
def file_update(output: str):
|
|
150
137
|
try:
|
|
138
|
+
# Initialize file on first write
|
|
139
|
+
if not self._file_initialized:
|
|
140
|
+
with open(self.log_file, "w") as f:
|
|
141
|
+
f.write("CAMEL Terminal Session\n")
|
|
142
|
+
f.write("=" * 50 + "\n")
|
|
143
|
+
f.write(f"Working Directory: {os.getcwd()}\n")
|
|
144
|
+
f.write("=" * 50 + "\n\n")
|
|
145
|
+
self._file_initialized = True
|
|
146
|
+
|
|
151
147
|
# Directly append to the end of the file
|
|
152
148
|
with open(self.log_file, "a") as f:
|
|
153
149
|
f.write(output)
|
|
@@ -165,6 +161,12 @@ class TerminalToolkit(BaseToolkit):
|
|
|
165
161
|
def _clone_current_environment(self):
|
|
166
162
|
r"""Create a new Python virtual environment."""
|
|
167
163
|
try:
|
|
164
|
+
if self.cloned_env_path is None:
|
|
165
|
+
self._update_terminal_output(
|
|
166
|
+
"Error: No environment path specified\n"
|
|
167
|
+
)
|
|
168
|
+
return
|
|
169
|
+
|
|
168
170
|
if os.path.exists(self.cloned_env_path):
|
|
169
171
|
self._update_terminal_output(
|
|
170
172
|
f"Using existing environment: {self.cloned_env_path}\n"
|
|
@@ -704,59 +706,35 @@ class TerminalToolkit(BaseToolkit):
|
|
|
704
706
|
elif command.startswith('pip'):
|
|
705
707
|
command = command.replace('pip', pip_path, 1)
|
|
706
708
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
output = process.stdout or ""
|
|
720
|
-
if process.stderr:
|
|
721
|
-
output += f"\nStderr Output:\n{process.stderr}"
|
|
722
|
-
|
|
723
|
-
# Update session information and terminal
|
|
724
|
-
self.shell_sessions[id]["output"] = output
|
|
725
|
-
self._update_terminal_output(output + "\n")
|
|
726
|
-
|
|
727
|
-
return output
|
|
728
|
-
|
|
729
|
-
else:
|
|
730
|
-
# Non-macOS systems use the Popen method
|
|
731
|
-
proc = subprocess.Popen(
|
|
732
|
-
command,
|
|
733
|
-
shell=True,
|
|
734
|
-
cwd=self.working_dir,
|
|
735
|
-
stdout=subprocess.PIPE,
|
|
736
|
-
stderr=subprocess.PIPE,
|
|
737
|
-
stdin=subprocess.PIPE,
|
|
738
|
-
text=True,
|
|
739
|
-
bufsize=1,
|
|
740
|
-
universal_newlines=True,
|
|
741
|
-
env=os.environ.copy(),
|
|
742
|
-
)
|
|
709
|
+
proc = subprocess.Popen(
|
|
710
|
+
command,
|
|
711
|
+
shell=True,
|
|
712
|
+
cwd=self.working_dir,
|
|
713
|
+
stdout=subprocess.PIPE,
|
|
714
|
+
stderr=subprocess.PIPE,
|
|
715
|
+
stdin=subprocess.PIPE,
|
|
716
|
+
text=True,
|
|
717
|
+
bufsize=1,
|
|
718
|
+
universal_newlines=True,
|
|
719
|
+
env=os.environ.copy(),
|
|
720
|
+
)
|
|
743
721
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
722
|
+
# Store the process and mark it as running
|
|
723
|
+
self.shell_sessions[id]["process"] = proc
|
|
724
|
+
self.shell_sessions[id]["running"] = True
|
|
747
725
|
|
|
748
|
-
|
|
749
|
-
|
|
726
|
+
# Get output
|
|
727
|
+
stdout, stderr = proc.communicate()
|
|
750
728
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
729
|
+
output = stdout or ""
|
|
730
|
+
if stderr:
|
|
731
|
+
output += f"\nStderr Output:\n{stderr}"
|
|
754
732
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
733
|
+
# Update session information and terminal
|
|
734
|
+
self.shell_sessions[id]["output"] = output
|
|
735
|
+
self._update_terminal_output(output + "\n")
|
|
758
736
|
|
|
759
|
-
|
|
737
|
+
return output
|
|
760
738
|
|
|
761
739
|
except Exception as e:
|
|
762
740
|
error_msg = f"Command execution error: {e!s}"
|
|
@@ -960,6 +938,169 @@ class TerminalToolkit(BaseToolkit):
|
|
|
960
938
|
logger.error(f"Error killing process: {e}")
|
|
961
939
|
return f"Error killing process: {e!s}"
|
|
962
940
|
|
|
941
|
+
def ask_user_for_help(self, id: str) -> str:
|
|
942
|
+
r"""Pauses agent execution to ask a human for help in the terminal.
|
|
943
|
+
|
|
944
|
+
This function should be called when an agent is stuck or needs
|
|
945
|
+
assistance with a task that requires manual intervention (e.g.,
|
|
946
|
+
solving a CAPTCHA or complex debugging). The human will take over the
|
|
947
|
+
specified terminal session to execute commands and then return control
|
|
948
|
+
to the agent.
|
|
949
|
+
|
|
950
|
+
Args:
|
|
951
|
+
id (str): Identifier of the shell session for the human to
|
|
952
|
+
interact with. If the session does not yet exist, it will be
|
|
953
|
+
created automatically.
|
|
954
|
+
|
|
955
|
+
Returns:
|
|
956
|
+
str: A status message indicating that the human has finished,
|
|
957
|
+
including the number of commands executed.
|
|
958
|
+
"""
|
|
959
|
+
# Input validation
|
|
960
|
+
if not id or not isinstance(id, str):
|
|
961
|
+
return "Error: Invalid session ID provided"
|
|
962
|
+
|
|
963
|
+
# Prevent concurrent human takeovers
|
|
964
|
+
if (
|
|
965
|
+
hasattr(self, '_human_takeover_active')
|
|
966
|
+
and self._human_takeover_active
|
|
967
|
+
):
|
|
968
|
+
return "Error: Human takeover already in progress"
|
|
969
|
+
|
|
970
|
+
try:
|
|
971
|
+
self._human_takeover_active = True
|
|
972
|
+
|
|
973
|
+
# Ensure the session exists so that the human can reuse it
|
|
974
|
+
if id not in self.shell_sessions:
|
|
975
|
+
self.shell_sessions[id] = {
|
|
976
|
+
"process": None,
|
|
977
|
+
"output": "",
|
|
978
|
+
"running": False,
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
command_count = 0
|
|
982
|
+
error_occurred = False
|
|
983
|
+
|
|
984
|
+
# Create clear banner message for user
|
|
985
|
+
takeover_banner = (
|
|
986
|
+
f"\n{'='*60}\n"
|
|
987
|
+
f"🤖 CAMEL Agent needs human help! Session: {id}\n"
|
|
988
|
+
f"📂 Working directory: {self.working_dir}\n"
|
|
989
|
+
f"{'='*60}\n"
|
|
990
|
+
f"💡 Type commands or '/exit' to return control to agent.\n"
|
|
991
|
+
f"{'='*60}\n"
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
# Print once to console for immediate visibility
|
|
995
|
+
print(takeover_banner, flush=True)
|
|
996
|
+
# Log for terminal output tracking
|
|
997
|
+
self._update_terminal_output(takeover_banner)
|
|
998
|
+
|
|
999
|
+
# Helper flag + event for coordination
|
|
1000
|
+
done_event = threading.Event()
|
|
1001
|
+
|
|
1002
|
+
def _human_loop() -> None:
|
|
1003
|
+
r"""Blocking loop that forwards human input to shell_exec."""
|
|
1004
|
+
nonlocal command_count, error_occurred
|
|
1005
|
+
try:
|
|
1006
|
+
while True:
|
|
1007
|
+
try:
|
|
1008
|
+
# Clear, descriptive prompt for user input
|
|
1009
|
+
user_cmd = input(f"🧑💻 [{id}]> ")
|
|
1010
|
+
if (
|
|
1011
|
+
user_cmd.strip()
|
|
1012
|
+
): # Only count non-empty commands
|
|
1013
|
+
command_count += 1
|
|
1014
|
+
except EOFError:
|
|
1015
|
+
# e.g. Ctrl_D / stdin closed, treat as exit.
|
|
1016
|
+
break
|
|
1017
|
+
except (KeyboardInterrupt, Exception) as e:
|
|
1018
|
+
logger.warning(
|
|
1019
|
+
f"Input error during human takeover: {e}"
|
|
1020
|
+
)
|
|
1021
|
+
error_occurred = True
|
|
1022
|
+
break
|
|
1023
|
+
|
|
1024
|
+
if user_cmd.strip() in {"/exit", "exit", "quit"}:
|
|
1025
|
+
break
|
|
1026
|
+
|
|
1027
|
+
try:
|
|
1028
|
+
exec_result = self.shell_exec(id, user_cmd)
|
|
1029
|
+
# Show the result immediately to the user
|
|
1030
|
+
if exec_result.strip():
|
|
1031
|
+
print(exec_result)
|
|
1032
|
+
logger.info(
|
|
1033
|
+
f"Human command executed: {user_cmd[:50]}..."
|
|
1034
|
+
)
|
|
1035
|
+
# Auto-exit after successful command
|
|
1036
|
+
break
|
|
1037
|
+
except Exception as e:
|
|
1038
|
+
error_msg = f"Error executing command: {e}"
|
|
1039
|
+
logger.error(f"Error executing human command: {e}")
|
|
1040
|
+
print(error_msg) # Show error to user immediately
|
|
1041
|
+
self._update_terminal_output(f"{error_msg}\n")
|
|
1042
|
+
error_occurred = True
|
|
1043
|
+
|
|
1044
|
+
except Exception as e:
|
|
1045
|
+
logger.error(f"Unexpected error in human loop: {e}")
|
|
1046
|
+
error_occurred = True
|
|
1047
|
+
finally:
|
|
1048
|
+
# Notify completion clearly
|
|
1049
|
+
finish_msg = (
|
|
1050
|
+
f"\n{'='*60}\n"
|
|
1051
|
+
f"✅ Human assistance completed! "
|
|
1052
|
+
f"Commands: {command_count}\n"
|
|
1053
|
+
f"🤖 Returning control to CAMEL agent...\n"
|
|
1054
|
+
f"{'='*60}\n"
|
|
1055
|
+
)
|
|
1056
|
+
print(finish_msg, flush=True)
|
|
1057
|
+
self._update_terminal_output(finish_msg)
|
|
1058
|
+
done_event.set()
|
|
1059
|
+
|
|
1060
|
+
# Start interactive thread (non-daemon for proper cleanup)
|
|
1061
|
+
thread = threading.Thread(target=_human_loop, daemon=False)
|
|
1062
|
+
thread.start()
|
|
1063
|
+
|
|
1064
|
+
# Block until human signals completion with timeout
|
|
1065
|
+
if done_event.wait(timeout=600): # 10 minutes timeout
|
|
1066
|
+
thread.join(timeout=10) # Give thread time to cleanup
|
|
1067
|
+
|
|
1068
|
+
# Generate detailed status message
|
|
1069
|
+
status = "completed successfully"
|
|
1070
|
+
if error_occurred:
|
|
1071
|
+
status = "completed with some errors"
|
|
1072
|
+
|
|
1073
|
+
result_msg = (
|
|
1074
|
+
f"Human assistance {status} for session '{id}'. "
|
|
1075
|
+
f"Total commands executed: {command_count}. "
|
|
1076
|
+
f"Working directory: {self.working_dir}"
|
|
1077
|
+
)
|
|
1078
|
+
logger.info(result_msg)
|
|
1079
|
+
return result_msg
|
|
1080
|
+
else:
|
|
1081
|
+
timeout_msg = (
|
|
1082
|
+
f"Human takeover for session '{id}' timed out after 10 "
|
|
1083
|
+
"minutes"
|
|
1084
|
+
)
|
|
1085
|
+
logger.warning(timeout_msg)
|
|
1086
|
+
return timeout_msg
|
|
1087
|
+
|
|
1088
|
+
except Exception as e:
|
|
1089
|
+
error_msg = f"Error during human takeover for session '{id}': {e}"
|
|
1090
|
+
logger.error(error_msg)
|
|
1091
|
+
# Notify user of the error clearly
|
|
1092
|
+
error_banner = (
|
|
1093
|
+
f"\n{'='*60}\n"
|
|
1094
|
+
f"❌ Error in human takeover! Session: {id}\n"
|
|
1095
|
+
f"❗ {e}\n"
|
|
1096
|
+
f"{'='*60}\n"
|
|
1097
|
+
)
|
|
1098
|
+
print(error_banner, flush=True)
|
|
1099
|
+
return error_msg
|
|
1100
|
+
finally:
|
|
1101
|
+
# Always reset the flag
|
|
1102
|
+
self._human_takeover_active = False
|
|
1103
|
+
|
|
963
1104
|
def __del__(self):
|
|
964
1105
|
r"""Clean up resources when the object is being destroyed.
|
|
965
1106
|
Terminates all running processes and closes any open file handles.
|
|
@@ -1041,4 +1182,5 @@ class TerminalToolkit(BaseToolkit):
|
|
|
1041
1182
|
FunctionTool(self.shell_wait),
|
|
1042
1183
|
FunctionTool(self.shell_write_to_process),
|
|
1043
1184
|
FunctionTool(self.shell_kill_process),
|
|
1185
|
+
FunctionTool(self.ask_user_for_help),
|
|
1044
1186
|
]
|
|
@@ -26,7 +26,7 @@ from PIL import Image
|
|
|
26
26
|
from camel.logger import get_logger
|
|
27
27
|
from camel.toolkits.base import BaseToolkit
|
|
28
28
|
from camel.toolkits.function_tool import FunctionTool
|
|
29
|
-
from camel.utils import
|
|
29
|
+
from camel.utils import dependencies_required
|
|
30
30
|
|
|
31
31
|
logger = get_logger(__name__)
|
|
32
32
|
|
|
@@ -57,7 +57,6 @@ def _capture_screenshot(video_file: str, timestamp: float) -> Image.Image:
|
|
|
57
57
|
return Image.open(io.BytesIO(out))
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
@MCPServer()
|
|
61
60
|
class VideoDownloaderToolkit(BaseToolkit):
|
|
62
61
|
r"""A class for downloading videos and optionally splitting them into
|
|
63
62
|
chunks.
|
|
@@ -123,6 +122,9 @@ class VideoDownloaderToolkit(BaseToolkit):
|
|
|
123
122
|
yt-dlp will detect if the video is downloaded automatically so there
|
|
124
123
|
is no need to check if the video exists.
|
|
125
124
|
|
|
125
|
+
Args:
|
|
126
|
+
url (str): The URL of the video to download.
|
|
127
|
+
|
|
126
128
|
Returns:
|
|
127
129
|
str: The path to the downloaded video file.
|
|
128
130
|
"""
|
|
@@ -175,7 +177,8 @@ class VideoDownloaderToolkit(BaseToolkit):
|
|
|
175
177
|
dividing the video into equal parts if an integer is provided.
|
|
176
178
|
|
|
177
179
|
Args:
|
|
178
|
-
|
|
180
|
+
video_path (str): The local path or URL of the video to take
|
|
181
|
+
screenshots.
|
|
179
182
|
amount (int): the amount of evenly split screenshots to capture.
|
|
180
183
|
|
|
181
184
|
Returns:
|
|
@@ -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
|
+
from typing import List, Optional
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
|
|
18
|
+
from camel.agents import ChatAgent
|
|
19
|
+
from camel.messages import BaseMessage
|
|
20
|
+
from camel.models import BaseModelBackend, ModelFactory
|
|
21
|
+
from camel.types import ModelPlatformType, ModelType
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MessageSummary(BaseModel):
|
|
25
|
+
r"""Schema for structured message summaries.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
summary (str): A brief, one-sentence summary of the conversation.
|
|
29
|
+
participants (List[str]): The roles of participants involved.
|
|
30
|
+
key_topics_and_entities (List[str]): Important topics, concepts, and
|
|
31
|
+
entities discussed.
|
|
32
|
+
decisions_and_outcomes (List[str]): Key decisions, conclusions, or
|
|
33
|
+
outcomes reached.
|
|
34
|
+
action_items (List[str]): A list of specific tasks or actions to be
|
|
35
|
+
taken, with assignees if mentioned.
|
|
36
|
+
progress_on_main_task (str): A summary of progress made on the
|
|
37
|
+
primary task.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
summary: str = Field(
|
|
41
|
+
description="A brief, one-sentence summary of the conversation."
|
|
42
|
+
)
|
|
43
|
+
participants: List[str] = Field(
|
|
44
|
+
description="The roles of participants involved."
|
|
45
|
+
)
|
|
46
|
+
key_topics_and_entities: List[str] = Field(
|
|
47
|
+
description="Important topics, concepts, and entities discussed."
|
|
48
|
+
)
|
|
49
|
+
decisions_and_outcomes: List[str] = Field(
|
|
50
|
+
description="Key decisions, conclusions, or outcomes reached."
|
|
51
|
+
)
|
|
52
|
+
action_items: List[str] = Field(
|
|
53
|
+
description=(
|
|
54
|
+
"A list of specific tasks or actions to be taken, with assignees "
|
|
55
|
+
"if mentioned."
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
progress_on_main_task: str = Field(
|
|
59
|
+
description="A summary of progress made on the primary task."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class MessageSummarizer:
|
|
64
|
+
r"""Utility class for generating structured summaries of chat messages.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
model_backend (Optional[BaseModelBackend], optional):
|
|
68
|
+
The model backend to use for summarization.
|
|
69
|
+
If not provided, a default model backend will be created.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
model_backend: Optional[BaseModelBackend] = None,
|
|
75
|
+
):
|
|
76
|
+
if model_backend is None:
|
|
77
|
+
self.model_backend = ModelFactory.create(
|
|
78
|
+
model_platform=ModelPlatformType.DEFAULT,
|
|
79
|
+
model_type=ModelType.GPT_4O_MINI,
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
self.model_backend = model_backend
|
|
83
|
+
self.agent = ChatAgent(
|
|
84
|
+
BaseMessage.make_assistant_message(
|
|
85
|
+
role_name="Message Summarizer",
|
|
86
|
+
content=(
|
|
87
|
+
"You are an expert conversation summarizer. Your task is "
|
|
88
|
+
"to analyze chat messages and create a structured summary "
|
|
89
|
+
"in JSON format. The summary should capture:\n"
|
|
90
|
+
"- summary: A brief, one-sentence summary of the "
|
|
91
|
+
"conversation.\n"
|
|
92
|
+
"- participants: The roles of participants involved.\n"
|
|
93
|
+
"- key_topics_and_entities: Important topics, concepts, "
|
|
94
|
+
"and entities discussed.\n"
|
|
95
|
+
"- decisions_and_outcomes: Key decisions, conclusions, or "
|
|
96
|
+
"outcomes reached.\n"
|
|
97
|
+
"- action_items: A list of specific tasks or actions to "
|
|
98
|
+
"be taken, with assignees if mentioned.\n"
|
|
99
|
+
"- progress_on_main_task: A summary of progress made on "
|
|
100
|
+
"the primary task.\n\n"
|
|
101
|
+
"Your response must be a JSON object that strictly "
|
|
102
|
+
"adheres to this structure. Be concise and accurate."
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
model=self.model_backend,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def summarize(self, messages: List[BaseMessage]) -> MessageSummary:
|
|
109
|
+
r"""Generate a structured summary of the provided messages.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
messages (List[BaseMessage]): List of messages to summarize.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
MessageSummary: Structured summary of the conversation.
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
ValueError: If the messages list is empty or if the model's
|
|
119
|
+
response cannot be parsed as valid JSON.
|
|
120
|
+
"""
|
|
121
|
+
if not messages:
|
|
122
|
+
raise ValueError("Cannot summarize an empty list of messages.")
|
|
123
|
+
|
|
124
|
+
# Construct prompt from messages
|
|
125
|
+
message_text = "\n".join(
|
|
126
|
+
f"{msg.role_name}: {msg.content}" for msg in messages
|
|
127
|
+
)
|
|
128
|
+
prompt = (
|
|
129
|
+
"Please analyze the following chat messages and generate a "
|
|
130
|
+
"structured summary.\n\n"
|
|
131
|
+
f"MESSAGES:\n\"\"\"\n{message_text}\n\"\"\"\n\n"
|
|
132
|
+
"Your response must be a JSON object that strictly adheres to the "
|
|
133
|
+
"required format."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Get structured summary from model with forced JSON response
|
|
137
|
+
response = self.agent.step(prompt, response_format=MessageSummary)
|
|
138
|
+
|
|
139
|
+
if response.msg is None or response.msg.parsed is None:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
"Failed to get a structured summary from the model."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
summary = response.msg.parsed
|
|
145
|
+
if not isinstance(summary, MessageSummary):
|
|
146
|
+
raise ValueError("The parsed response is not a MessageSummary.")
|
|
147
|
+
|
|
148
|
+
return summary
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: camel-ai
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.71a2
|
|
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
|
|
@@ -54,7 +54,6 @@ Requires-Dist: ffmpeg-python<0.3,>=0.2.0; extra == 'all'
|
|
|
54
54
|
Requires-Dist: firecrawl-py<2,>=1.0.0; extra == 'all'
|
|
55
55
|
Requires-Dist: fish-audio-sdk<2025,>=2024.12.5; extra == 'all'
|
|
56
56
|
Requires-Dist: flask>=2.0; extra == 'all'
|
|
57
|
-
Requires-Dist: fpdf>=1.7.2; extra == 'all'
|
|
58
57
|
Requires-Dist: google-api-python-client==2.166.0; extra == 'all'
|
|
59
58
|
Requires-Dist: google-auth-httplib2==0.2.0; extra == 'all'
|
|
60
59
|
Requires-Dist: google-auth-oauthlib==1.2.1; extra == 'all'
|
|
@@ -99,6 +98,7 @@ Requires-Dist: pygithub<3,>=2.6.0; extra == 'all'
|
|
|
99
98
|
Requires-Dist: pylatex>=1.4.2; extra == 'all'
|
|
100
99
|
Requires-Dist: pymilvus<3,>=2.4.0; extra == 'all'
|
|
101
100
|
Requires-Dist: pymupdf<2,>=1.22.5; extra == 'all'
|
|
101
|
+
Requires-Dist: pymupdf>=1.26.1; extra == 'all'
|
|
102
102
|
Requires-Dist: pyobvector>=0.1.18; extra == 'all'
|
|
103
103
|
Requires-Dist: pyowm<4,>=3.3.0; extra == 'all'
|
|
104
104
|
Requires-Dist: pytelegrambotapi<5,>=4.18.0; extra == 'all'
|
|
@@ -206,7 +206,6 @@ Requires-Dist: chunkr-ai>=0.0.50; extra == 'document-tools'
|
|
|
206
206
|
Requires-Dist: crawl4ai>=0.3.745; extra == 'document-tools'
|
|
207
207
|
Requires-Dist: docx2txt<0.9,>=0.8; extra == 'document-tools'
|
|
208
208
|
Requires-Dist: docx>=0.2.4; extra == 'document-tools'
|
|
209
|
-
Requires-Dist: fpdf>=1.7.2; extra == 'document-tools'
|
|
210
209
|
Requires-Dist: markitdown==0.1.1; extra == 'document-tools'
|
|
211
210
|
Requires-Dist: numpy<=2.2,>=1.2; extra == 'document-tools'
|
|
212
211
|
Requires-Dist: openapi-spec-validator<0.8,>=0.7.1; extra == 'document-tools'
|
|
@@ -215,6 +214,7 @@ Requires-Dist: pandasai<3,>=2.3.0; extra == 'document-tools'
|
|
|
215
214
|
Requires-Dist: prance<24,>=23.6.21.0; extra == 'document-tools'
|
|
216
215
|
Requires-Dist: pylatex>=1.4.2; extra == 'document-tools'
|
|
217
216
|
Requires-Dist: pymupdf<2,>=1.22.5; extra == 'document-tools'
|
|
217
|
+
Requires-Dist: pymupdf>=1.26.1; extra == 'document-tools'
|
|
218
218
|
Requires-Dist: python-pptx>=1.0.2; extra == 'document-tools'
|
|
219
219
|
Requires-Dist: tabulate>=0.9.0; extra == 'document-tools'
|
|
220
220
|
Requires-Dist: unstructured==0.16.20; extra == 'document-tools'
|
|
@@ -254,7 +254,6 @@ Requires-Dist: docx>=0.2.4; extra == 'owl'
|
|
|
254
254
|
Requires-Dist: duckduckgo-search<7,>=6.3.5; extra == 'owl'
|
|
255
255
|
Requires-Dist: e2b-code-interpreter<2,>=1.0.3; extra == 'owl'
|
|
256
256
|
Requires-Dist: ffmpeg-python<0.3,>=0.2.0; extra == 'owl'
|
|
257
|
-
Requires-Dist: fpdf>=1.7.2; extra == 'owl'
|
|
258
257
|
Requires-Dist: html2text>=2024.2.26; extra == 'owl'
|
|
259
258
|
Requires-Dist: imageio[pyav]<3,>=2.34.2; extra == 'owl'
|
|
260
259
|
Requires-Dist: markitdown==0.1.1; extra == 'owl'
|
|
@@ -272,6 +271,7 @@ Requires-Dist: pyautogui<0.10,>=0.9.54; extra == 'owl'
|
|
|
272
271
|
Requires-Dist: pydub<0.26,>=0.25.1; extra == 'owl'
|
|
273
272
|
Requires-Dist: pylatex>=1.4.2; extra == 'owl'
|
|
274
273
|
Requires-Dist: pymupdf<2,>=1.22.5; extra == 'owl'
|
|
274
|
+
Requires-Dist: pymupdf>=1.26.1; extra == 'owl'
|
|
275
275
|
Requires-Dist: pytesseract>=0.3.13; extra == 'owl'
|
|
276
276
|
Requires-Dist: python-dotenv<2,>=1.0.0; extra == 'owl'
|
|
277
277
|
Requires-Dist: python-pptx>=1.0.2; extra == 'owl'
|