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
|
@@ -0,0 +1,1063 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import glob
|
|
5
|
+
from typing import Optional, TYPE_CHECKING
|
|
6
|
+
from dolphin.core import flags
|
|
7
|
+
from dolphin.core.config.global_config import GlobalConfig
|
|
8
|
+
from dolphin.core.context.context import Context
|
|
9
|
+
from dolphin.core.common.constants import (
|
|
10
|
+
KEY_MAX_ANSWER_CONTENT_LENGTH,
|
|
11
|
+
KEY_SESSION_ID,
|
|
12
|
+
KEY_USER_ID,
|
|
13
|
+
)
|
|
14
|
+
from dolphin.core.context_engineer.core.context_manager import (
|
|
15
|
+
ContextManager,
|
|
16
|
+
)
|
|
17
|
+
from dolphin.core.executor.executor import Executor
|
|
18
|
+
from dolphin.core.common.object_type import ObjectTypeFactory
|
|
19
|
+
from dolphin.core.context.var_output import VarOutput
|
|
20
|
+
from dolphin.core.logging.logger import get_logger
|
|
21
|
+
from dolphin.core.common.enums import MessageRole
|
|
22
|
+
|
|
23
|
+
# Import sdk/lib modules under TYPE_CHECKING to avoid circular imports
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from dolphin.lib.memory.manager import MemoryManager
|
|
26
|
+
from dolphin.sdk.skill.global_skills import GlobalSkills
|
|
27
|
+
from dolphin.sdk.skill.traditional_toolkit import TriditionalToolkit
|
|
28
|
+
from dolphin.lib.vm.vm import VM, VMFactory
|
|
29
|
+
|
|
30
|
+
logger = get_logger()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DolphinExecutor:
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
global_config: GlobalConfig | None = None,
|
|
37
|
+
global_configpath="./config/global.yaml",
|
|
38
|
+
global_skills=None,
|
|
39
|
+
global_types=None,
|
|
40
|
+
type_folders=None,
|
|
41
|
+
context_manager: Optional[ContextManager] = None,
|
|
42
|
+
verbose: bool = False,
|
|
43
|
+
is_cli: bool = False,
|
|
44
|
+
):
|
|
45
|
+
# Lazy imports to avoid circular dependencies
|
|
46
|
+
from dolphin.sdk.skill.global_skills import GlobalSkills
|
|
47
|
+
from dolphin.lib.memory.manager import MemoryManager
|
|
48
|
+
from dolphin.lib.vm.vm import VM, VMFactory
|
|
49
|
+
|
|
50
|
+
# Initialize configuration with fallback logic
|
|
51
|
+
if global_config is not None:
|
|
52
|
+
self.config = global_config
|
|
53
|
+
elif os.path.exists(global_configpath):
|
|
54
|
+
self.config = GlobalConfig.from_yaml(global_configpath)
|
|
55
|
+
else:
|
|
56
|
+
self.config = GlobalConfig()
|
|
57
|
+
self.global_skills = (
|
|
58
|
+
global_skills if global_skills is not None else GlobalSkills(self.config)
|
|
59
|
+
)
|
|
60
|
+
self.global_types = (
|
|
61
|
+
global_types if global_types is not None else ObjectTypeFactory()
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Auto-load type definitions if not provided and type_folders is not specified
|
|
65
|
+
if global_types is None and type_folders is None:
|
|
66
|
+
self._loadDefaultTypeFiles()
|
|
67
|
+
elif type_folders is not None:
|
|
68
|
+
self._loadTypeFilesFromFolders(type_folders)
|
|
69
|
+
|
|
70
|
+
self.memory_manager = MemoryManager(global_config=self.config)
|
|
71
|
+
self.context_manager = context_manager
|
|
72
|
+
if self.context_manager is None:
|
|
73
|
+
if self.config.context_engineer_config is not None and \
|
|
74
|
+
self.config.context_engineer_config.context_config is not None:
|
|
75
|
+
self.context_manager = ContextManager(context_config=self.config.context_engineer_config.context_config)
|
|
76
|
+
else:
|
|
77
|
+
self.context_manager = ContextManager()
|
|
78
|
+
|
|
79
|
+
self.context = Context(
|
|
80
|
+
config=self.config,
|
|
81
|
+
global_skills=self.global_skills,
|
|
82
|
+
memory_manager=self.memory_manager,
|
|
83
|
+
global_types=self.global_types,
|
|
84
|
+
context_manager=self.context_manager,
|
|
85
|
+
verbose=verbose,
|
|
86
|
+
is_cli=is_cli,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
self.vm: VM = None
|
|
90
|
+
if self.config.vm_config is not None:
|
|
91
|
+
self.vm = VMFactory.createVM(self.config.vm_config)
|
|
92
|
+
|
|
93
|
+
# Coroutine execution components
|
|
94
|
+
self.state_registry = None
|
|
95
|
+
self.snapshot_store = None
|
|
96
|
+
self._init_coroutine_components()
|
|
97
|
+
# Debug controller reference (for CLI post-mortem access)
|
|
98
|
+
self._debug_controller = None
|
|
99
|
+
|
|
100
|
+
def _init_coroutine_components(self):
|
|
101
|
+
"""Initialize coroutine execution component"""
|
|
102
|
+
from dolphin.core.coroutine import (
|
|
103
|
+
ExecutionStateRegistry,
|
|
104
|
+
)
|
|
105
|
+
from dolphin.core.coroutine.context_snapshot_store import (
|
|
106
|
+
MemoryContextSnapshotStore,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
self.state_registry = ExecutionStateRegistry()
|
|
110
|
+
self.snapshot_store = MemoryContextSnapshotStore()
|
|
111
|
+
|
|
112
|
+
def _loadDefaultTypeFiles(self):
|
|
113
|
+
"""
|
|
114
|
+
Auto-load type definitions from common directories
|
|
115
|
+
"""
|
|
116
|
+
# Common directories to search for .type files
|
|
117
|
+
default_type_paths = [
|
|
118
|
+
"./examples/types", # Project examples types
|
|
119
|
+
"./types", # Root types directory
|
|
120
|
+
"./src/types", # Source types directory
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
for type_path in default_type_paths:
|
|
124
|
+
if os.path.exists(type_path):
|
|
125
|
+
self._loadTypeFilesFromFolder(type_path)
|
|
126
|
+
|
|
127
|
+
def _loadTypeFilesFromFolders(self, type_folders):
|
|
128
|
+
"""
|
|
129
|
+
Load type definitions from specified folders
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
type_folders: List of folder paths or single folder path
|
|
133
|
+
"""
|
|
134
|
+
if isinstance(type_folders, str):
|
|
135
|
+
type_folders = [type_folders]
|
|
136
|
+
|
|
137
|
+
for folder in type_folders:
|
|
138
|
+
if os.path.exists(folder):
|
|
139
|
+
self._loadTypeFilesFromFolder(folder)
|
|
140
|
+
else:
|
|
141
|
+
logger.warning(f"Type folder not found: {folder}")
|
|
142
|
+
|
|
143
|
+
def _loadTypeFilesFromFolder(self, folder_path):
|
|
144
|
+
"""
|
|
145
|
+
Scan for .type files in the specified folder and load them into global_types
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
folder_path: Path to the folder containing .type files
|
|
149
|
+
"""
|
|
150
|
+
# Get all .type files recursively
|
|
151
|
+
search_pattern = os.path.join(folder_path, "**", "*.type")
|
|
152
|
+
type_files = glob.glob(search_pattern, recursive=True)
|
|
153
|
+
|
|
154
|
+
for file_path in type_files:
|
|
155
|
+
try:
|
|
156
|
+
self.global_types.load(file_path)
|
|
157
|
+
logger.debug(f"Loaded type definition: {file_path}")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.warning(f"Failed to load type file {file_path}: {e}")
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
def load_type(self, type_json: dict):
|
|
163
|
+
"""
|
|
164
|
+
Load a type definition from JSON data into global_types
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
type_json (dict): JSON type definition
|
|
168
|
+
"""
|
|
169
|
+
self.global_types.load_from_json(type_json)
|
|
170
|
+
|
|
171
|
+
def load_type_from_file(self, file_path: str):
|
|
172
|
+
"""
|
|
173
|
+
Load a type definition from a .type file
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
file_path (str): Path to the .type file
|
|
177
|
+
"""
|
|
178
|
+
self.global_types.load(file_path)
|
|
179
|
+
|
|
180
|
+
async def executor_init(self, infos):
|
|
181
|
+
for key, value in infos.items():
|
|
182
|
+
if key == "config":
|
|
183
|
+
self.config_initialize(value)
|
|
184
|
+
if key == "variables":
|
|
185
|
+
await self.context_initialize(param_dict=value)
|
|
186
|
+
elif key == "tools":
|
|
187
|
+
await self.context_initialize(tool_dict=value)
|
|
188
|
+
elif key == "skillkit":
|
|
189
|
+
await self.context_initialize(skillkit=value)
|
|
190
|
+
elif key == "skillkit_hook":
|
|
191
|
+
await self.context_initialize(skillkit_hook=value)
|
|
192
|
+
else:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
def config_initialize(self, configs):
|
|
196
|
+
# Lazy imports to avoid circular dependencies
|
|
197
|
+
from dolphin.lib.memory.manager import MemoryManager
|
|
198
|
+
from dolphin.lib.vm.vm import VM, VMFactory
|
|
199
|
+
|
|
200
|
+
self.config = GlobalConfig.from_dict(configs)
|
|
201
|
+
self.memory_manager = MemoryManager(global_config=self.config)
|
|
202
|
+
# Recreate Context while keeping the original verbose and is_cli settings
|
|
203
|
+
current_verbose = (
|
|
204
|
+
getattr(self.context, "verbose", False)
|
|
205
|
+
if hasattr(self, "context")
|
|
206
|
+
else False
|
|
207
|
+
)
|
|
208
|
+
current_is_cli = (
|
|
209
|
+
getattr(self.context, "is_cli", False)
|
|
210
|
+
if hasattr(self, "context")
|
|
211
|
+
else False
|
|
212
|
+
)
|
|
213
|
+
self.context = Context(
|
|
214
|
+
config=self.config,
|
|
215
|
+
global_skills=self.global_skills,
|
|
216
|
+
memory_manager=self.memory_manager,
|
|
217
|
+
global_types=self.global_types,
|
|
218
|
+
verbose=current_verbose,
|
|
219
|
+
is_cli=current_is_cli,
|
|
220
|
+
) # Initialize context here with preserved verbose/is_cli settings
|
|
221
|
+
|
|
222
|
+
self.vm: VM = None
|
|
223
|
+
if self.config.vm_config is not None:
|
|
224
|
+
self.vm = VMFactory.createVM(self.config.vm_config)
|
|
225
|
+
|
|
226
|
+
async def context_initialize(
|
|
227
|
+
self,
|
|
228
|
+
param_dict=None,
|
|
229
|
+
tool_dict=None,
|
|
230
|
+
skillkit=None,
|
|
231
|
+
skillkit_hook=None,
|
|
232
|
+
):
|
|
233
|
+
if param_dict is not None:
|
|
234
|
+
for name, value in param_dict.items():
|
|
235
|
+
if VarOutput.is_serialized_dict(value):
|
|
236
|
+
value = VarOutput.from_dict(value)
|
|
237
|
+
self.context.set_variable(name=name, value=value)
|
|
238
|
+
|
|
239
|
+
if tool_dict is not None:
|
|
240
|
+
from dolphin.sdk.skill.traditional_toolkit import TriditionalToolkit
|
|
241
|
+
triditional_toolkit = TriditionalToolkit.buildFromTooldict(tool_dict)
|
|
242
|
+
self.context.set_skills(triditional_toolkit)
|
|
243
|
+
elif skillkit is not None:
|
|
244
|
+
self.context.set_skills(skillkit)
|
|
245
|
+
elif skillkit_hook is not None:
|
|
246
|
+
self.context.set_skillkit_hook(skillkit_hook)
|
|
247
|
+
|
|
248
|
+
def set_context(self, context: Context):
|
|
249
|
+
self.context = context
|
|
250
|
+
|
|
251
|
+
async def continue_exploration(self, **kwargs):
|
|
252
|
+
"""Continue exploring based on the existing context (multi-turn dialogue scenario)
|
|
253
|
+
|
|
254
|
+
This method reuses the message history, variable pool, and other states from the current context,
|
|
255
|
+
and executes a new explore session to process the user's subsequent input.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
model: Model name; if empty, the model used in the previous session from context will be used
|
|
259
|
+
use_history: Whether to use historical messages, default is True
|
|
260
|
+
mode: Exploration mode ("prompt" or "tool_call"); if empty, the mode used in the previous session from context will be used
|
|
261
|
+
skills: List of skills; if empty, the skill configuration used in the previous session from context will be used
|
|
262
|
+
tools: Alias for skills
|
|
263
|
+
content: User input content (optional)
|
|
264
|
+
output_var: Name of the output variable, default is "result"
|
|
265
|
+
**kwargs: Additional parameters
|
|
266
|
+
|
|
267
|
+
Note:
|
|
268
|
+
The mode and skills parameters will automatically inherit the configuration from the previous round in context.
|
|
269
|
+
To override, explicitly pass them via kwargs.
|
|
270
|
+
|
|
271
|
+
Yields:
|
|
272
|
+
Execution results
|
|
273
|
+
"""
|
|
274
|
+
if flags.is_enabled(flags.EXPLORE_BLOCK_V2):
|
|
275
|
+
raise NotImplementedError(
|
|
276
|
+
"continue_exploration 暂不支持 EXPLORE_BLOCK_V2 模式,"
|
|
277
|
+
"请使用 flags.set_flag(flags.EXPLORE_BLOCK_V2, False) 禁用后再调用"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Ensure interrupt state is cleared for the new exploration round
|
|
281
|
+
if hasattr(self.context, "clear_interrupt"):
|
|
282
|
+
self.context.clear_interrupt()
|
|
283
|
+
|
|
284
|
+
from dolphin.core.code_block.explore_block import ExploreBlock
|
|
285
|
+
from dolphin.core.trajectory.recorder import Recorder
|
|
286
|
+
from dolphin.core.runtime.runtime_instance import ProgressInstance
|
|
287
|
+
|
|
288
|
+
# Create exploration block (default prompt mode)
|
|
289
|
+
explore_block = ExploreBlock(
|
|
290
|
+
context=self.context,
|
|
291
|
+
debug_infos=None,
|
|
292
|
+
tools_format=kwargs.get("tools_format", "medium"),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Manually register block and progress to runtime_graph
|
|
296
|
+
if self.context.runtime_graph:
|
|
297
|
+
self.context.runtime_graph.set_block(explore_block)
|
|
298
|
+
|
|
299
|
+
progress = ProgressInstance(self.context)
|
|
300
|
+
if self.context.runtime_graph:
|
|
301
|
+
self.context.runtime_graph.set_progress(progress)
|
|
302
|
+
|
|
303
|
+
explore_block.recorder = Recorder(
|
|
304
|
+
self.context,
|
|
305
|
+
progress=progress,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Extract parameters from kwargs
|
|
309
|
+
model_name = kwargs.get("model") or self.context.get_last_model_name() or ""
|
|
310
|
+
use_history = kwargs.get("use_history", True)
|
|
311
|
+
|
|
312
|
+
# Sync the model name to ExploreBlock
|
|
313
|
+
if model_name:
|
|
314
|
+
explore_block.model = model_name
|
|
315
|
+
|
|
316
|
+
# Use the continue_exploration method of ExploreBlock
|
|
317
|
+
async for result in explore_block.continue_exploration(
|
|
318
|
+
model=model_name,
|
|
319
|
+
use_history=use_history,
|
|
320
|
+
**kwargs,
|
|
321
|
+
):
|
|
322
|
+
yield result
|
|
323
|
+
|
|
324
|
+
async def run(self, content, output_variables: Optional[list] = None, **kwargs):
|
|
325
|
+
start_time = time.perf_counter()
|
|
326
|
+
|
|
327
|
+
# Pass the debug mode parameter to Executor
|
|
328
|
+
debug_mode = kwargs.get("debug_mode", False)
|
|
329
|
+
break_on_start = kwargs.get("break_on_start", False)
|
|
330
|
+
break_at = kwargs.get("break_at", None)
|
|
331
|
+
self.executor = Executor(
|
|
332
|
+
context=self.context,
|
|
333
|
+
debug_mode=debug_mode,
|
|
334
|
+
break_on_start=break_on_start,
|
|
335
|
+
break_at=break_at,
|
|
336
|
+
)
|
|
337
|
+
# expose debug controller for post-mortem if present
|
|
338
|
+
try:
|
|
339
|
+
self._debug_controller = getattr(self.executor, "debug_controller", None)
|
|
340
|
+
except Exception:
|
|
341
|
+
self._debug_controller = None
|
|
342
|
+
|
|
343
|
+
async for result in self.executor.run(
|
|
344
|
+
content, output_variables=output_variables, **kwargs
|
|
345
|
+
):
|
|
346
|
+
yield result
|
|
347
|
+
end_time = time.perf_counter()
|
|
348
|
+
self.context.debug(f"Time taken: {end_time - start_time} seconds")
|
|
349
|
+
|
|
350
|
+
async def run_and_get_result(
|
|
351
|
+
self, content, output_variables: Optional[list] = None, **kwargs
|
|
352
|
+
):
|
|
353
|
+
start_time = time.perf_counter()
|
|
354
|
+
|
|
355
|
+
start_time = time.perf_counter()
|
|
356
|
+
# Pass the debug mode parameter to Executor
|
|
357
|
+
debug_mode = kwargs.get("debug_mode", False)
|
|
358
|
+
break_on_start = kwargs.get("break_on_start", False)
|
|
359
|
+
break_at = kwargs.get("break_at", None)
|
|
360
|
+
self.executor = Executor(
|
|
361
|
+
context=self.context,
|
|
362
|
+
debug_mode=debug_mode,
|
|
363
|
+
break_on_start=break_on_start,
|
|
364
|
+
break_at=break_at,
|
|
365
|
+
)
|
|
366
|
+
# expose debug controller for post-mortem if present
|
|
367
|
+
try:
|
|
368
|
+
self._debug_controller = getattr(self.executor, "debug_controller", None)
|
|
369
|
+
except Exception:
|
|
370
|
+
self._debug_controller = None
|
|
371
|
+
|
|
372
|
+
async for result in self.executor.run_and_get_result(
|
|
373
|
+
content, output_variables=output_variables, **kwargs
|
|
374
|
+
):
|
|
375
|
+
yield result
|
|
376
|
+
end_time = time.perf_counter()
|
|
377
|
+
self.context.debug(f"Time taken: {end_time - start_time} seconds")
|
|
378
|
+
|
|
379
|
+
def _prepare_for_run(self, **kwargs):
|
|
380
|
+
# Only set skills if not already set (to preserve agent skills set by Environment)
|
|
381
|
+
if self.context.is_skillkit_empty():
|
|
382
|
+
# Try to get all skills including custom skills from global skills
|
|
383
|
+
# If global_skills has getAllSkills method, use it; otherwise fall back to installed skills
|
|
384
|
+
if hasattr(self.global_skills, "getAllSkills") and callable(
|
|
385
|
+
getattr(self.global_skills, "getAllSkills")
|
|
386
|
+
):
|
|
387
|
+
all_skills = self.global_skills.getAllSkills()
|
|
388
|
+
self.context.set_skills(all_skills)
|
|
389
|
+
else:
|
|
390
|
+
installed_skills = self.global_skills.getInstalledSkills()
|
|
391
|
+
self.context.set_skills(installed_skills)
|
|
392
|
+
|
|
393
|
+
if KEY_USER_ID in kwargs:
|
|
394
|
+
self.context.set_variable(name=KEY_USER_ID, value=kwargs.get(KEY_USER_ID))
|
|
395
|
+
|
|
396
|
+
if KEY_SESSION_ID in kwargs:
|
|
397
|
+
self.context.set_variable(
|
|
398
|
+
name=KEY_SESSION_ID, value=kwargs.get(KEY_SESSION_ID)
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
if KEY_MAX_ANSWER_CONTENT_LENGTH in kwargs:
|
|
402
|
+
self.context.set_variable(
|
|
403
|
+
name=KEY_MAX_ANSWER_CONTENT_LENGTH,
|
|
404
|
+
value=kwargs.get(KEY_MAX_ANSWER_CONTENT_LENGTH),
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def shutdown(self):
|
|
408
|
+
"""
|
|
409
|
+
Gracefully shutdown the DolphinExecutor and its components.
|
|
410
|
+
"""
|
|
411
|
+
pass
|
|
412
|
+
|
|
413
|
+
def get_execution_trace(self, title=None):
|
|
414
|
+
"""Generate and return execution trace information for the current execution context.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
title (str, optional): Trace title. If not provided, use the default title.
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
str: Execution trace information (text/JSON string) containing call_chain and LLM details.
|
|
421
|
+
"""
|
|
422
|
+
trace_data = self.context.get_execution_trace(title)
|
|
423
|
+
|
|
424
|
+
# If the lower layer has already returned a string, pass it through directly.
|
|
425
|
+
if isinstance(trace_data, str):
|
|
426
|
+
return trace_data
|
|
427
|
+
|
|
428
|
+
# Default conversion to JSON text, ensuring that the upper layer writing files is str
|
|
429
|
+
try:
|
|
430
|
+
import json
|
|
431
|
+
|
|
432
|
+
return json.dumps(trace_data, ensure_ascii=False, indent=2)
|
|
433
|
+
except Exception:
|
|
434
|
+
# Fallback: use repr when not serializable to avoid throwing errors again
|
|
435
|
+
return repr(trace_data)
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def debug_controller(self):
|
|
439
|
+
"""Expose a debug controller instance for post-mortem.
|
|
440
|
+
|
|
441
|
+
Priority:
|
|
442
|
+
- Controller captured from the last execution step/run (coroutine path)
|
|
443
|
+
- Controller from the currently bound low-level Executor (run path)
|
|
444
|
+
"""
|
|
445
|
+
if self._debug_controller is not None:
|
|
446
|
+
return self._debug_controller
|
|
447
|
+
try:
|
|
448
|
+
return getattr(self.executor, "debug_controller", None)
|
|
449
|
+
except Exception:
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
# Backward compatibility: retain old method names
|
|
453
|
+
def get_profile(self, title=None):
|
|
454
|
+
"""[Deprecated] Please use get_execution_trace() instead"""
|
|
455
|
+
import warnings
|
|
456
|
+
warnings.warn("get_profile() 已废弃,请使用 get_execution_trace()", DeprecationWarning, stacklevel=2)
|
|
457
|
+
return self.get_execution_trace(title)
|
|
458
|
+
|
|
459
|
+
# === Exception handling helper ===
|
|
460
|
+
def _handle_execution_exception(
|
|
461
|
+
self,
|
|
462
|
+
e: Exception,
|
|
463
|
+
frame,
|
|
464
|
+
block_pointer: int,
|
|
465
|
+
):
|
|
466
|
+
"""Unified handler for execution exceptions (UserInterrupt, ToolInterrupt, others).
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
e: The exception to handle
|
|
470
|
+
frame: Current execution frame
|
|
471
|
+
block_pointer: Current block pointer position
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
StepResult if interrupt was handled, None if exception should be re-raised
|
|
475
|
+
"""
|
|
476
|
+
import traceback
|
|
477
|
+
from dolphin.core.utils.tools import ToolInterrupt
|
|
478
|
+
from dolphin.core.common.exceptions import UserInterrupt
|
|
479
|
+
from dolphin.core.coroutine.execution_frame import FrameStatus, WaitReason
|
|
480
|
+
from dolphin.core.coroutine.step_result import StepResult
|
|
481
|
+
from dolphin.core.coroutine import ResumeHandle
|
|
482
|
+
|
|
483
|
+
# Handle user interruption (user actively interrupted execution)
|
|
484
|
+
if isinstance(e, UserInterrupt):
|
|
485
|
+
frame.status = FrameStatus.WAITING_FOR_INTERVENTION
|
|
486
|
+
frame.wait_reason = WaitReason.USER_INTERRUPT
|
|
487
|
+
intervention_snapshot_id = self._save_frame_snapshot(frame)
|
|
488
|
+
frame.error = {
|
|
489
|
+
"error_type": "UserInterrupt",
|
|
490
|
+
"message": str(e),
|
|
491
|
+
"at_block": block_pointer,
|
|
492
|
+
"intervention_snapshot_id": intervention_snapshot_id,
|
|
493
|
+
}
|
|
494
|
+
return StepResult.user_interrupted(
|
|
495
|
+
resume_handle=ResumeHandle.create_user_interrupt_handle(
|
|
496
|
+
frame_id=frame.frame_id,
|
|
497
|
+
snapshot_id=intervention_snapshot_id,
|
|
498
|
+
current_block=block_pointer,
|
|
499
|
+
),
|
|
500
|
+
result={"partial_output": self.context.get_user_variables()},
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# Handle tool interruption (tool requested user input)
|
|
504
|
+
if isinstance(e, ToolInterrupt):
|
|
505
|
+
frame.status = FrameStatus.WAITING_FOR_INTERVENTION
|
|
506
|
+
frame.wait_reason = WaitReason.TOOL_REQUEST
|
|
507
|
+
intervention_snapshot_id = self._save_frame_snapshot(frame)
|
|
508
|
+
frame.error = {
|
|
509
|
+
"error_type": "ToolInterrupt",
|
|
510
|
+
"message": str(e),
|
|
511
|
+
"tool_name": getattr(e, "tool_name", ""),
|
|
512
|
+
"tool_args": getattr(e, "tool_args", []),
|
|
513
|
+
"at_block": block_pointer,
|
|
514
|
+
"intervention_snapshot_id": intervention_snapshot_id,
|
|
515
|
+
}
|
|
516
|
+
return StepResult.interrupted(
|
|
517
|
+
resume_handle=ResumeHandle.create_handle(
|
|
518
|
+
frame_id=frame.frame_id,
|
|
519
|
+
snapshot_id=intervention_snapshot_id,
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
# Other errors: mark failed and return None to signal re-raise
|
|
524
|
+
frame.status = FrameStatus.FAILED
|
|
525
|
+
error_snapshot_id = self._save_frame_snapshot(frame)
|
|
526
|
+
frame.error = {
|
|
527
|
+
"error_type": type(e).__name__,
|
|
528
|
+
"message": str(e),
|
|
529
|
+
"stack": traceback.format_exc(),
|
|
530
|
+
"at_block": block_pointer,
|
|
531
|
+
"error_snapshot_id": error_snapshot_id,
|
|
532
|
+
}
|
|
533
|
+
return None # Caller should re-raise the exception
|
|
534
|
+
|
|
535
|
+
# === Added coroutine methods ===
|
|
536
|
+
async def start_coroutine(self, content, **kwargs):
|
|
537
|
+
"""Starts a resumable execution"""
|
|
538
|
+
from dolphin.core.coroutine import ExecutionFrame
|
|
539
|
+
|
|
540
|
+
self._prepare_for_run(**kwargs)
|
|
541
|
+
|
|
542
|
+
# 1. Create an execution frame and save the original content
|
|
543
|
+
frame = ExecutionFrame.create_root_frame()
|
|
544
|
+
frame.original_content = content
|
|
545
|
+
|
|
546
|
+
# Save debug-related parameters to the execution frame (as part of the execution context)
|
|
547
|
+
frame.debug_mode = kwargs.get("debug_mode", False)
|
|
548
|
+
frame.break_on_start = kwargs.get("break_on_start", False)
|
|
549
|
+
frame.break_at = kwargs.get("break_at", None)
|
|
550
|
+
|
|
551
|
+
# 2. Initialize context and create snapshot
|
|
552
|
+
snapshot = self._create_snapshot(frame.frame_id)
|
|
553
|
+
frame.context_snapshot_id = snapshot.snapshot_id
|
|
554
|
+
|
|
555
|
+
# 3. Register to the state manager
|
|
556
|
+
self.state_registry.register_frame(frame)
|
|
557
|
+
self.snapshot_store.save_snapshot(snapshot)
|
|
558
|
+
|
|
559
|
+
# 4. Record the currently active coroutine
|
|
560
|
+
self._current_frame_id = frame.frame_id
|
|
561
|
+
|
|
562
|
+
return frame
|
|
563
|
+
|
|
564
|
+
async def step_coroutine(self, frame_id: str):
|
|
565
|
+
"""Execute one step and return a unified StepResult.
|
|
566
|
+
|
|
567
|
+
Returns:
|
|
568
|
+
StepResult: Unified execution result, containing status and optional result data/restore handle.
|
|
569
|
+
- StepResult.running(): Still executing.
|
|
570
|
+
- StepResult.completed(result): Execution completed, contains final result.
|
|
571
|
+
- StepResult.interrupted(handle): Tool interrupted, contains restore handle.
|
|
572
|
+
"""
|
|
573
|
+
import traceback
|
|
574
|
+
from dolphin.core.coroutine.execution_frame import FrameStatus
|
|
575
|
+
from dolphin.core.coroutine.step_result import StepResult
|
|
576
|
+
|
|
577
|
+
# 1. Get frame and restore context
|
|
578
|
+
frame = self.state_registry.get_frame(frame_id)
|
|
579
|
+
if not frame:
|
|
580
|
+
raise ValueError(f"Frame not found: {frame_id}")
|
|
581
|
+
|
|
582
|
+
snapshot = self.snapshot_store.load_snapshot(frame.context_snapshot_id)
|
|
583
|
+
if not snapshot:
|
|
584
|
+
raise ValueError(f"Snapshot not found: {frame.context_snapshot_id}")
|
|
585
|
+
|
|
586
|
+
self._restore_context(snapshot)
|
|
587
|
+
|
|
588
|
+
try:
|
|
589
|
+
# 2. Get the parsed blocks (cached in the frame or re-parsed)
|
|
590
|
+
blocks = self._get_or_parse_blocks(frame)
|
|
591
|
+
|
|
592
|
+
# 3. Execute single step (read debug configuration from frame)
|
|
593
|
+
executor = Executor(
|
|
594
|
+
context=self.context,
|
|
595
|
+
step_mode=True,
|
|
596
|
+
debug_mode=frame.debug_mode,
|
|
597
|
+
break_on_start=frame.break_on_start,
|
|
598
|
+
break_at=frame.break_at,
|
|
599
|
+
)
|
|
600
|
+
# capture debug controller for post-mortem access
|
|
601
|
+
try:
|
|
602
|
+
self._debug_controller = getattr(executor, "debug_controller", None)
|
|
603
|
+
except Exception:
|
|
604
|
+
self._debug_controller = None
|
|
605
|
+
|
|
606
|
+
step_info = None
|
|
607
|
+
|
|
608
|
+
async for result in executor.run_step(blocks, frame.block_pointer):
|
|
609
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
610
|
+
# This is the step completion information
|
|
611
|
+
step_info = result
|
|
612
|
+
|
|
613
|
+
new_pointer, is_complete = (
|
|
614
|
+
step_info if step_info else (frame.block_pointer, True)
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
# 4. Update Status
|
|
618
|
+
frame.block_pointer = new_pointer
|
|
619
|
+
|
|
620
|
+
if is_complete:
|
|
621
|
+
frame.status = FrameStatus.COMPLETED
|
|
622
|
+
|
|
623
|
+
# 5. Save State
|
|
624
|
+
self._save_frame_snapshot(frame)
|
|
625
|
+
|
|
626
|
+
# 6. Return the completion status and result variables
|
|
627
|
+
if is_complete:
|
|
628
|
+
# Return user-defined variables as results (excluding internal variables)
|
|
629
|
+
return StepResult.completed(result=self.context.get_user_variables())
|
|
630
|
+
else:
|
|
631
|
+
return StepResult.running()
|
|
632
|
+
|
|
633
|
+
except Exception as e:
|
|
634
|
+
result = self._handle_execution_exception(e, frame, frame.block_pointer)
|
|
635
|
+
if result is not None:
|
|
636
|
+
return result
|
|
637
|
+
raise e
|
|
638
|
+
|
|
639
|
+
async def run_coroutine(self, frame_id: str, progress_callback=None):
|
|
640
|
+
"""Execute continuously until interrupted or completed
|
|
641
|
+
|
|
642
|
+
Returns:
|
|
643
|
+
StepResult: Unified execution result
|
|
644
|
+
- StepResult.completed(result): Execution completed, containing final result
|
|
645
|
+
- StepResult.interrupted(handle): Tool interrupted, containing recovery handle
|
|
646
|
+
|
|
647
|
+
Semantics:
|
|
648
|
+
- Execute subsequent blocks sequentially starting from the current frame's block_pointer
|
|
649
|
+
- If ToolInterrupt is triggered during execution, save an interrupt snapshot and return interrupt status
|
|
650
|
+
- If all blocks are executed, save a completion snapshot and return completion status
|
|
651
|
+
|
|
652
|
+
Note:
|
|
653
|
+
- Unlike step_coroutine, this method does not save snapshots at the end of each block; it saves only once at "interrupt/completion"
|
|
654
|
+
"""
|
|
655
|
+
import traceback
|
|
656
|
+
from dolphin.core.coroutine.execution_frame import FrameStatus
|
|
657
|
+
from dolphin.core.coroutine.step_result import StepResult
|
|
658
|
+
|
|
659
|
+
# 1. Get frame and restore context
|
|
660
|
+
frame = self.state_registry.get_frame(frame_id)
|
|
661
|
+
if not frame:
|
|
662
|
+
raise ValueError(f"Frame not found: {frame_id}")
|
|
663
|
+
|
|
664
|
+
snapshot = self.snapshot_store.load_snapshot(frame.context_snapshot_id)
|
|
665
|
+
if not snapshot:
|
|
666
|
+
raise ValueError(f"Snapshot not found: {frame.context_snapshot_id}")
|
|
667
|
+
|
|
668
|
+
self._restore_context(snapshot)
|
|
669
|
+
|
|
670
|
+
try:
|
|
671
|
+
# 2. Get the parsed blocks (cached in the frame or re-parsed)
|
|
672
|
+
blocks = self._get_or_parse_blocks(frame)
|
|
673
|
+
|
|
674
|
+
# 3. Execute continuously until interrupted or completed (read debug configuration from frame)
|
|
675
|
+
executor = Executor(
|
|
676
|
+
context=self.context,
|
|
677
|
+
step_mode=False,
|
|
678
|
+
debug_mode=frame.debug_mode,
|
|
679
|
+
break_on_start=frame.break_on_start,
|
|
680
|
+
break_at=frame.break_at,
|
|
681
|
+
)
|
|
682
|
+
# capture debug controller for post-mortem access
|
|
683
|
+
try:
|
|
684
|
+
self._debug_controller = getattr(executor, "debug_controller", None)
|
|
685
|
+
except Exception:
|
|
686
|
+
self._debug_controller = None
|
|
687
|
+
|
|
688
|
+
pointer = frame.block_pointer
|
|
689
|
+
while pointer < len(blocks):
|
|
690
|
+
current_block = blocks[pointer]
|
|
691
|
+
try:
|
|
692
|
+
# Pass through and consume the progress items (dict/[]/strings, etc.) generated by block
|
|
693
|
+
# If progress_callback is provided, it will be called once for each progress item generated.
|
|
694
|
+
async for resp in executor.blocks_act([current_block]):
|
|
695
|
+
if progress_callback is not None:
|
|
696
|
+
try:
|
|
697
|
+
progress_callback(resp)
|
|
698
|
+
except Exception:
|
|
699
|
+
# Callbacks should not affect the main execution flow
|
|
700
|
+
pass
|
|
701
|
+
except Exception as e:
|
|
702
|
+
result = self._handle_execution_exception(e, frame, pointer)
|
|
703
|
+
if result is not None:
|
|
704
|
+
return result
|
|
705
|
+
raise e
|
|
706
|
+
|
|
707
|
+
# The current block has completed normally, advance the pointer
|
|
708
|
+
pointer += 1
|
|
709
|
+
|
|
710
|
+
# 4. Completed: Update pointers and state, save snapshot only here
|
|
711
|
+
frame.block_pointer = pointer
|
|
712
|
+
frame.status = FrameStatus.COMPLETED
|
|
713
|
+
self._save_frame_snapshot(frame)
|
|
714
|
+
|
|
715
|
+
# Returns the completion status
|
|
716
|
+
return StepResult.completed(result=self.context.get_user_variables())
|
|
717
|
+
|
|
718
|
+
except Exception as e:
|
|
719
|
+
# Fallback exception handling (theoretically handled in inner loop)
|
|
720
|
+
result = self._handle_execution_exception(e, frame, frame.block_pointer)
|
|
721
|
+
if result is not None:
|
|
722
|
+
return result
|
|
723
|
+
raise e
|
|
724
|
+
|
|
725
|
+
async def pause_coroutine(self, frame_id: str):
|
|
726
|
+
"""Pause execution"""
|
|
727
|
+
from dolphin.core.coroutine import ResumeHandle
|
|
728
|
+
from dolphin.core.coroutine.execution_frame import FrameStatus
|
|
729
|
+
|
|
730
|
+
frame = self.state_registry.get_frame(frame_id)
|
|
731
|
+
if not frame:
|
|
732
|
+
raise ValueError(f"Frame not found: {frame_id}")
|
|
733
|
+
|
|
734
|
+
frame.status = FrameStatus.PAUSED
|
|
735
|
+
self.state_registry.update_frame(frame)
|
|
736
|
+
|
|
737
|
+
return ResumeHandle.create_handle(
|
|
738
|
+
frame_id=frame_id, snapshot_id=frame.context_snapshot_id
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
async def resume_coroutine(self, handle, updates=None):
|
|
742
|
+
"""Resume execution, and optionally inject updated data into the context"""
|
|
743
|
+
from dolphin.core.coroutine.execution_frame import FrameStatus
|
|
744
|
+
|
|
745
|
+
# 1. Get frame
|
|
746
|
+
frame = self.state_registry.get_frame(handle.frame_id)
|
|
747
|
+
if not frame:
|
|
748
|
+
raise ValueError(f"Frame not found: {handle.frame_id}")
|
|
749
|
+
|
|
750
|
+
# Check if recovery is from tool interruption
|
|
751
|
+
is_intervention_resume = frame.status == FrameStatus.WAITING_FOR_INTERVENTION
|
|
752
|
+
|
|
753
|
+
# 2. Restore context from snapshot
|
|
754
|
+
snapshot = self.snapshot_store.load_snapshot(handle.snapshot_id)
|
|
755
|
+
if not snapshot:
|
|
756
|
+
raise ValueError(f"Snapshot not found: {handle.snapshot_id}")
|
|
757
|
+
|
|
758
|
+
self._restore_context(snapshot)
|
|
759
|
+
|
|
760
|
+
# 3. Processing Data Injection
|
|
761
|
+
if updates:
|
|
762
|
+
# Apply updates data
|
|
763
|
+
for key, value in updates.items():
|
|
764
|
+
self.context.set_variable(key, value)
|
|
765
|
+
|
|
766
|
+
# 4. Special handling for interrupt recovery
|
|
767
|
+
if is_intervention_resume:
|
|
768
|
+
from dolphin.core.coroutine.execution_frame import WaitReason
|
|
769
|
+
|
|
770
|
+
# Handle user interruption (UserInterrupt)
|
|
771
|
+
if frame.wait_reason == WaitReason.USER_INTERRUPT:
|
|
772
|
+
# If there is new user input provided via updates, inject it as a message
|
|
773
|
+
if updates and "__user_interrupt_input__" in updates:
|
|
774
|
+
user_input = updates.pop("__user_interrupt_input__")
|
|
775
|
+
# Use the convenient method to add user message
|
|
776
|
+
self.context.add_user_message(user_input, bucket="conversation_history")
|
|
777
|
+
logger.info(f"UserInterrupt resume: injected user input of length {len(user_input)}")
|
|
778
|
+
|
|
779
|
+
# Clean up status once handled
|
|
780
|
+
frame.wait_reason = None
|
|
781
|
+
frame.error = None
|
|
782
|
+
|
|
783
|
+
# Handle tool interruption (ToolInterrupt)
|
|
784
|
+
elif frame.error and "tool_name" in frame.error:
|
|
785
|
+
tool_name = frame.error["tool_name"]
|
|
786
|
+
tool_args = frame.error.get("tool_args", [])
|
|
787
|
+
|
|
788
|
+
# If there are tools to recover data, set them to the context
|
|
789
|
+
if updates and "tool_result" in updates:
|
|
790
|
+
self.context.set_variable("tool_result", updates["tool_result"])
|
|
791
|
+
|
|
792
|
+
# Clean up error messages, as the interruption has been resolved
|
|
793
|
+
frame.error = None
|
|
794
|
+
|
|
795
|
+
# 5. Create new snapshot (if updated)
|
|
796
|
+
if updates or is_intervention_resume:
|
|
797
|
+
self._save_frame_snapshot(frame)
|
|
798
|
+
|
|
799
|
+
# 6. Set the frame status to RUNNING and save
|
|
800
|
+
frame.status = FrameStatus.RUNNING
|
|
801
|
+
self.state_registry.update_frame(frame)
|
|
802
|
+
|
|
803
|
+
return frame
|
|
804
|
+
|
|
805
|
+
async def terminate_coroutine(self, frame_id: str, terminate_children: bool = True):
|
|
806
|
+
"""Terminate execution frame tree
|
|
807
|
+
|
|
808
|
+
Args:
|
|
809
|
+
frame_id: The ID of the frame to terminate
|
|
810
|
+
terminate_children: Whether to terminate all child frames as well, default is True
|
|
811
|
+
|
|
812
|
+
Returns:
|
|
813
|
+
The terminated frame
|
|
814
|
+
|
|
815
|
+
Raises:
|
|
816
|
+
ValueError: If the frame does not exist
|
|
817
|
+
"""
|
|
818
|
+
from dolphin.core.coroutine.execution_frame import FrameStatus
|
|
819
|
+
|
|
820
|
+
# 1. Get frame
|
|
821
|
+
frame = self.state_registry.get_frame(frame_id)
|
|
822
|
+
if not frame:
|
|
823
|
+
raise ValueError(f"Frame not found: {frame_id}")
|
|
824
|
+
|
|
825
|
+
# 2. Terminate all subframes (if needed)
|
|
826
|
+
if terminate_children and frame.children:
|
|
827
|
+
for child_id in frame.children:
|
|
828
|
+
try:
|
|
829
|
+
await self.terminate_coroutine(child_id, terminate_children=True)
|
|
830
|
+
except ValueError:
|
|
831
|
+
# The subframe may no longer exist, ignore the error
|
|
832
|
+
pass
|
|
833
|
+
|
|
834
|
+
# 3. Set frame status to TERMINATED
|
|
835
|
+
frame.status = FrameStatus.TERMINATED
|
|
836
|
+
frame.update_timestamp()
|
|
837
|
+
|
|
838
|
+
# 4. Save frame state
|
|
839
|
+
self.state_registry.update_frame(frame)
|
|
840
|
+
|
|
841
|
+
return frame
|
|
842
|
+
|
|
843
|
+
def _create_snapshot(self, frame_id: str):
|
|
844
|
+
"""Create context snapshot"""
|
|
845
|
+
return self.context.export_runtime_state(frame_id)
|
|
846
|
+
|
|
847
|
+
def _restore_context(self, snapshot):
|
|
848
|
+
"""Restore context from snapshot"""
|
|
849
|
+
self.context.apply_runtime_state(snapshot)
|
|
850
|
+
|
|
851
|
+
def _save_frame_snapshot(self, frame):
|
|
852
|
+
"""Create and save a context snapshot of the execution frame
|
|
853
|
+
|
|
854
|
+
Args:
|
|
855
|
+
frame: The execution frame to save a snapshot of
|
|
856
|
+
|
|
857
|
+
Returns:
|
|
858
|
+
str: The ID of the newly created snapshot
|
|
859
|
+
"""
|
|
860
|
+
new_snapshot = self._create_snapshot(frame.frame_id)
|
|
861
|
+
self.snapshot_store.save_snapshot(new_snapshot)
|
|
862
|
+
frame.context_snapshot_id = new_snapshot.snapshot_id
|
|
863
|
+
self.state_registry.update_frame(frame)
|
|
864
|
+
return new_snapshot.snapshot_id
|
|
865
|
+
|
|
866
|
+
def _get_or_parse_blocks(self, frame):
|
|
867
|
+
"""Get or parse blocks"""
|
|
868
|
+
content = frame.original_content
|
|
869
|
+
if not content:
|
|
870
|
+
return []
|
|
871
|
+
|
|
872
|
+
executor = Executor(context=self.context)
|
|
873
|
+
return executor.get_parsed_blocks(content)
|
|
874
|
+
|
|
875
|
+
# === New High-Level API (for Multi-turn Dialogue Scenarios) ===
|
|
876
|
+
|
|
877
|
+
async def append_incremental_message(self, payload: dict):
|
|
878
|
+
"""Append an incremental message to the current coroutine (utilizing prefix cache)
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
payload: State or event information (arbitrary structure), for example {"event_type": "...", "content": "...", "metadata": {...}}
|
|
882
|
+
|
|
883
|
+
Design philosophy:
|
|
884
|
+
- Messages history accumulates like a log, facilitating prefix cache
|
|
885
|
+
- Each call appends only an incremental message
|
|
886
|
+
- Synchronously updates the history variable in context, compatible with /explore/(history=true)
|
|
887
|
+
"""
|
|
888
|
+
# Format as user message
|
|
889
|
+
message_content = self._format_round_message(payload)
|
|
890
|
+
|
|
891
|
+
# Append to messages (normal path, keep compatibility)
|
|
892
|
+
self.context.add_user_message(message_content, bucket="conversation_history")
|
|
893
|
+
|
|
894
|
+
# Update the history variable at the same time (for the old path /explore/(history=true))
|
|
895
|
+
from dolphin.core.common.enums import Messages
|
|
896
|
+
history_raw = self.context.get_history_messages(normalize=False)
|
|
897
|
+
|
|
898
|
+
# Convert to list format if needed (handle different return types)
|
|
899
|
+
if history_raw is None:
|
|
900
|
+
history_list = []
|
|
901
|
+
elif isinstance(history_raw, Messages):
|
|
902
|
+
# Convert Messages object to list of dicts
|
|
903
|
+
history_list = history_raw.get_messages_as_dict()
|
|
904
|
+
elif isinstance(history_raw, list):
|
|
905
|
+
history_list = history_raw
|
|
906
|
+
else:
|
|
907
|
+
logger.warning(f"Unexpected history type: {type(history_raw)}, initializing as empty list")
|
|
908
|
+
history_list = []
|
|
909
|
+
|
|
910
|
+
history_list.append({"role": MessageRole.USER.value, "content": message_content})
|
|
911
|
+
self.context.set_variable("history", history_list)
|
|
912
|
+
|
|
913
|
+
# Update the snapshot of the current coroutine
|
|
914
|
+
if hasattr(self, "_current_frame_id") and self._current_frame_id:
|
|
915
|
+
frame = self.state_registry.get_frame(self._current_frame_id)
|
|
916
|
+
if frame:
|
|
917
|
+
self._save_frame_snapshot(frame)
|
|
918
|
+
|
|
919
|
+
def _format_round_message(self, round_info: dict) -> str:
|
|
920
|
+
"""Format round information into message content"""
|
|
921
|
+
import json
|
|
922
|
+
|
|
923
|
+
# If the message field already exists, use it directly.
|
|
924
|
+
if "message" in round_info:
|
|
925
|
+
return round_info["message"]
|
|
926
|
+
|
|
927
|
+
# Otherwise, convert the entire dict to JSON format
|
|
928
|
+
return json.dumps(round_info, ensure_ascii=False, indent=2)
|
|
929
|
+
|
|
930
|
+
async def inject_context(self, updates: dict):
|
|
931
|
+
"""Inject context variables (variable replacement mode) into the currently active coroutine.
|
|
932
|
+
|
|
933
|
+
⚠️ Note: This method creates a new snapshot and cannot utilize prefix cache
|
|
934
|
+
⚠️ For multi-turn conversation scenarios, append_round_message() is recommended
|
|
935
|
+
|
|
936
|
+
Args:
|
|
937
|
+
updates: Dictionary of variables to inject
|
|
938
|
+
"""
|
|
939
|
+
if not hasattr(self, "_current_frame_id") or not self._current_frame_id:
|
|
940
|
+
raise ValueError("没有活跃的协程")
|
|
941
|
+
|
|
942
|
+
frame = self.state_registry.get_frame(self._current_frame_id)
|
|
943
|
+
from dolphin.core.coroutine import ResumeHandle
|
|
944
|
+
|
|
945
|
+
handle = ResumeHandle(frame.frame_id, frame.context_snapshot_id, "")
|
|
946
|
+
await self.resume_coroutine(handle, updates=updates)
|
|
947
|
+
|
|
948
|
+
async def step_current_coroutine(self):
|
|
949
|
+
"""Execute the next step of the currently active coroutine.
|
|
950
|
+
|
|
951
|
+
Returns:
|
|
952
|
+
Execution result (bool indicating whether completion is achieved, or ResumeHandle indicating interruption)
|
|
953
|
+
|
|
954
|
+
Internal implementation:
|
|
955
|
+
Calls step_coroutine(self._current_frame_id)
|
|
956
|
+
"""
|
|
957
|
+
if not hasattr(self, "_current_frame_id") or not self._current_frame_id:
|
|
958
|
+
raise ValueError("没有活跃的协程")
|
|
959
|
+
|
|
960
|
+
return await self.step_coroutine(self._current_frame_id)
|
|
961
|
+
|
|
962
|
+
def is_waiting_for_intervention(self) -> bool:
|
|
963
|
+
"""Check whether the current coroutine is waiting for an interrupt handler.
|
|
964
|
+
|
|
965
|
+
Returns:
|
|
966
|
+
bool: Whether it is waiting for an interrupt
|
|
967
|
+
"""
|
|
968
|
+
if not hasattr(self, "_current_frame_id") or not self._current_frame_id:
|
|
969
|
+
return False
|
|
970
|
+
|
|
971
|
+
frame = self.state_registry.get_frame(self._current_frame_id)
|
|
972
|
+
from dolphin.core.coroutine.execution_frame import FrameStatus
|
|
973
|
+
|
|
974
|
+
return frame.status == FrameStatus.WAITING_FOR_INTERVENTION
|
|
975
|
+
|
|
976
|
+
def get_intervention_data(self) -> dict:
|
|
977
|
+
"""Get interrupt data (such as tool call parameters)
|
|
978
|
+
|
|
979
|
+
Returns:
|
|
980
|
+
dict: Interrupt-related data
|
|
981
|
+
"""
|
|
982
|
+
if not hasattr(self, "_current_frame_id") or not self._current_frame_id:
|
|
983
|
+
raise ValueError("没有活跃的协程")
|
|
984
|
+
|
|
985
|
+
frame = self.state_registry.get_frame(self._current_frame_id)
|
|
986
|
+
if frame.error and frame.error.get("error_type") == "ToolInterrupt":
|
|
987
|
+
return {
|
|
988
|
+
"tool_name": frame.error.get("tool_name", ""),
|
|
989
|
+
"tool_args": frame.error.get("tool_args", []),
|
|
990
|
+
"at_block": frame.error.get("at_block", 0),
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return {}
|
|
994
|
+
|
|
995
|
+
def get_messages_range(self, start: int, end: int):
|
|
996
|
+
"""Read messages within the specified range (left-closed, right-open), indexed sequentially from the start of the session.
|
|
997
|
+
|
|
998
|
+
Args:
|
|
999
|
+
start: Starting index (inclusive)
|
|
1000
|
+
end: Ending index (exclusive)
|
|
1001
|
+
|
|
1002
|
+
Returns:
|
|
1003
|
+
PlainMessages (list[dict]), each containing fields such as role/content
|
|
1004
|
+
"""
|
|
1005
|
+
messages = self.context.get_messages()
|
|
1006
|
+
all_messages_dict = messages.get_messages_as_dict() # Return PlainMessages
|
|
1007
|
+
|
|
1008
|
+
# Ensure the index is within the valid range
|
|
1009
|
+
start = max(0, start)
|
|
1010
|
+
end = min(len(all_messages_dict), end)
|
|
1011
|
+
|
|
1012
|
+
return all_messages_dict[start:end]
|
|
1013
|
+
|
|
1014
|
+
def replace_messages_range(self, start: int, end: int, replacement):
|
|
1015
|
+
"""Replace the [start, end) interval with replacement (a string or a single message).
|
|
1016
|
+
|
|
1017
|
+
Note: The replacement will invalidate the prefix cache for this interval, and subsequent accumulation will continue from the new summary.
|
|
1018
|
+
When replacement is a str, it will be automatically wrapped as a user message; when it's a dict, it should include role/content.
|
|
1019
|
+
|
|
1020
|
+
Args:
|
|
1021
|
+
start: starting index (inclusive)
|
|
1022
|
+
end: ending index (exclusive)
|
|
1023
|
+
replacement: string or message dictionary
|
|
1024
|
+
"""
|
|
1025
|
+
from dolphin.core.common.enums import SingleMessage, MessageRole
|
|
1026
|
+
from datetime import datetime
|
|
1027
|
+
|
|
1028
|
+
messages = self.context.get_messages()
|
|
1029
|
+
all_messages = messages.messages if hasattr(messages, "messages") else []
|
|
1030
|
+
|
|
1031
|
+
# Ensure the index is within the valid range
|
|
1032
|
+
start = max(0, start)
|
|
1033
|
+
end = min(len(all_messages), end)
|
|
1034
|
+
|
|
1035
|
+
# Handle replacement, convert to SingleMessage
|
|
1036
|
+
if isinstance(replacement, str):
|
|
1037
|
+
replacement_msg = SingleMessage(
|
|
1038
|
+
role=MessageRole.USER,
|
|
1039
|
+
content=replacement,
|
|
1040
|
+
timestamp=datetime.now().isoformat(),
|
|
1041
|
+
)
|
|
1042
|
+
elif isinstance(replacement, dict):
|
|
1043
|
+
role = replacement.get("role", MessageRole.USER)
|
|
1044
|
+
if isinstance(role, str):
|
|
1045
|
+
role = MessageRole(role)
|
|
1046
|
+
replacement_msg = SingleMessage(
|
|
1047
|
+
role=role,
|
|
1048
|
+
content=replacement.get("content", ""),
|
|
1049
|
+
timestamp=datetime.now().isoformat(),
|
|
1050
|
+
)
|
|
1051
|
+
elif isinstance(replacement, SingleMessage):
|
|
1052
|
+
replacement_msg = replacement
|
|
1053
|
+
else:
|
|
1054
|
+
raise ValueError(f"Invalid replacement type: {type(replacement)}")
|
|
1055
|
+
|
|
1056
|
+
# Perform replacement
|
|
1057
|
+
all_messages[start:end] = [replacement_msg]
|
|
1058
|
+
|
|
1059
|
+
# Update the snapshot of the current coroutine
|
|
1060
|
+
if hasattr(self, "_current_frame_id") and self._current_frame_id:
|
|
1061
|
+
frame = self.state_registry.get_frame(self._current_frame_id)
|
|
1062
|
+
if frame:
|
|
1063
|
+
self._save_frame_snapshot(frame)
|