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.
Files changed (199) hide show
  1. DolphinLanguageSDK/__init__.py +58 -0
  2. dolphin/__init__.py +62 -0
  3. dolphin/cli/__init__.py +20 -0
  4. dolphin/cli/args/__init__.py +9 -0
  5. dolphin/cli/args/parser.py +567 -0
  6. dolphin/cli/builtin_agents/__init__.py +22 -0
  7. dolphin/cli/commands/__init__.py +4 -0
  8. dolphin/cli/interrupt/__init__.py +8 -0
  9. dolphin/cli/interrupt/handler.py +205 -0
  10. dolphin/cli/interrupt/keyboard.py +82 -0
  11. dolphin/cli/main.py +49 -0
  12. dolphin/cli/multimodal/__init__.py +34 -0
  13. dolphin/cli/multimodal/clipboard.py +327 -0
  14. dolphin/cli/multimodal/handler.py +249 -0
  15. dolphin/cli/multimodal/image_processor.py +214 -0
  16. dolphin/cli/multimodal/input_parser.py +149 -0
  17. dolphin/cli/runner/__init__.py +8 -0
  18. dolphin/cli/runner/runner.py +989 -0
  19. dolphin/cli/ui/__init__.py +10 -0
  20. dolphin/cli/ui/console.py +2795 -0
  21. dolphin/cli/ui/input.py +340 -0
  22. dolphin/cli/ui/layout.py +425 -0
  23. dolphin/cli/ui/stream_renderer.py +302 -0
  24. dolphin/cli/utils/__init__.py +8 -0
  25. dolphin/cli/utils/helpers.py +135 -0
  26. dolphin/cli/utils/version.py +49 -0
  27. dolphin/core/__init__.py +107 -0
  28. dolphin/core/agent/__init__.py +10 -0
  29. dolphin/core/agent/agent_state.py +69 -0
  30. dolphin/core/agent/base_agent.py +970 -0
  31. dolphin/core/code_block/__init__.py +0 -0
  32. dolphin/core/code_block/agent_init_block.py +0 -0
  33. dolphin/core/code_block/assign_block.py +98 -0
  34. dolphin/core/code_block/basic_code_block.py +1865 -0
  35. dolphin/core/code_block/explore_block.py +1327 -0
  36. dolphin/core/code_block/explore_block_v2.py +712 -0
  37. dolphin/core/code_block/explore_strategy.py +672 -0
  38. dolphin/core/code_block/judge_block.py +220 -0
  39. dolphin/core/code_block/prompt_block.py +32 -0
  40. dolphin/core/code_block/skill_call_deduplicator.py +291 -0
  41. dolphin/core/code_block/tool_block.py +129 -0
  42. dolphin/core/common/__init__.py +17 -0
  43. dolphin/core/common/constants.py +176 -0
  44. dolphin/core/common/enums.py +1173 -0
  45. dolphin/core/common/exceptions.py +133 -0
  46. dolphin/core/common/multimodal.py +539 -0
  47. dolphin/core/common/object_type.py +165 -0
  48. dolphin/core/common/output_format.py +432 -0
  49. dolphin/core/common/types.py +36 -0
  50. dolphin/core/config/__init__.py +16 -0
  51. dolphin/core/config/global_config.py +1289 -0
  52. dolphin/core/config/ontology_config.py +133 -0
  53. dolphin/core/context/__init__.py +12 -0
  54. dolphin/core/context/context.py +1580 -0
  55. dolphin/core/context/context_manager.py +161 -0
  56. dolphin/core/context/var_output.py +82 -0
  57. dolphin/core/context/variable_pool.py +356 -0
  58. dolphin/core/context_engineer/__init__.py +41 -0
  59. dolphin/core/context_engineer/config/__init__.py +5 -0
  60. dolphin/core/context_engineer/config/settings.py +402 -0
  61. dolphin/core/context_engineer/core/__init__.py +7 -0
  62. dolphin/core/context_engineer/core/budget_manager.py +327 -0
  63. dolphin/core/context_engineer/core/context_assembler.py +583 -0
  64. dolphin/core/context_engineer/core/context_manager.py +637 -0
  65. dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
  66. dolphin/core/context_engineer/example/incremental_example.py +267 -0
  67. dolphin/core/context_engineer/example/traditional_example.py +334 -0
  68. dolphin/core/context_engineer/services/__init__.py +5 -0
  69. dolphin/core/context_engineer/services/compressor.py +399 -0
  70. dolphin/core/context_engineer/utils/__init__.py +6 -0
  71. dolphin/core/context_engineer/utils/context_utils.py +441 -0
  72. dolphin/core/context_engineer/utils/message_formatter.py +270 -0
  73. dolphin/core/context_engineer/utils/token_utils.py +139 -0
  74. dolphin/core/coroutine/__init__.py +15 -0
  75. dolphin/core/coroutine/context_snapshot.py +154 -0
  76. dolphin/core/coroutine/context_snapshot_profile.py +922 -0
  77. dolphin/core/coroutine/context_snapshot_store.py +268 -0
  78. dolphin/core/coroutine/execution_frame.py +145 -0
  79. dolphin/core/coroutine/execution_state_registry.py +161 -0
  80. dolphin/core/coroutine/resume_handle.py +101 -0
  81. dolphin/core/coroutine/step_result.py +101 -0
  82. dolphin/core/executor/__init__.py +18 -0
  83. dolphin/core/executor/debug_controller.py +630 -0
  84. dolphin/core/executor/dolphin_executor.py +1063 -0
  85. dolphin/core/executor/executor.py +624 -0
  86. dolphin/core/flags/__init__.py +27 -0
  87. dolphin/core/flags/definitions.py +49 -0
  88. dolphin/core/flags/manager.py +113 -0
  89. dolphin/core/hook/__init__.py +95 -0
  90. dolphin/core/hook/expression_evaluator.py +499 -0
  91. dolphin/core/hook/hook_dispatcher.py +380 -0
  92. dolphin/core/hook/hook_types.py +248 -0
  93. dolphin/core/hook/isolated_variable_pool.py +284 -0
  94. dolphin/core/interfaces.py +53 -0
  95. dolphin/core/llm/__init__.py +0 -0
  96. dolphin/core/llm/llm.py +495 -0
  97. dolphin/core/llm/llm_call.py +100 -0
  98. dolphin/core/llm/llm_client.py +1285 -0
  99. dolphin/core/llm/message_sanitizer.py +120 -0
  100. dolphin/core/logging/__init__.py +20 -0
  101. dolphin/core/logging/logger.py +526 -0
  102. dolphin/core/message/__init__.py +8 -0
  103. dolphin/core/message/compressor.py +749 -0
  104. dolphin/core/parser/__init__.py +8 -0
  105. dolphin/core/parser/parser.py +405 -0
  106. dolphin/core/runtime/__init__.py +10 -0
  107. dolphin/core/runtime/runtime_graph.py +926 -0
  108. dolphin/core/runtime/runtime_instance.py +446 -0
  109. dolphin/core/skill/__init__.py +14 -0
  110. dolphin/core/skill/context_retention.py +157 -0
  111. dolphin/core/skill/skill_function.py +686 -0
  112. dolphin/core/skill/skill_matcher.py +282 -0
  113. dolphin/core/skill/skillkit.py +700 -0
  114. dolphin/core/skill/skillset.py +72 -0
  115. dolphin/core/trajectory/__init__.py +10 -0
  116. dolphin/core/trajectory/recorder.py +189 -0
  117. dolphin/core/trajectory/trajectory.py +522 -0
  118. dolphin/core/utils/__init__.py +9 -0
  119. dolphin/core/utils/cache_kv.py +212 -0
  120. dolphin/core/utils/tools.py +340 -0
  121. dolphin/lib/__init__.py +93 -0
  122. dolphin/lib/debug/__init__.py +8 -0
  123. dolphin/lib/debug/visualizer.py +409 -0
  124. dolphin/lib/memory/__init__.py +28 -0
  125. dolphin/lib/memory/async_processor.py +220 -0
  126. dolphin/lib/memory/llm_calls.py +195 -0
  127. dolphin/lib/memory/manager.py +78 -0
  128. dolphin/lib/memory/sandbox.py +46 -0
  129. dolphin/lib/memory/storage.py +245 -0
  130. dolphin/lib/memory/utils.py +51 -0
  131. dolphin/lib/ontology/__init__.py +12 -0
  132. dolphin/lib/ontology/basic/__init__.py +0 -0
  133. dolphin/lib/ontology/basic/base.py +102 -0
  134. dolphin/lib/ontology/basic/concept.py +130 -0
  135. dolphin/lib/ontology/basic/object.py +11 -0
  136. dolphin/lib/ontology/basic/relation.py +63 -0
  137. dolphin/lib/ontology/datasource/__init__.py +27 -0
  138. dolphin/lib/ontology/datasource/datasource.py +66 -0
  139. dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
  140. dolphin/lib/ontology/datasource/sql.py +845 -0
  141. dolphin/lib/ontology/mapping.py +177 -0
  142. dolphin/lib/ontology/ontology.py +733 -0
  143. dolphin/lib/ontology/ontology_context.py +16 -0
  144. dolphin/lib/ontology/ontology_manager.py +107 -0
  145. dolphin/lib/skill_results/__init__.py +31 -0
  146. dolphin/lib/skill_results/cache_backend.py +559 -0
  147. dolphin/lib/skill_results/result_processor.py +181 -0
  148. dolphin/lib/skill_results/result_reference.py +179 -0
  149. dolphin/lib/skill_results/skillkit_hook.py +324 -0
  150. dolphin/lib/skill_results/strategies.py +328 -0
  151. dolphin/lib/skill_results/strategy_registry.py +150 -0
  152. dolphin/lib/skillkits/__init__.py +44 -0
  153. dolphin/lib/skillkits/agent_skillkit.py +155 -0
  154. dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
  155. dolphin/lib/skillkits/env_skillkit.py +250 -0
  156. dolphin/lib/skillkits/mcp_adapter.py +616 -0
  157. dolphin/lib/skillkits/mcp_skillkit.py +771 -0
  158. dolphin/lib/skillkits/memory_skillkit.py +650 -0
  159. dolphin/lib/skillkits/noop_skillkit.py +31 -0
  160. dolphin/lib/skillkits/ontology_skillkit.py +89 -0
  161. dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
  162. dolphin/lib/skillkits/resource/__init__.py +52 -0
  163. dolphin/lib/skillkits/resource/models/__init__.py +6 -0
  164. dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
  165. dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
  166. dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
  167. dolphin/lib/skillkits/resource/skill_cache.py +215 -0
  168. dolphin/lib/skillkits/resource/skill_loader.py +395 -0
  169. dolphin/lib/skillkits/resource/skill_validator.py +406 -0
  170. dolphin/lib/skillkits/resource_skillkit.py +11 -0
  171. dolphin/lib/skillkits/search_skillkit.py +163 -0
  172. dolphin/lib/skillkits/sql_skillkit.py +274 -0
  173. dolphin/lib/skillkits/system_skillkit.py +509 -0
  174. dolphin/lib/skillkits/vm_skillkit.py +65 -0
  175. dolphin/lib/utils/__init__.py +9 -0
  176. dolphin/lib/utils/data_process.py +207 -0
  177. dolphin/lib/utils/handle_progress.py +178 -0
  178. dolphin/lib/utils/security.py +139 -0
  179. dolphin/lib/utils/text_retrieval.py +462 -0
  180. dolphin/lib/vm/__init__.py +11 -0
  181. dolphin/lib/vm/env_executor.py +895 -0
  182. dolphin/lib/vm/python_session_manager.py +453 -0
  183. dolphin/lib/vm/vm.py +610 -0
  184. dolphin/sdk/__init__.py +60 -0
  185. dolphin/sdk/agent/__init__.py +12 -0
  186. dolphin/sdk/agent/agent_factory.py +236 -0
  187. dolphin/sdk/agent/dolphin_agent.py +1106 -0
  188. dolphin/sdk/api/__init__.py +4 -0
  189. dolphin/sdk/runtime/__init__.py +8 -0
  190. dolphin/sdk/runtime/env.py +363 -0
  191. dolphin/sdk/skill/__init__.py +10 -0
  192. dolphin/sdk/skill/global_skills.py +706 -0
  193. dolphin/sdk/skill/traditional_toolkit.py +260 -0
  194. kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
  195. kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
  196. kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
  197. kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
  198. kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
  199. kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,624 @@
