kweaver-dolphin 0.1.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.
- DolphinLanguageSDK/__init__.py +58 -0
- dolphin/__init__.py +62 -0
- dolphin/cli/__init__.py +20 -0
- dolphin/cli/args/__init__.py +9 -0
- dolphin/cli/args/parser.py +567 -0
- dolphin/cli/builtin_agents/__init__.py +22 -0
- dolphin/cli/commands/__init__.py +4 -0
- dolphin/cli/interrupt/__init__.py +8 -0
- dolphin/cli/interrupt/handler.py +205 -0
- dolphin/cli/interrupt/keyboard.py +82 -0
- dolphin/cli/main.py +49 -0
- dolphin/cli/multimodal/__init__.py +34 -0
- dolphin/cli/multimodal/clipboard.py +327 -0
- dolphin/cli/multimodal/handler.py +249 -0
- dolphin/cli/multimodal/image_processor.py +214 -0
- dolphin/cli/multimodal/input_parser.py +149 -0
- dolphin/cli/runner/__init__.py +8 -0
- dolphin/cli/runner/runner.py +989 -0
- dolphin/cli/ui/__init__.py +10 -0
- dolphin/cli/ui/console.py +2795 -0
- dolphin/cli/ui/input.py +340 -0
- dolphin/cli/ui/layout.py +425 -0
- dolphin/cli/ui/stream_renderer.py +302 -0
- dolphin/cli/utils/__init__.py +8 -0
- dolphin/cli/utils/helpers.py +135 -0
- dolphin/cli/utils/version.py +49 -0
- dolphin/core/__init__.py +107 -0
- dolphin/core/agent/__init__.py +10 -0
- dolphin/core/agent/agent_state.py +69 -0
- dolphin/core/agent/base_agent.py +970 -0
- dolphin/core/code_block/__init__.py +0 -0
- dolphin/core/code_block/agent_init_block.py +0 -0
- dolphin/core/code_block/assign_block.py +98 -0
- dolphin/core/code_block/basic_code_block.py +1865 -0
- dolphin/core/code_block/explore_block.py +1327 -0
- dolphin/core/code_block/explore_block_v2.py +712 -0
- dolphin/core/code_block/explore_strategy.py +672 -0
- dolphin/core/code_block/judge_block.py +220 -0
- dolphin/core/code_block/prompt_block.py +32 -0
- dolphin/core/code_block/skill_call_deduplicator.py +291 -0
- dolphin/core/code_block/tool_block.py +129 -0
- dolphin/core/common/__init__.py +17 -0
- dolphin/core/common/constants.py +176 -0
- dolphin/core/common/enums.py +1173 -0
- dolphin/core/common/exceptions.py +133 -0
- dolphin/core/common/multimodal.py +539 -0
- dolphin/core/common/object_type.py +165 -0
- dolphin/core/common/output_format.py +432 -0
- dolphin/core/common/types.py +36 -0
- dolphin/core/config/__init__.py +16 -0
- dolphin/core/config/global_config.py +1289 -0
- dolphin/core/config/ontology_config.py +133 -0
- dolphin/core/context/__init__.py +12 -0
- dolphin/core/context/context.py +1580 -0
- dolphin/core/context/context_manager.py +161 -0
- dolphin/core/context/var_output.py +82 -0
- dolphin/core/context/variable_pool.py +356 -0
- dolphin/core/context_engineer/__init__.py +41 -0
- dolphin/core/context_engineer/config/__init__.py +5 -0
- dolphin/core/context_engineer/config/settings.py +402 -0
- dolphin/core/context_engineer/core/__init__.py +7 -0
- dolphin/core/context_engineer/core/budget_manager.py +327 -0
- dolphin/core/context_engineer/core/context_assembler.py +583 -0
- dolphin/core/context_engineer/core/context_manager.py +637 -0
- dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
- dolphin/core/context_engineer/example/incremental_example.py +267 -0
- dolphin/core/context_engineer/example/traditional_example.py +334 -0
- dolphin/core/context_engineer/services/__init__.py +5 -0
- dolphin/core/context_engineer/services/compressor.py +399 -0
- dolphin/core/context_engineer/utils/__init__.py +6 -0
- dolphin/core/context_engineer/utils/context_utils.py +441 -0
- dolphin/core/context_engineer/utils/message_formatter.py +270 -0
- dolphin/core/context_engineer/utils/token_utils.py +139 -0
- dolphin/core/coroutine/__init__.py +15 -0
- dolphin/core/coroutine/context_snapshot.py +154 -0
- dolphin/core/coroutine/context_snapshot_profile.py +922 -0
- dolphin/core/coroutine/context_snapshot_store.py +268 -0
- dolphin/core/coroutine/execution_frame.py +145 -0
- dolphin/core/coroutine/execution_state_registry.py +161 -0
- dolphin/core/coroutine/resume_handle.py +101 -0
- dolphin/core/coroutine/step_result.py +101 -0
- dolphin/core/executor/__init__.py +18 -0
- dolphin/core/executor/debug_controller.py +630 -0
- dolphin/core/executor/dolphin_executor.py +1063 -0
- dolphin/core/executor/executor.py +624 -0
- dolphin/core/flags/__init__.py +27 -0
- dolphin/core/flags/definitions.py +49 -0
- dolphin/core/flags/manager.py +113 -0
- dolphin/core/hook/__init__.py +95 -0
- dolphin/core/hook/expression_evaluator.py +499 -0
- dolphin/core/hook/hook_dispatcher.py +380 -0
- dolphin/core/hook/hook_types.py +248 -0
- dolphin/core/hook/isolated_variable_pool.py +284 -0
- dolphin/core/interfaces.py +53 -0
- dolphin/core/llm/__init__.py +0 -0
- dolphin/core/llm/llm.py +495 -0
- dolphin/core/llm/llm_call.py +100 -0
- dolphin/core/llm/llm_client.py +1285 -0
- dolphin/core/llm/message_sanitizer.py +120 -0
- dolphin/core/logging/__init__.py +20 -0
- dolphin/core/logging/logger.py +526 -0
- dolphin/core/message/__init__.py +8 -0
- dolphin/core/message/compressor.py +749 -0
- dolphin/core/parser/__init__.py +8 -0
- dolphin/core/parser/parser.py +405 -0
- dolphin/core/runtime/__init__.py +10 -0
- dolphin/core/runtime/runtime_graph.py +926 -0
- dolphin/core/runtime/runtime_instance.py +446 -0
- dolphin/core/skill/__init__.py +14 -0
- dolphin/core/skill/context_retention.py +157 -0
- dolphin/core/skill/skill_function.py +686 -0
- dolphin/core/skill/skill_matcher.py +282 -0
- dolphin/core/skill/skillkit.py +700 -0
- dolphin/core/skill/skillset.py +72 -0
- dolphin/core/trajectory/__init__.py +10 -0
- dolphin/core/trajectory/recorder.py +189 -0
- dolphin/core/trajectory/trajectory.py +522 -0
- dolphin/core/utils/__init__.py +9 -0
- dolphin/core/utils/cache_kv.py +212 -0
- dolphin/core/utils/tools.py +340 -0
- dolphin/lib/__init__.py +93 -0
- dolphin/lib/debug/__init__.py +8 -0
- dolphin/lib/debug/visualizer.py +409 -0
- dolphin/lib/memory/__init__.py +28 -0
- dolphin/lib/memory/async_processor.py +220 -0
- dolphin/lib/memory/llm_calls.py +195 -0
- dolphin/lib/memory/manager.py +78 -0
- dolphin/lib/memory/sandbox.py +46 -0
- dolphin/lib/memory/storage.py +245 -0
- dolphin/lib/memory/utils.py +51 -0
- dolphin/lib/ontology/__init__.py +12 -0
- dolphin/lib/ontology/basic/__init__.py +0 -0
- dolphin/lib/ontology/basic/base.py +102 -0
- dolphin/lib/ontology/basic/concept.py +130 -0
- dolphin/lib/ontology/basic/object.py +11 -0
- dolphin/lib/ontology/basic/relation.py +63 -0
- dolphin/lib/ontology/datasource/__init__.py +27 -0
- dolphin/lib/ontology/datasource/datasource.py +66 -0
- dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
- dolphin/lib/ontology/datasource/sql.py +845 -0
- dolphin/lib/ontology/mapping.py +177 -0
- dolphin/lib/ontology/ontology.py +733 -0
- dolphin/lib/ontology/ontology_context.py +16 -0
- dolphin/lib/ontology/ontology_manager.py +107 -0
- dolphin/lib/skill_results/__init__.py +31 -0
- dolphin/lib/skill_results/cache_backend.py +559 -0
- dolphin/lib/skill_results/result_processor.py +181 -0
- dolphin/lib/skill_results/result_reference.py +179 -0
- dolphin/lib/skill_results/skillkit_hook.py +324 -0
- dolphin/lib/skill_results/strategies.py +328 -0
- dolphin/lib/skill_results/strategy_registry.py +150 -0
- dolphin/lib/skillkits/__init__.py +44 -0
- dolphin/lib/skillkits/agent_skillkit.py +155 -0
- dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
- dolphin/lib/skillkits/env_skillkit.py +250 -0
- dolphin/lib/skillkits/mcp_adapter.py +616 -0
- dolphin/lib/skillkits/mcp_skillkit.py +771 -0
- dolphin/lib/skillkits/memory_skillkit.py +650 -0
- dolphin/lib/skillkits/noop_skillkit.py +31 -0
- dolphin/lib/skillkits/ontology_skillkit.py +89 -0
- dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
- dolphin/lib/skillkits/resource/__init__.py +52 -0
- dolphin/lib/skillkits/resource/models/__init__.py +6 -0
- dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
- dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
- dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
- dolphin/lib/skillkits/resource/skill_cache.py +215 -0
- dolphin/lib/skillkits/resource/skill_loader.py +395 -0
- dolphin/lib/skillkits/resource/skill_validator.py +406 -0
- dolphin/lib/skillkits/resource_skillkit.py +11 -0
- dolphin/lib/skillkits/search_skillkit.py +163 -0
- dolphin/lib/skillkits/sql_skillkit.py +274 -0
- dolphin/lib/skillkits/system_skillkit.py +509 -0
- dolphin/lib/skillkits/vm_skillkit.py +65 -0
- dolphin/lib/utils/__init__.py +9 -0
- dolphin/lib/utils/data_process.py +207 -0
- dolphin/lib/utils/handle_progress.py +178 -0
- dolphin/lib/utils/security.py +139 -0
- dolphin/lib/utils/text_retrieval.py +462 -0
- dolphin/lib/vm/__init__.py +11 -0
- dolphin/lib/vm/env_executor.py +895 -0
- dolphin/lib/vm/python_session_manager.py +453 -0
- dolphin/lib/vm/vm.py +610 -0
- dolphin/sdk/__init__.py +60 -0
- dolphin/sdk/agent/__init__.py +12 -0
- dolphin/sdk/agent/agent_factory.py +236 -0
- dolphin/sdk/agent/dolphin_agent.py +1106 -0
- dolphin/sdk/api/__init__.py +4 -0
- dolphin/sdk/runtime/__init__.py +8 -0
- dolphin/sdk/runtime/env.py +363 -0
- dolphin/sdk/skill/__init__.py +10 -0
- dolphin/sdk/skill/global_skills.py +706 -0
- dolphin/sdk/skill/traditional_toolkit.py +260 -0
- kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
- kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
- kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
- kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
- kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
- kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
dolphin/lib/vm/vm.py
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
import ast
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, Optional, List
|
|
5
|
+
import tempfile
|
|
6
|
+
import random
|
|
7
|
+
import string
|
|
8
|
+
|
|
9
|
+
from dolphin.core.config.global_config import VMConfig, VMConnectionType
|
|
10
|
+
from dolphin.core.utils.cache_kv import CacheKVMgr, GlobalCacheKVCenter
|
|
11
|
+
from dolphin.lib.utils.security import SecurityUtils
|
|
12
|
+
from dolphin.core.logging.logger import get_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = get_logger("vm")
|
|
16
|
+
|
|
17
|
+
PreImport = [
|
|
18
|
+
"datetime",
|
|
19
|
+
"json",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VM:
|
|
24
|
+
"""Virtual machine base class, defining the interface for virtual machine operations"""
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def connect(self) -> bool:
|
|
28
|
+
"""Connect to virtual machine
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
bool: Whether the connection was successful
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def execBash(self, command: str) -> str:
|
|
37
|
+
"""Execute Bash command
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
command: The Bash command to execute
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
str: Result of the command execution
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def execPython(
|
|
48
|
+
self, code: str, varDict: Optional[Dict[str, Any]] = None, **kwargs
|
|
49
|
+
) -> str:
|
|
50
|
+
"""Execute Python commands. The Python code can be directly generated as a parameter for tool invocation.
|
|
51
|
+
[Attention] The Python code should be directly generated as a parameter for tool invocation, and it is forbidden to generate code blocks like ```python separately!
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
code: The Python code to execute. The result should be assigned to return_value at the end of the code.
|
|
55
|
+
varDict: Optional dictionary of variables that will be set in the Python environment before executing the code.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: The command execution result, usually the value of return_value or the output/error during execution.
|
|
59
|
+
"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def disconnect(self) -> None:
|
|
64
|
+
"""Disconnect from the virtual machine"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def deserializePythonResult(result: str) -> Any:
|
|
69
|
+
if not result:
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
if result[0] == "[" or result[0] == "{":
|
|
73
|
+
return ast.literal_eval(result)
|
|
74
|
+
else:
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class VMSSH(VM):
|
|
79
|
+
"""SSH-based virtual machine connection implementation"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, config: VMConfig, cache_vm: CacheKVMgr):
|
|
82
|
+
"""Initialize SSH connection
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
config: Configuration object containing SSH connection information
|
|
86
|
+
"""
|
|
87
|
+
self.config = config
|
|
88
|
+
self.cache_vm = cache_vm
|
|
89
|
+
self.client = None
|
|
90
|
+
self.connected = False
|
|
91
|
+
self.attempt_count = 0
|
|
92
|
+
self._paramiko = None # 延迟导入 paramiko
|
|
93
|
+
|
|
94
|
+
def _get_paramiko(self):
|
|
95
|
+
"""获取 paramiko 模块(延迟导入)"""
|
|
96
|
+
if self._paramiko is None:
|
|
97
|
+
try:
|
|
98
|
+
import paramiko
|
|
99
|
+
self._paramiko = paramiko
|
|
100
|
+
except ImportError:
|
|
101
|
+
logger.error("paramiko is required for VMSSH but not installed. Please install it: pip install paramiko")
|
|
102
|
+
raise ImportError("paramiko is required for VMSSH but not installed. Please install it: pip install paramiko")
|
|
103
|
+
return self._paramiko
|
|
104
|
+
|
|
105
|
+
def connect(self) -> bool:
|
|
106
|
+
"""Establish SSH connection using Paramiko
|
|
107
|
+
|
|
108
|
+
Supports password authentication and SSH key authentication
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
bool: whether the connection is successful
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
paramiko = self._get_paramiko()
|
|
115
|
+
except ImportError:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
if not self.config.validate():
|
|
119
|
+
logger.error("VM配置验证failed")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
# Reset connection attempt count
|
|
123
|
+
self.attempt_count = 0
|
|
124
|
+
return self._attempt_connect(paramiko)
|
|
125
|
+
|
|
126
|
+
def _attempt_connect(self, paramiko) -> bool:
|
|
127
|
+
"""Try to connect, with retry support
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
paramiko: The paramiko module (passed to avoid repeated imports)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
bool: Whether the connection was successful
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
while self.attempt_count < self.config.retryCount:
|
|
137
|
+
try:
|
|
138
|
+
self.attempt_count += 1
|
|
139
|
+
logger.info(
|
|
140
|
+
f"正在connect to到 {self.config.host}:{self.config.port} (尝试 {self.attempt_count}/{self.config.retryCount})"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
self.client = paramiko.SSHClient()
|
|
144
|
+
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
145
|
+
|
|
146
|
+
connect_args = {
|
|
147
|
+
"hostname": self.config.host,
|
|
148
|
+
"port": self.config.port,
|
|
149
|
+
"username": self.config.username,
|
|
150
|
+
"timeout": self.config.timeout,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Use SSH key authenticationPrioritize usingSSH密钥认证
|
|
154
|
+
if self.config.sshKeyPath and os.path.exists(self.config.sshKeyPath):
|
|
155
|
+
logger.info(f"使用SSH密钥认证: {self.config.sshKeyPath}")
|
|
156
|
+
connect_args["key_filename"] = self.config.sshKeyPath
|
|
157
|
+
else:
|
|
158
|
+
# Use password authentication
|
|
159
|
+
logger.info("使用密码认证")
|
|
160
|
+
password = self._decryptPassword(self.config.encryptedPassword)
|
|
161
|
+
connect_args["password"] = password
|
|
162
|
+
|
|
163
|
+
self.client.connect(**connect_args)
|
|
164
|
+
self.connected = True
|
|
165
|
+
logger.info(f"Successfully connected to {self.config.host}")
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.warning(
|
|
170
|
+
f"connect tofailed (尝试 {self.attempt_count}/{self.config.retryCount}): {str(e)}"
|
|
171
|
+
)
|
|
172
|
+
if self.client:
|
|
173
|
+
self.client.close()
|
|
174
|
+
self.client = None
|
|
175
|
+
|
|
176
|
+
# If the maximum number of retries has been reached, return failure
|
|
177
|
+
if self.attempt_count >= self.config.retryCount:
|
|
178
|
+
logger.error(
|
|
179
|
+
f"connect tofailed,已达到最大重试次数 ({self.config.retryCount})"
|
|
180
|
+
)
|
|
181
|
+
self.connected = False
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
# Retry after a while
|
|
185
|
+
import time
|
|
186
|
+
|
|
187
|
+
time.sleep(2) # Wait 2 seconds before retrying
|
|
188
|
+
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
def _decryptPassword(self, encryptedPassword: str) -> str:
|
|
192
|
+
"""Decrypt stored encrypted password
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
encryptedPassword: Encrypted password
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
str: Decrypted password
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
# Get the key for decrypting passwords from environment variables
|
|
202
|
+
password = SecurityUtils.get_env_password()
|
|
203
|
+
# Decrypt saved encrypted passwords
|
|
204
|
+
return SecurityUtils.decrypt(encryptedPassword, password)
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.error(f"密码解密failed: {str(e)}")
|
|
207
|
+
# If decryption fails, it may be because the password was not encrypted; return directly.
|
|
208
|
+
return encryptedPassword
|
|
209
|
+
|
|
210
|
+
def _checkConnection(self) -> bool:
|
|
211
|
+
"""Check the connection status and attempt to connect if not connected.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
bool: Whether the connection is available
|
|
215
|
+
"""
|
|
216
|
+
if not self.connected or self.client is None:
|
|
217
|
+
return self.connect()
|
|
218
|
+
|
|
219
|
+
# Test whether the connection is still alive
|
|
220
|
+
try:
|
|
221
|
+
transport = self.client.get_transport()
|
|
222
|
+
if transport is None or not transport.is_active():
|
|
223
|
+
logger.warning("SSHconnect to已断开,尝试重新connect to")
|
|
224
|
+
self.disconnect()
|
|
225
|
+
return self.connect()
|
|
226
|
+
return True
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.warning(f"检查connect to状态时出错: {str(e)}")
|
|
229
|
+
self.disconnect()
|
|
230
|
+
return self.connect()
|
|
231
|
+
|
|
232
|
+
def execBash(self, command: str) -> str:
|
|
233
|
+
"""Execute Bash commands via SSH
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
command: The Bash command to execute
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
str: The result of the command execution
|
|
240
|
+
"""
|
|
241
|
+
if not self._checkConnection():
|
|
242
|
+
return "connect tofailed,无法执行命令"
|
|
243
|
+
|
|
244
|
+
command = self._preprocessCode(command, "bash")
|
|
245
|
+
try:
|
|
246
|
+
stdin, stdout, stderr = self.client.exec_command(
|
|
247
|
+
f"source base/bin/activate && {command}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Read command output
|
|
251
|
+
output = stdout.read().decode("utf-8")
|
|
252
|
+
error = stderr.read().decode("utf-8")
|
|
253
|
+
|
|
254
|
+
if error:
|
|
255
|
+
logger.warning(f"命令执行产生错误: {error}")
|
|
256
|
+
return f"输出: {output}\n错误: {error}"
|
|
257
|
+
|
|
258
|
+
return output
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"执行命令时出错: {str(e)}")
|
|
261
|
+
return f"执行命令时出错: {str(e)}"
|
|
262
|
+
|
|
263
|
+
def execPython(
|
|
264
|
+
self, code: str, varDict: Optional[Dict[str, Any]] = None, **kwargs
|
|
265
|
+
) -> str:
|
|
266
|
+
"""Execute Python code via SSH, supporting session state persistence.
|
|
267
|
+
|
|
268
|
+
To obtain the execution result, assign the value to be returned at the end of the provided code
|
|
269
|
+
to the special variable return_value.
|
|
270
|
+
For example: code="a=1\nb=2\nreturn_value = a+b"
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
code: The Python code to execute. The last line of the code should assign the result to return_value.
|
|
274
|
+
varDict: Optional dictionary of variables to set in the Python environment before executing the code.
|
|
275
|
+
session_id: Optional session ID to maintain execution state.
|
|
276
|
+
session_manager: Optional session manager instance.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
str: Command execution result, typically the value of return_value or output/errors during execution.
|
|
280
|
+
"""
|
|
281
|
+
# Check whether session management is used
|
|
282
|
+
session_id = kwargs.get("session_id")
|
|
283
|
+
session_manager = kwargs.get("session_manager")
|
|
284
|
+
|
|
285
|
+
if session_id and session_manager:
|
|
286
|
+
# Handling code with session manager
|
|
287
|
+
|
|
288
|
+
session = session_manager.get_or_create_session(session_id)
|
|
289
|
+
code = session_manager.prepare_session_code(code, session, varDict)
|
|
290
|
+
# No longer need to handle varDict separately, already handled in prepare_session_code
|
|
291
|
+
varDict = None
|
|
292
|
+
else:
|
|
293
|
+
# Original Code Processing
|
|
294
|
+
code = self._preprocessCode(code, "python")
|
|
295
|
+
|
|
296
|
+
# Generate random suffixes to avoid conflicts
|
|
297
|
+
random_suffix = "".join(
|
|
298
|
+
random.choices(string.ascii_lowercase + string.digits, k=8)
|
|
299
|
+
)
|
|
300
|
+
remoteTmpFile = f"/tmp/milkie_python_cmd_{os.getpid()}_{random_suffix}.py"
|
|
301
|
+
remoteTmpVarFile = None
|
|
302
|
+
result = "执行failed" # Default Result
|
|
303
|
+
localTmpFilePath = None
|
|
304
|
+
varDictTmpFilePath = None
|
|
305
|
+
try:
|
|
306
|
+
# Prepare list of Python script lines
|
|
307
|
+
script_lines = []
|
|
308
|
+
# Add necessary imports
|
|
309
|
+
script_lines.append("# -*- coding: utf-8 -*-")
|
|
310
|
+
script_lines.append("import json")
|
|
311
|
+
script_lines.append("import reprlib")
|
|
312
|
+
script_lines.append("return_value = None")
|
|
313
|
+
|
|
314
|
+
# Auto-configure matplotlib for Chinese font support
|
|
315
|
+
script_lines.append("")
|
|
316
|
+
script_lines.append("# 自动配置matplotlib中文字体支持")
|
|
317
|
+
script_lines.append("try:")
|
|
318
|
+
script_lines.append(" import matplotlib")
|
|
319
|
+
script_lines.append(" import matplotlib.pyplot as plt")
|
|
320
|
+
script_lines.append(" # 配置matplotlib使用中文字体")
|
|
321
|
+
script_lines.append(
|
|
322
|
+
" plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'WenQuanYi Micro Hei', 'WenQuanYi Zen Hei', 'DejaVu Sans']"
|
|
323
|
+
)
|
|
324
|
+
script_lines.append(
|
|
325
|
+
" plt.rcParams['axes.unicode_minus'] = False # 修复负号显示"
|
|
326
|
+
)
|
|
327
|
+
script_lines.append(" # 重建字体缓存以确保新字体生效")
|
|
328
|
+
script_lines.append(" matplotlib.font_manager.fontManager.__init__()")
|
|
329
|
+
script_lines.append("except ImportError:")
|
|
330
|
+
script_lines.append(" pass # matplotlib未安装时忽略字体配置")
|
|
331
|
+
script_lines.append("")
|
|
332
|
+
|
|
333
|
+
for preImport in PreImport:
|
|
334
|
+
# Avoid duplicate imports
|
|
335
|
+
if f"import {preImport}" not in script_lines:
|
|
336
|
+
script_lines.append(f"import {preImport}")
|
|
337
|
+
|
|
338
|
+
# Add user code
|
|
339
|
+
script_lines.append(code)
|
|
340
|
+
|
|
341
|
+
# Concatenate all lines into a script string
|
|
342
|
+
script_content = "\n".join(script_lines)
|
|
343
|
+
|
|
344
|
+
# Create a local temporary file to store the script
|
|
345
|
+
with tempfile.NamedTemporaryFile(
|
|
346
|
+
mode="w", suffix=".py", delete=False, encoding="utf-8"
|
|
347
|
+
) as localTmpFile:
|
|
348
|
+
localTmpFile.write(script_content)
|
|
349
|
+
localTmpFilePath = localTmpFile.name
|
|
350
|
+
|
|
351
|
+
logger.info(f"本地临时脚本: {localTmpFilePath}")
|
|
352
|
+
logger.info(f"远程临时脚本: {remoteTmpFile}")
|
|
353
|
+
|
|
354
|
+
# If a variable dictionary is provided, save it to a temporary JSON file and upload it
|
|
355
|
+
if varDict and isinstance(varDict, dict):
|
|
356
|
+
import json
|
|
357
|
+
|
|
358
|
+
# Temporary file for variable dictionary
|
|
359
|
+
with tempfile.NamedTemporaryFile(
|
|
360
|
+
mode="w", suffix=".json", delete=False, encoding="utf-8"
|
|
361
|
+
) as varDictTmpFile:
|
|
362
|
+
json.dump(varDict, varDictTmpFile, ensure_ascii=False)
|
|
363
|
+
varDictTmpFilePath = varDictTmpFile.name
|
|
364
|
+
|
|
365
|
+
# Upload variable dictionary file to remote server
|
|
366
|
+
remoteTmpVarFile = (
|
|
367
|
+
f"/tmp/milkie_vars_{os.getpid()}_{random_suffix}.json"
|
|
368
|
+
)
|
|
369
|
+
logger.info(
|
|
370
|
+
f"将变量字典保存到临时文件并上传: {varDictTmpFilePath} -> {remoteTmpVarFile}"
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if not self.uploadFile(varDictTmpFilePath, remoteTmpVarFile):
|
|
374
|
+
logger.error("上传变量字典文件failed")
|
|
375
|
+
raise IOError("上传变量字典文件failed")
|
|
376
|
+
|
|
377
|
+
# Modify the script to read a variable dictionary from a file
|
|
378
|
+
prepend_script = [
|
|
379
|
+
"# 从临时文件读取变量字典",
|
|
380
|
+
"import json",
|
|
381
|
+
f"with open('{remoteTmpVarFile}', 'r', encoding='utf-8') as var_file:",
|
|
382
|
+
" _var_dict = json.load(var_file)",
|
|
383
|
+
"globals().update(_var_dict)",
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
# Insert code to read variables at the beginning of the script
|
|
387
|
+
script_content = "\n".join(prepend_script) + "\n" + script_content
|
|
388
|
+
|
|
389
|
+
# Rewrite the updated script
|
|
390
|
+
with open(localTmpFilePath, "w", encoding="utf-8") as f:
|
|
391
|
+
f.write(script_content)
|
|
392
|
+
|
|
393
|
+
result = self.cache_vm.getValue("python", [{"content": script_content}])
|
|
394
|
+
if result:
|
|
395
|
+
return result
|
|
396
|
+
|
|
397
|
+
# Upload script files to remote server
|
|
398
|
+
if not self.uploadFile(localTmpFilePath, remoteTmpFile):
|
|
399
|
+
logger.error("上传脚本文件failed")
|
|
400
|
+
raise IOError("上传脚本文件failed")
|
|
401
|
+
|
|
402
|
+
# Execute remote script
|
|
403
|
+
execution_output = self.execBash(f"python3 {remoteTmpFile}")
|
|
404
|
+
result = execution_output.strip()
|
|
405
|
+
self.cache_vm.setValue("python", [{"content": script_content}], result)
|
|
406
|
+
|
|
407
|
+
except Exception as e:
|
|
408
|
+
logger.error(f"执行Python脚本过程中出错: {str(e)}")
|
|
409
|
+
result = f"执行Python脚本过程中出错: {str(e)}"
|
|
410
|
+
finally:
|
|
411
|
+
# Clean up temporary files
|
|
412
|
+
# self._cleanupTempFile(localTmpFilePath, "local temporary script")
|
|
413
|
+
# self._cleanupTempFile(varDictTmpFilePath, "temporary file for local variable dictionary")
|
|
414
|
+
|
|
415
|
+
# Clean remote files
|
|
416
|
+
# if remoteTmpFile:
|
|
417
|
+
# self._cleanupRemoteTempFile(remoteTmpFile, "remote temporary script")
|
|
418
|
+
|
|
419
|
+
# if remoteTmpVarFile:
|
|
420
|
+
# self._cleanupRemoteTempFile(remoteTmpVarFile, "remote variable dictionary temporary file")
|
|
421
|
+
pass
|
|
422
|
+
|
|
423
|
+
return result
|
|
424
|
+
|
|
425
|
+
def uploadFile(self, localPath: str, remotePath: str) -> bool:
|
|
426
|
+
"""Upload file to remote server
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
localPath: local file path
|
|
430
|
+
remotePath: remote file path
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
bool: whether the upload was successful
|
|
434
|
+
"""
|
|
435
|
+
if not self._checkConnection():
|
|
436
|
+
return False
|
|
437
|
+
|
|
438
|
+
try:
|
|
439
|
+
sftp = self.client.open_sftp()
|
|
440
|
+
|
|
441
|
+
# Ensure the target directory exists
|
|
442
|
+
remoteDir = os.path.dirname(remotePath)
|
|
443
|
+
if remoteDir:
|
|
444
|
+
try:
|
|
445
|
+
self.execBash(f"mkdir -p {remoteDir}")
|
|
446
|
+
except Exception as e:
|
|
447
|
+
logger.warning(f"创建目录failed: {remoteDir}: {str(e)}")
|
|
448
|
+
|
|
449
|
+
sftp.put(localPath, remotePath)
|
|
450
|
+
sftp.close()
|
|
451
|
+
logger.info(f"文件上传successful: {localPath} -> {remotePath}")
|
|
452
|
+
return True
|
|
453
|
+
except Exception as e:
|
|
454
|
+
logger.error(f"文件上传failed: {str(e)}")
|
|
455
|
+
return False
|
|
456
|
+
|
|
457
|
+
def downloadFile(self, remotePath: str, localPath: str) -> bool:
|
|
458
|
+
"""Download a file from a remote server.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
remotePath: Remote file path
|
|
462
|
+
localPath: Local file path
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
bool: Whether the download was successful
|
|
466
|
+
"""
|
|
467
|
+
if not self._checkConnection():
|
|
468
|
+
return False
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
sftp = self.client.open_sftp()
|
|
472
|
+
|
|
473
|
+
# Ensure the local target directory exists
|
|
474
|
+
localDir = os.path.dirname(localPath)
|
|
475
|
+
if localDir:
|
|
476
|
+
os.makedirs(localDir, exist_ok=True)
|
|
477
|
+
|
|
478
|
+
sftp.get(remotePath, localPath)
|
|
479
|
+
sftp.close()
|
|
480
|
+
logger.info(f"文件下载successful: {remotePath} -> {localPath}")
|
|
481
|
+
return True
|
|
482
|
+
except Exception as e:
|
|
483
|
+
logger.error(f"文件下载failed: {str(e)}")
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
def listDir(self, remotePath: str) -> List[str]:
|
|
487
|
+
"""List files and directories in the remote directory
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
remotePath: Remote directory path
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
List[str]: List of files and directories
|
|
494
|
+
"""
|
|
495
|
+
if not self._checkConnection():
|
|
496
|
+
return []
|
|
497
|
+
|
|
498
|
+
try:
|
|
499
|
+
sftp = self.client.open_sftp()
|
|
500
|
+
files = sftp.listdir(remotePath)
|
|
501
|
+
sftp.close()
|
|
502
|
+
return files
|
|
503
|
+
except Exception as e:
|
|
504
|
+
logger.error(f"列出目录failed: {str(e)}")
|
|
505
|
+
return []
|
|
506
|
+
|
|
507
|
+
def disconnect(self) -> None:
|
|
508
|
+
"""Disconnect SSH connection"""
|
|
509
|
+
if self.client:
|
|
510
|
+
self.client.close()
|
|
511
|
+
self.client = None
|
|
512
|
+
self.connected = False
|
|
513
|
+
logger.info(f"已断开与 {self.config.host} 的connect to")
|
|
514
|
+
|
|
515
|
+
def _preprocessCode(self, code: str, type: str) -> str:
|
|
516
|
+
"""Preprocessing code
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
code: The code to preprocess
|
|
520
|
+
"""
|
|
521
|
+
startFlag = f"```{type}"
|
|
522
|
+
endFlag = "```"
|
|
523
|
+
idxStart = code.find(startFlag)
|
|
524
|
+
if idxStart == -1:
|
|
525
|
+
return code
|
|
526
|
+
|
|
527
|
+
idxEnd = code.find(endFlag, idxStart + len(startFlag))
|
|
528
|
+
if idxEnd == -1:
|
|
529
|
+
return code[idxStart + len(startFlag) :]
|
|
530
|
+
return code[idxStart + len(startFlag) : idxEnd]
|
|
531
|
+
|
|
532
|
+
def _cleanupTempFile(self, filepath: Optional[str], description: str) -> None:
|
|
533
|
+
"""Clean up local temporary files
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
filepath: File path
|
|
537
|
+
description: File description, used for logging
|
|
538
|
+
"""
|
|
539
|
+
if filepath and os.path.exists(filepath):
|
|
540
|
+
try:
|
|
541
|
+
os.remove(filepath)
|
|
542
|
+
logger.info(f"已删除{description}: {filepath}")
|
|
543
|
+
except OSError as e:
|
|
544
|
+
logger.warning(f"删除{description}failed: {filepath}, Error: {e}")
|
|
545
|
+
|
|
546
|
+
def _cleanupRemoteTempFile(self, remotepath: str, description: str) -> None:
|
|
547
|
+
"""Clean up remote temporary files
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
remotepath: Remote file path
|
|
551
|
+
description: File description, used for logging
|
|
552
|
+
"""
|
|
553
|
+
cleanup_result = self.execBash(f"rm -f {remotepath}")
|
|
554
|
+
if any(
|
|
555
|
+
err in cleanup_result for err in ["错误", "Error", "No such file", "failed"]
|
|
556
|
+
):
|
|
557
|
+
logger.warning(
|
|
558
|
+
f"清理{description}可能failed: {remotepath}, 清理命令输出: {cleanup_result}"
|
|
559
|
+
)
|
|
560
|
+
else:
|
|
561
|
+
logger.info(f"已尝试清理{description}: {remotepath}")
|
|
562
|
+
|
|
563
|
+
def __enter__(self):
|
|
564
|
+
"""Support context management with the 'with' statement"""
|
|
565
|
+
self.connect()
|
|
566
|
+
return self
|
|
567
|
+
|
|
568
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
569
|
+
"""Close the connection when exiting the context"""
|
|
570
|
+
self.disconnect()
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
class VMFactory:
|
|
574
|
+
"""Virtual machine factory class, used to create VM instances of different types"""
|
|
575
|
+
|
|
576
|
+
cache_vm = GlobalCacheKVCenter.getCacheMgr("data/cache", category="vm")
|
|
577
|
+
|
|
578
|
+
@staticmethod
|
|
579
|
+
def createVM(config: VMConfig) -> VM:
|
|
580
|
+
"""Create the corresponding VM instance according to the configuration.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
config: VM configuration information
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
VM: The created VM instance
|
|
587
|
+
"""
|
|
588
|
+
if config.connection_type == VMConnectionType.SSH:
|
|
589
|
+
return VMSSH(config, VMFactory.cache_vm)
|
|
590
|
+
elif config.connection_type == VMConnectionType.DOCKER:
|
|
591
|
+
# Here you can implement VMs of Docker type
|
|
592
|
+
raise NotImplementedError("Docker类型的VM尚未实现")
|
|
593
|
+
else:
|
|
594
|
+
raise ValueError(f"不支持的VMconnect to类型: {config.connectionType}")
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
if __name__ == "__main__":
|
|
598
|
+
encryptedPassword = SecurityUtils.encrypt(
|
|
599
|
+
"password", SecurityUtils.get_env_password()
|
|
600
|
+
)
|
|
601
|
+
print(encryptedPassword)
|
|
602
|
+
vmConfig = VMConfig(
|
|
603
|
+
host="localhost",
|
|
604
|
+
port=2222,
|
|
605
|
+
username="myuser",
|
|
606
|
+
encryptedPassword=encryptedPassword,
|
|
607
|
+
connectionType=VMConnectionType.SSH,
|
|
608
|
+
)
|
|
609
|
+
vm = VMFactory.createVM(vmConfig)
|
|
610
|
+
print(vm.execPython("import random; print(random.randint(1, 10))"))
|
dolphin/sdk/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Dolphin SDK - 开发者 SDK(开发框架)
|
|
4
|
+
|
|
5
|
+
职责:
|
|
6
|
+
- Agent 开发框架(DolphinAgent)
|
|
7
|
+
- Skill 扩展开发(GlobalSkills)
|
|
8
|
+
- 运行时环境(Env)
|
|
9
|
+
- 开发者友好的 API 封装
|
|
10
|
+
|
|
11
|
+
依赖规则:
|
|
12
|
+
- dolphin.sdk → 依赖 dolphin.lib, dolphin.core
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Agent
|
|
16
|
+
from dolphin.sdk.agent.dolphin_agent import DolphinAgent
|
|
17
|
+
from dolphin.sdk.agent.agent_factory import AgentFactory
|
|
18
|
+
|
|
19
|
+
# Runtime
|
|
20
|
+
from dolphin.sdk.runtime.env import Env
|
|
21
|
+
|
|
22
|
+
# Skill
|
|
23
|
+
from dolphin.sdk.skill.global_skills import GlobalSkills
|
|
24
|
+
from dolphin.sdk.skill.traditional_toolkit import TriditionalToolkit
|
|
25
|
+
|
|
26
|
+
# 重新导出 core/lib 组件以便捷使用
|
|
27
|
+
from dolphin.core import (
|
|
28
|
+
BaseAgent,
|
|
29
|
+
AgentState,
|
|
30
|
+
Context,
|
|
31
|
+
Skillset,
|
|
32
|
+
Skillkit,
|
|
33
|
+
SkillFunction,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
from dolphin.lib import (
|
|
37
|
+
Ontology,
|
|
38
|
+
OntologyManager,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
# Agent
|
|
43
|
+
"DolphinAgent",
|
|
44
|
+
"AgentFactory",
|
|
45
|
+
# Runtime
|
|
46
|
+
"Env",
|
|
47
|
+
# Skill
|
|
48
|
+
"GlobalSkills",
|
|
49
|
+
"TriditionalToolkit",
|
|
50
|
+
# Re-exported from core
|
|
51
|
+
"BaseAgent",
|
|
52
|
+
"AgentState",
|
|
53
|
+
"Context",
|
|
54
|
+
"Skillset",
|
|
55
|
+
"Skillkit",
|
|
56
|
+
"SkillFunction",
|
|
57
|
+
# Re-exported from lib
|
|
58
|
+
"Ontology",
|
|
59
|
+
"OntologyManager",
|
|
60
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Agent 模块 - Agent 开发框架"""
|
|
3
|
+
|
|
4
|
+
from dolphin.sdk.agent.dolphin_agent import DolphinAgent
|
|
5
|
+
from dolphin.sdk.agent.agent_factory import AgentFactory
|
|
6
|
+
from dolphin.core.agent.agent_state import PauseType
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"DolphinAgent",
|
|
10
|
+
"AgentFactory",
|
|
11
|
+
"PauseType",
|
|
12
|
+
]
|