camel-ai 0.2.71a2__py3-none-any.whl → 0.2.71a4__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/_types.py +6 -2
- camel/agents/chat_agent.py +297 -16
- camel/interpreters/docker_interpreter.py +3 -2
- camel/loaders/base_loader.py +85 -0
- camel/messages/base.py +2 -6
- camel/services/agent_openapi_server.py +380 -0
- camel/societies/workforce/workforce.py +144 -33
- camel/toolkits/__init__.py +7 -4
- camel/toolkits/craw4ai_toolkit.py +2 -2
- camel/toolkits/file_write_toolkit.py +6 -6
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/__init__.py +2 -2
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/actions.py +47 -11
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/agent.py +21 -11
- camel/toolkits/{non_visual_browser_toolkit/nv_browser_session.py → hybrid_browser_toolkit/browser_session.py} +64 -10
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +1008 -0
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/snapshot.py +16 -4
- camel/toolkits/{non_visual_browser_toolkit/snapshot.js → hybrid_browser_toolkit/unified_analyzer.js} +202 -23
- camel/toolkits/note_taking_toolkit.py +90 -0
- camel/toolkits/openai_image_toolkit.py +292 -0
- camel/toolkits/slack_toolkit.py +4 -4
- camel/toolkits/terminal_toolkit.py +223 -73
- camel/types/agents/tool_calling_record.py +4 -1
- camel/types/enums.py +24 -24
- camel/utils/mcp_client.py +37 -1
- camel/utils/tool_result.py +44 -0
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a4.dist-info}/METADATA +58 -5
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a4.dist-info}/RECORD +30 -26
- camel/toolkits/dalle_toolkit.py +0 -175
- camel/toolkits/non_visual_browser_toolkit/browser_non_visual_toolkit.py +0 -446
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a4.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -134,7 +134,14 @@ class TerminalToolkit(BaseToolkit):
|
|
|
134
134
|
logger.info(f"Terminal output will be redirected to: {self.log_file}")
|
|
135
135
|
|
|
136
136
|
def file_update(output: str):
|
|
137
|
+
import sys
|
|
138
|
+
|
|
137
139
|
try:
|
|
140
|
+
# For macOS/Linux file-based mode, also write to stdout
|
|
141
|
+
# to provide real-time feedback in the user's terminal.
|
|
142
|
+
sys.stdout.write(output)
|
|
143
|
+
sys.stdout.flush()
|
|
144
|
+
|
|
138
145
|
# Initialize file on first write
|
|
139
146
|
if not self._file_initialized:
|
|
140
147
|
with open(self.log_file, "w") as f:
|
|
@@ -147,9 +154,6 @@ class TerminalToolkit(BaseToolkit):
|
|
|
147
154
|
# Directly append to the end of the file
|
|
148
155
|
with open(self.log_file, "a") as f:
|
|
149
156
|
f.write(output)
|
|
150
|
-
# If the output does not end with a newline, add one
|
|
151
|
-
if output and not output.endswith('\n'):
|
|
152
|
-
f.write('\n')
|
|
153
157
|
# Ensure the agent also receives the output
|
|
154
158
|
self.agent_queue.put(output)
|
|
155
159
|
except Exception as e:
|
|
@@ -334,17 +338,24 @@ class TerminalToolkit(BaseToolkit):
|
|
|
334
338
|
def file_find_in_content(
|
|
335
339
|
self, file: str, regex: str, sudo: bool = False
|
|
336
340
|
) -> str:
|
|
337
|
-
r"""Search for
|
|
341
|
+
r"""Search for text within a file's content using a regular expression.
|
|
342
|
+
|
|
343
|
+
This function is useful for finding specific patterns or lines of text
|
|
344
|
+
within a given file. It uses `grep` on Unix-like systems and `findstr`
|
|
345
|
+
on Windows.
|
|
338
346
|
|
|
339
347
|
Args:
|
|
340
|
-
file (str):
|
|
341
|
-
regex (str):
|
|
342
|
-
sudo (bool, optional): Whether to use sudo privileges
|
|
343
|
-
False. Note: Using sudo requires the
|
|
344
|
-
appropriate permissions.
|
|
348
|
+
file (str): The absolute path of the file to search within.
|
|
349
|
+
regex (str): The regular expression pattern to match.
|
|
350
|
+
sudo (bool, optional): Whether to use sudo privileges for the
|
|
351
|
+
search. Defaults to False. Note: Using sudo requires the
|
|
352
|
+
process to have appropriate permissions.
|
|
353
|
+
(default: :obj:`False`)
|
|
345
354
|
|
|
346
355
|
Returns:
|
|
347
|
-
str:
|
|
356
|
+
str: The matching content found in the file. If no matches are
|
|
357
|
+
found, an empty string is returned. Returns an error message
|
|
358
|
+
if the file does not exist or another error occurs.
|
|
348
359
|
"""
|
|
349
360
|
|
|
350
361
|
if not os.path.exists(file):
|
|
@@ -376,14 +387,21 @@ class TerminalToolkit(BaseToolkit):
|
|
|
376
387
|
return f"Error: {e!s}"
|
|
377
388
|
|
|
378
389
|
def file_find_by_name(self, path: str, glob: str) -> str:
|
|
379
|
-
r"""Find files by name
|
|
390
|
+
r"""Find files by name in a specified directory using a glob pattern.
|
|
391
|
+
|
|
392
|
+
This function recursively searches for files matching a given name or
|
|
393
|
+
pattern within a directory. It uses `find` on Unix-like systems and
|
|
394
|
+
`dir` on Windows.
|
|
380
395
|
|
|
381
396
|
Args:
|
|
382
|
-
path (str):
|
|
383
|
-
glob (str):
|
|
397
|
+
path (str): The absolute path of the directory to search in.
|
|
398
|
+
glob (str): The filename pattern to search for, using glob syntax
|
|
399
|
+
(e.g., "*.py", "data*").
|
|
384
400
|
|
|
385
401
|
Returns:
|
|
386
|
-
str:
|
|
402
|
+
str: A newline-separated string containing the paths of the files
|
|
403
|
+
that match the pattern. Returns an error message if the
|
|
404
|
+
directory does not exist or another error occurs.
|
|
387
405
|
"""
|
|
388
406
|
if not os.path.exists(path):
|
|
389
407
|
return f"Directory not found: {path}"
|
|
@@ -649,18 +667,37 @@ class TerminalToolkit(BaseToolkit):
|
|
|
649
667
|
|
|
650
668
|
return True, command
|
|
651
669
|
|
|
652
|
-
def shell_exec(
|
|
653
|
-
|
|
654
|
-
|
|
670
|
+
def shell_exec(
|
|
671
|
+
self, id: str, command: str, interactive: bool = False
|
|
672
|
+
) -> str:
|
|
673
|
+
r"""Executes a shell command in a specified session.
|
|
674
|
+
|
|
675
|
+
This function creates and manages shell sessions to execute commands,
|
|
676
|
+
simulating a real terminal. It can run commands in both non-interactive
|
|
677
|
+
(capturing output) and interactive modes. Each session is identified by
|
|
678
|
+
a unique ID. If a session with the given ID does not exist, it will be
|
|
679
|
+
created.
|
|
655
680
|
|
|
656
681
|
Args:
|
|
657
|
-
id (str):
|
|
658
|
-
|
|
682
|
+
id (str): A unique identifier for the shell session. This is used
|
|
683
|
+
to manage multiple concurrent shell processes.
|
|
684
|
+
command (str): The shell command to be executed.
|
|
685
|
+
interactive (bool, optional): If `True`, the command runs in
|
|
686
|
+
interactive mode, connecting it to the terminal's standard
|
|
687
|
+
input. This is useful for commands that require user input,
|
|
688
|
+
like `ssh`. Defaults to `False`. Interactive mode is only
|
|
689
|
+
supported on macOS and Linux. (default: :obj:`False`)
|
|
659
690
|
|
|
660
691
|
Returns:
|
|
661
|
-
str:
|
|
692
|
+
str: The standard output and standard error from the command. If an
|
|
693
|
+
error occurs during execution, a descriptive error message is
|
|
694
|
+
returned.
|
|
695
|
+
|
|
696
|
+
Note:
|
|
697
|
+
When `interactive` is set to `True`, this function may block if the
|
|
698
|
+
command requires input. In safe mode, some commands that are
|
|
699
|
+
considered dangerous are restricted.
|
|
662
700
|
"""
|
|
663
|
-
# Command execution must be within the working directory
|
|
664
701
|
error_msg = self._enforce_working_dir_for_execution(self.working_dir)
|
|
665
702
|
if error_msg:
|
|
666
703
|
return error_msg
|
|
@@ -673,7 +710,6 @@ class TerminalToolkit(BaseToolkit):
|
|
|
673
710
|
return f"Command rejected: {sanitized_command}"
|
|
674
711
|
command = sanitized_command
|
|
675
712
|
|
|
676
|
-
# If the session does not exist, create a new session
|
|
677
713
|
if id not in self.shell_sessions:
|
|
678
714
|
self.shell_sessions[id] = {
|
|
679
715
|
"process": None,
|
|
@@ -682,7 +718,6 @@ class TerminalToolkit(BaseToolkit):
|
|
|
682
718
|
}
|
|
683
719
|
|
|
684
720
|
try:
|
|
685
|
-
# First, log the command to be executed
|
|
686
721
|
self._update_terminal_output(f"\n$ {command}\n")
|
|
687
722
|
|
|
688
723
|
if command.startswith('python') or command.startswith('pip'):
|
|
@@ -706,42 +741,130 @@ class TerminalToolkit(BaseToolkit):
|
|
|
706
741
|
elif command.startswith('pip'):
|
|
707
742
|
command = command.replace('pip', pip_path, 1)
|
|
708
743
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
744
|
+
if not interactive:
|
|
745
|
+
proc = subprocess.Popen(
|
|
746
|
+
command,
|
|
747
|
+
shell=True,
|
|
748
|
+
cwd=self.working_dir,
|
|
749
|
+
stdout=subprocess.PIPE,
|
|
750
|
+
stderr=subprocess.PIPE,
|
|
751
|
+
stdin=subprocess.PIPE,
|
|
752
|
+
text=True,
|
|
753
|
+
bufsize=1,
|
|
754
|
+
universal_newlines=True,
|
|
755
|
+
env=os.environ.copy(),
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
self.shell_sessions[id]["process"] = proc
|
|
759
|
+
self.shell_sessions[id]["running"] = True
|
|
760
|
+
stdout, stderr = proc.communicate()
|
|
761
|
+
output = stdout or ""
|
|
762
|
+
if stderr:
|
|
763
|
+
output += f"\nStderr Output:\n{stderr}"
|
|
764
|
+
self.shell_sessions[id]["output"] = output
|
|
765
|
+
self._update_terminal_output(output + "\n")
|
|
766
|
+
return output
|
|
767
|
+
|
|
768
|
+
# Interactive mode with real-time streaming via PTY
|
|
769
|
+
if self.os_type not in ['Darwin', 'Linux']:
|
|
770
|
+
return (
|
|
771
|
+
"Interactive mode is not supported on "
|
|
772
|
+
f"{self.os_type} due to PTY limitations."
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
import pty
|
|
776
|
+
import select
|
|
777
|
+
import sys
|
|
778
|
+
import termios
|
|
779
|
+
import tty
|
|
780
|
+
|
|
781
|
+
# Fork a new process with a PTY
|
|
782
|
+
pid, master_fd = pty.fork()
|
|
783
|
+
|
|
784
|
+
if pid == 0: # Child process
|
|
785
|
+
# Execute the command in the child process
|
|
786
|
+
try:
|
|
787
|
+
import shlex
|
|
788
|
+
|
|
789
|
+
parts = shlex.split(command)
|
|
790
|
+
if not parts:
|
|
791
|
+
logger.error("Error: Empty command")
|
|
792
|
+
os._exit(1)
|
|
793
|
+
|
|
794
|
+
os.chdir(self.working_dir)
|
|
795
|
+
os.execvp(parts[0], parts)
|
|
796
|
+
except (ValueError, IndexError, OSError) as e:
|
|
797
|
+
logger.error(f"Command execution error: {e}")
|
|
798
|
+
os._exit(127)
|
|
799
|
+
except Exception as e:
|
|
800
|
+
logger.error(f"Unexpected error: {e}")
|
|
801
|
+
os._exit(1)
|
|
721
802
|
|
|
722
|
-
#
|
|
723
|
-
self.shell_sessions[id]["
|
|
803
|
+
# Parent process
|
|
804
|
+
self.shell_sessions[id]["process_id"] = pid
|
|
724
805
|
self.shell_sessions[id]["running"] = True
|
|
806
|
+
output_lines: List[str] = []
|
|
807
|
+
original_settings = termios.tcgetattr(sys.stdin)
|
|
725
808
|
|
|
726
|
-
|
|
727
|
-
|
|
809
|
+
try:
|
|
810
|
+
tty.setraw(sys.stdin.fileno())
|
|
728
811
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
812
|
+
while True:
|
|
813
|
+
# Check if the child process has exited
|
|
814
|
+
try:
|
|
815
|
+
wait_pid, status = os.waitpid(pid, os.WNOHANG)
|
|
816
|
+
if wait_pid == pid:
|
|
817
|
+
self.shell_sessions[id]["running"] = False
|
|
818
|
+
break
|
|
819
|
+
except OSError:
|
|
820
|
+
# Process already reaped
|
|
821
|
+
self.shell_sessions[id]["running"] = False
|
|
822
|
+
break
|
|
823
|
+
|
|
824
|
+
# Use select to wait for I/O on stdin or master PTY
|
|
825
|
+
r, _, _ = select.select(
|
|
826
|
+
[sys.stdin, master_fd], [], [], 0.1
|
|
827
|
+
)
|
|
732
828
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
829
|
+
if master_fd in r:
|
|
830
|
+
try:
|
|
831
|
+
data = os.read(master_fd, 1024)
|
|
832
|
+
if not data:
|
|
833
|
+
break
|
|
834
|
+
decoded_data = data.decode(
|
|
835
|
+
'utf-8', errors='replace'
|
|
836
|
+
)
|
|
837
|
+
# Echo to user's terminal and log
|
|
838
|
+
self._update_terminal_output(decoded_data)
|
|
839
|
+
output_lines.append(decoded_data)
|
|
840
|
+
except OSError:
|
|
841
|
+
break # PTY has been closed
|
|
736
842
|
|
|
737
|
-
|
|
843
|
+
if sys.stdin in r:
|
|
844
|
+
try:
|
|
845
|
+
user_input = os.read(sys.stdin.fileno(), 1024)
|
|
846
|
+
if not user_input:
|
|
847
|
+
break
|
|
848
|
+
os.write(master_fd, user_input)
|
|
849
|
+
except OSError:
|
|
850
|
+
break
|
|
851
|
+
|
|
852
|
+
finally:
|
|
853
|
+
if original_settings is not None:
|
|
854
|
+
termios.tcsetattr(
|
|
855
|
+
sys.stdin, termios.TCSADRAIN, original_settings
|
|
856
|
+
)
|
|
857
|
+
if master_fd:
|
|
858
|
+
os.close(master_fd)
|
|
859
|
+
|
|
860
|
+
final_output = "".join(output_lines)
|
|
861
|
+
self.shell_sessions[id]["output"] = final_output
|
|
862
|
+
return final_output
|
|
738
863
|
|
|
739
864
|
except Exception as e:
|
|
740
865
|
error_msg = f"Command execution error: {e!s}"
|
|
741
866
|
logger.error(error_msg)
|
|
742
867
|
self._update_terminal_output(f"\nError: {error_msg}\n")
|
|
743
|
-
|
|
744
|
-
# More detailed error information
|
|
745
868
|
import traceback
|
|
746
869
|
|
|
747
870
|
detailed_error = traceback.format_exc()
|
|
@@ -751,13 +874,19 @@ class TerminalToolkit(BaseToolkit):
|
|
|
751
874
|
)
|
|
752
875
|
|
|
753
876
|
def shell_view(self, id: str) -> str:
|
|
754
|
-
r"""View the
|
|
877
|
+
r"""View the full output history of a specified shell session.
|
|
878
|
+
|
|
879
|
+
Retrieves the accumulated output (both stdout and stderr) generated by
|
|
880
|
+
commands in the specified session since its creation. This is useful
|
|
881
|
+
for checking the complete history of a session, especially after a
|
|
882
|
+
command has finished execution.
|
|
755
883
|
|
|
756
884
|
Args:
|
|
757
|
-
id (str):
|
|
885
|
+
id (str): The unique identifier of the shell session to view.
|
|
758
886
|
|
|
759
887
|
Returns:
|
|
760
|
-
str:
|
|
888
|
+
str: The complete output history of the shell session. Returns an
|
|
889
|
+
error message if the session is not found.
|
|
761
890
|
"""
|
|
762
891
|
if id not in self.shell_sessions:
|
|
763
892
|
return f"Shell session not found: {id}"
|
|
@@ -788,16 +917,22 @@ class TerminalToolkit(BaseToolkit):
|
|
|
788
917
|
return f"Error: {e!s}"
|
|
789
918
|
|
|
790
919
|
def shell_wait(self, id: str, seconds: Optional[int] = None) -> str:
|
|
791
|
-
r"""Wait for
|
|
792
|
-
|
|
920
|
+
r"""Wait for a command to finish in a specified shell session.
|
|
921
|
+
|
|
922
|
+
Blocks execution and waits for the running process in a shell session
|
|
923
|
+
to complete. This is useful for ensuring a long-running command has
|
|
924
|
+
finished before proceeding.
|
|
793
925
|
|
|
794
926
|
Args:
|
|
795
|
-
id (str):
|
|
796
|
-
seconds (Optional[int], optional):
|
|
797
|
-
If None
|
|
927
|
+
id (str): The unique identifier of the target shell session.
|
|
928
|
+
seconds (Optional[int], optional): The maximum time to wait, in
|
|
929
|
+
seconds. If `None`, it waits indefinitely.
|
|
930
|
+
(default: :obj:`None`)
|
|
798
931
|
|
|
799
932
|
Returns:
|
|
800
|
-
str:
|
|
933
|
+
str: A message indicating that the process has completed, including
|
|
934
|
+
the final output. If the process times out, it returns a
|
|
935
|
+
timeout message.
|
|
801
936
|
"""
|
|
802
937
|
if id not in self.shell_sessions:
|
|
803
938
|
return f"Shell session not found: {id}"
|
|
@@ -857,13 +992,20 @@ class TerminalToolkit(BaseToolkit):
|
|
|
857
992
|
) -> str:
|
|
858
993
|
r"""Write input to a running process in a specified shell session.
|
|
859
994
|
|
|
995
|
+
Sends a string of text to the standard input of a running process.
|
|
996
|
+
This is useful for interacting with commands that require input. This
|
|
997
|
+
function cannot be used with a command that was started in
|
|
998
|
+
interactive mode.
|
|
999
|
+
|
|
860
1000
|
Args:
|
|
861
|
-
id (str):
|
|
862
|
-
input (str):
|
|
863
|
-
press_enter (bool):
|
|
1001
|
+
id (str): The unique identifier of the target shell session.
|
|
1002
|
+
input (str): The text to write to the process's stdin.
|
|
1003
|
+
press_enter (bool): If `True`, a newline character (`\n`) is
|
|
1004
|
+
appended to the input, simulating pressing the Enter key.
|
|
864
1005
|
|
|
865
1006
|
Returns:
|
|
866
|
-
str:
|
|
1007
|
+
str: A status message indicating whether the input was sent, or an
|
|
1008
|
+
error message if the operation fails.
|
|
867
1009
|
"""
|
|
868
1010
|
if id not in self.shell_sessions:
|
|
869
1011
|
return f"Shell session not found: {id}"
|
|
@@ -899,11 +1041,17 @@ class TerminalToolkit(BaseToolkit):
|
|
|
899
1041
|
def shell_kill_process(self, id: str) -> str:
|
|
900
1042
|
r"""Terminate a running process in a specified shell session.
|
|
901
1043
|
|
|
1044
|
+
Forcibly stops a command that is currently running in a shell session.
|
|
1045
|
+
This is useful for ending processes that are stuck, running too long,
|
|
1046
|
+
or need to be cancelled.
|
|
1047
|
+
|
|
902
1048
|
Args:
|
|
903
|
-
id (str):
|
|
1049
|
+
id (str): The unique identifier of the shell session containing the
|
|
1050
|
+
process to be terminated.
|
|
904
1051
|
|
|
905
1052
|
Returns:
|
|
906
|
-
str:
|
|
1053
|
+
str: A status message indicating that the process has been
|
|
1054
|
+
terminated, or an error message if the operation fails.
|
|
907
1055
|
"""
|
|
908
1056
|
if id not in self.shell_sessions:
|
|
909
1057
|
return f"Shell session not found: {id}"
|
|
@@ -939,22 +1087,24 @@ class TerminalToolkit(BaseToolkit):
|
|
|
939
1087
|
return f"Error killing process: {e!s}"
|
|
940
1088
|
|
|
941
1089
|
def ask_user_for_help(self, id: str) -> str:
|
|
942
|
-
r"""
|
|
1090
|
+
r"""Pause the agent and ask a human for help with a command.
|
|
943
1091
|
|
|
944
|
-
This function should be
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
specified
|
|
948
|
-
to the
|
|
1092
|
+
This function should be used when the agent is stuck and requires
|
|
1093
|
+
manual intervention, such as solving a CAPTCHA or debugging a complex
|
|
1094
|
+
issue. It pauses the agent's execution and allows a human to take
|
|
1095
|
+
control of a specified shell session. The human can execute one
|
|
1096
|
+
command to resolve the issue, and then control is returned to the
|
|
1097
|
+
agent.
|
|
949
1098
|
|
|
950
1099
|
Args:
|
|
951
|
-
id (str):
|
|
952
|
-
interact with. If the session does not
|
|
953
|
-
created
|
|
1100
|
+
id (str): The identifier of the shell session for the human to
|
|
1101
|
+
interact with. If the session does not exist, it will be
|
|
1102
|
+
created.
|
|
954
1103
|
|
|
955
1104
|
Returns:
|
|
956
1105
|
str: A status message indicating that the human has finished,
|
|
957
|
-
including the number of commands executed.
|
|
1106
|
+
including the number of commands executed. If the takeover
|
|
1107
|
+
times out or fails, an error message is returned.
|
|
958
1108
|
"""
|
|
959
1109
|
# Input validation
|
|
960
1110
|
if not id or not isinstance(id, str):
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# See the License for the specific language governing permissions and
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
-
from typing import Any, Dict
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
15
|
|
|
16
16
|
from pydantic import BaseModel
|
|
17
17
|
|
|
@@ -24,12 +24,15 @@ class ToolCallingRecord(BaseModel):
|
|
|
24
24
|
args (Dict[str, Any]): The dictionary of arguments passed to the tool.
|
|
25
25
|
result (Any): The execution result of calling this tool.
|
|
26
26
|
tool_call_id (str): The ID of the tool call, if available.
|
|
27
|
+
images (Optional[List[str]]): List of base64-encoded images returned
|
|
28
|
+
by the tool, if any.
|
|
27
29
|
"""
|
|
28
30
|
|
|
29
31
|
tool_name: str
|
|
30
32
|
args: Dict[str, Any]
|
|
31
33
|
result: Any
|
|
32
34
|
tool_call_id: str
|
|
35
|
+
images: Optional[List[str]] = None
|
|
33
36
|
|
|
34
37
|
def __str__(self) -> str:
|
|
35
38
|
r"""Overridden version of the string function.
|
camel/types/enums.py
CHANGED
|
@@ -388,14 +388,14 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
388
388
|
WATSONX_MISTRAL_LARGE = "mistralai/mistral-large"
|
|
389
389
|
|
|
390
390
|
# Qianfan models
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
391
|
+
ERNIE_X1_TURBO_32K = "ernie-x1-turbo-32k"
|
|
392
|
+
ERNIE_X1_32K = "ernie-x1-32k"
|
|
393
|
+
ERNIE_X1_32K_PREVIEW = "ernie-x1-32k-preview"
|
|
394
|
+
ERNIE_4_5_TURBO_128K = "ernie-4.5-turbo-128k"
|
|
395
|
+
ERNIE_4_5_TURBO_32K = "ernie-4.5-turbo-32k"
|
|
396
|
+
DEEPSEEK_V3 = "deepseek-v3"
|
|
397
|
+
DEEPSEEK_R1 = "deepseek-r1"
|
|
398
|
+
QWEN3_235B_A22B = "qwen3-235b-a22b"
|
|
399
399
|
|
|
400
400
|
# Crynux models
|
|
401
401
|
CRYNUX_DEEPSEEK_R1_DISTILL_QWEN_1_5B = (
|
|
@@ -882,14 +882,14 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
882
882
|
@property
|
|
883
883
|
def is_qianfan(self) -> bool:
|
|
884
884
|
return self in {
|
|
885
|
-
ModelType.
|
|
886
|
-
ModelType.
|
|
887
|
-
ModelType.
|
|
888
|
-
ModelType.
|
|
889
|
-
ModelType.
|
|
890
|
-
ModelType.
|
|
891
|
-
ModelType.
|
|
892
|
-
ModelType.
|
|
885
|
+
ModelType.ERNIE_X1_TURBO_32K,
|
|
886
|
+
ModelType.ERNIE_X1_32K,
|
|
887
|
+
ModelType.ERNIE_X1_32K_PREVIEW,
|
|
888
|
+
ModelType.ERNIE_4_5_TURBO_128K,
|
|
889
|
+
ModelType.ERNIE_4_5_TURBO_32K,
|
|
890
|
+
ModelType.DEEPSEEK_V3,
|
|
891
|
+
ModelType.DEEPSEEK_R1,
|
|
892
|
+
ModelType.QWEN3_235B_A22B,
|
|
893
893
|
}
|
|
894
894
|
|
|
895
895
|
@property
|
|
@@ -1063,11 +1063,11 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
1063
1063
|
ModelType.CRYNUX_QWEN_2_5_7B_INSTRUCT,
|
|
1064
1064
|
ModelType.CRYNUX_NOUS_HERMES_3_LLAMA_3_1_8B,
|
|
1065
1065
|
ModelType.CRYNUX_NOUS_HERMES_3_LLAMA_3_2_3B,
|
|
1066
|
-
ModelType.
|
|
1067
|
-
ModelType.
|
|
1068
|
-
ModelType.
|
|
1069
|
-
ModelType.
|
|
1070
|
-
ModelType.
|
|
1066
|
+
ModelType.ERNIE_X1_TURBO_32K,
|
|
1067
|
+
ModelType.ERNIE_X1_32K,
|
|
1068
|
+
ModelType.ERNIE_X1_32K_PREVIEW,
|
|
1069
|
+
ModelType.ERNIE_4_5_TURBO_32K,
|
|
1070
|
+
ModelType.QWEN3_235B_A22B,
|
|
1071
1071
|
}:
|
|
1072
1072
|
return 32_000
|
|
1073
1073
|
elif self in {
|
|
@@ -1148,7 +1148,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
1148
1148
|
return 65_535
|
|
1149
1149
|
elif self in {
|
|
1150
1150
|
ModelType.NOVITA_QWEN_2_5_V1_72B,
|
|
1151
|
-
ModelType.
|
|
1151
|
+
ModelType.DEEPSEEK_R1,
|
|
1152
1152
|
}:
|
|
1153
1153
|
return 96_000
|
|
1154
1154
|
elif self in {
|
|
@@ -1203,8 +1203,8 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
1203
1203
|
ModelType.NETMIND_DEEPSEEK_V3,
|
|
1204
1204
|
ModelType.NOVITA_DEEPSEEK_V3_0324,
|
|
1205
1205
|
ModelType.MISTRAL_MEDIUM_3,
|
|
1206
|
-
ModelType.
|
|
1207
|
-
ModelType.
|
|
1206
|
+
ModelType.ERNIE_4_5_TURBO_128K,
|
|
1207
|
+
ModelType.DEEPSEEK_V3,
|
|
1208
1208
|
}:
|
|
1209
1209
|
return 128_000
|
|
1210
1210
|
elif self in {
|
camel/utils/mcp_client.py
CHANGED
|
@@ -113,7 +113,11 @@ class ServerConfig(BaseModel):
|
|
|
113
113
|
# Advanced options
|
|
114
114
|
sse_read_timeout: float = 300.0 # 5 minutes
|
|
115
115
|
terminate_on_close: bool = True
|
|
116
|
-
|
|
116
|
+
|
|
117
|
+
# New transport type parameter
|
|
118
|
+
type: Optional[str] = None
|
|
119
|
+
|
|
120
|
+
# Legacy parameter for backward compatibility
|
|
117
121
|
prefer_sse: bool = False
|
|
118
122
|
|
|
119
123
|
@model_validator(mode='after')
|
|
@@ -128,11 +132,43 @@ class ServerConfig(BaseModel):
|
|
|
128
132
|
if self.command and self.url:
|
|
129
133
|
raise ValueError("Cannot specify both 'command' and 'url'")
|
|
130
134
|
|
|
135
|
+
# Validate type if provided
|
|
136
|
+
if self.type is not None:
|
|
137
|
+
valid_types = {"stdio", "sse", "streamable_http", "websocket"}
|
|
138
|
+
if self.type not in valid_types:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"Invalid type: "
|
|
141
|
+
f"'{self.type}'. "
|
|
142
|
+
f"Valid options: {valid_types}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Issue deprecation warning if prefer_sse is used
|
|
146
|
+
if self.prefer_sse and self.type is None:
|
|
147
|
+
import warnings
|
|
148
|
+
|
|
149
|
+
warnings.warn(
|
|
150
|
+
"The 'prefer_sse' parameter is deprecated. "
|
|
151
|
+
"Use 'type=\"sse\"' instead.",
|
|
152
|
+
DeprecationWarning,
|
|
153
|
+
stacklevel=2,
|
|
154
|
+
)
|
|
155
|
+
|
|
131
156
|
return self
|
|
132
157
|
|
|
133
158
|
@property
|
|
134
159
|
def transport_type(self) -> TransportType:
|
|
135
160
|
r"""Automatically detect transport type based on configuration."""
|
|
161
|
+
# Use explicit transport type if provided
|
|
162
|
+
if self.type is not None:
|
|
163
|
+
transport_map = {
|
|
164
|
+
"stdio": TransportType.STDIO,
|
|
165
|
+
"sse": TransportType.SSE,
|
|
166
|
+
"streamable_http": TransportType.STREAMABLE_HTTP,
|
|
167
|
+
"websocket": TransportType.WEBSOCKET,
|
|
168
|
+
}
|
|
169
|
+
return transport_map[self.type]
|
|
170
|
+
|
|
171
|
+
# If no type is provided, fall back to automatic detection
|
|
136
172
|
if self.command:
|
|
137
173
|
return TransportType.STDIO
|
|
138
174
|
elif self.url:
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
|
|
15
|
+
from typing import List, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ToolResult:
|
|
19
|
+
r"""Special result type for tools that can return images along with text.
|
|
20
|
+
|
|
21
|
+
This class is used by ChatAgent to detect when a tool returns visual
|
|
22
|
+
content that should be included in the conversation context.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, text: str, images: Optional[List[str]] = None):
|
|
26
|
+
r"""Initialize a tool result.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
text (str): The text description or result of the tool operation.
|
|
30
|
+
images (Optional[List[str]]): List of base64-encoded images to
|
|
31
|
+
include in the conversation context. Images should be encoded
|
|
32
|
+
as "data:image/{format};base64,{data}" format.
|
|
33
|
+
"""
|
|
34
|
+
self.text = text
|
|
35
|
+
self.images = images or []
|
|
36
|
+
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
r"""Return the text representation of the result."""
|
|
39
|
+
return self.text
|
|
40
|
+
|
|
41
|
+
def __repr__(self) -> str:
|
|
42
|
+
r"""Return a detailed representation of the result."""
|
|
43
|
+
img_count = len(self.images) if self.images else 0
|
|
44
|
+
return f"ToolResult(text='{self.text[:50]}...', images={img_count})"
|