beswarm 0.1.34__py3-none-any.whl → 0.1.36__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.
@@ -1,6 +1,7 @@
1
1
  import subprocess
2
2
  from .registry import register_tool
3
3
 
4
+ import re
4
5
  import html
5
6
 
6
7
  def unescape_html(input_string: str) -> str:
@@ -15,39 +16,137 @@ def unescape_html(input_string: str) -> str:
15
16
  """
16
17
  return html.unescape(input_string)
17
18
 
19
+ def get_python_executable(command: str) -> str:
20
+ """
21
+ 获取 Python 可执行文件的路径。
22
+
23
+ Returns:
24
+ str: Python 可执行文件的路径。
25
+ """
26
+ cmd_parts = command.split(None, 1)
27
+ if cmd_parts:
28
+ executable = cmd_parts[0]
29
+ args_str = cmd_parts[1] if len(cmd_parts) > 1 else ""
30
+
31
+ # 检查是否是 python 可执行文件 (如 python, python3, pythonX.Y)
32
+ is_python_exe = False
33
+ if executable == "python" or re.match(r"^python[23]?(\.\d+)?$", executable):
34
+ is_python_exe = True
35
+
36
+ if is_python_exe:
37
+ # 检查参数中是否已经有 -u 选项
38
+ args_list = args_str.split()
39
+ has_u_option = "-u" in args_list
40
+ if not has_u_option:
41
+ if args_str:
42
+ command = f"{executable} -u {args_str}"
43
+ return command
44
+
18
45
  # 执行命令
19
46
  @register_tool()
20
47
  def excute_command(command):
21
48
  """
22
- 执行命令并返回输出结果
49
+ 执行命令并返回输出结果 (标准输出会实时打印到控制台)
23
50
  禁止用于查看pdf,禁止使用 pdftotext 命令
24
51
 
25
52
  参数:
26
53
  command: 要执行的命令,可以克隆仓库,安装依赖,运行代码等
27
54
 
28
55
  返回:
