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,207 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def escape(prompt: str):
|
|
5
|
+
# to prevent 'format' exception in get_template_vars
|
|
6
|
+
return prompt.replace("{", "{{").replace("}", "}}")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def unescape(prompt: str):
|
|
10
|
+
return re.sub(r"\{{2,}", "{", re.sub(r"\}{2,}", "}", prompt))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_nested_value(var_name, data: dict):
|
|
14
|
+
"""Recursively parse nested variable names, supporting a mix of array indexing and attribute access
|
|
15
|
+
For example: var[0].field[1].subfield, user['name'], header["x-user"]
|
|
16
|
+
"""
|
|
17
|
+
if not var_name:
|
|
18
|
+
return data
|
|
19
|
+
|
|
20
|
+
# If the variable name starts with a square bracket, process the content within the square brackets directly.
|
|
21
|
+
if var_name.startswith("["):
|
|
22
|
+
bracket_end = var_name.find("]")
|
|
23
|
+
if bracket_end == -1:
|
|
24
|
+
raise ValueError(f"变量名 '{var_name}' 中的方括号不匹配")
|
|
25
|
+
|
|
26
|
+
# Extract content within square brackets
|
|
27
|
+
bracket_content = var_name[1:bracket_end]
|
|
28
|
+
|
|
29
|
+
# Check if it's a string key (quoted)
|
|
30
|
+
if (bracket_content.startswith("'") and bracket_content.endswith("'")) or (
|
|
31
|
+
bracket_content.startswith('"') and bracket_content.endswith('"')
|
|
32
|
+
):
|
|
33
|
+
# String key, remove quotes
|
|
34
|
+
key_name = bracket_content[1:-1]
|
|
35
|
+
|
|
36
|
+
# Get dictionary value
|
|
37
|
+
if not isinstance(data, dict):
|
|
38
|
+
raise ValueError("数据不是字典类型,无法使用键访问")
|
|
39
|
+
|
|
40
|
+
if key_name not in data:
|
|
41
|
+
raise ValueError(f"键 '{key_name}' 不存在于数据中")
|
|
42
|
+
|
|
43
|
+
dict_value = data[key_name]
|
|
44
|
+
remaining = var_name[bracket_end + 1 :]
|
|
45
|
+
|
|
46
|
+
if not remaining:
|
|
47
|
+
return dict_value
|
|
48
|
+
elif remaining.startswith("."):
|
|
49
|
+
# Continue recursive processing
|
|
50
|
+
if not isinstance(dict_value, dict):
|
|
51
|
+
raise ValueError("字典值不是字典类型,无法使用点号访问")
|
|
52
|
+
return get_nested_value(remaining[1:], dict_value)
|
|
53
|
+
elif remaining.startswith("["):
|
|
54
|
+
# Continue recursive processing
|
|
55
|
+
return get_nested_value(remaining, dict_value)
|
|
56
|
+
else:
|
|
57
|
+
raise ValueError(f"变量名 '{var_name}' 格式错误,无法解析")
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
# Numeric Indexing (Array Access)
|
|
61
|
+
try:
|
|
62
|
+
index = int(bracket_content)
|
|
63
|
+
except ValueError:
|
|
64
|
+
# Index is not a number
|
|
65
|
+
raise ValueError(f"数组索引 '{bracket_content}' 不是有效的数字")
|
|
66
|
+
|
|
67
|
+
# Get array elements
|
|
68
|
+
if not isinstance(data, list):
|
|
69
|
+
raise ValueError("数据不是列表类型,无法使用索引访问")
|
|
70
|
+
|
|
71
|
+
if not (0 <= index < len(data)):
|
|
72
|
+
raise ValueError(f"数组索引 {index} 超出范围 [0, {len(data)})")
|
|
73
|
+
|
|
74
|
+
array_element = data[index]
|
|
75
|
+
remaining = var_name[bracket_end + 1 :]
|
|
76
|
+
|
|
77
|
+
if not remaining:
|
|
78
|
+
return array_element
|
|
79
|
+
elif remaining.startswith("."):
|
|
80
|
+
# Continue recursive processing
|
|
81
|
+
if not isinstance(array_element, dict):
|
|
82
|
+
raise ValueError("数组元素不是字典类型,无法使用点号访问")
|
|
83
|
+
return get_nested_value(remaining[1:], array_element)
|
|
84
|
+
elif remaining.startswith("["):
|
|
85
|
+
# Continue recursive processing
|
|
86
|
+
if not isinstance(array_element, list):
|
|
87
|
+
raise ValueError("数组元素不是列表类型,无法使用索引访问")
|
|
88
|
+
return get_nested_value(remaining, array_element)
|
|
89
|
+
else:
|
|
90
|
+
raise ValueError(f"变量名 '{var_name}' 格式错误,无法解析")
|
|
91
|
+
|
|
92
|
+
# Find the position of the first operator
|
|
93
|
+
dot_pos = var_name.find(".")
|
|
94
|
+
bracket_start = var_name.find("[")
|
|
95
|
+
|
|
96
|
+
# If there are neither dots nor square brackets, return directly.
|
|
97
|
+
if dot_pos == -1 and bracket_start == -1:
|
|
98
|
+
if var_name not in data:
|
|
99
|
+
raise ValueError(f"变量 '{var_name}' 不存在于数据中")
|
|
100
|
+
return data.get(var_name)
|
|
101
|
+
|
|
102
|
+
# Determine the first operator
|
|
103
|
+
first_op_pos = -1
|
|
104
|
+
if dot_pos == -1:
|
|
105
|
+
first_op_pos = bracket_start
|
|
106
|
+
elif bracket_start == -1:
|
|
107
|
+
first_op_pos = dot_pos
|
|
108
|
+
else:
|
|
109
|
+
first_op_pos = min(dot_pos, bracket_start)
|
|
110
|
+
|
|
111
|
+
# Extract the name of the key currently being accessed
|
|
112
|
+
current_key = var_name[:first_op_pos]
|
|
113
|
+
rest = var_name[first_op_pos:]
|
|
114
|
+
|
|
115
|
+
# Get current value
|
|
116
|
+
if current_key not in data:
|
|
117
|
+
raise ValueError(f"变量 '{current_key}' 不存在于数据中")
|
|
118
|
+
current_value = data.get(current_key)
|
|
119
|
+
|
|
120
|
+
# If there are no remaining parts, return the current value
|
|
121
|
+
if not rest:
|
|
122
|
+
return current_value
|
|
123
|
+
|
|
124
|
+
# Process the remaining part
|
|
125
|
+
if rest.startswith("."):
|
|
126
|
+
# Dot access, recursively process the remaining part
|
|
127
|
+
if not isinstance(current_value, dict):
|
|
128
|
+
raise ValueError(f"变量 '{current_key}' 不是字典类型,无法使用点号访问")
|
|
129
|
+
return get_nested_value(rest[1:], current_value)
|
|
130
|
+
elif rest.startswith("["):
|
|
131
|
+
# Bracket Access
|
|
132
|
+
bracket_end = rest.find("]")
|
|
133
|
+
if bracket_end == -1:
|
|
134
|
+
# No matching right parenthesis found
|
|
135
|
+
raise ValueError(f"变量名 '{var_name}' 中的方括号不匹配")
|
|
136
|
+
|
|
137
|
+
# Extract content within square brackets
|
|
138
|
+
bracket_content = rest[1:bracket_end]
|
|
139
|
+
|
|
140
|
+
# Check if it's a string key (quoted)
|
|
141
|
+
if (bracket_content.startswith("'") and bracket_content.endswith("'")) or (
|
|
142
|
+
bracket_content.startswith('"') and bracket_content.endswith('"')
|
|
143
|
+
):
|
|
144
|
+
# String key, remove quotes
|
|
145
|
+
key_name = bracket_content[1:-1]
|
|
146
|
+
|
|
147
|
+
# Get dictionary value
|
|
148
|
+
if not isinstance(current_value, dict):
|
|
149
|
+
raise ValueError(f"变量 '{current_key}' 不是字典类型,无法使用键访问")
|
|
150
|
+
|
|
151
|
+
if key_name not in current_value:
|
|
152
|
+
raise ValueError(f"键 '{key_name}' 不存在于字典 '{current_key}' 中")
|
|
153
|
+
|
|
154
|
+
dict_value = current_value[key_name]
|
|
155
|
+
remaining = rest[bracket_end + 1 :]
|
|
156
|
+
|
|
157
|
+
if not remaining:
|
|
158
|
+
return dict_value
|
|
159
|
+
elif remaining.startswith("."):
|
|
160
|
+
# Continue recursive processing
|
|
161
|
+
if not isinstance(dict_value, dict):
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"字典值 '{current_key}['{key_name}']' 不是字典类型,无法使用点号访问"
|
|
164
|
+
)
|
|
165
|
+
return get_nested_value(remaining[1:], dict_value)
|
|
166
|
+
elif remaining.startswith("["):
|
|
167
|
+
# Continue recursive processing
|
|
168
|
+
return get_nested_value(remaining, dict_value)
|
|
169
|
+
|
|
170
|
+
else:
|
|
171
|
+
# Numeric Indexing (Array Access)
|
|
172
|
+
try:
|
|
173
|
+
index = int(bracket_content)
|
|
174
|
+
except ValueError:
|
|
175
|
+
# Index is not a number
|
|
176
|
+
raise ValueError(f"数组索引 '{bracket_content}' 不是有效的数字")
|
|
177
|
+
|
|
178
|
+
# Get array elements
|
|
179
|
+
if not isinstance(current_value, list):
|
|
180
|
+
raise ValueError(f"变量 '{current_key}' 不是列表类型,无法使用索引访问")
|
|
181
|
+
|
|
182
|
+
if not (0 <= index < len(current_value)):
|
|
183
|
+
raise ValueError(f"数组索引 {index} 超出范围 [0, {len(current_value)})")
|
|
184
|
+
|
|
185
|
+
array_element = current_value[index]
|
|
186
|
+
remaining = rest[bracket_end + 1 :]
|
|
187
|
+
|
|
188
|
+
if not remaining:
|
|
189
|
+
return array_element
|
|
190
|
+
elif remaining.startswith("."):
|
|
191
|
+
# Continue recursive processing
|
|
192
|
+
if not isinstance(array_element, dict):
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"数组元素 '{current_key}[{index}]' 不是字典类型,无法使用点号访问"
|
|
195
|
+
)
|
|
196
|
+
return get_nested_value(remaining[1:], array_element)
|
|
197
|
+
elif remaining.startswith("["):
|
|
198
|
+
# Continue recursive processing
|
|
199
|
+
if not isinstance(array_element, list):
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f"数组元素 '{current_key}[{index}]' 不是列表类型,无法使用索引访问"
|
|
202
|
+
)
|
|
203
|
+
return get_nested_value(remaining, array_element)
|
|
204
|
+
else:
|
|
205
|
+
raise ValueError(f"变量名 '{var_name}' 格式错误,无法解析")
|
|
206
|
+
|
|
207
|
+
raise ValueError(f"变量名 '{var_name}' 格式错误,无法解析")
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
|
+
from dolphin.core.logging.logger import console
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Thread-safe global variables
|
|
10
|
+
class ProgressManager:
|
|
11
|
+
def __init__(self, max_size=1000, expire_seconds=3600):
|
|
12
|
+
self.progress_map: Dict[str, List[Dict]] = OrderedDict()
|
|
13
|
+
self.progress_set: Dict[str, Dict[str, bool]] = {}
|
|
14
|
+
self.lock = threading.RLock()
|
|
15
|
+
self.max_size = max_size
|
|
16
|
+
self.expire_seconds = expire_seconds
|
|
17
|
+
self.last_access: Dict[str, float] = {}
|
|
18
|
+
|
|
19
|
+
def _cleanup_expired(self):
|
|
20
|
+
"""Clean up expired progress data"""
|
|
21
|
+
current_time = time.time()
|
|
22
|
+
expired_keys = []
|
|
23
|
+
|
|
24
|
+
for key, last_time in self.last_access.items():
|
|
25
|
+
if current_time - last_time > self.expire_seconds:
|
|
26
|
+
expired_keys.append(key)
|
|
27
|
+
|
|
28
|
+
for key in expired_keys:
|
|
29
|
+
self._remove_key(key)
|
|
30
|
+
|
|
31
|
+
def _remove_key(self, key: str):
|
|
32
|
+
"""Remove data for the specified key"""
|
|
33
|
+
if key in self.progress_map:
|
|
34
|
+
del self.progress_map[key]
|
|
35
|
+
if key in self.progress_set:
|
|
36
|
+
del self.progress_set[key]
|
|
37
|
+
if key in self.last_access:
|
|
38
|
+
del self.last_access[key]
|
|
39
|
+
|
|
40
|
+
def _enforce_max_size(self):
|
|
41
|
+
"""Enforce maximum size limit"""
|
|
42
|
+
while len(self.progress_map) > self.max_size:
|
|
43
|
+
# Remove the oldest entry
|
|
44
|
+
oldest_key = next(iter(self.progress_map))
|
|
45
|
+
self._remove_key(oldest_key)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Global progress manager instance
|
|
49
|
+
_progress_manager = ProgressManager()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def handle_progress(assistant_message_id: str, progresses: List[Dict]) -> List[Dict]:
|
|
53
|
+
"""Thread-safe version for processing progress information
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
assistant_message_id: Assistant message ID
|
|
57
|
+
progresses: List of progress information (in dictionary format)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Processed list of progress information
|
|
61
|
+
"""
|
|
62
|
+
with _progress_manager.lock:
|
|
63
|
+
# Clean up expired data
|
|
64
|
+
_progress_manager._cleanup_expired()
|
|
65
|
+
|
|
66
|
+
# Update access time
|
|
67
|
+
_progress_manager.last_access[assistant_message_id] = time.time()
|
|
68
|
+
|
|
69
|
+
# Get the progress collection of the current message
|
|
70
|
+
progress_set_for_message = _progress_manager.progress_set.get(
|
|
71
|
+
assistant_message_id, {}
|
|
72
|
+
)
|
|
73
|
+
current_progress: Optional[Dict] = None
|
|
74
|
+
|
|
75
|
+
# Traverse all progress information
|
|
76
|
+
for progress in progresses:
|
|
77
|
+
status = progress.get("status", "")
|
|
78
|
+
|
|
79
|
+
if status in ["completed", "failed"]:
|
|
80
|
+
# Use more efficient hash values for deduplication
|
|
81
|
+
progress_hash = hash(json.dumps(progress, sort_keys=True))
|
|
82
|
+
|
|
83
|
+
# Check if already exists to avoid duplication
|
|
84
|
+
if progress_hash not in progress_set_for_message:
|
|
85
|
+
# Ensure that there is a corresponding list in progress_map.
|
|
86
|
+
if assistant_message_id not in _progress_manager.progress_map:
|
|
87
|
+
_progress_manager.progress_map[assistant_message_id] = []
|
|
88
|
+
|
|
89
|
+
# Add to history progress list
|
|
90
|
+
_progress_manager.progress_map[assistant_message_id].append(
|
|
91
|
+
progress
|
|
92
|
+
)
|
|
93
|
+
# Marked as existing
|
|
94
|
+
progress_set_for_message[progress_hash] = True
|
|
95
|
+
|
|
96
|
+
elif status == "processing":
|
|
97
|
+
# Record the current progress being processed
|
|
98
|
+
current_progress = progress
|
|
99
|
+
|
|
100
|
+
# Update progress_set
|
|
101
|
+
_progress_manager.progress_set[assistant_message_id] = progress_set_for_message
|
|
102
|
+
|
|
103
|
+
# Enforce size limits
|
|
104
|
+
_progress_manager._enforce_max_size()
|
|
105
|
+
|
|
106
|
+
# Build return result
|
|
107
|
+
result = []
|
|
108
|
+
|
|
109
|
+
# Add historical progress (completed and failed)
|
|
110
|
+
if assistant_message_id in _progress_manager.progress_map:
|
|
111
|
+
result.extend(_progress_manager.progress_map[assistant_message_id])
|
|
112
|
+
|
|
113
|
+
# Add the progress of the currently processed item (if any)
|
|
114
|
+
if current_progress is not None:
|
|
115
|
+
result.append(current_progress)
|
|
116
|
+
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cleanup_progress(assistant_message_id: str) -> None:
|
|
121
|
+
"""Thread-safe version to clear progress data for the specified message ID"""
|
|
122
|
+
with _progress_manager.lock:
|
|
123
|
+
_progress_manager._remove_key(assistant_message_id)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def initialize_progress(assistant_message_id: str) -> None:
|
|
127
|
+
"""Initialize progress data for the specified message ID (thread-safe version)"""
|
|
128
|
+
with _progress_manager.lock:
|
|
129
|
+
_progress_manager.progress_map[assistant_message_id] = []
|
|
130
|
+
_progress_manager.progress_set[assistant_message_id] = {}
|
|
131
|
+
_progress_manager.last_access[assistant_message_id] = time.time()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_progress_stats() -> Dict:
|
|
135
|
+
"""Get progress manager statistics (for monitoring)"""
|
|
136
|
+
with _progress_manager.lock:
|
|
137
|
+
return {
|
|
138
|
+
"total_sessions": len(_progress_manager.progress_map),
|
|
139
|
+
"total_progress_items": sum(
|
|
140
|
+
len(items) for items in _progress_manager.progress_map.values()
|
|
141
|
+
),
|
|
142
|
+
"max_size": _progress_manager.max_size,
|
|
143
|
+
"expire_seconds": _progress_manager.expire_seconds,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# Usage Examples
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
# Initialize
|
|
150
|
+
message_id = "msg_123"
|
|
151
|
+
initialize_progress(message_id)
|
|
152
|
+
|
|
153
|
+
# Example data
|
|
154
|
+
progresses = [
|
|
155
|
+
{
|
|
156
|
+
"agent_name": "agent1",
|
|
157
|
+
"stage": "thinking",
|
|
158
|
+
"status": "completed",
|
|
159
|
+
"answer": "Thinking completed",
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"agent_name": "agent1",
|
|
163
|
+
"stage": "executing",
|
|
164
|
+
"status": "processing",
|
|
165
|
+
"answer": "正在执行",
|
|
166
|
+
},
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
# Processing Progress
|
|
170
|
+
result = handle_progress(message_id, progresses)
|
|
171
|
+
console(f"处理结果: {len(result)} 个进度")
|
|
172
|
+
|
|
173
|
+
# Cleanup
|
|
174
|
+
cleanup_progress(message_id)
|
|
175
|
+
|
|
176
|
+
# Get statistical information
|
|
177
|
+
stats = get_progress_stats()
|
|
178
|
+
console(f"统计信息: {stats}")
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from dolphin.core.logging.logger import get_logger
|
|
5
|
+
|
|
6
|
+
logger = get_logger()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SecurityUtils:
|
|
10
|
+
"""Security utility class, providing encryption and decryption functions"""
|
|
11
|
+
|
|
12
|
+
# Default salt value; in actual applications, it should be obtained from environment variables or configurations.
|
|
13
|
+
DEFAULT_SALT = b"dolphin_default_salt_value"
|
|
14
|
+
|
|
15
|
+
# 缓存 cryptography 模块(延迟导入)
|
|
16
|
+
_cryptography = None
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def _get_cryptography(cls):
|
|
20
|
+
"""获取 cryptography 模块(延迟导入)"""
|
|
21
|
+
if cls._cryptography is None:
|
|
22
|
+
try:
|
|
23
|
+
from cryptography.fernet import Fernet
|
|
24
|
+
from cryptography.hazmat.backends import default_backend
|
|
25
|
+
from cryptography.hazmat.primitives import hashes
|
|
26
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
27
|
+
cls._cryptography = {
|
|
28
|
+
'Fernet': Fernet,
|
|
29
|
+
'default_backend': default_backend,
|
|
30
|
+
'hashes': hashes,
|
|
31
|
+
'PBKDF2HMAC': PBKDF2HMAC,
|
|
32
|
+
}
|
|
33
|
+
except ImportError:
|
|
34
|
+
logger.error("cryptography is required for SecurityUtils but not installed. Please install it: pip install cryptography")
|
|
35
|
+
raise ImportError("cryptography is required for SecurityUtils but not installed. Please install it: pip install cryptography")
|
|
36
|
+
return cls._cryptography
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _get_key(password: str, salt: Optional[bytes] = None) -> bytes:
|
|
40
|
+
"""Generate encryption key
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
password: The password used to generate the key
|
|
44
|
+
salt: Optional salt value; if not provided, a default salt value is used
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
bytes: The generated key
|
|
48
|
+
"""
|
|
49
|
+
crypto = SecurityUtils._get_cryptography()
|
|
50
|
+
PBKDF2HMAC = crypto['PBKDF2HMAC']
|
|
51
|
+
hashes = crypto['hashes']
|
|
52
|
+
default_backend = crypto['default_backend']
|
|
53
|
+
|
|
54
|
+
if not salt:
|
|
55
|
+
salt = SecurityUtils.DEFAULT_SALT
|
|
56
|
+
|
|
57
|
+
kdf = PBKDF2HMAC(
|
|
58
|
+
algorithm=hashes.SHA256(),
|
|
59
|
+
length=32,
|
|
60
|
+
salt=salt,
|
|
61
|
+
iterations=100000,
|
|
62
|
+
backend=default_backend(),
|
|
63
|
+
)
|
|
64
|
+
return base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def encrypt(text: str, password: str, salt: Optional[bytes] = None) -> str:
|
|
68
|
+
"""Encrypted text
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
text: The text to be encrypted
|
|
72
|
+
password: The password used to generate the key
|
|
73
|
+
salt: Optional salt value
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
str: The encrypted text (Base64 encoded)
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
crypto = SecurityUtils._get_cryptography()
|
|
80
|
+
Fernet = crypto['Fernet']
|
|
81
|
+
key = SecurityUtils._get_key(password, salt)
|
|
82
|
+
f = Fernet(key)
|
|
83
|
+
encrypted_data = f.encrypt(text.encode())
|
|
84
|
+
return base64.urlsafe_b64encode(encrypted_data).decode()
|
|
85
|
+
except ImportError:
|
|
86
|
+
raise
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(f"加密failed: {str(e)}")
|
|
89
|
+
raise RuntimeError(f"加密failed: {str(e)}")
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def decrypt(
|
|
93
|
+
encrypted_text: str, password: str, salt: Optional[bytes] = None
|
|
94
|
+
) -> str:
|
|
95
|
+
"""Decrypt text
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
encrypted_text: The encrypted text (Base64 encoded)
|
|
99
|
+
password: The password used to generate the key
|
|
100
|
+
salt: Optional salt value
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
str: The decrypted text
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
crypto = SecurityUtils._get_cryptography()
|
|
107
|
+
Fernet = crypto['Fernet']
|
|
108
|
+
key = SecurityUtils._get_key(password, salt)
|
|
109
|
+
f = Fernet(key)
|
|
110
|
+
# First, perform Base64 decoding on the encrypted text.
|
|
111
|
+
decoded_data = base64.urlsafe_b64decode(encrypted_text.encode())
|
|
112
|
+
decrypted_data = f.decrypt(decoded_data)
|
|
113
|
+
return decrypted_data.decode()
|
|
114
|
+
except ImportError:
|
|
115
|
+
raise
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"解密failed: {str(e)}")
|
|
118
|
+
return encrypted_text # Return the original encrypted text when decryption fails
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def generate_key() -> str:
|
|
122
|
+
"""Generate a new Fernet key
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
str: The generated key (Base64-encoded string)
|
|
126
|
+
"""
|
|
127
|
+
crypto = SecurityUtils._get_cryptography()
|
|
128
|
+
Fernet = crypto['Fernet']
|
|
129
|
+
key = Fernet.generate_key()
|
|
130
|
+
return key.decode()
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def get_env_password() -> str:
|
|
134
|
+
"""Get password from environment variables
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
str: Password obtained from environment variables, or default value if not set
|
|
138
|
+
"""
|
|
139
|
+
return os.environ.get("DOLPHIN_PASSWORD", "default_password_please_change_me")
|