1
+ import ast
2
+ import asyncio
3
+ import re
4
+ import traceback
5
+ from typing import Optional
6
+
7
+ from dolphin.core import flags
8
+ from dolphin.core.code_block.assign_block import AssignBlock
9
+ from dolphin.core.logging.logger import get_logger
10
+ from dolphin.core.code_block.explore_block import ExploreBlock
11
+ from dolphin.core.code_block.explore_block_v2 import ExploreBlockV2
12
+ from dolphin.core.code_block.judge_block import JudgeBlock
13
+ from dolphin.core.code_block.prompt_block import PromptBlock
14
+ from dolphin.core.code_block.tool_block import ToolBlock
15
+ from dolphin.core.common.enums import StreamItem, count_occurrences
16
+ from dolphin.core.common.constants import KEY_STATUS, KEY_PREVIOUS_STATUS
17
+ from dolphin.core.context.context import Context
18
+ from dolphin.core.parser.parser import Parser
19
+ from dolphin.core.utils.tools import ToolInterrupt
20
+
21
+
22
+ def split_by_multiple_prefixes(text, prefixes):
23
+ # Initialize result list
24
+ result = []
25
+ # Current processing position
26
+ current_pos = 0
27
+
28
+ while current_pos < len(text):
29
+ # Find the next closest delimiter position
30
+ next_pos = len(text)
31
+ found_prefix = None
32
+
33
+ # Find all possible delimiters after the current position
34
+ for prefix in prefixes:
35
+ pos = text.find(prefix, current_pos + 1) # +1 is to avoid repeated lookups at the current position
36
+ if pos != -1 and pos < next_pos:
37
+ next_pos = pos
38
+ found_prefix = prefix
39
+
40
+ # If the separator is found
41
+ if found_prefix:
42
+ # Add the current paragraph to the result.
43
+ segment = text[current_pos:next_pos]
44
+ if segment: # Add only non-empty segments
45
+ result.append(segment)
46
+ current_pos = next_pos
47
+ else:
48
+ # If no more delimiters are found, add the remaining part.
49
+ segment = text[current_pos:]
50
+ if segment: # Add only non-empty paragraphs
51
+ result.append(segment)
52
+ break
53
+
54
+ return result
55
+
56
+
57
+ def split_and_join(string_list, int_list):
58
+ result = []
59
+ # Traverse split points
60
+ for i in range(len(int_list)):
61
+ start = int_list[i]
62
+ # If it is the last split point, end equals the list length.
63
+ if i == len(int_list) - 1:
64
+ end = len(string_list)
65
+ else:
66
+ end = int_list[i + 1]
67
+ # Concatenate the strings within this range.
68
+ joined_str = "".join(string_list[start:end])
69
+ result.append(joined_str)
70
+ return result
71
+
72
+
73
+ class Executor:
74
+ def __init__(
75
+ self,
76
+ context: Context,
77
+ debug_info: Optional[dict] = None,
78
+ breakpoint_infos: Optional[dict] = None,
79
+ step_mode: bool = False,
80
+ debug_mode: bool = False,
81
+ break_on_start: bool = False,
82
+ break_at: Optional[list] = None,
83
+ ):
84
+ self.context = context
85
+ self.debug_info = debug_info
86
+ self.breakpoint_infos = breakpoint_infos
87
+ self.step_mode = step_mode # Deprecated: kept for backward compatibility
88
+ self.debug_mode = debug_mode # Added: Whether to enable debug mode
89
+ self.debug_controller = None
90
+
91
+ # If debug mode is enabled, initialize the debug controller.
92
+ if self.debug_mode:
93
+ from dolphin.core.executor.debug_controller import DebugController
94
+
95
+ self.debug_controller = DebugController(
96
+ self.context,
97
+ break_on_start=break_on_start,
98
+ break_at=break_at,
99
+ )
100
+ self.debug_controller.enable_step_mode()
101
+
102
+ self.logger = get_logger("executor")
103
+
104
+ # Trajectory configuration: read from context attribute
105
+ # If trajectorypath is specified, stage trajectory saving is automatically enabled
106
+ self.trajectory_path = getattr(context, "trajectorypath", None)
107
+ self.agent_name = getattr(context, "agent_name", "main")
108
+
109
+ # Initialize trajectory recording
110
+ if self.trajectory_path:
111
+ self.context.init_trajectory(self.trajectory_path)
112
+
113
+ self.parser = Parser(context=self.context)
114
+ self.tool_block = ToolBlock(context=self.context)
115
+
116
+ self.explore_block = (
117
+ ExploreBlockV2(context=self.context)
118
+ if flags.is_enabled(flags.EXPLORE_BLOCK_V2)
119
+ else ExploreBlock(context=self.context)
120
+ )
121
+
122
+ self.judge_block = JudgeBlock(context=self.context)
123
+ self.prompt_block = PromptBlock(context=self.context)
124
+ self.assign_block = AssignBlock(context=self.context)
125
+
126
+
127
+ def _increment_stage_counter(self, stage_name: str):
128
+ """Only increment counters for the specified stage, without saving trajectories.
129
+
130
+ Used for Blocks that need to manage trajectory saving themselves (e.g., ExploreBlock)
131
+
132
+ Args:
133
+ stage_name: Name of the stage, such as 'judge', 'tool', 'explore', etc.
134
+ """
135
+ status = self.context.get_var_value(KEY_STATUS)
136
+
137
+ # Initialize status dict if None
138
+ if status is None:
139
+ status = {}
140
+
141
+ counter_key = f"{stage_name}_time"
142
+
143
+ # Initialize counter if it doesn't exist
144
+ if counter_key not in status:
145
+ status[counter_key] = 0
146
+
147
+ status[counter_key] += 1
148
+ self.context.set_variable(KEY_STATUS, status)
149
+
150
+ def _save_stage_trajectory(self, stage_name: str):
151
+ """Save stage trajectory to trajectory file
152
+
153
+ Args:
154
+ stage_name: Stage name, such as 'judge', 'tool', 'explore', etc.
155
+ """
156
+ if not self.context.trajectory:
157
+ return
158
+
159
+ try:
160
+ status = self.context.get_var_value(KEY_STATUS) or {}
161
+ counter_key = f"{stage_name}_time"
162
+ stage_index = status.get(counter_key, 0)
163
+
164
+ tools = self.context.skillkit.getSkillsSchema()
165
+
166
+ # Get model name from context (set by ExploreBlock during llm_chat_stream)
167
+ # Returns None if no model has been used yet in this session
168
+ current_model = self.context.get_last_model_name()
169
+
170
+ self.context.trajectory.finalize_stage(
171
+ stage_name=stage_name,
172
+ stage_index=stage_index,
173
+ context_manager=self.context.context_manager,
174
+ tools=tools,
175
+ user_id=self.context.user_id or "",
176
+ model=current_model,
177
+ )
178
+ self.logger.debug(f"Saved stage trajectory for {stage_name} (index: {stage_index})")
179
+ except Exception as e:
180
+ # Record errors without interrupting execution
181
+ self.logger.warning(f"Failed to save stage trajectory for {stage_name}: {e}", exc_info=True)
182
+
183
+ def _increment_and_save_stage(self, stage_name: str):
184
+ """Increment the counter for the specified stage and save the stage trajectory.
185
+
186
+ This is a convenient method that combines counter incrementing and trajectory saving.
187
+ For Blocks that need to manage trajectory themselves (such as ExploreBlock),
188
+ they can call _increment_stage_counter() only.
189
+
190
+ Args:
191
+ stage_name: Name of the stage, such as 'judge', 'tool', 'explore', etc.
192
+ """
193
+ self._increment_stage_counter(stage_name)
194
+ self._save_stage_trajectory(stage_name)
195
+
196
+ async def blocks_act(self, blocks):
197
+ for block_index, action_block in enumerate(blocks):
198
+ # Debug mode: check whether pause is needed
199
+ if self.debug_controller and self.debug_controller.should_pause_at_block(
200
+ block_index
201
+ ):
202
+ if not await self.debug_controller.pause_and_wait_for_input(
203
+ block_index, action_block
204
+ ):
205
+ # User exits debugging, stops execution
206
+ return
207
+ previous_status = self.context.get_var_value(KEY_PREVIOUS_STATUS)
208
+ status = self.context.get_var_value(KEY_STATUS)
209
+
210
+ # Check if status variables are None and reinitialize if needed
211
+ if status is None:
212
+ status = {
213
+ "tool_time": 0,
214
+ "judge_time": 0,
215
+ "prompt_time": 0,
216
+ "explore_time": 0,
217
+ "assign_time": 0,
218
+ }
219
+ self.context.set_variable(KEY_STATUS, status)
220
+ else:
221
+ # Ensure all required keys exist in status dict
222
+ required_keys = ["tool_time", "judge_time", "prompt_time", "explore_time", "assign_time"]
223
+ for key in required_keys:
224
+ if key not in status:
225
+ status[key] = 0
226
+ self.context.set_variable(KEY_STATUS, status)
227
+
228
+ if previous_status is None:
229
+ previous_status = {
230
+ "tool_time": 0,
231
+ "judge_time": 0,
232
+ "prompt_time": 0,
233
+ "explore_time": 0,
234
+ "assign_time": 0,
235
+ }
236
+ self.context.set_variable(KEY_PREVIOUS_STATUS, previous_status)
237
+ else:
238
+ # Ensure all required keys exist in previous_status dict
239
+ required_keys = ["tool_time", "judge_time", "prompt_time", "explore_time", "assign_time"]
240
+ for key in required_keys:
241
+ if key not in previous_status:
242
+ previous_status[key] = 0
243
+ self.context.set_variable(KEY_PREVIOUS_STATUS, previous_status)
244
+
245
+ if action_block[0] == "if":
246
+ async for resp in self.ifelse_block(action_block[1]):
247
+ yield resp
248
+ elif action_block[0] == "for":
249
+ async for resp in self.for_block(action_block[1]):
250
+ yield resp
251
+ elif action_block[0] == "judge":
252
+ if (
253
+ self.step_mode
254
+ or status["judge_time"] >= previous_status["judge_time"]
255
+ ):
256
+ async for resp in self.judge_block.execute(action_block[1]):
257
+ yield resp
258
+ self._increment_and_save_stage("judge")
259
+ elif action_block[0] == "tool":
260
+ if (
261
+ self.step_mode
262
+ or status["tool_time"] >= previous_status["tool_time"]
263
+ ):
264
+ async for resp in self.tool_block.execute(action_block[1]):
265
+ yield resp
266
+ self._increment_and_save_stage("tool")
267
+ elif action_block[0] == "explore":
268
+ if (
269
+ self.step_mode
270
+ or status["explore_time"] >= previous_status["explore_time"]
271
+ ):
272
+ async for resp in self.explore_block.execute(action_block[1]):
273
+ yield resp
274
+ # ExploreBlock saves trajectory itself in _update_history_and_cleanup()
275
+ # Here we only increment the counter to avoid saving repeatedly
276
+ self._increment_stage_counter("explore")
277
+ elif action_block[0] == "prompt":
278
+ if (
279
+ self.step_mode
280
+ or status["prompt_time"] >= previous_status["prompt_time"]
281
+ ):
282
+ async for resp in self.prompt_block.execute(action_block[1]):
283
+ yield resp
284
+ self._increment_and_save_stage("prompt")
285
+ elif action_block[0] == "assign":
286
+ if (
287
+ self.step_mode
288
+ or status["assign_time"] >= previous_status["assign_time"]
289
+ ):
290
+ async for resp in self.assign_block.execute(action_block[1]):
291
+ yield resp
292
+ self._increment_and_save_stage("assign")
293
+ elif action_block[0] == "parallel":
294
+ async for resp in self.parallel_block(action_block[1]):
295
+ yield resp
296
+
297
+ async def ifelse_block(self, content):
298
+ pre = ["/if/", "elif", "/for/", "/parallel/", "else", "/end/"]
299
+ split_result = split_by_multiple_prefixes(content, pre)
300
+ count = 1
301
+ elif_list = []
302
+ else_list = []
303
+ for i in range(1, len(split_result)):
304
+ if split_result[i].startswith("/if/"):
305
+ count += 1
306
+ elif split_result[i].startswith("elif"):
307
+ if count == 1:
308
+ elif_list.append(i)
309
+ elif split_result[i].startswith("else"):
310
+ if count == 1:
311
+ else_list.append(i)
312
+ elif split_result[i].startswith("/end/"):
313
+ count -= 1
314
+ elif split_result[i].startswith("/for/"):
315
+ count += 1
316
+ elif split_result[i].startswith("/parallel/"):
317
+ count += 1
318
+ join_list = [0] + elif_list + else_list
319
+ join_str_list = split_and_join(split_result, join_list)
320
+ join_str_list[-1] = join_str_list[-1][:-5]
321
+ ifelse_list = []
322
+ try:
323
+ for i in range(len(join_str_list)):
324
+ join_str = join_str_list[i]
325
+ condition, action = join_str_list[i].split(":", 1)
326
+ condition = condition.strip()
327
+ action = action.strip()
328
+ ifelse_list.append((condition, action))
329
+ except Exception:
330
+ raise Exception(
331
+ f"Syntax Error({content}),check the '/if/','elif','else','/end/'"
332
+ )
333
+ variables = self.context.get_all_variables_values()
334
+ for condition, action in ifelse_list:
335
+ if condition[:4] == "/if/" or condition[:4] == "elif":
336
+ result = eval(condition[4:].replace("$", ""), globals(), variables)
337
+ if result:
338
+ action_blocks = self.parser.parse(self, action)
339
+ async for resp in self.blocks_act(action_blocks):
340
+ yield resp
341
+ break
342
+ else:
343
+ action_blocks = self.parser.parse(self, action)
344
+ async for resp in self.blocks_act(action_blocks):
345
+ yield resp
346
+
347
+ async def for_block(self, content):
348
+ content = content[:-5]
349
+ pattern = r"/for/\s*\$(\s*[^ ]+)\s*in\s*\$(\s*[^:]+)\s*:"
350
+
351
+ match = re.search(pattern, content)
352
+ if match:
353
+ var_temp_name = match.group(1).strip()
354
+ var_loop_name = match.group(2).strip()
355
+ loop_obj = self.context.get_var_value(var_loop_name)
356
+
357
+ try:
358
+ # Compatibility: allow looping over prompt/skill result objects
359
+ if isinstance(loop_obj, list):
360
+ lst = loop_obj
361
+ elif isinstance(loop_obj, StreamItem):
362
+ if isinstance(loop_obj.output_var_value, list):
363
+ lst = loop_obj.output_var_value
364
+ elif isinstance(loop_obj.answer, str) and loop_obj.answer.startswith("[") and loop_obj.answer.endswith("]"):
365
+ lst = ast.literal_eval(loop_obj.answer)
366
+ else:
367
+ raise TypeError("Variable is not a list.")
368
+ elif isinstance(loop_obj, dict):
369
+ # Duck-typing compatibility for prompt/skill result dicts:
370
+ # accept only if it carries an iterable result payload.
371
+ if isinstance(loop_obj.get("output_var_value"), list):
372
+ lst = loop_obj.get("output_var_value")
373
+ elif isinstance(loop_obj.get("answer"), list):
374
+ lst = loop_obj.get("answer")
375
+ elif (
376
+ isinstance(loop_obj.get("answer"), str)
377
+ and loop_obj.get("answer").startswith("[")
378
+ and loop_obj.get("answer").endswith("]")
379
+ ):
380
+ lst = ast.literal_eval(loop_obj.get("answer"))
381
+ else:
382
+ raise TypeError("Variable is not a list.")
383
+ elif (
384
+ isinstance(loop_obj, str)
385
+ and loop_obj.startswith("[")
386
+ and loop_obj.endswith("]")
387
+ ):
388
+ lst = ast.literal_eval(loop_obj)
389
+ else:
390
+ raise TypeError("Variable is not a list.")
391
+ except Exception as e:
392
+ raise TypeError(
393
+ f"Syntax Error for loop variable ${var_loop_name}({loop_obj}), error: {e}"
394
+ )
395
+
396
+ for i, element in enumerate(lst):
397
+ self.context.set_variable(var_temp_name, element)
398
+ action_blocks = self.parser.parse(
399
+ self, ":".join(content.split(":")[1:])
400
+ )
401
+ async for resp in self.blocks_act(action_blocks):
402
+ yield resp
403
+
404
+ async def parallel_block(self, content):
405
+ content = content[10:-5]
406
+ action_blocks = self.parser.parse(self, content)
407
+
408
+ # Create a list of asynchronous generator objects
409
+ tasks = []
410
+ for i in range(len(action_blocks)):
411
+ tasks.append(("task" + str(i + 1), self.blocks_act([action_blocks[i]])))
412
+ active_generators = list(tasks)
413
+
414
+ # Loop until all generators are complete
415
+ while active_generators:
416
+ # Create the task for the current iteration
417
+ current_tasks = []
418
+
419
+ for name, gen in active_generators:
420
+ # Create a task to get the next value for each generator
421
+ current_tasks.append((name, asyncio.create_task(gen.__anext__())))
422
+
423
+ # Wait for all tasks to complete the current iteration
424
+ await asyncio.sleep(0)
425
+
426
+ # Process results and update the active generator list
427
+ next_active = []
428
+ for (name, gen), (_, task_obj) in zip(active_generators, current_tasks):
429
+ try:
430
+ result = await task_obj
431
+ if result is not None:
432
+ yield ""
433
+ next_active.append((name, gen))
434
+ else:
435
+ # Generator has completed
436
+ yield ""
437
+ except StopAsyncIteration:
438
+ # Generator has completed
439
+ yield ""
440
+
441
+ active_generators = next_active
442
+ yield ""
443
+ yield ""
444
+
445
+ def _preparation(self, content):
446
+ # remove comment
447
+ uncommented_content = self.parser.remove_comment(content)
448
+ # Split the string into a list by lines
449
+ lines = uncommented_content.splitlines()
450
+
451
+ # Filter out lines that start with 'import'
452
+ filtered_lines = [
453
+ line.lstrip() for line in lines if not line.strip().startswith("import")
454
+ ]
455
+
456
+ # Reconstruct the filtered lines into a string
457
+ content_new = "\n".join(filtered_lines)
458
+ # Split only the outermost blocks
459
+ num0 = count_occurrences(["/if/", "/for/", "/parallel"], content_new)
460
+ num1 = count_occurrences(["/end/"], content_new)
461
+ if num0 != num1:
462
+ raise Exception(
463
+ f"Syntax Error({content}),check the '/if/','/for/','/end/'"
464
+ )
465
+ # State Initialization
466
+ # Check if this is an interrupt recovery scenario by looking for intervention markers
467
+ is_interrupt_recovery = (
468
+ self.context.get_var_value("intervention_tool_block_vars") is not None
469
+ or self.context.get_var_value("intervention_judge_block_vars") is not None
470
+ or self.context.get_var_value("intervention_explore_block_vars") is not None
471
+ ) and self.context.get_var_value("tool") is not None
472
+
473
+ if self.context.get_var_value(KEY_STATUS):
474
+ previous_status = self.context.get_var_value(KEY_STATUS)
475
+ if isinstance(previous_status, dict) and all(
476
+ key in previous_status
477
+ for key in [
478
+ "tool_time",
479
+ "judge_time",
480
+ "prompt_time",
481
+ "explore_time",
482
+ "assign_time",
483
+ ]
484
+ ):
485
+ if is_interrupt_recovery:
486
+ # In interrupt recovery scenario, keep status unchanged
487
+ # and set previous_status to 0 so all blocks will execute
488
+ self.context.set_variable(
489
+ KEY_PREVIOUS_STATUS,
490
+ {
491
+ "tool_time": 0,
492
+ "judge_time": 0,
493
+ "prompt_time": 0,
494
+ "explore_time": 0,
495
+ "assign_time": 0,
496
+ },
497
+ )
498
+ # Keep the existing status (don't reset it)
499
+ else:
500
+ # Normal scenario: save current status as previous_status
501
+ self.context.set_variable(KEY_PREVIOUS_STATUS, previous_status)
502
+ # Reset status to 0 for new execution
503
+ self.context.set_variable(
504
+ KEY_STATUS,
505
+ {
506
+ "tool_time": 0,
507
+ "judge_time": 0,
508
+ "prompt_time": 0,
509
+ "explore_time": 0,
510
+ "assign_time": 0,
511
+ },
512
+ )
513
+ else:
514
+ self.context.set_variable(
515
+ KEY_PREVIOUS_STATUS,
516
+ {
517
+ "tool_time": 0,
518
+ "judge_time": 0,
519
+ "prompt_time": 0,
520
+ "explore_time": 0,
521
+ "assign_time": 0,
522
+ },
523
+ )
524
+ self.context.set_variable(
525
+ KEY_STATUS,
526
+ {
527
+ "tool_time": 0,
528
+ "judge_time": 0,
529
+ "prompt_time": 0,
530
+ "explore_time": 0,
531
+ "assign_time": 0,
532
+ },
533
+ )
534
+ else:
535
+ self.context.set_variable(
536
+ KEY_PREVIOUS_STATUS,
537
+ {
538
+ "tool_time": 0,
539
+ "judge_time": 0,
540
+ "prompt_time": 0,
541
+ "explore_time": 0,
542
+ "assign_time": 0,
543
+ },
544
+ )
545
+ self.context.set_variable(
546
+ KEY_STATUS,
547
+ {
548
+ "tool_time": 0,
549
+ "judge_time": 0,
550
+ "prompt_time": 0,
551
+ "explore_time": 0,
552
+ "assign_time": 0,
553
+ },
554
+ )
555
+
556
+ return self.parser.parse(self, content_new)
557
+
558
+ def _clean_up(self):
559
+ self.context.delete_variable(KEY_PREVIOUS_STATUS)
560
+ self.context.delete_variable(KEY_STATUS)
561
+
562
+ async def run(self, content, output_variables: list[str] = [], **kwargs):
563
+ blocks = self._preparation(content)
564
+ # Precompute output_variables conditions to avoid checking them in each iteration
565
+ should_filter = output_variables is not None and len(output_variables) > 0
566
+
567
+ try:
568
+ # Execute all blocks, blocks_act will generate the result of each block and return it streamingly.
569
+ async for resp in self.blocks_act(blocks):
570
+ # Return the result based on the precomputed condition
571
+ if should_filter:
572
+ # Return only the specified variables using the efficient get_variables method
573
+ filtered_variables = self.context.get_variables_values(
574
+ output_variables
575
+ )
576
+ yield filtered_variables
577
+ else:
578
+ # Return all variables
579
+ yield self.context.get_all_variables()
580
+ except ToolInterrupt:
581
+ # Similarly return the filtered variables when interrupted
582
+ if should_filter:
583
+ filtered_variables = self.context.get_variables_values(output_variables)
584
+ yield filtered_variables
585
+ else:
586
+ yield self.context.get_all_variables()
587
+ raise
588
+ finally:
589
+ self._clean_up()
590
+
591
+ async def run_and_get_result(self, content, **kwargs):
592
+ blocks = self._preparation(content)
593
+ try:
594
+ async for resp in self.blocks_act(blocks):
595
+ yield resp
596
+ except ToolInterrupt:
597
+ raise Exception(f"ToolInterrupt traceback[{traceback.format_exc()}]")
598
+ except Exception as e:
599
+ raise Exception(
600
+ f"Tool block execution failed: {str(e)} traceback[{traceback.format_exc()}]"
601
+ )
602
+ finally:
603
+ self._clean_up()
604
+
605
+ async def run_step(self, blocks, block_pointer: int):
606
+ """Execute a single step, return (new pointer position, whether completed)"""
607
+
608
+ if block_pointer >= len(blocks):
609
+ yield (block_pointer, True)
610
+ return
611
+
612
+ # Execute the current block
613
+ current_block = blocks[block_pointer]
614
+ async for result in self.blocks_act([current_block]):
615
+ yield result
616
+
617
+ # Return step completion information
618
+ new_pointer = block_pointer + 1
619
+ is_complete = new_pointer >= len(blocks)
620
+ yield (new_pointer, is_complete)
621
+
622
+ def get_parsed_blocks(self, content: str):
623
+ """Get parsed blocks (cache mechanism)"""
624
+ return self._preparation(content)
@@ -0,0 +1,27 @@
1
+ """Flags facade: Only responsible for exporting APIs and constants"""
2
+
3
+ from .manager import (
4
+ is_enabled,
5
+ override,
6
+ reset,
7
+ get_all,
8
+ set_flag,
9
+ ) # Functional API
10
+ from .definitions import (
11
+ EXPLORE_BLOCK_V2,
12
+ DEBUG_MODE,
13
+ DISABLE_LLM_CACHE,
14
+ ENABLE_PARALLEL_TOOL_CALLS,
15
+ )
16
+
17
+ __all__ = [
18
+ "is_enabled",
19
+ "override",
20
+ "reset",
21
+ "get_all",
22
+ "set_flag",
23
+ "EXPLORE_BLOCK_V2",
24
+ "DEBUG_MODE",
25
+ "DISABLE_LLM_CACHE",
26
+ "ENABLE_PARALLEL_TOOL_CALLS",
27
+ ]