autoglm-gui 1.4.0__py3-none-any.whl → 1.5.0__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.
- AutoGLM_GUI/__init__.py +11 -0
- AutoGLM_GUI/__main__.py +26 -8
- AutoGLM_GUI/actions/__init__.py +6 -0
- AutoGLM_GUI/actions/handler.py +196 -0
- AutoGLM_GUI/actions/types.py +15 -0
- AutoGLM_GUI/adb/__init__.py +53 -0
- AutoGLM_GUI/adb/apps.py +227 -0
- AutoGLM_GUI/adb/connection.py +323 -0
- AutoGLM_GUI/adb/device.py +171 -0
- AutoGLM_GUI/adb/input.py +67 -0
- AutoGLM_GUI/adb/screenshot.py +11 -0
- AutoGLM_GUI/adb/timing.py +167 -0
- AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
- AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
- AutoGLM_GUI/adb_plus/screenshot.py +22 -1
- AutoGLM_GUI/adb_plus/serial.py +38 -20
- AutoGLM_GUI/adb_plus/touch.py +4 -9
- AutoGLM_GUI/agents/__init__.py +51 -0
- AutoGLM_GUI/agents/events.py +19 -0
- AutoGLM_GUI/agents/factory.py +153 -0
- AutoGLM_GUI/agents/glm/__init__.py +7 -0
- AutoGLM_GUI/agents/glm/agent.py +292 -0
- AutoGLM_GUI/agents/glm/message_builder.py +81 -0
- AutoGLM_GUI/agents/glm/parser.py +110 -0
- AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
- AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
- AutoGLM_GUI/agents/mai/__init__.py +28 -0
- AutoGLM_GUI/agents/mai/agent.py +405 -0
- AutoGLM_GUI/agents/mai/parser.py +254 -0
- AutoGLM_GUI/agents/mai/prompts.py +103 -0
- AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
- AutoGLM_GUI/agents/protocols.py +27 -0
- AutoGLM_GUI/agents/stream_runner.py +188 -0
- AutoGLM_GUI/api/__init__.py +71 -11
- AutoGLM_GUI/api/agents.py +190 -229
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +112 -28
- AutoGLM_GUI/api/health.py +13 -0
- AutoGLM_GUI/api/history.py +78 -0
- AutoGLM_GUI/api/layered_agent.py +306 -181
- AutoGLM_GUI/api/mcp.py +11 -10
- AutoGLM_GUI/api/media.py +64 -1
- AutoGLM_GUI/api/scheduled_tasks.py +98 -0
- AutoGLM_GUI/api/version.py +23 -10
- AutoGLM_GUI/api/workflows.py +2 -1
- AutoGLM_GUI/config.py +72 -14
- AutoGLM_GUI/config_manager.py +98 -27
- AutoGLM_GUI/device_adapter.py +263 -0
- AutoGLM_GUI/device_manager.py +248 -29
- AutoGLM_GUI/device_protocol.py +266 -0
- AutoGLM_GUI/devices/__init__.py +49 -0
- AutoGLM_GUI/devices/adb_device.py +200 -0
- AutoGLM_GUI/devices/mock_device.py +185 -0
- AutoGLM_GUI/devices/remote_device.py +177 -0
- AutoGLM_GUI/exceptions.py +3 -3
- AutoGLM_GUI/history_manager.py +164 -0
- AutoGLM_GUI/i18n.py +81 -0
- AutoGLM_GUI/metrics.py +13 -20
- AutoGLM_GUI/model/__init__.py +5 -0
- AutoGLM_GUI/model/message_builder.py +69 -0
- AutoGLM_GUI/model/types.py +24 -0
- AutoGLM_GUI/models/__init__.py +10 -0
- AutoGLM_GUI/models/history.py +96 -0
- AutoGLM_GUI/models/scheduled_task.py +71 -0
- AutoGLM_GUI/parsers/__init__.py +22 -0
- AutoGLM_GUI/parsers/base.py +50 -0
- AutoGLM_GUI/parsers/phone_parser.py +58 -0
- AutoGLM_GUI/phone_agent_manager.py +118 -367
- AutoGLM_GUI/platform_utils.py +31 -2
- AutoGLM_GUI/prompt_config.py +15 -0
- AutoGLM_GUI/prompts/__init__.py +32 -0
- AutoGLM_GUI/scheduler_manager.py +304 -0
- AutoGLM_GUI/schemas.py +272 -63
- AutoGLM_GUI/scrcpy_stream.py +159 -37
- AutoGLM_GUI/server.py +3 -1
- AutoGLM_GUI/socketio_server.py +114 -29
- AutoGLM_GUI/state.py +10 -30
- AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-BQm96DAl.js} +1 -1
- AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
- AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
- AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
- AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
- AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
- AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
- AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-CmZSnDqc.js} +1 -1
- AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
- AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
- AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
- AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
- AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
- AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
- AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
- AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/types.py +142 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +178 -92
- autoglm_gui-1.5.0.dist-info/RECORD +157 -0
- mai_agent/base.py +137 -0
- mai_agent/mai_grounding_agent.py +263 -0
- mai_agent/mai_naivigation_agent.py +526 -0
- mai_agent/prompt.py +148 -0
- mai_agent/unified_memory.py +67 -0
- mai_agent/utils.py +73 -0
- AutoGLM_GUI/api/dual_model.py +0 -311
- AutoGLM_GUI/dual_model/__init__.py +0 -53
- AutoGLM_GUI/dual_model/decision_model.py +0 -664
- AutoGLM_GUI/dual_model/dual_agent.py +0 -917
- AutoGLM_GUI/dual_model/protocols.py +0 -354
- AutoGLM_GUI/dual_model/vision_model.py +0 -442
- AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
- AutoGLM_GUI/phone_agent_patches.py +0 -146
- AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
- AutoGLM_GUI/static/assets/dialog-BfdcBs1x.js +0 -45
- AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
- AutoGLM_GUI/static/assets/index-DHF1NZh0.js +0 -12
- AutoGLM_GUI/static/assets/workflows-xiplap-r.js +0 -1
- autoglm_gui-1.4.0.dist-info/RECORD +0 -100
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Copyright (c) 2025, Alibaba Cloud and its affiliates;
|
|
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
|
+
|
|
14
|
+
"""Unified memory structures for trajectory tracking."""
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from PIL import Image
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class TrajStep:
|
|
24
|
+
"""
|
|
25
|
+
Represents a single step in an agent's trajectory.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
screenshot: PIL Image of the screen at this step.
|
|
29
|
+
accessibility_tree: Accessibility tree data for the screen.
|
|
30
|
+
prediction: Raw model prediction/response.
|
|
31
|
+
action: Parsed action dictionary.
|
|
32
|
+
conclusion: Conclusion or summary of the step.
|
|
33
|
+
thought: Model's reasoning/thinking process.
|
|
34
|
+
step_index: Index of this step in the trajectory.
|
|
35
|
+
agent_type: Type of agent that produced this step.
|
|
36
|
+
model_name: Name of the model used.
|
|
37
|
+
screenshot_bytes: Original screenshot as bytes (for compatibility).
|
|
38
|
+
structured_action: Structured action with metadata.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
screenshot: Image.Image
|
|
42
|
+
accessibility_tree: Optional[Dict[str, Any]]
|
|
43
|
+
prediction: str
|
|
44
|
+
action: Dict[str, Any]
|
|
45
|
+
conclusion: str
|
|
46
|
+
thought: str
|
|
47
|
+
step_index: int
|
|
48
|
+
agent_type: str
|
|
49
|
+
model_name: str
|
|
50
|
+
screenshot_bytes: Optional[bytes] = None
|
|
51
|
+
structured_action: Optional[Dict[str, Any]] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class TrajMemory:
|
|
56
|
+
"""
|
|
57
|
+
Container for a complete trajectory of agent steps.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
task_goal: The goal/instruction for this trajectory.
|
|
61
|
+
task_id: Unique identifier for the task.
|
|
62
|
+
steps: List of trajectory steps.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
task_goal: str
|
|
66
|
+
task_id: str
|
|
67
|
+
steps: List[TrajStep] = field(default_factory=list)
|
mai_agent/utils.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright (c) 2025, Alibaba Cloud and its affiliates;
|
|
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
|
+
|
|
14
|
+
"""Utility functions for image processing and conversion."""
|
|
15
|
+
|
|
16
|
+
import base64
|
|
17
|
+
from io import BytesIO
|
|
18
|
+
from typing import Union, Optional, Tuple, Dict, Any
|
|
19
|
+
|
|
20
|
+
from PIL import Image
|
|
21
|
+
from PIL import ImageDraw
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def safe_pil_to_bytes(image: Union[Image.Image, bytes]) -> bytes:
|
|
25
|
+
if isinstance(image, Image.Image):
|
|
26
|
+
img_byte_arr = BytesIO()
|
|
27
|
+
image.save(img_byte_arr, format="PNG")
|
|
28
|
+
return img_byte_arr.getvalue()
|
|
29
|
+
elif isinstance(image, bytes):
|
|
30
|
+
return image
|
|
31
|
+
else:
|
|
32
|
+
raise TypeError(f"Expected PIL Image or bytes, got {type(image)}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def pil_to_base64(image: Image.Image) -> str:
|
|
36
|
+
buffer = BytesIO()
|
|
37
|
+
image.save(buffer, format="PNG")
|
|
38
|
+
return base64.b64encode(buffer.getvalue()).decode("utf-8")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def save_screenshot(screenshot: Image.Image, path: str) -> None:
|
|
42
|
+
screenshot.save(path)
|
|
43
|
+
print(f"Screenshot saved in {path}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def extract_click_coordinates(action: Dict[str, Any]) -> Tuple[float, float]:
|
|
47
|
+
x = action.get("coordinate")[0]
|
|
48
|
+
y = action.get("coordinate")[1]
|
|
49
|
+
action_corr = (x, y)
|
|
50
|
+
return action_corr
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Function to draw points on an image
|
|
54
|
+
def draw_clicks_on_image(
|
|
55
|
+
image_path: str,
|
|
56
|
+
click_coords: Tuple[float, float],
|
|
57
|
+
output_path: Optional[str] = None,
|
|
58
|
+
) -> Image.Image:
|
|
59
|
+
image = Image.open(image_path)
|
|
60
|
+
draw = ImageDraw.Draw(image)
|
|
61
|
+
|
|
62
|
+
# Draw each click coordinate as a red circle
|
|
63
|
+
(x, y) = click_coords
|
|
64
|
+
radius = 20
|
|
65
|
+
if x and y: # if get the coordinate, draw a circle
|
|
66
|
+
draw.ellipse(
|
|
67
|
+
(x - radius, y - radius, x + radius, y + radius), fill="red", outline="red"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Save the modified image
|
|
71
|
+
if output_path:
|
|
72
|
+
save_screenshot(image, output_path)
|
|
73
|
+
return image
|
AutoGLM_GUI/api/dual_model.py
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
"""双模型协作API端点"""
|
|
2
|
-
|
|
3
|
-
import threading
|
|
4
|
-
from typing import Any, Optional
|
|
5
|
-
|
|
6
|
-
from fastapi import APIRouter, HTTPException
|
|
7
|
-
from fastapi.responses import StreamingResponse
|
|
8
|
-
from pydantic import BaseModel
|
|
9
|
-
|
|
10
|
-
from AutoGLM_GUI.logger import logger
|
|
11
|
-
from AutoGLM_GUI.dual_model import (
|
|
12
|
-
DecisionModelConfig,
|
|
13
|
-
DualModelAgent,
|
|
14
|
-
DualModelEvent,
|
|
15
|
-
DualModelEventType,
|
|
16
|
-
)
|
|
17
|
-
from AutoGLM_GUI.dual_model.protocols import ThinkingMode
|
|
18
|
-
from phone_agent.model import ModelConfig
|
|
19
|
-
|
|
20
|
-
router = APIRouter(prefix="/api/dual", tags=["dual-model"])
|
|
21
|
-
|
|
22
|
-
# 活跃的双模型会话 (device_id -> (agent, stop_event))
|
|
23
|
-
_active_dual_sessions: dict[str, tuple[DualModelAgent, threading.Event]] = {}
|
|
24
|
-
_active_dual_sessions_lock = threading.Lock()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class DualModelInitRequest(BaseModel):
|
|
28
|
-
"""双模型初始化请求"""
|
|
29
|
-
|
|
30
|
-
device_id: str
|
|
31
|
-
|
|
32
|
-
# 决策大模型配置
|
|
33
|
-
decision_base_url: str
|
|
34
|
-
decision_api_key: str
|
|
35
|
-
decision_model_name: str
|
|
36
|
-
|
|
37
|
-
# 视觉小模型配置(复用现有配置)
|
|
38
|
-
vision_base_url: Optional[str] = None
|
|
39
|
-
vision_api_key: Optional[str] = None
|
|
40
|
-
vision_model_name: Optional[str] = None
|
|
41
|
-
|
|
42
|
-
max_steps: int = 50
|
|
43
|
-
thinking_mode: str = "deep" # fast, deep, turbo
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class DualModelChatRequest(BaseModel):
|
|
47
|
-
"""双模型聊天请求"""
|
|
48
|
-
|
|
49
|
-
device_id: str
|
|
50
|
-
message: str
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class DualModelAbortRequest(BaseModel):
|
|
54
|
-
"""中止请求"""
|
|
55
|
-
|
|
56
|
-
device_id: str
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class DualModelStatusResponse(BaseModel):
|
|
60
|
-
"""状态响应"""
|
|
61
|
-
|
|
62
|
-
active: bool
|
|
63
|
-
device_id: Optional[str] = None
|
|
64
|
-
state: Optional[dict] = None
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@router.post("/init")
|
|
68
|
-
def init_dual_model(request: DualModelInitRequest) -> dict:
|
|
69
|
-
"""初始化双模型Agent"""
|
|
70
|
-
from AutoGLM_GUI.config import config
|
|
71
|
-
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
72
|
-
|
|
73
|
-
device_id = request.device_id
|
|
74
|
-
thinking_mode_map = {
|
|
75
|
-
"fast": ThinkingMode.FAST,
|
|
76
|
-
"deep": ThinkingMode.DEEP,
|
|
77
|
-
"turbo": ThinkingMode.TURBO,
|
|
78
|
-
}
|
|
79
|
-
thinking_mode = thinking_mode_map.get(request.thinking_mode, ThinkingMode.DEEP)
|
|
80
|
-
logger.info(f"初始化双模型Agent: {device_id}, 模式: {thinking_mode.value}")
|
|
81
|
-
|
|
82
|
-
# 检查设备是否已有单模型Agent初始化
|
|
83
|
-
manager = PhoneAgentManager.get_instance()
|
|
84
|
-
if not manager.is_initialized(device_id):
|
|
85
|
-
raise HTTPException(
|
|
86
|
-
status_code=400, detail="设备尚未初始化单模型Agent,请先调用 /api/init"
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
# 获取视觉模型配置
|
|
90
|
-
vision_base_url = request.vision_base_url or config.base_url
|
|
91
|
-
vision_api_key = request.vision_api_key or config.api_key
|
|
92
|
-
vision_model_name = request.vision_model_name or config.model_name
|
|
93
|
-
|
|
94
|
-
if not vision_base_url:
|
|
95
|
-
raise HTTPException(status_code=400, detail="视觉模型base_url未配置")
|
|
96
|
-
|
|
97
|
-
# 创建配置
|
|
98
|
-
decision_config = DecisionModelConfig(
|
|
99
|
-
base_url=request.decision_base_url,
|
|
100
|
-
api_key=request.decision_api_key,
|
|
101
|
-
model_name=request.decision_model_name,
|
|
102
|
-
thinking_mode=thinking_mode,
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
vision_config = ModelConfig(
|
|
106
|
-
base_url=vision_base_url,
|
|
107
|
-
api_key=vision_api_key,
|
|
108
|
-
model_name=vision_model_name,
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
# 创建双模型Agent
|
|
112
|
-
try:
|
|
113
|
-
agent = DualModelAgent(
|
|
114
|
-
decision_config=decision_config,
|
|
115
|
-
vision_config=vision_config,
|
|
116
|
-
device_id=device_id,
|
|
117
|
-
max_steps=request.max_steps,
|
|
118
|
-
thinking_mode=thinking_mode,
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# 存储到活跃会话
|
|
122
|
-
with _active_dual_sessions_lock:
|
|
123
|
-
# 清理旧会话
|
|
124
|
-
if device_id in _active_dual_sessions:
|
|
125
|
-
old_agent, old_event = _active_dual_sessions[device_id]
|
|
126
|
-
old_event.set()
|
|
127
|
-
|
|
128
|
-
_active_dual_sessions[device_id] = (agent, threading.Event())
|
|
129
|
-
|
|
130
|
-
logger.info(f"双模型Agent初始化成功: {device_id}")
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
"success": True,
|
|
134
|
-
"device_id": device_id,
|
|
135
|
-
"message": "双模型Agent初始化成功",
|
|
136
|
-
"decision_model": request.decision_model_name,
|
|
137
|
-
"vision_model": vision_model_name,
|
|
138
|
-
"thinking_mode": thinking_mode.value,
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
except Exception as e:
|
|
142
|
-
logger.error(f"双模型Agent初始化失败: {e}")
|
|
143
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
@router.post("/chat/stream")
|
|
147
|
-
def dual_model_chat_stream(request: DualModelChatRequest):
|
|
148
|
-
"""双模型聊天(SSE流式)"""
|
|
149
|
-
device_id = request.device_id
|
|
150
|
-
|
|
151
|
-
with _active_dual_sessions_lock:
|
|
152
|
-
if device_id not in _active_dual_sessions:
|
|
153
|
-
raise HTTPException(
|
|
154
|
-
status_code=400, detail="双模型Agent未初始化,请先调用 /api/dual/init"
|
|
155
|
-
)
|
|
156
|
-
agent, stop_event = _active_dual_sessions[device_id]
|
|
157
|
-
|
|
158
|
-
# 重置停止事件
|
|
159
|
-
stop_event.clear()
|
|
160
|
-
|
|
161
|
-
def event_generator():
|
|
162
|
-
"""SSE事件生成器"""
|
|
163
|
-
try:
|
|
164
|
-
logger.info(f"开始双模型任务: {request.message[:50]}...")
|
|
165
|
-
|
|
166
|
-
# 在后台线程运行Agent
|
|
167
|
-
result_holder: list[Any] = [None]
|
|
168
|
-
error_holder: list[Any] = [None]
|
|
169
|
-
|
|
170
|
-
def run_agent():
|
|
171
|
-
try:
|
|
172
|
-
result = agent.run(request.message)
|
|
173
|
-
result_holder[0] = result
|
|
174
|
-
except Exception as e:
|
|
175
|
-
error_holder[0] = e
|
|
176
|
-
|
|
177
|
-
thread = threading.Thread(target=run_agent, daemon=True)
|
|
178
|
-
thread.start()
|
|
179
|
-
|
|
180
|
-
# 持续发送事件
|
|
181
|
-
while thread.is_alive() or not agent.event_queue.empty():
|
|
182
|
-
if stop_event.is_set():
|
|
183
|
-
agent.abort()
|
|
184
|
-
yield "event: aborted\n"
|
|
185
|
-
yield 'data: {"type": "aborted", "message": "任务被用户中断"}\n\n'
|
|
186
|
-
break
|
|
187
|
-
|
|
188
|
-
# 获取事件
|
|
189
|
-
try:
|
|
190
|
-
events = agent.get_events(timeout=0.1)
|
|
191
|
-
for event in events:
|
|
192
|
-
yield event.to_sse()
|
|
193
|
-
|
|
194
|
-
# 如果是完成或错误事件,结束循环
|
|
195
|
-
if event.type in [
|
|
196
|
-
DualModelEventType.TASK_COMPLETE,
|
|
197
|
-
DualModelEventType.ERROR,
|
|
198
|
-
]:
|
|
199
|
-
return
|
|
200
|
-
except Exception:
|
|
201
|
-
continue
|
|
202
|
-
|
|
203
|
-
# 等待线程完成
|
|
204
|
-
thread.join(timeout=5)
|
|
205
|
-
|
|
206
|
-
# 检查错误
|
|
207
|
-
if error_holder[0]:
|
|
208
|
-
error_event = DualModelEvent(
|
|
209
|
-
type=DualModelEventType.ERROR,
|
|
210
|
-
data={"message": str(error_holder[0])},
|
|
211
|
-
)
|
|
212
|
-
yield error_event.to_sse()
|
|
213
|
-
|
|
214
|
-
# 如果没有发送完成事件,发送一个
|
|
215
|
-
if result_holder[0] and not stop_event.is_set():
|
|
216
|
-
result = result_holder[0]
|
|
217
|
-
if isinstance(result, dict):
|
|
218
|
-
done_event = DualModelEvent(
|
|
219
|
-
type=DualModelEventType.TASK_COMPLETE,
|
|
220
|
-
data={
|
|
221
|
-
"success": result.get("success", False),
|
|
222
|
-
"message": result.get("message", ""),
|
|
223
|
-
"steps": result.get("steps", 0),
|
|
224
|
-
},
|
|
225
|
-
)
|
|
226
|
-
yield done_event.to_sse()
|
|
227
|
-
|
|
228
|
-
except Exception as e:
|
|
229
|
-
logger.exception(f"双模型任务异常: {e}")
|
|
230
|
-
error_event = DualModelEvent(
|
|
231
|
-
type=DualModelEventType.ERROR,
|
|
232
|
-
data={"message": str(e)},
|
|
233
|
-
)
|
|
234
|
-
yield error_event.to_sse()
|
|
235
|
-
|
|
236
|
-
return StreamingResponse(
|
|
237
|
-
event_generator(),
|
|
238
|
-
media_type="text/event-stream",
|
|
239
|
-
headers={
|
|
240
|
-
"Cache-Control": "no-cache",
|
|
241
|
-
"Connection": "keep-alive",
|
|
242
|
-
"X-Accel-Buffering": "no",
|
|
243
|
-
},
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
@router.post("/chat/abort")
|
|
248
|
-
def abort_dual_model_chat(request: DualModelAbortRequest) -> dict:
|
|
249
|
-
"""中止双模型聊天"""
|
|
250
|
-
device_id = request.device_id
|
|
251
|
-
|
|
252
|
-
with _active_dual_sessions_lock:
|
|
253
|
-
if device_id in _active_dual_sessions:
|
|
254
|
-
agent, stop_event = _active_dual_sessions[device_id]
|
|
255
|
-
stop_event.set()
|
|
256
|
-
agent.abort()
|
|
257
|
-
logger.info(f"双模型任务已中止: {device_id}")
|
|
258
|
-
return {"success": True, "message": "已发送中止信号"}
|
|
259
|
-
else:
|
|
260
|
-
return {"success": False, "message": "未找到活跃的双模型会话"}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
@router.get("/status")
|
|
264
|
-
def get_dual_model_status(device_id: Optional[str] = None) -> DualModelStatusResponse:
|
|
265
|
-
"""获取双模型状态"""
|
|
266
|
-
with _active_dual_sessions_lock:
|
|
267
|
-
if device_id:
|
|
268
|
-
if device_id in _active_dual_sessions:
|
|
269
|
-
agent, _ = _active_dual_sessions[device_id]
|
|
270
|
-
return DualModelStatusResponse(
|
|
271
|
-
active=True,
|
|
272
|
-
device_id=device_id,
|
|
273
|
-
state=agent.get_state(),
|
|
274
|
-
)
|
|
275
|
-
else:
|
|
276
|
-
return DualModelStatusResponse(active=False, device_id=device_id)
|
|
277
|
-
else:
|
|
278
|
-
# 返回所有活跃会话
|
|
279
|
-
return DualModelStatusResponse(
|
|
280
|
-
active=len(_active_dual_sessions) > 0,
|
|
281
|
-
state={"active_devices": list(_active_dual_sessions.keys())},
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
@router.post("/reset")
|
|
286
|
-
def reset_dual_model(request: DualModelAbortRequest) -> dict:
|
|
287
|
-
"""重置双模型Agent"""
|
|
288
|
-
device_id = request.device_id
|
|
289
|
-
|
|
290
|
-
with _active_dual_sessions_lock:
|
|
291
|
-
if device_id in _active_dual_sessions:
|
|
292
|
-
agent, stop_event = _active_dual_sessions[device_id]
|
|
293
|
-
stop_event.set()
|
|
294
|
-
agent.reset()
|
|
295
|
-
logger.info(f"双模型Agent已重置: {device_id}")
|
|
296
|
-
return {"success": True, "message": "双模型Agent已重置"}
|
|
297
|
-
else:
|
|
298
|
-
return {"success": False, "message": "未找到双模型会话"}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
@router.delete("/session/{device_id}")
|
|
302
|
-
def delete_dual_model_session(device_id: str) -> dict:
|
|
303
|
-
"""删除双模型会话"""
|
|
304
|
-
with _active_dual_sessions_lock:
|
|
305
|
-
if device_id in _active_dual_sessions:
|
|
306
|
-
agent, stop_event = _active_dual_sessions.pop(device_id)
|
|
307
|
-
stop_event.set()
|
|
308
|
-
logger.info(f"双模型会话已删除: {device_id}")
|
|
309
|
-
return {"success": True, "message": "双模型会话已删除"}
|
|
310
|
-
else:
|
|
311
|
-
return {"success": False, "message": "未找到双模型会话"}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
双模型协作模块
|
|
3
|
-
|
|
4
|
-
大模型(GLM-4.7): 负责任务分析、决策制定、内容生成
|
|
5
|
-
小模型(autoglm-phone): 负责屏幕识别、动作执行
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from .decision_model import (
|
|
9
|
-
DecisionModel,
|
|
10
|
-
Decision,
|
|
11
|
-
TaskPlan,
|
|
12
|
-
ActionSequence,
|
|
13
|
-
ActionStep,
|
|
14
|
-
)
|
|
15
|
-
from .vision_model import VisionModel, ScreenDescription, ExecutionResult
|
|
16
|
-
from .dual_agent import DualModelAgent, DualModelCallbacks
|
|
17
|
-
from .protocols import (
|
|
18
|
-
DualModelConfig,
|
|
19
|
-
DecisionModelConfig,
|
|
20
|
-
DualModelState,
|
|
21
|
-
DualModelEvent,
|
|
22
|
-
DualModelEventType,
|
|
23
|
-
ModelRole,
|
|
24
|
-
ModelStage,
|
|
25
|
-
ThinkingMode,
|
|
26
|
-
DECISION_SYSTEM_PROMPT,
|
|
27
|
-
DECISION_SYSTEM_PROMPT_TURBO,
|
|
28
|
-
VISION_DESCRIBE_PROMPT,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
__all__ = [
|
|
32
|
-
"DecisionModel",
|
|
33
|
-
"Decision",
|
|
34
|
-
"TaskPlan",
|
|
35
|
-
"ActionSequence",
|
|
36
|
-
"ActionStep",
|
|
37
|
-
"VisionModel",
|
|
38
|
-
"ScreenDescription",
|
|
39
|
-
"ExecutionResult",
|
|
40
|
-
"DualModelAgent",
|
|
41
|
-
"DualModelCallbacks",
|
|
42
|
-
"DualModelConfig",
|
|
43
|
-
"DecisionModelConfig",
|
|
44
|
-
"DualModelState",
|
|
45
|
-
"DualModelEvent",
|
|
46
|
-
"DualModelEventType",
|
|
47
|
-
"ModelRole",
|
|
48
|
-
"ModelStage",
|
|
49
|
-
"ThinkingMode",
|
|
50
|
-
"DECISION_SYSTEM_PROMPT",
|
|
51
|
-
"DECISION_SYSTEM_PROMPT_TURBO",
|
|
52
|
-
"VISION_DESCRIBE_PROMPT",
|
|
53
|
-
]
|