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,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
|
+
|