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,220 @@
1
+ import traceback
2
+ from typing import Any, Optional, AsyncGenerator
3
+
4
+ from dolphin.core.code_block.basic_code_block import BasicCodeBlock
5
+ from dolphin.core.common.enums import CategoryBlock
6
+ from dolphin.core.context.context import Context
7
+ from dolphin.core.llm.llm_client import LLMClient
8
+ from dolphin.core.utils.tools import ToolInterrupt
9
+ from dolphin.core.context.var_output import SourceType
10
+
11
+
12
+ class JudgeBlock(BasicCodeBlock):
13
+ def __init__(self, context: Context, debug_infos=None):
14
+ super().__init__(context)
15
+
16
+ self.llm_client = LLMClient(self.context)
17
+ self.debug_info = debug_infos
18
+
19
+ async def judge_tool_call(
20
+ self,
21
+ judge_str: Optional[str],
22
+ system_prompt: Optional[str],
23
+ tools_list: Optional[list[str]],
24
+ model: Optional[str] = None,
25
+ history: Optional[bool] = None,
26
+ ttc_mode: Optional[str] = None,
27
+ ) -> tuple[Optional[str], Optional[dict]]:
28
+ """Using LLM's function calling capability for tool selection"""
29
+ # Save original attributes
30
+ original_content = self.content
31
+ original_system_prompt = getattr(self, "system_prompt", None)
32
+ original_skills = getattr(self, "skills", None)
33
+ original_model = getattr(self, "model", None)
34
+ original_history = getattr(self, "history", None)
35
+ original_ttc_mode = getattr(self, "ttc_mode", None)
36
+
37
+ try:
38
+ # Temporarily set attributes to adapt to llm_chat
39
+ self.content = judge_str
40
+ self.system_prompt = system_prompt or ""
41
+ self.skills = tools_list
42
+ self.model = model
43
+ self.history = history
44
+ self.ttc_mode = ttc_mode
45
+
46
+ # Get tool list and skillkit information
47
+ available_skill_names = [
48
+ str(name) for name in self.get_skillkit().getSkillNames()
49
+ ]
50
+
51
+ # To ensure the LLM prioritizes using tools, add an explicit system prompt
52
+ original_system_prompt = self.system_prompt
53
+ if available_skill_names:
54
+ tool_instruction = f"You have access to these tools: {', '.join(available_skill_names)}. Please use the appropriate tool if it can help complete the task."
55
+ if self.system_prompt:
56
+ self.system_prompt = f"{self.system_prompt}\n\n{tool_instruction}"
57
+ else:
58
+ self.system_prompt = tool_instruction
59
+
60
+ # Call llm_chat with_skill=True to enable early stopping for tool calling
61
+ func_name: Optional[str] = None
62
+ func_args: Optional[dict[str, Any]] = None
63
+
64
+ async for item in self.llm_chat(
65
+ "judge", with_skill=True, early_stop_on_tool_call=True
66
+ ):
67
+ # Check if a complete tool invocation exists (arguments not being None indicates that parameters have been fully parsed)
68
+ if isinstance(item, dict) and "tool_call" in item and item["tool_call"]:
69
+ assert isinstance(item["tool_call"], dict), (
70
+ "tool_call is not a dict"
71
+ )
72
+
73
+ tool_call = item["tool_call"]
74
+ func_name = tool_call["name"]
75
+ func_args = tool_call[
76
+ "arguments"
77
+ ] # None indicates incomplete, dict indicates complete parameters
78
+
79
+ # If arguments is not None, it indicates that the tool invocation is complete.
80
+ if func_args is not None:
81
+ break
82
+
83
+ # If no complete tool invocation is detected, use the default value.
84
+ if func_args is None:
85
+ func_args = {}
86
+
87
+ self.context.debug(
88
+ f"judge_tool_call[{self.output_var}] [{judge_str}] tool_name[{func_name}] tool_args[{func_args}]"
89
+ )
90
+
91
+ return func_name, func_args
92
+
93
+ except Exception as e:
94
+ raise Exception(f"(Judge Block) judge_tool_call failed: {str(e)}")
95
+
96
+ finally:
97
+ # Restore original attributes
98
+ self.content = original_content
99
+ self.system_prompt = original_system_prompt # Here the state will be restored to the previous state before modification.
100
+ self.skills = original_skills
101
+ self.model = original_model
102
+ self.history = original_history
103
+ self.ttc_mode = original_ttc_mode
104
+
105
+ async def execute(
106
+ self,
107
+ content,
108
+ category: CategoryBlock = CategoryBlock.JUDGE,
109
+ replace_variables=True,
110
+ ) -> AsyncGenerator[Any, None]:
111
+ # Execute the parent class logic
112
+ async for _ in super().execute(content, category, replace_variables):
113
+ pass
114
+
115
+ self.block_start_log("judge")
116
+
117
+ assert self.recorder, "recorder is None"
118
+
119
+ try:
120
+ gvpool_all_keys = self.context.get_all_variables().keys()
121
+ if (
122
+ "intervention_judge_block_vars" in gvpool_all_keys
123
+ and "tool" in gvpool_all_keys
124
+ ):
125
+ intervention_vars = self.context.get_var_value(
126
+ "intervention_judge_block_vars"
127
+ )
128
+ assert intervention_vars is not None, "intervention_vars is None"
129
+
130
+ tool_name = intervention_vars["tool_name"]
131
+ judge_call_info = intervention_vars["judge_call_info"]
132
+
133
+ self.recorder.set_output_var(
134
+ judge_call_info["assign_type"], judge_call_info["output_var"]
135
+ )
136
+
137
+ self.context.delete_variable("intervention_judge_block_vars")
138
+
139
+ input_dict = self.context.get_var_value("tool")
140
+ assert input_dict is not None, "input_dict is None"
141
+
142
+ new_tool_name = input_dict["tool_name"]
143
+ assert new_tool_name == tool_name, (
144
+ "(judge_block) new_tool_name 和中断之前的 tool_name不一致"
145
+ )
146
+
147
+ raw_tool_args = input_dict["tool_args"]
148
+ new_tool_args = {arg["key"]: arg["value"] for arg in raw_tool_args}
149
+
150
+ props = {"intervention": False, "gvp": self.context}
151
+ self.context.delete_variable("tool")
152
+
153
+ async for resp_item in self.skill_run(
154
+ source_type=SourceType.SKILL,
155
+ skill_name=tool_name,
156
+ skill_params_json=new_tool_args,
157
+ props=props,
158
+ ):
159
+ yield resp_item
160
+ else:
161
+ self.recorder.set_output_var(self.assign_type, self.output_var)
162
+
163
+ # Execute the prompt; determine whether to invoke a tool. If so, invoke the tool; otherwise, directly request the LLM.
164
+ # Use the LLM chat-based implementation for tool judgment
165
+ tool_name, tool_args = await self.judge_tool_call(
166
+ judge_str=self.content,
167
+ system_prompt=self.system_prompt,
168
+ tools_list=self.skills,
169
+ model=self.model,
170
+ history=self.history,
171
+ ttc_mode=self.ttc_mode,
172
+ )
173
+ self.context.debug(
174
+ f"judge_block[{self.output_var}] [{self.content}] tool_name[{tool_name}] tool_args[{tool_args}]"
175
+ )
176
+ if tool_name:
177
+ # Ensure that output_var has been set (super().execute() should have already set it, but confirm here)
178
+ if self.recorder and hasattr(self.recorder, "set_output_var"):
179
+ self.recorder.set_output_var(self.assign_type, self.output_var)
180
+
181
+ intervention_vars = {
182
+ "tool_name": tool_name,
183
+ "judge_call_info": {
184
+ "judge_str": self.content,
185
+ "assign_type": self.assign_type,
186
+ "output_var": self.output_var,
187
+ "params": self.params,
188
+ },
189
+ }
190
+
191
+ try:
192
+ self.context.set_variable(
193
+ "intervention_judge_block_vars", intervention_vars
194
+ )
195
+
196
+ props = {"gvp": self.context}
197
+
198
+ async for resp_item in self.skill_run(
199
+ source_type=SourceType.SKILL,
200
+ skill_name=tool_name,
201
+ skill_params_json=tool_args or {},
202
+ props=props,
203
+ ):
204
+ yield resp_item
205
+
206
+ except ToolInterrupt as e:
207
+ raise e
208
+ except Exception as e:
209
+ raise Exception(
210
+ f"Judge block execution[{content}] tool[{tool_name}] failed: {str(e)}"
211
+ )
212
+ else:
213
+ async for item in self.llm_chat(lang_mode="judge"):
214
+ yield item
215
+ except ToolInterrupt as e:
216
+ raise e
217
+ except Exception as e:
218
+ raise Exception(
219
+ f"Judge block execution failed: {str(e)} traceback: {traceback.format_exc()}"
220
+ )
@@ -0,0 +1,32 @@
1
+ import traceback
2
+ from typing import Any, AsyncGenerator, Dict
3
+ from dolphin.core.code_block.basic_code_block import BasicCodeBlock
4
+ from dolphin.core.common.enums import CategoryBlock
5
+ from dolphin.core.context.context import Context
6
+ from dolphin.core.llm.llm_client import LLMClient
7
+
8
+
9
+ class PromptBlock(BasicCodeBlock):
10
+ def __init__(self, context: Context, debug_infos=None):
11
+ super().__init__(context)
12
+ self.debug_info = debug_infos
13
+ self.llm_client = LLMClient(self.context)
14
+ self.debug_info = debug_infos
15
+
16
+ async def execute(
17
+ self,
18
+ content,
19
+ category: CategoryBlock = CategoryBlock.PROMPT,
20
+ replace_variables=True,
21
+ ) -> AsyncGenerator[Dict[str, Any], None]:
22
+ async for _ in super().execute(content, category, replace_variables):
23
+ pass
24
+
25
+ self.block_start_log("prompt")
26
+ try:
27
+ async for item in self.llm_chat(lang_mode="prompt"):
28
+ yield item
29
+ except Exception as e:
30
+ raise Exception(
31
+ f"Prompt execution failed: {str(e)} traceback: {traceback.format_exc()}"
32
+ )
@@ -0,0 +1,291 @@
1
+ """Unified skill invocation deduplication interface and implementation
2
+
3
+ This module provides an abstract base class and default implementation for skill invocation deduplication,
4
+ used to detect and handle duplicate tool calls, preventing infinite loops.
5
+ """
6
+
7
+ import json
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any, Optional, Dict, Tuple
10
+
11
+
12
+ class SkillCallDeduplicator(ABC):
13
+ """Base class for skill call deduplicator
14
+
15
+ Provides a unified interface for detecting duplicate tool calls.
16
+ Different exploration strategies can use different implementations.
17
+ """
18
+ MAX_DUPLICATE_COUNT = 5
19
+
20
+ @abstractmethod
21
+ def add(self, skill_call: Any, result: Optional[str] = None):
22
+ """Add call record
23
+
24
+ Args:
25
+ skill_call: Skill call information, which can be a dict or tuple
26
+ result: Optional call result, used for intelligent retry judgment
27
+ """
28
+ pass
29
+
30
+ @abstractmethod
31
+ def is_duplicate(self, skill_call: Any) -> bool:
32
+ """Check if it's a repeated call (exceeding the maximum number of repetitions)
33
+
34
+ Args:
35
+ skill_call: Skill call information
36
+
37
+ Returns:
38
+ bool: Returns True if the number of repetitions exceeds the threshold
39
+ """
40
+ pass
41
+
42
+ @abstractmethod
43
+ def clear(self):
44
+ """Clear all records"""
45
+ pass
46
+
47
+ @abstractmethod
48
+ def get_call_key(self, skill_call: Any) -> str:
49
+ """Get the unique identifier of the call, used to determine whether two calls are equivalent.
50
+
51
+ Args:
52
+ skill_call: Information about the skill call
53
+
54
+ Returns:
55
+ str: A normalized call identifier string
56
+ """
57
+ pass
58
+
59
+ @abstractmethod
60
+ def get_history(self) -> list:
61
+ """Get the history of all recorded skill calls.
62
+
63
+ Returns:
64
+ List of skill call dictionaries
65
+ """
66
+ pass
67
+
68
+
69
+ class NoOpSkillCallDeduplicator(SkillCallDeduplicator):
70
+ """Empty implementation of skill call deduplicator
71
+
72
+ Use when deduplication logic needs to be disabled. This implementation:
73
+ - Never marks calls as duplicates
74
+ - Records no call information
75
+ """
76
+
77
+ def add(self, skill_call: Any, result: Optional[str] = None):
78
+ """Do not record anything"""
79
+ return
80
+
81
+ def is_duplicate(self, skill_call: Any) -> bool:
82
+ """Never consider it a repeated call"""
83
+ return False
84
+
85
+ def clear(self):
86
+ """Do nothing"""
87
+ return
88
+
89
+ def get_call_key(self, skill_call: Any) -> str:
90
+ """Return an empty string as a placeholder"""
91
+ return ""
92
+
93
+ def get_history(self) -> list:
94
+ """Return empty list as no history is recorded"""
95
+ return []
96
+
97
+
98
+ class DefaultSkillCallDeduplicator(SkillCallDeduplicator):
99
+ """Default skill call deduplication implementation
100
+
101
+ Supports two skill_call formats:
102
+ 1. dict format: {"name": "skill_name", "arguments": {...}} (used by ToolCallStrategy)
103
+ 2. tuple format: (skill_name, params_dict) (used by PromptStrategy)
104
+
105
+ Features:
106
+ - Uses normalized JSON as unique identifier
107
+ - Supports intelligent retry logic (some tools like snapshot allow retries when results are invalid)
108
+ - Caches call results for retry determination
109
+ """
110
+
111
+ def __init__(self):
112
+ self.skillcalls: Dict[str, int] = {}
113
+ self.call_results: Dict[str, str] = {}
114
+ # Cache call_key to avoid duplicate serialization
115
+ self._call_key_cache: Dict[int, str] = {}
116
+
117
+ def clear(self):
118
+ """Clear all records"""
119
+ self.skillcalls.clear()
120
+ self.call_results.clear()
121
+ self._call_key_cache.clear()
122
+
123
+ def get_history(self) -> list:
124
+ """Get the history of all recorded skill calls.
125
+
126
+ Returns:
127
+ List of skill call dictionaries with name and arguments
128
+ """
129
+ history = []
130
+ for call_key in self.skillcalls.keys():
131
+ try:
132
+ # Parse the call_key format: "skill_name:json_args"
133
+ if ':' in call_key:
134
+ name, args_str = call_key.split(':', 1)
135
+ try:
136
+ args = json.loads(args_str)
137
+ except json.JSONDecodeError:
138
+ args = args_str
139
+ history.append({"name": name, "arguments": args})
140
+ else:
141
+ history.append({"name": call_key, "arguments": {}})
142
+ except Exception:
143
+ # If parsing fails, add as-is
144
+ history.append({"raw": call_key})
145
+ return history
146
+
147
+ def get_call_key(self, skill_call: Any) -> str:
148
+ """Get the standardized string representation of a skill call.
149
+
150
+ Supports two formats:
151
+ - dict: {"name": "xxx", "arguments": {...}}
152
+ - tuple: ("xxx", {...})
153
+
154
+ Uses the normalized JSON string of the skill name and arguments as the unique identifier.
155
+ """
156
+ # Use object id as cache key
157
+ cache_key = id(skill_call)
158
+ if cache_key in self._call_key_cache:
159
+ return self._call_key_cache[cache_key]
160
+
161
+ skill_name, arguments = self._extract_skill_info(skill_call)
162
+
163
+ # Normalized parameters: sorting keys, ensuring consistency
164
+ try:
165
+ if isinstance(arguments, dict):
166
+ normalized_args = json.dumps(
167
+ arguments, sort_keys=True, ensure_ascii=False, separators=(",", ":")
168
+ )
169
+ else:
170
+ normalized_args = json.dumps(
171
+ arguments, ensure_ascii=False, separators=(",", ":")
172
+ )
173
+ except (TypeError, ValueError):
174
+ # Fallback to string representation when serialization fails
175
+ normalized_args = str(arguments).strip()
176
+
177
+ call_key = f"{skill_name}:{normalized_args}"
178
+ self._call_key_cache[cache_key] = call_key
179
+ return call_key
180
+
181
+ def _extract_skill_info(self, skill_call: Any) -> Tuple[str, Any]:
182
+ """Extract skill name and parameters from skill_call
183
+
184
+ Args:
185
+ skill_call: skill call information
186
+
187
+ Returns:
188
+ Tuple[str, Any]: (skill name, parameters)
189
+ """
190
+ if isinstance(skill_call, dict):
191
+ # dict format: {"name": "xxx", "arguments": {...}}
192
+ skill_name = skill_call.get("name", "")
193
+ arguments = skill_call.get("arguments", {})
194
+ elif isinstance(skill_call, (list, tuple)) and len(skill_call) >= 2:
195
+ # tuple/list format: ("xxx", {...})
196
+ skill_name = skill_call[0]
197
+ arguments = skill_call[1]
198
+ else:
199
+ # Other formats, try stringifying
200
+ skill_name = str(skill_call)
201
+ arguments = {}
202
+
203
+ return skill_name, arguments
204
+
205
+ def add(self, skill_call: Any, result: Optional[str] = None):
206
+ """Add skill call record
207
+
208
+ Args:
209
+ skill_call: skill call information
210
+ result: optional call result
211
+ """
212
+ key = self.get_call_key(skill_call)
213
+ self.skillcalls[key] = self.skillcalls.get(key, 0) + 1
214
+ if result is not None:
215
+ self.call_results[key] = result
216
+
217
+ def is_duplicate(self, skill_call: Any) -> bool:
218
+ """Check if it's a repeated call (exceeding the maximum number of repetitions)
219
+
220
+ Features:
221
+ - For certain tools (e.g., snapshot), re-calling is allowed if the previous call result is invalid
222
+
223
+ Args:
224
+ skill_call: Skill call information
225
+
226
+ Returns:
227
+ bool: Returns True if the repetition count exceeds the threshold
228
+ """
229
+ key = self.get_call_key(skill_call)
230
+
231
+ # Smart retry: Certain tools allow retries when the result is invalid
232
+ if self._should_allow_retry(skill_call, key):
233
+ return False
234
+
235
+ return self.skillcalls.get(key, 0) >= self.MAX_DUPLICATE_COUNT
236
+
237
+ def _should_allow_retry(self, skill_call: Any, call_key: str) -> bool:
238
+ """Determine whether retrying a skill call should be allowed.
239
+
240
+ For certain tools (e.g., browser_snapshot), retries are allowed if previous results were invalid.
241
+
242
+ Args:
243
+ skill_call: Information about the skill call
244
+ call_key: Standardized key for the call
245
+
246
+ Returns:
247
+ bool: Whether retrying is allowed
248
+ """
249
+ skill_name, arguments = self._extract_skill_info(skill_call)
250
+
251
+ # Calls without arguments are not specially handled
252
+ if not arguments:
253
+ return False
254
+
255
+ # For snapshot tools, check whether previous results are valid
256
+ if "snapshot" in skill_name.lower():
257
+ previous_result = self.call_results.get(call_key)
258
+ if previous_result is not None:
259
+ result_str = str(previous_result).strip().lower()
260
+ # If the previous result is very short or contains error messages, retries are allowed
261
+ return (
262
+ len(result_str) < 50
263
+ or "about:blank" in result_str
264
+ or "error" in result_str
265
+ or "empty" in result_str
266
+ )
267
+
268
+ return False
269
+
270
+ def get_duplicate_count(self, skill_call: Any) -> int:
271
+ """Get the number of repetitions for a skill call
272
+
273
+ Args:
274
+ skill_call: Information about the skill call
275
+
276
+ Returns:
277
+ int: Number of repetitions
278
+ """
279
+ key = self.get_call_key(skill_call)
280
+ return self.skillcalls.get(key, 0)
281
+
282
+ def repr_skill_call(self, skill_call: Any) -> str:
283
+ """Get the string representation of a skill call (for logging)
284
+
285
+ Args:
286
+ skill_call: Information about the skill call
287
+
288
+ Returns:
289
+ str: String representation of the call
290
+ """
291
+ return self.get_call_key(skill_call)
@@ -0,0 +1,129 @@
1
+ from dolphin.core.code_block.basic_code_block import BasicCodeBlock
2
+ from dolphin.core.utils.tools import ToolInterrupt
3
+ from dolphin.core.common.enums import CategoryBlock, TypeStage
4
+ from dolphin.core.context.context import Context
5
+ from dolphin.core.logging.logger import console
6
+ from dolphin.core.context.var_output import SourceType
7
+ from typing import Optional, AsyncGenerator, Dict, Any
8
+
9
+
10
+ class ToolBlock(BasicCodeBlock):
11
+ def __init__(self, context: Context, debug_infos: Optional[dict] = None):
12
+ super().__init__(context=context)
13
+ self.already_append_flag = False
14
+
15
+ def parse_tool_call(self):
16
+ """Parse tool calls, now using the unified parse_block_content method
17
+
18
+ Args:
19
+ content: Tool call content
20
+
21
+ Returns:
22
+ Dictionary containing the parsing results
23
+ """
24
+ # Return a compatible format
25
+ return {
26
+ "tool_name": self.content, # The tool name is stored in content.
27
+ "args": self.params,
28
+ "assign_type": self.assign_type,
29
+ "output_var": self.output_var,
30
+ }
31
+
32
+ async def execute(
33
+ self,
34
+ content,
35
+ category: CategoryBlock = CategoryBlock.TOOL,
36
+ replace_variables=True,
37
+ ) -> AsyncGenerator[Dict[str, Any], None]:
38
+ async for _ in super().execute(content, category, replace_variables):
39
+ pass
40
+
41
+ self.block_start_log("tool")
42
+ try:
43
+ gvpool_all_keys = self.context.get_all_variables().keys()
44
+ if (
45
+ "intervention_tool_block_vars" in gvpool_all_keys
46
+ and "tool" in gvpool_all_keys
47
+ ):
48
+ intervention_vars = self.context.get_var_value(
49
+ "intervention_tool_block_vars"
50
+ )
51
+ assert intervention_vars is not None, "intervention_vars is None"
52
+
53
+ tool_name = intervention_vars["tool_name"]
54
+ tool_call_info = intervention_vars["tool_call_info"]
55
+ self.context.delete_variable("intervention_tool_block_vars")
56
+ if self.recorder is not None:
57
+ self.recorder.set_output_var(
58
+ tool_call_info["assign_type"], tool_call_info["output_var"]
59
+ )
60
+
61
+ input_dict = self.context.get_var_value("tool")
62
+ assert input_dict is not None, "input_dict is None"
63
+
64
+ new_tool_name = input_dict["tool_name"]
65
+ assert new_tool_name == tool_name, (
66
+ "(tool_block) new_tool_name 和中断之前的 tool_name不一致"
67
+ )
68
+ tool_obj = self.context.get_skill(tool_name)
69
+ raw_tool_args = input_dict["tool_args"]
70
+ new_tool_args = {arg["key"]: arg["value"] for arg in raw_tool_args}
71
+
72
+ props = {"intervention": False, "gvp": self.context}
73
+ input_dict = self.context.delete_variable("tool")
74
+
75
+ resp_item = None
76
+ async for resp_item in self.skill_run(
77
+ source_type=SourceType.SKILL,
78
+ skill_name=tool_name,
79
+ skill_params_json=new_tool_args,
80
+ props=props,
81
+ ):
82
+ yield resp_item
83
+
84
+ if self.recorder is not None:
85
+ self.recorder.update(
86
+ stage=TypeStage.SKILL,
87
+ item=resp_item,
88
+ skill_name=tool_name,
89
+ skill_args=new_tool_args,
90
+ skill_type=self.context.get_skill_type(tool_name),
91
+ source_type=SourceType.SKILL,
92
+ is_completed=True,
93
+ )
94
+ yield {"data": resp_item}
95
+ else:
96
+ # step1: First parse, then retrieve the actual values from gvpool when actually calling the function (the actual variable values might be of type dict, list)
97
+ tool_call_info = self.parse_tool_call()
98
+ if self.recorder is not None:
99
+ self.recorder.set_output_var(
100
+ tool_call_info["assign_type"], tool_call_info["output_var"]
101
+ )
102
+
103
+ # step2: Obtain the tool object and execute the tool call
104
+ tool_name = tool_call_info["tool_name"]
105
+
106
+ intervention_vars = {
107
+ "tool_name": tool_call_info["tool_name"],
108
+ "tool_call_info": tool_call_info,
109
+ }
110
+
111
+ self.context.set_variable(
112
+ "intervention_tool_block_vars", intervention_vars
113
+ )
114
+ interventions = self.context.get_var_value("interventions") or []
115
+ interventions += [intervention_vars]
116
+ self.context.set_variable("interventions", interventions)
117
+
118
+ async for resp_item in self.skill_run(
119
+ source_type=SourceType.SKILL,
120
+ skill_name=tool_name,
121
+ skill_params_json=tool_call_info["args"],
122
+ ):
123
+ yield resp_item
124
+
125
+ console("\n", verbose=self.context.is_verbose())
126
+ except ToolInterrupt as e:
127
+ raise e
128
+ except Exception as e:
129
+ raise Exception(f"Tool block execution failed: {str(e)}")
@@ -0,0 +1,17 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Common 模块 - 核心公共定义"""
3
+
4
+ from dolphin.core.common.constants import *
5
+ from dolphin.core.common.enums import MessageRole, SkillType, KnowledgePoint, SingleMessage, ToolCallInfo
6
+ from dolphin.core.common.types import *
7
+ from dolphin.core.common.exceptions import DolphinException
8
+
9
+ __all__ = [
10
+ "MessageRole",
11
+ "SkillType",
12
+ "DolphinException",
13
+ "KnowledgePoint",
14
+ "SingleMessage",
15
+ "ToolCallInfo",
16
+ ]
17
+