29
- 命令执行的输出结果或错误信息
56
+ 命令执行的最终状态和收集到的输出/错误信息
30
57
  """
31
58
  try:
32
- # 使用subprocess.run捕获命令输出
33
- command = unescape_html(command)
34
- result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
35
- # 返回命令的标准输出
36
- if "pip install" in command:
37
- stdout_log = "\n".join([x for x in result.stdout.split('\n') if '━━' not in x])
38
- else:
39
- stdout_log = result.stdout
40
- return f"执行命令成功:\n{stdout_log}"
41
- except subprocess.CalledProcessError as e:
42
- if "pip install" in command:
43
- stdout_log = "\n".join([x for x in e.stdout.split('\n') if '━━' not in x])
59
+ command = unescape_html(command) # 保留 HTML 解码
60
+
61
+ command = get_python_executable(command)
62
+
63
+
64
+ # 使用 Popen 以便实时处理输出
65
+ # bufsize=1 表示行缓冲, universal_newlines=True 与 text=True 效果类似,用于文本模式
66
+ process = subprocess.Popen(
67
+ command,
68
+ shell=True,
69
+ stdout=subprocess.PIPE,
70
+ stderr=subprocess.PIPE,
71
+ text=True,
72
+ bufsize=1,
73
+ universal_newlines=True
74
+ )
75
+
76
+ stdout_lines = []
77
+
78
+ # 实时打印 stdout
79
+ # print(f"--- 开始执行命令: {command} ---")
80
+ if process.stdout:
81
+ for line in iter(process.stdout.readline, ''):
82
+ # 对 pip install 命令的输出进行过滤,去除进度条相关的行
83
+ if "pip install" in command and '━━' in line:
84
+ continue
85
+ print(line, end='', flush=True) # 实时打印到控制台,并刷新缓冲区
86
+ stdout_lines.append(line) # 收集行以供后续返回
87
+ process.stdout.close()
88
+ # print(f"\n--- 命令实时输出结束 ---")
89
+
90
+ # 等待命令完成
91
+ process.wait()
92
+
93
+ # 获取 stderr (命令完成后一次性读取)
94
+ stderr_output = ""
95
+ if process.stderr:
96
+ stderr_output = process.stderr.read()
97
+ process.stderr.close()
98
+
99
+ # 组合最终的 stdout 日志 (已经过 pip install 过滤)
100
+ final_stdout_log = "".join(stdout_lines)
101
+
102
+ if process.returncode == 0:
103
+ return f"执行命令成功:\n{final_stdout_log}"
44
104
  else:
45
- stdout_log = e.stdout
46
- # 如果命令执行失败,返回错误信息和错误输出
47
- return f"执行命令失败 (退出码 {e.returncode}):\n错误: {e.stderr}\n输出: {stdout_log}"
105
+ return f"执行命令失败 (退出码 {process.returncode}):\n错误: {stderr_output}\n输出: {final_stdout_log}"
106
+
107
+ except FileNotFoundError:
108
+ # 当 shell=True 时,命令未找到通常由 shell 处理,并返回非零退出码。
109
+ # 此处捕获 FileNotFoundError 主要用于 Popen 自身无法启动命令的场景 (例如 shell 本身未找到)。
110
+ return f"执行命令失败: 命令或程序未找到 ({command})"
48
111
  except Exception as e:
112
+ # 其他未知异常
49
113
  return f"执行命令时发生异常: {e}"
50
114
 
51
115
  if __name__ == "__main__":
52
- print(excute_command("ls -l && echo 'Hello, World!'"))
53
- print(excute_command("ls -l && echo 'Hello, World!'"))
116
+ # print(excute_command("ls -l && echo 'Hello, World!'"))
117
+ # print(excute_command("ls -l && echo 'Hello, World!'"))
118
+
119
+ # tqdm_script = """
120
+ # import time
121
+ # from tqdm import tqdm
122
+
123
+ # for i in range(10):
124
+ # print(f"TQDM 进度条测试: {i}")
125
+ # time.sleep(1)
126
+ # print('\\n-------TQDM 任务完成.')
127
+ # """
128
+ # processed_tqdm_script = tqdm_script.replace('"', '\\"')
129
+ # tqdm_command = f"python -u -u -c \"{processed_tqdm_script}\""
130
+ # # print(f"执行: {tqdm_command}")
131
+ # print(excute_command(tqdm_command))
132
+
133
+
134
+ # long_running_command_unix = "echo '开始长时间任务...' && for i in 1 2 3; do echo \"正在处理步骤 $i/3...\"; sleep 1; done && echo '长时间任务完成!'"
135
+ # print(f"执行: {long_running_command_unix}")
136
+ # print(excute_command(long_running_command_unix))
137
+
138
+
139
+ # long_running_command_unix = "pip install torch"
140
+ # print(f"执行: {long_running_command_unix}")
141
+ # print(excute_command(long_running_command_unix))
142
+
143
+
144
+ # python_long_task_command = """
145
+ # python -c "import time; print('Python 长时间任务启动...'); [print(f'Python 任务进度: {i+1}/3', flush=True) or time.sleep(1) for i in range(3)]; print('Python 长时间任务完成.')"
146
+ # """
147
+ # python_long_task_command = python_long_task_command.strip() # 移除可能的前后空白
148
+ # print(f"执行: {python_long_task_command}")
149
+ # print(excute_command(python_long_task_command))
150
+
151
+ print(get_python_executable("python -c 'print(123)'"))
152
+ # python -m beswarm.aient.src.aient.plugins.excute_command
@@ -0,0 +1,145 @@
1
+ import os
2
+ import io
3
+ import copy
4
+ import base64
5
+ import platform
6
+ import pyautogui
7
+ from datetime import datetime
8
+ from ..aient.src.aient.plugins import register_tool, get_function_call_list
9
+
10
+ from ..aient.src.aient.models import chatgpt
11
+ from ..aient.src.aient.prompt import system_prompt, instruction_system_prompt
12
+ from ..aient.src.aient.core.utils import get_image_message, get_text_message
13
+
14
+ from ..utils import extract_xml_content
15
+
16
+ async def get_current_screen_image_message(prompt):
17
+ print("instruction agent 正在截取当前屏幕...")
18
+ try:
19
+ # 使用 pyautogui 截取屏幕,返回 PIL Image 对象
20
+ screenshot = pyautogui.screenshot()
21
+ # img_width, img_height = screenshot.size # 获取截图尺寸
22
+ img_width, img_height = pyautogui.size()
23
+ print(f"截图成功,尺寸: {img_width}x{img_height}")
24
+
25
+ # 将 PIL Image 对象转换为 Base64 编码的 PNG 字符串
26
+ buffered = io.BytesIO()
27
+ screenshot.save(buffered, format="PNG")
28
+ base64_encoded_image = base64.b64encode(buffered.getvalue()).decode("utf-8")
29
+ IMAGE_MIME_TYPE = "image/png" # 截图格式为 PNG
30
+
31
+ except ImportError:
32
+ # Pillow 也是 pyautogui 的依赖,但以防万一单独处理
33
+ print("\n❌ 请安装所需库: pip install Pillow pyautogui")
34
+ return False
35
+ except Exception as e:
36
+ print(f"\n❌ 截取屏幕或处理图像时出错: {e}")
37
+ return False
38
+
39
+ engine_type = "gpt"
40
+ message_list = []
41
+ text_message = await get_text_message(prompt, engine_type)
42
+ image_message = await get_image_message(f"data:{IMAGE_MIME_TYPE};base64," + base64_encoded_image, engine_type)
43
+ message_list.append(text_message)
44
+ message_list.append(image_message)
45
+ return message_list
46
+
47
+ @register_tool()
48
+ async def UIworker(goal, tools, work_dir, cache_messages=None):
49
+ """
50
+ 启动一个 **工作智能体 (Worker Agent)** 来自动完成指定的任务目标 (`goal`)。
51
+
52
+ 这个工作智能体接收一个清晰的任务描述、一组可供调用的工具 (`tools`),以及一个工作目录 (`work_dir`)。
53
+ 它会利用语言模型的能力,结合可用的工具,自主规划并逐步执行必要的操作,直到最终完成指定的任务目标。
54
+ 核心功能是根据输入的目标,驱动整个任务执行流程。
55
+
56
+ Args:
57
+ goal (str): 需要完成的具体任务目标描述。工作智能体将围绕此目标进行工作。必须清晰、具体。
58
+ tools (list[str]): 一个包含可用工具函数对象的列表。工作智能体在执行任务时可能会调用这些工具来与环境交互(例如读写文件、执行命令等)。
59
+ work_dir (str): 工作目录的绝对路径。工作智能体将在此目录上下文中执行操作。
60
+
61
+ Returns:
62
+ str: 当任务成功完成时,返回字符串 "任务已完成"。
63
+ """
64
+
65
+ tools_json = [value for _, value in get_function_call_list(tools).items()]
66
+ work_agent_system_prompt = system_prompt.format(
67
+ os_version=platform.platform(),
68
+ workspace_path=work_dir,
69
+ shell=os.getenv('SHELL', 'Unknown'),
70
+ tools_list=tools_json
71
+ )
72
+
73
+ work_agent_config = {
74
+ "api_key": os.getenv("API_KEY"),
75
+ "api_url": os.getenv("BASE_URL"),
76
+ "engine": os.getenv("MODEL"),
77
+ "system_prompt": work_agent_system_prompt,
78
+ "print_log": True,
79
+ # "max_tokens": 8000,
80
+ "temperature": 0.5,
81
+ "function_call_max_loop": 100,
82
+ }
83
+ if cache_messages:
84
+ work_agent_config["cache_messages"] = cache_messages
85
+
86
+ instruction_agent_config = {
87
+ "api_key": os.getenv("API_KEY"),
88
+ "api_url": os.getenv("BASE_URL"),
89
+ "engine": os.getenv("MODEL"),
90
+ "system_prompt": instruction_system_prompt.format(os_version=platform.platform(), tools_list=tools_json, workspace_path=work_dir, current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
91
+ "print_log": False,
92
+ # "max_tokens": 4000,
93
+ "temperature": 0.7,
94
+ "use_plugins": False,
95
+ }
96
+
97
+ # 工作agent初始化
98
+ work_agent = chatgpt(**work_agent_config)
99
+ async def instruction_agent_task():
100
+ while True:
101
+ instruction_prompt = f"""
102
+ 任务目标: {goal}
103
+
104
+ 以上对话都是工作智能体的对话历史。
105
+
106
+ 根据以上对话历史和目标,请生成下一步指令。如果任务已完成,请回复"任务已完成"。
107
+ """
108
+ # 让指令agent分析对话历史并生成新指令
109
+ instruction_agent = chatgpt(**instruction_agent_config)
110
+ instruction_agent.conversation["default"] = copy.deepcopy(work_agent.conversation["default"])
111
+ new_prompt = await get_current_screen_image_message(instruction_prompt)
112
+ next_instruction = await instruction_agent.ask_async(new_prompt)
113
+ print("\n🤖 指令智能体生成的下一步指令:", next_instruction)
114
+ if "fetch_gpt_response_stream HTTP Error', 'status_code': 404" in next_instruction:
115
+ raise Exception(f"Model: {instruction_agent_config['engine']} not found!")
116
+ if "'status_code': 413" in next_instruction:
117
+ raise Exception(f"The request body is too long, please try again.")
118
+ next_instruction = extract_xml_content(next_instruction, "instructions")
119
+ if not next_instruction:
120
+ print("\n❌ 指令智能体生成的指令不符合要求,请重新生成。")
121
+ continue
122
+ else:
123
+ break
124
+ return next_instruction
125
+
126
+ need_instruction = True
127
+ while True:
128
+ next_instruction = ''
129
+ if need_instruction:
130
+ next_instruction = await instruction_agent_task()
131
+
132
+ # 检查任务是否完成
133
+ if "任务已完成" in next_instruction:
134
+ print("\n✅ 任务已完成!")
135
+ break
136
+ new_prompt = await get_current_screen_image_message(next_instruction)
137
+ result = await work_agent.ask_async(new_prompt)
138
+ if result.strip() == '':
139
+ print("\n❌ 工作智能体回复为空,请重新生成指令。")
140
+ need_instruction = False
141
+ continue
142
+ print("✅ 工作智能体回复:", result)
143
+ need_instruction = True
144
+
145
+ return "任务已完成"
beswarm/tools/__init__.py CHANGED
@@ -1,8 +1,11 @@
1
1
  from .think import think
2
2
  from .edit_file import edit_file
3
3
  from .worker import worker
4
+ from .UIworker import UIworker
5
+
4
6
  from .search_arxiv import search_arxiv
5
7
  from .repomap import get_code_repo_map
8
+ from .click import find_and_click_element, scroll_screen
6
9
  #显式导入 aient.plugins 中的所需内容
7
10
  from ..aient.src.aient.plugins import (
8
11
  excute_command,
@@ -15,6 +18,7 @@ from ..aient.src.aient.plugins import (
15
18
  write_to_file,
16
19
  download_read_arxiv_pdf,
17
20
  get_url_content,
21
+ register_tool,
18
22
  )
19
23
 
20
24
  __all__ = [
@@ -34,4 +38,8 @@ __all__ = [
34
38
  "write_to_file",
35
39
  "download_read_arxiv_pdf",
36
40
  "get_url_content",
41
+ "find_and_click_element",
42
+ "scroll_screen",
43
+ "register_tool",
44
+ "UIworker",
37
45
  ]