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,700 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Callable, List, Tuple, Dict, Optional
|
|
7
|
+
|
|
8
|
+
from dolphin.core.logging.logger import MaxLenLog
|
|
9
|
+
from dolphin.core.skill.skill_function import SkillFunction
|
|
10
|
+
from dolphin.core.skill.skill_matcher import SkillMatcher
|
|
11
|
+
from dolphin.core.logging.logger import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger("skill")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SkillExecRecord:
|
|
17
|
+
"""Skill Execution Log"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, toolCall: Tuple[str, dict], tool: SkillFunction, result: Any):
|
|
20
|
+
self.toolCall = toolCall
|
|
21
|
+
self.tool = tool
|
|
22
|
+
self.result = result
|
|
23
|
+
|
|
24
|
+
def __str__(self) -> str:
|
|
25
|
+
return f"toolCall: {self.toolCall}, tool: {self.tool}, result: {self.result[:MaxLenLog]}"
|
|
26
|
+
|
|
27
|
+
def get_tool_call(self) -> Tuple[str, dict]:
|
|
28
|
+
return self.toolCall
|
|
29
|
+
|
|
30
|
+
def get_tool(self) -> SkillFunction:
|
|
31
|
+
return self.tool
|
|
32
|
+
|
|
33
|
+
def get_result(self) -> Any:
|
|
34
|
+
return self.result
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Skillkit:
|
|
38
|
+
def __init__(self) -> None:
|
|
39
|
+
self.records = []
|
|
40
|
+
self.queryAsArg = False
|
|
41
|
+
self._skills_cache: Optional[List[SkillFunction]] = None
|
|
42
|
+
"""Skill result processing strategy configuration. The strategies used must be registered strategies in StrategyRegistry.
|
|
43
|
+
Example:
|
|
44
|
+
[
|
|
45
|
+
{
|
|
46
|
+
"strategy": "summary",
|
|
47
|
+
"category": "llm",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"strategy": "preview",
|
|
51
|
+
"category": "app",
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
"""
|
|
55
|
+
self.result_process_strategy_cfg: list[Dict[str, str]] = None
|
|
56
|
+
|
|
57
|
+
def getName(self) -> str:
|
|
58
|
+
return "skillkit"
|
|
59
|
+
|
|
60
|
+
# ─────────────────────────────────────────────────────────────
|
|
61
|
+
# UI Rendering Protocol (Custom UI Support)
|
|
62
|
+
# ─────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
def has_custom_ui(self, skill_name: str) -> bool:
|
|
65
|
+
"""Check if this skillkit provides custom UI rendering for a skill.
|
|
66
|
+
|
|
67
|
+
Subclasses can override this to indicate that they provide
|
|
68
|
+
custom UI rendering instead of the default skill_call box.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
skill_name: Name of the skill being rendered
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
True if custom UI is provided, False to use default rendering
|
|
75
|
+
"""
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
def render_skill_start(
|
|
79
|
+
self,
|
|
80
|
+
skill_name: str,
|
|
81
|
+
params: dict,
|
|
82
|
+
verbose: bool = True
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Custom UI rendering for skill start (before execution).
|
|
85
|
+
|
|
86
|
+
Called instead of skill_call_start when has_custom_ui returns True.
|
|
87
|
+
Subclasses should override to provide custom rendering.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
skill_name: Name of the skill being called
|
|
91
|
+
params: Parameters passed to the skill
|
|
92
|
+
verbose: Whether to render UI
|
|
93
|
+
"""
|
|
94
|
+
pass # Default: no-op, subclasses implement
|
|
95
|
+
|
|
96
|
+
def render_skill_end(
|
|
97
|
+
self,
|
|
98
|
+
skill_name: str,
|
|
99
|
+
params: dict,
|
|
100
|
+
result: Any,
|
|
101
|
+
success: bool = True,
|
|
102
|
+
duration_ms: float = 0,
|
|
103
|
+
verbose: bool = True
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Custom UI rendering for skill end (after execution).
|
|
106
|
+
|
|
107
|
+
Called instead of skill_call_end when has_custom_ui returns True.
|
|
108
|
+
Subclasses should override to provide custom rendering.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
skill_name: Name of the skill that completed
|
|
112
|
+
params: Parameters that were passed to the skill
|
|
113
|
+
result: Result from the skill execution
|
|
114
|
+
success: Whether the skill succeeded
|
|
115
|
+
duration_ms: Execution duration in milliseconds
|
|
116
|
+
verbose: Whether to render UI
|
|
117
|
+
"""
|
|
118
|
+
pass # Default: no-op, subclasses implement
|
|
119
|
+
|
|
120
|
+
def _createSkills(self) -> List[SkillFunction]:
|
|
121
|
+
"""Subclasses override this method to create the skill list.
|
|
122
|
+
|
|
123
|
+
This is the template method pattern: subclasses implement skill creation,
|
|
124
|
+
and the base class handles owner binding in getSkills().
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List[SkillFunction]: List of skills created by this skillkit
|
|
128
|
+
"""
|
|
129
|
+
return []
|
|
130
|
+
|
|
131
|
+
def getSkills(self) -> List[SkillFunction]:
|
|
132
|
+
"""Get the skill list with owner_skillkit automatically bound.
|
|
133
|
+
|
|
134
|
+
This method caches the skills and ensures owner_skillkit is set
|
|
135
|
+
for all skills. Subclasses should override _createSkills() instead
|
|
136
|
+
of this method.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List[SkillFunction]: List of skills with owner_skillkit set
|
|
140
|
+
"""
|
|
141
|
+
if self._skills_cache is None:
|
|
142
|
+
self._skills_cache = self._createSkills()
|
|
143
|
+
self._bindOwnerToSkills(self._skills_cache)
|
|
144
|
+
return self._skills_cache
|
|
145
|
+
|
|
146
|
+
def _bindOwnerToSkills(self, skills: List[SkillFunction]) -> None:
|
|
147
|
+
"""Bind owner_skillkit to all skills that don't have one set.
|
|
148
|
+
|
|
149
|
+
This passes the Skillkit object (self) so that metadata prompt
|
|
150
|
+
can be collected dynamically via skill.owner_skillkit.get_metadata_prompt().
|
|
151
|
+
"""
|
|
152
|
+
for skill in skills:
|
|
153
|
+
if hasattr(skill, "set_owner_skillkit"):
|
|
154
|
+
current_owner = getattr(skill, "get_owner_skillkit", lambda: None)()
|
|
155
|
+
if current_owner is None:
|
|
156
|
+
skill.set_owner_skillkit(self)
|
|
157
|
+
|
|
158
|
+
def invalidateSkillsCache(self) -> None:
|
|
159
|
+
"""Invalidate the skills cache, forcing recreation on next getSkills() call."""
|
|
160
|
+
self._skills_cache = None
|
|
161
|
+
|
|
162
|
+
def getResultProcessStrategyCfg(self) -> list[Dict[str, str]]:
|
|
163
|
+
return self.result_process_strategy_cfg
|
|
164
|
+
|
|
165
|
+
def setResultProcessStrategyCfg(
|
|
166
|
+
self, result_process_strategy_cfg: list[Dict[str, str]]
|
|
167
|
+
) -> None:
|
|
168
|
+
self.result_process_strategy_cfg = result_process_strategy_cfg
|
|
169
|
+
|
|
170
|
+
def getSkillNames(self) -> List[str]:
|
|
171
|
+
return [skill.get_function_name() for skill in self.getSkills()]
|
|
172
|
+
|
|
173
|
+
def setGlobalConfig(self, globalConfig):
|
|
174
|
+
self.globalConfig = globalConfig
|
|
175
|
+
|
|
176
|
+
def getCertainSkills(
|
|
177
|
+
self, skillNames: List[str] | str | None
|
|
178
|
+
) -> List[SkillFunction]:
|
|
179
|
+
if skillNames is None:
|
|
180
|
+
return self.getSkills()
|
|
181
|
+
elif isinstance(skillNames, str):
|
|
182
|
+
# Use SkillMatcher to support wildcard matching
|
|
183
|
+
return SkillMatcher.filter_skills_by_pattern(self.getSkills(), skillNames)
|
|
184
|
+
else:
|
|
185
|
+
# Use SkillMatcher to support wildcard matching
|
|
186
|
+
return SkillMatcher.filter_skills_by_patterns(self.getSkills(), skillNames)
|
|
187
|
+
|
|
188
|
+
def hasSkill(self, skillName: str) -> bool:
|
|
189
|
+
return SkillMatcher.get_skill_by_name(self.getSkills(), skillName) is not None
|
|
190
|
+
|
|
191
|
+
def getSkill(self, skillName: str) -> Optional[SkillFunction]:
|
|
192
|
+
return SkillMatcher.get_skill_by_name(self.getSkills(), skillName)
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def getSkillsWithSingleSkill(skill: Callable) -> List[SkillFunction]:
|
|
196
|
+
return [SkillFunction(skill)]
|
|
197
|
+
|
|
198
|
+
def getSkillsSchema(self) -> list:
|
|
199
|
+
return [skill.get_openai_tool_schema() for skill in self.getSkills()]
|
|
200
|
+
|
|
201
|
+
def getSkillsSchemaForCertainSkills(self, skillNames: List[str]) -> list:
|
|
202
|
+
return [
|
|
203
|
+
skill.get_openai_tool_schema()
|
|
204
|
+
for skill in self.getCertainSkills(skillNames)
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
def getSkillsDict(self) -> dict:
|
|
208
|
+
return {skill.get_function_name(): skill for skill in self.getSkills()}
|
|
209
|
+
|
|
210
|
+
def getSchemas(self, skillNames: Optional[List[str]] = None) -> str:
|
|
211
|
+
skills = self.getCertainSkills(skillNames)
|
|
212
|
+
functionSchemas = [
|
|
213
|
+
json.dumps(skill.get_openai_tool_schema()["function"], ensure_ascii=False)
|
|
214
|
+
for skill in skills
|
|
215
|
+
]
|
|
216
|
+
return "|".join(functionSchemas)
|
|
217
|
+
|
|
218
|
+
# =========================
|
|
219
|
+
# Compression (generic)
|
|
220
|
+
# =========================
|
|
221
|
+
|
|
222
|
+
# Default rules for compressing skill-call messages; subclasses can override
|
|
223
|
+
DEFAULT_COMPRESS_RULES: Dict[str, Dict[str, List[str]]] = {}
|
|
224
|
+
|
|
225
|
+
def get_compress_rules(self) -> Dict[str, Dict[str, List[str]]]:
|
|
226
|
+
"""Return default compression rules for this skillkit instance."""
|
|
227
|
+
return self.DEFAULT_COMPRESS_RULES
|
|
228
|
+
|
|
229
|
+
@classmethod
|
|
230
|
+
def set_default_compress_rules(cls, rules: Dict[str, Dict[str, List[str]]]):
|
|
231
|
+
"""Set default compression rules at class level."""
|
|
232
|
+
cls.DEFAULT_COMPRESS_RULES = rules or {}
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def compress_message_with_rules(
|
|
236
|
+
message: str,
|
|
237
|
+
rules: Optional[Dict[str, Dict[str, List[str]]]] = None,
|
|
238
|
+
marker_prefix: str = "=>#",
|
|
239
|
+
) -> str:
|
|
240
|
+
"""
|
|
241
|
+
Compress skill-call messages using include/exclude rules per skill name.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
message: Raw message text containing markers like '=>#skillName:{...}'.
|
|
245
|
+
rules: Per-skill rules, e.g. {"_cog_think": {"include": ["action"]}}
|
|
246
|
+
marker_prefix: Prefix that denotes a skill-call marker.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Compressed message text.
|
|
250
|
+
"""
|
|
251
|
+
import json
|
|
252
|
+
import re
|
|
253
|
+
from dolphin.core.utils.tools import (
|
|
254
|
+
extract_json_from_response,
|
|
255
|
+
safe_json_loads,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
active_rules: Dict[str, Dict[str, List[str]]] = rules or {}
|
|
259
|
+
|
|
260
|
+
def apply_rule(skill_name: str, data: dict) -> tuple[dict, bool]:
|
|
261
|
+
"""Return (possibly_transformed_data, applied_flag)."""
|
|
262
|
+
rule = active_rules.get(skill_name) or active_rules.get("*")
|
|
263
|
+
if not rule:
|
|
264
|
+
return data, False
|
|
265
|
+
include_fields = (
|
|
266
|
+
rule.get("include") if isinstance(rule.get("include"), list) else []
|
|
267
|
+
)
|
|
268
|
+
exclude_fields = (
|
|
269
|
+
rule.get("exclude") if isinstance(rule.get("exclude"), list) else []
|
|
270
|
+
)
|
|
271
|
+
if include_fields:
|
|
272
|
+
return ({k: v for k, v in data.items() if k in include_fields}, True)
|
|
273
|
+
if exclude_fields:
|
|
274
|
+
return (
|
|
275
|
+
{k: v for k, v in data.items() if k not in exclude_fields},
|
|
276
|
+
True,
|
|
277
|
+
)
|
|
278
|
+
return data, False
|
|
279
|
+
|
|
280
|
+
# Regex to find markers and capture the skill name
|
|
281
|
+
pattern = re.compile(re.escape(marker_prefix) + r"([A-Za-z0-9_]+):")
|
|
282
|
+
|
|
283
|
+
idx = 0
|
|
284
|
+
out_parts: List[str] = []
|
|
285
|
+
for match in pattern.finditer(message):
|
|
286
|
+
start, end = match.start(), match.end()
|
|
287
|
+
skill_name = match.group(1)
|
|
288
|
+
# Append text before marker and the marker itself
|
|
289
|
+
out_parts.append(message[idx:start])
|
|
290
|
+
out_parts.append(message[start:end])
|
|
291
|
+
|
|
292
|
+
# Locate JSON object starting after ':' using shared util
|
|
293
|
+
brace_start = message.find("{", end)
|
|
294
|
+
if brace_start == -1:
|
|
295
|
+
idx = end
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
json_text = extract_json_from_response(message[brace_start:])
|
|
299
|
+
if not json_text or not json_text.startswith("{"):
|
|
300
|
+
# Not a proper JSON, keep raw char and move one step
|
|
301
|
+
out_parts.append(message[end : brace_start + 1])
|
|
302
|
+
idx = brace_start + 1
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
next_idx = brace_start + len(json_text)
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
data = safe_json_loads(json_text, strict=False)
|
|
309
|
+
if isinstance(data, dict):
|
|
310
|
+
transformed, applied = apply_rule(skill_name, data)
|
|
311
|
+
if applied:
|
|
312
|
+
out_parts.append(json.dumps(transformed, ensure_ascii=False))
|
|
313
|
+
else:
|
|
314
|
+
# No rule applied: keep original JSON text unchanged
|
|
315
|
+
out_parts.append(json_text)
|
|
316
|
+
else:
|
|
317
|
+
# Non-dict payloads: keep original
|
|
318
|
+
out_parts.append(json_text)
|
|
319
|
+
except Exception:
|
|
320
|
+
out_parts.append(json_text)
|
|
321
|
+
|
|
322
|
+
idx = next_idx
|
|
323
|
+
|
|
324
|
+
out_parts.append(message[idx:])
|
|
325
|
+
return "".join(out_parts)
|
|
326
|
+
|
|
327
|
+
def getSkillsDescs(self) -> dict[str, str]:
|
|
328
|
+
return {
|
|
329
|
+
skill.get_function_name(): skill.get_function_description()
|
|
330
|
+
for skill in self.getSkills()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
def getFormattedToolsDescription(self, format_type: str = "medium") -> str:
|
|
334
|
+
"""
|
|
335
|
+
Get formatted tools description for LLM prompts
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
format_type (str): Format type - "concise", "medium", or "detailed"
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
str: Formatted tools description
|
|
342
|
+
"""
|
|
343
|
+
skills = self.getSkills()
|
|
344
|
+
if not skills:
|
|
345
|
+
return "No tools available"
|
|
346
|
+
|
|
347
|
+
if format_type.lower() == "concise":
|
|
348
|
+
return self._formatToolsConcise(skills)
|
|
349
|
+
elif format_type.lower() == "medium":
|
|
350
|
+
return self._formatToolsMedium(skills)
|
|
351
|
+
elif format_type.lower() == "detailed":
|
|
352
|
+
return self._formatToolsDetailed(skills)
|
|
353
|
+
else:
|
|
354
|
+
return self._formatToolsMedium(skills) # Default to medium format
|
|
355
|
+
|
|
356
|
+
def _formatToolsConcise(self, skills: List[SkillFunction]) -> str:
|
|
357
|
+
"""
|
|
358
|
+
Format tools in concise style: toolName - brief description
|
|
359
|
+
"""
|
|
360
|
+
formatted_tools = []
|
|
361
|
+
for skill in skills:
|
|
362
|
+
name = skill.get_function_name()
|
|
363
|
+
desc = skill.get_function_description()
|
|
364
|
+
# Extract first sentence as brief description
|
|
365
|
+
brief_desc = desc.split(".")[0] if desc else "Tool function"
|
|
366
|
+
formatted_tools.append(f"- {name}: {brief_desc}")
|
|
367
|
+
|
|
368
|
+
return "\n".join(formatted_tools)
|
|
369
|
+
|
|
370
|
+
def _formatToolsMedium(self, skills: List[SkillFunction]) -> str:
|
|
371
|
+
"""
|
|
372
|
+
Format tools in medium style: toolName(key_params) - description + purpose
|
|
373
|
+
"""
|
|
374
|
+
formatted_tools = []
|
|
375
|
+
for skill in skills:
|
|
376
|
+
name = skill.get_function_name()
|
|
377
|
+
desc = skill.get_function_description()
|
|
378
|
+
|
|
379
|
+
# Extract key parameters from schema
|
|
380
|
+
key_params = self._extractKeyParameters(skill)
|
|
381
|
+
param_str = f"({key_params})" if key_params else ""
|
|
382
|
+
|
|
383
|
+
# Format: toolName(params) - description
|
|
384
|
+
formatted_tools.append(f"- {name}{param_str}: {desc}")
|
|
385
|
+
|
|
386
|
+
return "\n".join(formatted_tools)
|
|
387
|
+
|
|
388
|
+
def _formatToolsDetailed(self, skills: List[SkillFunction]) -> str:
|
|
389
|
+
"""
|
|
390
|
+
Format tools in detailed style: full schema with parameters and types
|
|
391
|
+
"""
|
|
392
|
+
formatted_tools = []
|
|
393
|
+
for skill in skills:
|
|
394
|
+
name = skill.get_function_name()
|
|
395
|
+
desc = skill.get_function_description()
|
|
396
|
+
|
|
397
|
+
# Get parameter details from schema
|
|
398
|
+
param_details = self._extractParameterDetails(skill)
|
|
399
|
+
|
|
400
|
+
tool_block = [f"**{name}**"]
|
|
401
|
+
tool_block.append(f" Description: {desc}")
|
|
402
|
+
|
|
403
|
+
if param_details:
|
|
404
|
+
tool_block.append(" Parameters:")
|
|
405
|
+
for param_info in param_details:
|
|
406
|
+
tool_block.append(f" - {param_info}")
|
|
407
|
+
else:
|
|
408
|
+
tool_block.append(" Parameters: None")
|
|
409
|
+
|
|
410
|
+
formatted_tools.append("\n".join(tool_block))
|
|
411
|
+
|
|
412
|
+
return "\n\n".join(formatted_tools)
|
|
413
|
+
|
|
414
|
+
def _extractKeyParameters(self, skill: SkillFunction) -> str:
|
|
415
|
+
"""
|
|
416
|
+
Extract key parameters from skill schema for medium format
|
|
417
|
+
"""
|
|
418
|
+
try:
|
|
419
|
+
schema = skill.get_openai_tool_schema()
|
|
420
|
+
if "function" in schema and "parameters" in schema["function"]:
|
|
421
|
+
params = schema["function"]["parameters"]
|
|
422
|
+
if "properties" in params:
|
|
423
|
+
param_names = list(params["properties"].keys())
|
|
424
|
+
# Show first 3 key parameters
|
|
425
|
+
if len(param_names) <= 3:
|
|
426
|
+
return ", ".join(param_names)
|
|
427
|
+
else:
|
|
428
|
+
return ", ".join(param_names[:3]) + ", ..."
|
|
429
|
+
return ""
|
|
430
|
+
except Exception:
|
|
431
|
+
return ""
|
|
432
|
+
|
|
433
|
+
def _extractParameterDetails(self, skill: SkillFunction) -> List[str]:
|
|
434
|
+
"""
|
|
435
|
+
Extract detailed parameter information for detailed format
|
|
436
|
+
"""
|
|
437
|
+
try:
|
|
438
|
+
schema = skill.get_openai_tool_schema()
|
|
439
|
+
if "function" in schema and "parameters" in schema["function"]:
|
|
440
|
+
params = schema["function"]["parameters"]
|
|
441
|
+
if "properties" in params:
|
|
442
|
+
param_details = []
|
|
443
|
+
properties = params["properties"]
|
|
444
|
+
required = params.get("required", [])
|
|
445
|
+
|
|
446
|
+
for param_name, param_info in properties.items():
|
|
447
|
+
param_type = param_info.get("type", "any")
|
|
448
|
+
param_desc = param_info.get("description", "")
|
|
449
|
+
is_required = (
|
|
450
|
+
" (required)" if param_name in required else " (optional)"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
param_line = f"{param_name} ({param_type}){is_required}"
|
|
454
|
+
if param_desc:
|
|
455
|
+
param_line += f": {param_desc}"
|
|
456
|
+
param_details.append(param_line)
|
|
457
|
+
|
|
458
|
+
return param_details
|
|
459
|
+
return []
|
|
460
|
+
except Exception:
|
|
461
|
+
return []
|
|
462
|
+
|
|
463
|
+
def getSessionId(
|
|
464
|
+
self,
|
|
465
|
+
session_id: Optional[str] = None,
|
|
466
|
+
props: Optional[Dict[str, Any]] = None,
|
|
467
|
+
**kwargs,
|
|
468
|
+
) -> str:
|
|
469
|
+
# First try to get session_id directly
|
|
470
|
+
if session_id:
|
|
471
|
+
return session_id
|
|
472
|
+
|
|
473
|
+
# Fallback to the original method via props/gvp
|
|
474
|
+
if props:
|
|
475
|
+
context = props.get("gvp")
|
|
476
|
+
if context and hasattr(context, "get_session_id"):
|
|
477
|
+
session_id = context.get_session_id()
|
|
478
|
+
return session_id
|
|
479
|
+
|
|
480
|
+
def get_metadata_prompt(self) -> str:
|
|
481
|
+
"""Return metadata prompt to inject into system prompt.
|
|
482
|
+
|
|
483
|
+
Subclasses can override this to provide fixed metadata content
|
|
484
|
+
that should be injected into the system prompt. This is useful
|
|
485
|
+
for resource/guidance type skillkits that need to expose
|
|
486
|
+
available resources to the LLM upfront.
|
|
487
|
+
|
|
488
|
+
By default, returns an empty string (no metadata injection).
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
Markdown string to append to system prompt, or empty string
|
|
492
|
+
if no metadata injection is needed.
|
|
493
|
+
"""
|
|
494
|
+
return ""
|
|
495
|
+
|
|
496
|
+
@staticmethod
|
|
497
|
+
def collect_metadata_from_skills(skillkit: "Skillkit") -> str:
|
|
498
|
+
"""Collect metadata prompts from a skillkit via skill.owner_skillkit.
|
|
499
|
+
|
|
500
|
+
This static method traverses all skills in the given skillkit and
|
|
501
|
+
collects metadata prompts from their owner skillkits. Only skillkits
|
|
502
|
+
that override get_metadata_prompt() (like ResourceSkillkit) will
|
|
503
|
+
return non-empty metadata.
|
|
504
|
+
|
|
505
|
+
This is the central utility for metadata collection, used by
|
|
506
|
+
ExploreStrategy and ExploreBlockV2 to inject metadata into system prompt.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
skillkit: The skillkit containing skills to inspect
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
Combined metadata prompts separated by double newlines,
|
|
513
|
+
or empty string if none
|
|
514
|
+
"""
|
|
515
|
+
if skillkit is None:
|
|
516
|
+
return ""
|
|
517
|
+
|
|
518
|
+
# Safely get skills list, handling various skillkit implementations
|
|
519
|
+
try:
|
|
520
|
+
skills = skillkit.getSkills() if hasattr(skillkit, 'getSkills') else []
|
|
521
|
+
if not skills:
|
|
522
|
+
return ""
|
|
523
|
+
except Exception:
|
|
524
|
+
return ""
|
|
525
|
+
|
|
526
|
+
seen_skillkit_ids = set()
|
|
527
|
+
prompts = []
|
|
528
|
+
|
|
529
|
+
for skill in skills:
|
|
530
|
+
owner = getattr(skill, 'owner_skillkit', None)
|
|
531
|
+
if owner is None:
|
|
532
|
+
continue
|
|
533
|
+
|
|
534
|
+
owner_id = id(owner)
|
|
535
|
+
if owner_id in seen_skillkit_ids:
|
|
536
|
+
continue
|
|
537
|
+
seen_skillkit_ids.add(owner_id)
|
|
538
|
+
|
|
539
|
+
if hasattr(owner, 'get_metadata_prompt'):
|
|
540
|
+
try:
|
|
541
|
+
prompt = owner.get_metadata_prompt()
|
|
542
|
+
if prompt:
|
|
543
|
+
prompts.append(prompt)
|
|
544
|
+
except Exception:
|
|
545
|
+
pass
|
|
546
|
+
|
|
547
|
+
return "\n\n".join(prompts)
|
|
548
|
+
|
|
549
|
+
def isEmpty(self) -> bool:
|
|
550
|
+
return len(self.getSkills()) == 0
|
|
551
|
+
|
|
552
|
+
def isQueryAsArg(self) -> bool:
|
|
553
|
+
return self.queryAsArg
|
|
554
|
+
|
|
555
|
+
def _logAndCreateRecord(
|
|
556
|
+
self, skillName: str, kwargs: dict, skill: SkillFunction, result: Any
|
|
557
|
+
) -> SkillExecRecord:
|
|
558
|
+
"""Log execution result and create SkillExecRecord"""
|
|
559
|
+
if result is None:
|
|
560
|
+
raise ValueError(f"funcCall func[{skillName}] result[{result}]")
|
|
561
|
+
|
|
562
|
+
logger.info(f"funcCall func[{skillName}] result[{str(result)[:MaxLenLog]}]")
|
|
563
|
+
return SkillExecRecord((skillName, kwargs), skill, result)
|
|
564
|
+
|
|
565
|
+
def exec(self, skillName: str, **kwargs) -> SkillExecRecord:
|
|
566
|
+
skill = self.getSkill(skillName)
|
|
567
|
+
if skill is None:
|
|
568
|
+
raise ValueError(f"skill[{skillName}] not found")
|
|
569
|
+
|
|
570
|
+
result = self.run(skill, **kwargs)
|
|
571
|
+
return self._logAndCreateRecord(skillName, kwargs, skill, result)
|
|
572
|
+
|
|
573
|
+
async def aexec(self, skillName: str, **kwargs) -> SkillExecRecord:
|
|
574
|
+
"""
|
|
575
|
+
Execute a skill by name (async version)
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
skillName: Name of the skill to execute
|
|
579
|
+
**kwargs: Arguments to pass to the skill
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
SkillExecRecord containing execution results
|
|
583
|
+
"""
|
|
584
|
+
skill = self.getSkill(skillName)
|
|
585
|
+
if skill is None:
|
|
586
|
+
raise ValueError(f"skill[{skillName}] not found")
|
|
587
|
+
|
|
588
|
+
# Execute the function directly for better performance
|
|
589
|
+
if not hasattr(skill, "func"):
|
|
590
|
+
raise ValueError(
|
|
591
|
+
f"Expected SkillFunction object with 'func' attribute, got {type(skill)}"
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
if inspect.iscoroutinefunction(skill.func):
|
|
595
|
+
result = await skill.func(**kwargs)
|
|
596
|
+
else:
|
|
597
|
+
result = skill.func(**kwargs)
|
|
598
|
+
|
|
599
|
+
return self._logAndCreateRecord(skillName, kwargs, skill, result)
|
|
600
|
+
|
|
601
|
+
@staticmethod
|
|
602
|
+
def run(func: SkillFunction, **kwargs):
|
|
603
|
+
# Check if the function is async and handle accordingly
|
|
604
|
+
if inspect.iscoroutinefunction(func.func):
|
|
605
|
+
# Handle async function in sync context
|
|
606
|
+
try:
|
|
607
|
+
# Try to get the current event loop
|
|
608
|
+
loop = asyncio.get_event_loop()
|
|
609
|
+
if loop.is_running():
|
|
610
|
+
# If we're already in an event loop, we need to handle this specially
|
|
611
|
+
# This is typically for MCP skills that need to run in event loop context
|
|
612
|
+
|
|
613
|
+
# Create a task and wait for it using Future
|
|
614
|
+
future = asyncio.Future()
|
|
615
|
+
|
|
616
|
+
def on_task_complete(task):
|
|
617
|
+
try:
|
|
618
|
+
if task.cancelled():
|
|
619
|
+
future.set_exception(asyncio.CancelledError())
|
|
620
|
+
elif task.exception():
|
|
621
|
+
future.set_exception(task.exception())
|
|
622
|
+
else:
|
|
623
|
+
future.set_result(task.result())
|
|
624
|
+
except Exception as e:
|
|
625
|
+
future.set_exception(e)
|
|
626
|
+
|
|
627
|
+
# Schedule the async function as a task
|
|
628
|
+
task = loop.create_task(func.func(**kwargs))
|
|
629
|
+
task.add_done_callback(on_task_complete)
|
|
630
|
+
|
|
631
|
+
# Wait for the task to complete using a polling approach
|
|
632
|
+
import time
|
|
633
|
+
|
|
634
|
+
timeout = 30 # 30 second timeout
|
|
635
|
+
start_time = time.time()
|
|
636
|
+
|
|
637
|
+
while not future.done():
|
|
638
|
+
if time.time() - start_time > timeout:
|
|
639
|
+
task.cancel()
|
|
640
|
+
raise asyncio.TimeoutError(
|
|
641
|
+
f"Async skill function timeout after {timeout}s"
|
|
642
|
+
)
|
|
643
|
+
time.sleep(0.01) # Small sleep to avoid busy waiting
|
|
644
|
+
|
|
645
|
+
# Get the result
|
|
646
|
+
if future.cancelled():
|
|
647
|
+
raise asyncio.CancelledError(
|
|
648
|
+
"Async skill function was cancelled"
|
|
649
|
+
)
|
|
650
|
+
elif future.exception():
|
|
651
|
+
raise future.exception() # type: ignore
|
|
652
|
+
else:
|
|
653
|
+
return future.result()
|
|
654
|
+
else:
|
|
655
|
+
return loop.run_until_complete(func.func(**kwargs))
|
|
656
|
+
except RuntimeError:
|
|
657
|
+
# No event loop, create a new one
|
|
658
|
+
return asyncio.run(func.func(**kwargs))
|
|
659
|
+
else:
|
|
660
|
+
# Handle sync function directly
|
|
661
|
+
return func.func(**kwargs)
|
|
662
|
+
|
|
663
|
+
@staticmethod
|
|
664
|
+
async def arun(skill: SkillFunction, skill_params: Optional[dict] = None, **kwargs):
|
|
665
|
+
"""
|
|
666
|
+
Execute a SkillFunction skill and yield results as an async generator
|
|
667
|
+
|
|
668
|
+
Args:
|
|
669
|
+
skill: SkillFunction object to execute
|
|
670
|
+
**kwargs: Arguments to pass to the function
|
|
671
|
+
|
|
672
|
+
Yields:
|
|
673
|
+
Execution results from the function
|
|
674
|
+
"""
|
|
675
|
+
if not hasattr(skill, "func"):
|
|
676
|
+
raise ValueError(
|
|
677
|
+
f"Expected SkillFunction object with 'func' attribute, got {type(skill)}"
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
if inspect.isasyncgenfunction(skill.func):
|
|
681
|
+
# For async generator functions, yield each result
|
|
682
|
+
# Merge parameter dictionaries to avoid duplicate keyword arguments
|
|
683
|
+
merged_params = {**skill_params} if skill_params else {}
|
|
684
|
+
merged_params.update(kwargs)
|
|
685
|
+
async for result in skill.func(**merged_params):
|
|
686
|
+
yield result
|
|
687
|
+
|
|
688
|
+
elif inspect.iscoroutinefunction(skill.func):
|
|
689
|
+
# For regular async functions, await and yield single result
|
|
690
|
+
# Merge parameter dictionaries to avoid duplicate keyword arguments
|
|
691
|
+
merged_params = {**skill_params} if skill_params else {}
|
|
692
|
+
merged_params.update(kwargs)
|
|
693
|
+
result = await skill.func(**merged_params)
|
|
694
|
+
yield result
|
|
695
|
+
else:
|
|
696
|
+
# Merge parameter dictionaries to avoid duplicate keyword arguments
|
|
697
|
+
merged_params = {**skill_params} if skill_params else {}
|
|
698
|
+
merged_params.update(kwargs)
|
|
699
|
+
result = skill.func(**merged_params)
|
|
700
|
+
yield result
|