kweaver-dolphin 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. DolphinLanguageSDK/__init__.py +58 -0
  2. dolphin/__init__.py +62 -0
  3. dolphin/cli/__init__.py +20 -0
  4. dolphin/cli/args/__init__.py +9 -0
  5. dolphin/cli/args/parser.py +567 -0
  6. dolphin/cli/builtin_agents/__init__.py +22 -0
  7. dolphin/cli/commands/__init__.py +4 -0
  8. dolphin/cli/interrupt/__init__.py +8 -0
  9. dolphin/cli/interrupt/handler.py +205 -0
  10. dolphin/cli/interrupt/keyboard.py +82 -0
  11. dolphin/cli/main.py +49 -0
  12. dolphin/cli/multimodal/__init__.py +34 -0
  13. dolphin/cli/multimodal/clipboard.py +327 -0
  14. dolphin/cli/multimodal/handler.py +249 -0
  15. dolphin/cli/multimodal/image_processor.py +214 -0
  16. dolphin/cli/multimodal/input_parser.py +149 -0
  17. dolphin/cli/runner/__init__.py +8 -0
  18. dolphin/cli/runner/runner.py +989 -0
  19. dolphin/cli/ui/__init__.py +10 -0
  20. dolphin/cli/ui/console.py +2795 -0
  21. dolphin/cli/ui/input.py +340 -0
  22. dolphin/cli/ui/layout.py +425 -0
  23. dolphin/cli/ui/stream_renderer.py +302 -0
  24. dolphin/cli/utils/__init__.py +8 -0
  25. dolphin/cli/utils/helpers.py +135 -0
  26. dolphin/cli/utils/version.py +49 -0
  27. dolphin/core/__init__.py +107 -0
  28. dolphin/core/agent/__init__.py +10 -0
  29. dolphin/core/agent/agent_state.py +69 -0
  30. dolphin/core/agent/base_agent.py +970 -0
  31. dolphin/core/code_block/__init__.py +0 -0
  32. dolphin/core/code_block/agent_init_block.py +0 -0
  33. dolphin/core/code_block/assign_block.py +98 -0
  34. dolphin/core/code_block/basic_code_block.py +1865 -0
  35. dolphin/core/code_block/explore_block.py +1327 -0
  36. dolphin/core/code_block/explore_block_v2.py +712 -0
  37. dolphin/core/code_block/explore_strategy.py +672 -0
  38. dolphin/core/code_block/judge_block.py +220 -0
  39. dolphin/core/code_block/prompt_block.py +32 -0
  40. dolphin/core/code_block/skill_call_deduplicator.py +291 -0
  41. dolphin/core/code_block/tool_block.py +129 -0
  42. dolphin/core/common/__init__.py +17 -0
  43. dolphin/core/common/constants.py +176 -0
  44. dolphin/core/common/enums.py +1173 -0
  45. dolphin/core/common/exceptions.py +133 -0
  46. dolphin/core/common/multimodal.py +539 -0
  47. dolphin/core/common/object_type.py +165 -0
  48. dolphin/core/common/output_format.py +432 -0
  49. dolphin/core/common/types.py +36 -0
  50. dolphin/core/config/__init__.py +16 -0
  51. dolphin/core/config/global_config.py +1289 -0
  52. dolphin/core/config/ontology_config.py +133 -0
  53. dolphin/core/context/__init__.py +12 -0
  54. dolphin/core/context/context.py +1580 -0
  55. dolphin/core/context/context_manager.py +161 -0
  56. dolphin/core/context/var_output.py +82 -0
  57. dolphin/core/context/variable_pool.py +356 -0
  58. dolphin/core/context_engineer/__init__.py +41 -0
  59. dolphin/core/context_engineer/config/__init__.py +5 -0
  60. dolphin/core/context_engineer/config/settings.py +402 -0
  61. dolphin/core/context_engineer/core/__init__.py +7 -0
  62. dolphin/core/context_engineer/core/budget_manager.py +327 -0
  63. dolphin/core/context_engineer/core/context_assembler.py +583 -0
  64. dolphin/core/context_engineer/core/context_manager.py +637 -0
  65. dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
  66. dolphin/core/context_engineer/example/incremental_example.py +267 -0
  67. dolphin/core/context_engineer/example/traditional_example.py +334 -0
  68. dolphin/core/context_engineer/services/__init__.py +5 -0
  69. dolphin/core/context_engineer/services/compressor.py +399 -0
  70. dolphin/core/context_engineer/utils/__init__.py +6 -0
  71. dolphin/core/context_engineer/utils/context_utils.py +441 -0
  72. dolphin/core/context_engineer/utils/message_formatter.py +270 -0
  73. dolphin/core/context_engineer/utils/token_utils.py +139 -0
  74. dolphin/core/coroutine/__init__.py +15 -0
  75. dolphin/core/coroutine/context_snapshot.py +154 -0
  76. dolphin/core/coroutine/context_snapshot_profile.py +922 -0
  77. dolphin/core/coroutine/context_snapshot_store.py +268 -0
  78. dolphin/core/coroutine/execution_frame.py +145 -0
  79. dolphin/core/coroutine/execution_state_registry.py +161 -0
  80. dolphin/core/coroutine/resume_handle.py +101 -0
  81. dolphin/core/coroutine/step_result.py +101 -0
  82. dolphin/core/executor/__init__.py +18 -0
  83. dolphin/core/executor/debug_controller.py +630 -0
  84. dolphin/core/executor/dolphin_executor.py +1063 -0
  85. dolphin/core/executor/executor.py +624 -0
  86. dolphin/core/flags/__init__.py +27 -0
  87. dolphin/core/flags/definitions.py +49 -0
  88. dolphin/core/flags/manager.py +113 -0
  89. dolphin/core/hook/__init__.py +95 -0
  90. dolphin/core/hook/expression_evaluator.py +499 -0
  91. dolphin/core/hook/hook_dispatcher.py +380 -0
  92. dolphin/core/hook/hook_types.py +248 -0
  93. dolphin/core/hook/isolated_variable_pool.py +284 -0
  94. dolphin/core/interfaces.py +53 -0
  95. dolphin/core/llm/__init__.py +0 -0
  96. dolphin/core/llm/llm.py +495 -0
  97. dolphin/core/llm/llm_call.py +100 -0
  98. dolphin/core/llm/llm_client.py +1285 -0
  99. dolphin/core/llm/message_sanitizer.py +120 -0
  100. dolphin/core/logging/__init__.py +20 -0
  101. dolphin/core/logging/logger.py +526 -0
  102. dolphin/core/message/__init__.py +8 -0
  103. dolphin/core/message/compressor.py +749 -0
  104. dolphin/core/parser/__init__.py +8 -0
  105. dolphin/core/parser/parser.py +405 -0
  106. dolphin/core/runtime/__init__.py +10 -0
  107. dolphin/core/runtime/runtime_graph.py +926 -0
  108. dolphin/core/runtime/runtime_instance.py +446 -0
  109. dolphin/core/skill/__init__.py +14 -0
  110. dolphin/core/skill/context_retention.py +157 -0
  111. dolphin/core/skill/skill_function.py +686 -0
  112. dolphin/core/skill/skill_matcher.py +282 -0
  113. dolphin/core/skill/skillkit.py +700 -0
  114. dolphin/core/skill/skillset.py +72 -0
  115. dolphin/core/trajectory/__init__.py +10 -0
  116. dolphin/core/trajectory/recorder.py +189 -0
  117. dolphin/core/trajectory/trajectory.py +522 -0
  118. dolphin/core/utils/__init__.py +9 -0
  119. dolphin/core/utils/cache_kv.py +212 -0
  120. dolphin/core/utils/tools.py +340 -0
  121. dolphin/lib/__init__.py +93 -0
  122. dolphin/lib/debug/__init__.py +8 -0
  123. dolphin/lib/debug/visualizer.py +409 -0
  124. dolphin/lib/memory/__init__.py +28 -0
  125. dolphin/lib/memory/async_processor.py +220 -0
  126. dolphin/lib/memory/llm_calls.py +195 -0
  127. dolphin/lib/memory/manager.py +78 -0
  128. dolphin/lib/memory/sandbox.py +46 -0
  129. dolphin/lib/memory/storage.py +245 -0
  130. dolphin/lib/memory/utils.py +51 -0
  131. dolphin/lib/ontology/__init__.py +12 -0
  132. dolphin/lib/ontology/basic/__init__.py +0 -0
  133. dolphin/lib/ontology/basic/base.py +102 -0
  134. dolphin/lib/ontology/basic/concept.py +130 -0
  135. dolphin/lib/ontology/basic/object.py +11 -0
  136. dolphin/lib/ontology/basic/relation.py +63 -0
  137. dolphin/lib/ontology/datasource/__init__.py +27 -0
  138. dolphin/lib/ontology/datasource/datasource.py +66 -0
  139. dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
  140. dolphin/lib/ontology/datasource/sql.py +845 -0
  141. dolphin/lib/ontology/mapping.py +177 -0
  142. dolphin/lib/ontology/ontology.py +733 -0
  143. dolphin/lib/ontology/ontology_context.py +16 -0
  144. dolphin/lib/ontology/ontology_manager.py +107 -0
  145. dolphin/lib/skill_results/__init__.py +31 -0
  146. dolphin/lib/skill_results/cache_backend.py +559 -0
  147. dolphin/lib/skill_results/result_processor.py +181 -0
  148. dolphin/lib/skill_results/result_reference.py +179 -0
  149. dolphin/lib/skill_results/skillkit_hook.py +324 -0
  150. dolphin/lib/skill_results/strategies.py +328 -0
  151. dolphin/lib/skill_results/strategy_registry.py +150 -0
  152. dolphin/lib/skillkits/__init__.py +44 -0
  153. dolphin/lib/skillkits/agent_skillkit.py +155 -0
  154. dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
  155. dolphin/lib/skillkits/env_skillkit.py +250 -0
  156. dolphin/lib/skillkits/mcp_adapter.py +616 -0
  157. dolphin/lib/skillkits/mcp_skillkit.py +771 -0
  158. dolphin/lib/skillkits/memory_skillkit.py +650 -0
  159. dolphin/lib/skillkits/noop_skillkit.py +31 -0
  160. dolphin/lib/skillkits/ontology_skillkit.py +89 -0
  161. dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
  162. dolphin/lib/skillkits/resource/__init__.py +52 -0
  163. dolphin/lib/skillkits/resource/models/__init__.py +6 -0
  164. dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
  165. dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
  166. dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
  167. dolphin/lib/skillkits/resource/skill_cache.py +215 -0
  168. dolphin/lib/skillkits/resource/skill_loader.py +395 -0
  169. dolphin/lib/skillkits/resource/skill_validator.py +406 -0
  170. dolphin/lib/skillkits/resource_skillkit.py +11 -0
  171. dolphin/lib/skillkits/search_skillkit.py +163 -0
  172. dolphin/lib/skillkits/sql_skillkit.py +274 -0
  173. dolphin/lib/skillkits/system_skillkit.py +509 -0
  174. dolphin/lib/skillkits/vm_skillkit.py +65 -0
  175. dolphin/lib/utils/__init__.py +9 -0
  176. dolphin/lib/utils/data_process.py +207 -0
  177. dolphin/lib/utils/handle_progress.py +178 -0
  178. dolphin/lib/utils/security.py +139 -0
  179. dolphin/lib/utils/text_retrieval.py +462 -0
  180. dolphin/lib/vm/__init__.py +11 -0
  181. dolphin/lib/vm/env_executor.py +895 -0
  182. dolphin/lib/vm/python_session_manager.py +453 -0
  183. dolphin/lib/vm/vm.py +610 -0
  184. dolphin/sdk/__init__.py +60 -0
  185. dolphin/sdk/agent/__init__.py +12 -0
  186. dolphin/sdk/agent/agent_factory.py +236 -0
  187. dolphin/sdk/agent/dolphin_agent.py +1106 -0
  188. dolphin/sdk/api/__init__.py +4 -0
  189. dolphin/sdk/runtime/__init__.py +8 -0
  190. dolphin/sdk/runtime/env.py +363 -0
  191. dolphin/sdk/skill/__init__.py +10 -0
  192. dolphin/sdk/skill/global_skills.py +706 -0
  193. dolphin/sdk/skill/traditional_toolkit.py +260 -0
  194. kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
  195. kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
  196. kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
  197. kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
  198. kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
  199. kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1289 @@
1
+ from enum import Enum
2
+ import json
3
+ import os
4
+ import re
5
+ from typing import Optional, Dict, Any, List
6
+
7
+ from dolphin.core.context_engineer.config.settings import ContextConfig
8
+ import yaml
9
+
10
+ from dolphin.core.common.enums import Messages
11
+ from dolphin.core.config.ontology_config import OntologyConfig
12
+ from dolphin.core.utils.cache_kv import GlobalCacheKVCenter
13
+
14
+
15
+ def _resolve_env_var(value: str) -> str:
16
+ """Resolve environment variable references in string values.
17
+
18
+ Supports format: ${VAR_NAME}
19
+ If the environment variable is not found, raises ValueError (fail fast).
20
+
21
+ Args:
22
+ value: String value that may contain environment variable references
23
+
24
+ Returns:
25
+ Resolved string with environment variables substituted
26
+
27
+ Raises:
28
+ ValueError: If referenced environment variable is not found
29
+ """
30
+ if not isinstance(value, str):
31
+ return value
32
+
33
+ # If value doesn't contain ${}, return as-is
34
+ if '${' not in value:
35
+ return value
36
+
37
+ # Pattern to match ${VAR_NAME} format
38
+ pattern = r'\$\{([^}]+)\}'
39
+
40
+ def replace_env_var(match):
41
+ var_name = match.group(1)
42
+ env_value = os.getenv(var_name)
43
+ if env_value is None:
44
+ raise ValueError(
45
+ f"Environment variable '{var_name}' not found. "
46
+ f"Please set it before running the application."
47
+ )
48
+ return env_value
49
+
50
+ result = re.sub(pattern, replace_env_var, value)
51
+ return result
52
+
53
+
54
+ class TypeAPI(Enum):
55
+ OPENAI = "openai"
56
+ AISHU_MODEL_FACTORY = "aishu_model_factory"
57
+
58
+ @staticmethod
59
+ def from_str(type_api_str: str) -> "TypeAPI":
60
+ if type_api_str == TypeAPI.AISHU_MODEL_FACTORY.value:
61
+ return TypeAPI.AISHU_MODEL_FACTORY
62
+ elif type_api_str == TypeAPI.OPENAI.value:
63
+ return TypeAPI.OPENAI
64
+ else:
65
+ raise ValueError(f"不支持的API类型: {type_api_str}")
66
+
67
+
68
+ class CloudConfig:
69
+ def __init__(
70
+ self,
71
+ api: str = None,
72
+ api_key: str = None,
73
+ user_id: str = None,
74
+ headers: dict = None,
75
+ ):
76
+ self.api = api
77
+ self.api_key = api_key
78
+ self.user_id = user_id
79
+ self.headers = headers or {}
80
+
81
+ @staticmethod
82
+ def from_dict(config_dict) -> "CloudConfig":
83
+ api = _resolve_env_var(config_dict.get("api")) if config_dict.get("api") else None
84
+ api_key = _resolve_env_var(config_dict.get("api_key")) if config_dict.get("api_key") else None
85
+ user_id = None
86
+
87
+ if "userid" in config_dict:
88
+ user_id = _resolve_env_var(config_dict.get("userid"))
89
+ headers = {"x-user-id": user_id}
90
+ else:
91
+ user_id = _resolve_env_var(
92
+ config_dict.get("headers", {}).get(
93
+ "x-user-id", "default-x-user-id"
94
+ )
95
+ )
96
+ headers = {"x-user-id": user_id}
97
+
98
+ if "security_token" in config_dict:
99
+ headers["security-token"] = _resolve_env_var(config_dict.get("security_token"))
100
+
101
+ # Merge the headers field in the configuration file
102
+ if "headers" in config_dict:
103
+ # Resolve environment variables in headers values
104
+ resolved_headers = {
105
+ k: _resolve_env_var(v) if isinstance(v, str) else v
106
+ for k, v in config_dict["headers"].items()
107
+ }
108
+ headers.update(resolved_headers)
109
+
110
+ headers["Authorization"] = f"Bearer {api_key}"
111
+ headers["Content-Type"] = "application/json"
112
+ return CloudConfig(api=api, api_key=api_key, user_id=user_id, headers=headers)
113
+
114
+ def to_dict(self) -> dict:
115
+ """Convert to dictionary"""
116
+ return {
117
+ "api": self.api,
118
+ "api_key": self.api_key,
119
+ "user_id": self.user_id,
120
+ "headers": self.headers,
121
+ }
122
+
123
+
124
+ class AllCloudsConfig:
125
+ def __init__(self, default_cloud: str, clouds: dict):
126
+ self.default_cloud = default_cloud
127
+ self.clouds = clouds
128
+
129
+ def get_cloud_config(self, cloud_name: Optional[str]) -> CloudConfig:
130
+ if cloud_name in self.clouds:
131
+ return self.clouds[cloud_name]
132
+ elif cloud_name is None:
133
+ return self.clouds[self.default_cloud]
134
+ else:
135
+ raise ValueError(f"cloud_name {cloud_name} not found in clouds")
136
+
137
+ @staticmethod
138
+ def from_dict(config_dict: dict) -> "AllCloudsConfig":
139
+ clouds = {}
140
+ default_cloud = config_dict.get("default")
141
+ for cloud_name, cloud_config in config_dict.items():
142
+ if cloud_name == "default":
143
+ continue
144
+
145
+ if cloud_name not in clouds:
146
+ clouds[cloud_name] = CloudConfig.from_dict(cloud_config)
147
+ else:
148
+ raise ValueError(f"cloud_name {cloud_name} already exists in clouds")
149
+ return AllCloudsConfig(default_cloud, clouds)
150
+
151
+
152
+ class LLMConfig:
153
+ def __init__(
154
+ self,
155
+ name: str,
156
+ temperature: float,
157
+ top_p: float,
158
+ top_k: int,
159
+ frequency_penalty: int,
160
+ presence_penalty: int,
161
+ max_tokens: int,
162
+ icon: str,
163
+ model_name: str,
164
+ type_api: TypeAPI,
165
+ api: str,
166
+ userid: str,
167
+ headers: dict,
168
+ security_token: str,
169
+ id: str,
170
+ ):
171
+ self.name = name
172
+ self.temperature = temperature
173
+ self.top_p = top_p
174
+ self.top_k = top_k
175
+ self.frequency_penalty = frequency_penalty
176
+ self.presence_penalty = presence_penalty
177
+ self.max_tokens = max_tokens
178
+ self.icon = icon
179
+ self.model_name = model_name
180
+ self.type_api = type_api
181
+ self.api = api
182
+ self.userid = userid
183
+ self.headers = headers or {}
184
+ self.security_token = security_token
185
+ self.id = id
186
+
187
+ @staticmethod
188
+ def from_dict(llm_name, config_dict) -> "LLMConfig":
189
+ temperature = config_dict.get("temperature", 0)
190
+ top_p = config_dict.get("top_p", 0.95)
191
+ top_k = config_dict.get("top_k", 1)
192
+ frequency_penalty = config_dict.get("frequency_penalty", 0)
193
+ presence_penalty = config_dict.get("presence_penalty", 0)
194
+ max_tokens = config_dict.get("max_tokens", 8192)
195
+ icon = config_dict.get("icon", "")
196
+ model_name = config_dict.get("model_name")
197
+ type_api = TypeAPI.from_str(
198
+ config_dict.get("type_api", TypeAPI.AISHU_MODEL_FACTORY.value)
199
+ )
200
+ api = _resolve_env_var(config_dict.get("api")) if config_dict.get("api") else None
201
+ userid = _resolve_env_var(config_dict.get("userid")) if config_dict.get("userid") else None
202
+ # Resolve environment variables in headers values
203
+ headers_raw = config_dict.get("headers", {})
204
+ headers = {
205
+ k: _resolve_env_var(v) if isinstance(v, str) else v
206
+ for k, v in headers_raw.items()
207
+ } if headers_raw else {}
208
+ security_token = _resolve_env_var(config_dict.get("security_token")) if config_dict.get("security_token") else None
209
+ id_value = config_dict.get("id")
210
+ return LLMConfig(
211
+ name=llm_name,
212
+ temperature=temperature,
213
+ top_p=top_p,
214
+ top_k=top_k,
215
+ frequency_penalty=frequency_penalty,
216
+ presence_penalty=presence_penalty,
217
+ max_tokens=max_tokens,
218
+ icon=icon,
219
+ model_name=model_name,
220
+ type_api=type_api,
221
+ api=api,
222
+ userid=userid,
223
+ headers=headers,
224
+ security_token=security_token,
225
+ id=id_value,
226
+ )
227
+
228
+ def to_dict(self) -> dict:
229
+ return {
230
+ "llm_name": self.name,
231
+ "model_name": self.model_name,
232
+ "type_api": self.type_api,
233
+ "api": self.api,
234
+ "userid": self.userid,
235
+ "headers": self.headers,
236
+ "security_token": self.security_token,
237
+ "id": self.id,
238
+ "icon": self.icon,
239
+ "temperature": self.temperature,
240
+ "max_tokens": self.max_tokens,
241
+ }
242
+
243
+
244
+ class LLMInstanceConfig:
245
+ def __init__(self, llm_name: str, cloud_config: CloudConfig, llm_config: LLMConfig):
246
+ self.llm_name = llm_name
247
+ self.cloud_config = cloud_config
248
+ self.llm_config = llm_config
249
+
250
+ # LLMConfig Attribute Accessor
251
+ @property
252
+ def name(self) -> str:
253
+ return self.llm_config.name
254
+
255
+ @property
256
+ def temperature(self) -> float:
257
+ return self.llm_config.temperature
258
+
259
+ @property
260
+ def top_p(self) -> float:
261
+ return self.llm_config.top_p
262
+
263
+ @property
264
+ def top_k(self) -> int:
265
+ return self.llm_config.top_k
266
+
267
+ @property
268
+ def frequency_penalty(self) -> int:
269
+ return self.llm_config.frequency_penalty
270
+
271
+ @property
272
+ def presence_penalty(self) -> int:
273
+ return self.llm_config.presence_penalty
274
+
275
+ @property
276
+ def max_tokens(self) -> int:
277
+ return self.llm_config.max_tokens
278
+
279
+ @property
280
+ def icon(self) -> str:
281
+ return self.llm_config.icon
282
+
283
+ @property
284
+ def model_name(self) -> str:
285
+ return self.llm_config.model_name
286
+
287
+ @property
288
+ def llm_api(self) -> str:
289
+ return self.llm_config.api
290
+
291
+ @property
292
+ def userid(self) -> str:
293
+ return self.llm_config.userid
294
+
295
+ @property
296
+ def llm_headers(self) -> dict:
297
+ return self.llm_config.headers
298
+
299
+ @property
300
+ def security_token(self) -> str:
301
+ return self.llm_config.security_token
302
+
303
+ @property
304
+ def id(self) -> str:
305
+ return self.llm_config.id
306
+
307
+ @property
308
+ def type_api(self) -> TypeAPI:
309
+ return self.llm_config.type_api
310
+
311
+ # CloudConfig Attribute Accessor
312
+ @property
313
+ def api(self) -> str:
314
+ return self.cloud_config.api
315
+
316
+ @property
317
+ def api_key(self) -> str:
318
+ return self.cloud_config.api_key
319
+
320
+ def set_api_key(self, api_key: str):
321
+ self.cloud_config.api_key = api_key
322
+
323
+ @property
324
+ def user_id(self) -> str:
325
+ return self.cloud_config.user_id
326
+
327
+ @property
328
+ def headers(self) -> dict:
329
+ return self.cloud_config.headers
330
+
331
+ # Convenient method:Prioritize using LLMConfig 的 api,如果没有则使用 CloudConfig 的 api
332
+ @property
333
+ def effective_api(self) -> str:
334
+ return self.llm_config.api if self.llm_config.api else self.cloud_config.api
335
+
336
+ @property
337
+ def effective_headers(self) -> dict:
338
+ # Merge CloudConfig and LLMConfig headers
339
+ combined_headers = {}
340
+ if self.cloud_config.headers:
341
+ combined_headers.update(self.cloud_config.headers)
342
+ if self.llm_config.headers:
343
+ combined_headers.update(self.llm_config.headers)
344
+ return combined_headers
345
+
346
+ @staticmethod
347
+ def from_dict(llm_name, all_clouds_config, config_dict) -> "LLMInstanceConfig":
348
+ cloud_name = config_dict.get("cloud", None)
349
+ if cloud_name:
350
+ cloud_config = all_clouds_config.get_cloud_config(cloud_name)
351
+ else:
352
+ cloud_config = CloudConfig.from_dict(config_dict)
353
+
354
+ llm_config = LLMConfig.from_dict(llm_name, config_dict)
355
+ return LLMInstanceConfig(
356
+ llm_name=llm_name, cloud_config=cloud_config, llm_config=llm_config
357
+ )
358
+
359
+ def to_dict(self) -> dict:
360
+ return {
361
+ "llm_name": self.llm_name,
362
+ "cloud_config": self.cloud_config.to_dict(),
363
+ "llm_config": self.llm_config.to_dict(),
364
+ }
365
+
366
+
367
+ class VMConnectionType(Enum):
368
+ SSH = 0
369
+ DOCKER = 1
370
+
371
+
372
+ class VMConfig:
373
+ def __init__(
374
+ self,
375
+ connectionType: VMConnectionType,
376
+ host: str,
377
+ port: int,
378
+ username: str,
379
+ encryptedPassword: str,
380
+ sshKeyPath: str = None,
381
+ timeout: int = 10,
382
+ retryCount: int = 3,
383
+ ):
384
+ self.connectionType = connectionType
385
+ self.connection_type = connectionType # Add alias for backward compatibility
386
+ self.host = host
387
+ self.port = port
388
+ self.username = username
389
+ self.encryptedPassword = encryptedPassword
390
+ self.sshKeyPath = sshKeyPath
391
+ self.timeout = timeout
392
+ self.retryCount = retryCount
393
+
394
+ @staticmethod
395
+ def fromArgs(config: dict):
396
+ # Get basic configuration
397
+ connectionType = (
398
+ VMConnectionType.SSH
399
+ if config["connection_type"] == "ssh"
400
+ else VMConnectionType.DOCKER
401
+ )
402
+ host = config["host"]
403
+ port = config["port"]
404
+ username = config["username"]
405
+ encryptedPassword = config["encrypted_password"]
406
+
407
+ # Get optional configuration
408
+ sshKeyPath = config.get("ssh_key_path", None)
409
+ timeout = config.get("timeout", 10)
410
+ retryCount = config.get("retry_count", 3)
411
+
412
+ return VMConfig(
413
+ connectionType=connectionType,
414
+ host=host,
415
+ port=port,
416
+ username=username,
417
+ encryptedPassword=encryptedPassword,
418
+ sshKeyPath=sshKeyPath,
419
+ timeout=timeout,
420
+ retryCount=retryCount,
421
+ )
422
+
423
+ def validate(self) -> bool:
424
+ """Validate whether the configuration is valid
425
+
426
+ Returns:
427
+ bool: Whether the configuration is valid
428
+ """
429
+ # Validate basic parameters
430
+ if not self.host or not isinstance(self.host, str):
431
+ return False
432
+
433
+ if not isinstance(self.port, int) or self.port <= 0 or self.port > 65535:
434
+ return False
435
+
436
+ if not self.username or not isinstance(self.username, str):
437
+ return False
438
+
439
+ # At least one of password and SSH key must be provided
440
+ if not self.encryptedPassword and not self.sshKeyPath:
441
+ return False
442
+
443
+ # If an SSH key path is specified, check whether the file exists
444
+ if self.sshKeyPath and not os.path.exists(self.sshKeyPath):
445
+ return False
446
+
447
+ return True
448
+
449
+ def toDict(self) -> dict:
450
+ """Convert configuration to dictionary
451
+
452
+ Returns:
453
+ dict: configuration dictionary
454
+ """
455
+ return {
456
+ "connection_type": (
457
+ "ssh" if self.connectionType == VMConnectionType.SSH else "docker"
458
+ ),
459
+ "host": self.host,
460
+ "port": self.port,
461
+ "username": self.username,
462
+ "encrypted_password": self.encryptedPassword,
463
+ "ssh_key_path": self.sshKeyPath,
464
+ "timeout": self.timeout,
465
+ "retry_count": self.retryCount,
466
+ }
467
+
468
+ def __str__(self) -> str:
469
+ """Return the string representation of the configuration
470
+
471
+ Returns:
472
+ str: The string representation of the configuration
473
+ """
474
+ return f"VMConfig(type={self.connectionType.name}, host={self.host}, port={self.port}, username={self.username})"
475
+
476
+
477
+ class ContextConstraints:
478
+ """Context Constraint Parameters
479
+
480
+ Parameter Description:
481
+ - max_input_tokens: Maximum number of input tokens the model can accept, which is the model's input capacity limit
482
+ - reserve_output_tokens: Number of tokens reserved for model output, ensuring sufficient space to generate a response
483
+ - preserve_system: Whether to retain system messages during compression
484
+
485
+ Calculation Logic:
486
+ Available input tokens = max_input_tokens - reserve_output_tokens
487
+
488
+ Recommendation:
489
+ - max_input_tokens: Usually set to the model's maximum context length (e.g., 128K, 32K, etc.)
490
+ - reserve_output_tokens: Set to the expected maximum output length, typically the model's max_tokens configuration value
491
+
492
+ Automatic Adjustment:
493
+ When model_config is provided, the system automatically sets reserve_output_tokens to model_config.max_tokens
494
+ """
495
+
496
+ def __init__(
497
+ self,
498
+ max_input_tokens: int = 64000, # Maximum input token count
499
+ reserve_output_tokens: int = 8192, # Number of tokens reserved for output
500
+ preserve_system: bool = True, # Whether to retain system messages
501
+ ):
502
+ self.max_input_tokens = max_input_tokens
503
+ self.reserve_output_tokens = reserve_output_tokens
504
+ self.preserve_system = preserve_system
505
+
506
+ @staticmethod
507
+ def from_dict(config_dict: dict) -> "ContextConstraints":
508
+ """Create constraints from dictionary"""
509
+ return ContextConstraints(
510
+ max_input_tokens=config_dict.get("max_input_tokens", 64000),
511
+ reserve_output_tokens=config_dict.get("reserve_output_tokens", 8192),
512
+ preserve_system=config_dict.get("preserve_system", True),
513
+ )
514
+
515
+ def to_dict(self) -> dict:
516
+ """Convert to dictionary"""
517
+ return {
518
+ "max_input_tokens": self.max_input_tokens,
519
+ "reserve_output_tokens": self.reserve_output_tokens,
520
+ "preserve_system": self.preserve_system,
521
+ }
522
+
523
+
524
+ class MemoryConfig:
525
+ """Configuration for the memory system."""
526
+
527
+ def __init__(
528
+ self,
529
+ enabled: bool = False,
530
+ storage_path: str = "data/memory/",
531
+ dialog_path: str = "data/dialog/",
532
+ default_top_k: int = 10,
533
+ ):
534
+ self.enabled = enabled
535
+ self.storage_path = storage_path
536
+ self.dialog_path = dialog_path
537
+ self.default_top_k = default_top_k
538
+
539
+ @staticmethod
540
+ def from_dict(config_dict: dict) -> "MemoryConfig":
541
+ return MemoryConfig(
542
+ enabled=config_dict.get("enabled", False),
543
+ storage_path=config_dict.get("storage_path", "data/memory/"),
544
+ dialog_path=config_dict.get("dialog_path", "data/dialog/"),
545
+ default_top_k=config_dict.get("default_top_k", 10),
546
+ )
547
+
548
+ def to_dict(self) -> dict:
549
+ return {
550
+ "enabled": self.enabled,
551
+ "storage_path": self.storage_path,
552
+ "dialog_path": self.dialog_path,
553
+ "default_top_k": self.default_top_k,
554
+ }
555
+
556
+
557
+ class MCPServerConfig:
558
+ """MCP Server Configuration"""
559
+
560
+ def __init__(
561
+ self,
562
+ name: str,
563
+ connection_mode: str = "stdio", # "stdio" or "http"
564
+ command: str = None, # stdio mode usage
565
+ args: List[str] = None, # stdio mode usage
566
+ url: str = None, # HTTP Mode Usage
567
+ env: Optional[Dict[str, str]] = None,
568
+ timeout: int = 30,
569
+ enabled: bool = False,
570
+ auth: Optional[Dict[str, str]] = None,
571
+ ):
572
+ self.name = name
573
+ self.connection_mode = connection_mode
574
+ self.command = command # Start command, such as "npx"
575
+ self.args = args or [] # Parameter List
576
+ self.url = url # HTTP Server URL
577
+ self.env = env # Environment Variables
578
+ self.timeout = timeout
579
+ self.enabled = enabled
580
+ self.auth = auth # Authentication Information
581
+
582
+ # Validate configuration
583
+ if connection_mode == "stdio":
584
+ if not command:
585
+ raise ValueError(f"stdio mode requires 'command' for server {name}")
586
+ elif connection_mode == "http":
587
+ if not url:
588
+ raise ValueError(f"http mode requires 'url' for server {name}")
589
+ else:
590
+ raise ValueError(
591
+ f"Invalid connection_mode: {connection_mode}, must be 'stdio' or 'http'"
592
+ )
593
+
594
+ @staticmethod
595
+ def from_dict(config_dict: dict) -> "MCPServerConfig":
596
+ return MCPServerConfig(
597
+ name=config_dict["name"],
598
+ connection_mode=config_dict.get("connection_mode", "stdio"),
599
+ command=config_dict.get("command"),
600
+ args=config_dict.get("args", []),
601
+ url=config_dict.get("url"),
602
+ env=config_dict.get("env"),
603
+ timeout=config_dict.get("timeout", 30),
604
+ enabled=config_dict.get("enabled", True),
605
+ auth=config_dict.get("auth"),
606
+ )
607
+
608
+ def to_dict(self) -> dict:
609
+ """Convert to dictionary"""
610
+ return {
611
+ "name": self.name,
612
+ "connection_mode": self.connection_mode,
613
+ "command": self.command,
614
+ "args": self.args,
615
+ "url": self.url,
616
+ "env": self.env,
617
+ "timeout": self.timeout,
618
+ "enabled": self.enabled,
619
+ "auth": self.auth,
620
+ }
621
+
622
+
623
+ class MCPConfig:
624
+ """MCP Configuration"""
625
+
626
+ def __init__(self, enabled: bool = True, servers: List[MCPServerConfig] = None):
627
+ self.enabled = enabled
628
+ self.servers = servers or []
629
+
630
+ @staticmethod
631
+ def from_dict(config_dict: dict) -> "MCPConfig":
632
+ servers = []
633
+ for server_dict in config_dict.get("servers", []):
634
+ server = MCPServerConfig.from_dict(server_dict)
635
+ servers.append(server)
636
+
637
+ return MCPConfig(enabled=config_dict.get("enabled", True), servers=servers)
638
+
639
+ def to_dict(self) -> dict:
640
+ """Convert to dictionary"""
641
+ return {
642
+ "enabled": self.enabled,
643
+ "servers": [server.to_dict() for server in self.servers],
644
+ }
645
+
646
+
647
+ class SkillConfig:
648
+ """Skill Loading Configuration"""
649
+
650
+ def __init__(self, enabled_skills: List[str] = None):
651
+ """Initialize skill configuration
652
+
653
+ Args:
654
+ enabled_skills: List of enabled skills, None means load all skills
655
+ Supports the following formats:
656
+ - "vm_skillkit": Load a specific skillkit
657
+ - "mcp": Load all MCP servers
658
+ - "mcp.filesystem": Load a specific MCP server
659
+ """
660
+ self.enabled_skills = enabled_skills
661
+
662
+ @staticmethod
663
+ def _normalize_skill_name(name: str) -> str:
664
+ """Normalize skill identifiers for backward compatibility.
665
+
666
+ Historically, config examples used names like "vm_skillkit" while the
667
+ entry-point based loader uses names like "vm". We accept both by
668
+ normalizing the "_skillkit" suffix for non-namespaced skill ids.
669
+ """
670
+ if not isinstance(name, str):
671
+ return name
672
+ # Keep namespaced ids as-is (e.g. "mcp.playwright", "system_functions.grep")
673
+ if "." in name:
674
+ return name
675
+ if name.endswith("_skillkit"):
676
+ return name[: -len("_skillkit")]
677
+ return name
678
+
679
+ def should_load_skill(self, skill_name: str) -> bool:
680
+ """Check whether a certain skill should be loaded
681
+
682
+ Args:
683
+ skill_name: Name of the skill
684
+
685
+ Returns:
686
+ bool: Whether it should be loaded
687
+ """
688
+ if self.enabled_skills is None:
689
+ return True
690
+
691
+ # If it's an empty list, no skills will be loaded.
692
+ if len(self.enabled_skills) == 0:
693
+ return False
694
+
695
+ normalized_enabled = set()
696
+ for enabled in self.enabled_skills:
697
+ normalized_enabled.add(enabled)
698
+ normalized_enabled.add(self._normalize_skill_name(enabled))
699
+
700
+ normalized_skill_name = self._normalize_skill_name(skill_name)
701
+
702
+ # Check direct match
703
+ if skill_name in normalized_enabled or normalized_skill_name in normalized_enabled:
704
+ return True
705
+
706
+ # Check MCP mode matching
707
+ if skill_name.startswith("mcp."):
708
+ # If "mcp" is included, load all MCP servers
709
+ if "mcp" in normalized_enabled:
710
+ return True
711
+ # If a specific MCP server name is included, load that server
712
+ if skill_name in normalized_enabled:
713
+ return True
714
+
715
+ return False
716
+
717
+ def should_load_mcp_server(self, server_name: str) -> bool:
718
+ """Check whether a certain MCP server should be loaded
719
+
720
+ Args:
721
+ server_name: Name of the MCP server
722
+
723
+ Returns:
724
+ bool: Whether it should be loaded
725
+ """
726
+ if self.enabled_skills is None:
727
+ return True
728
+
729
+ # If "mcp" is included, load all MCP servers
730
+ if "mcp" in self.enabled_skills:
731
+ return True
732
+
733
+ # If a specific MCP server name is included, load that server.
734
+ if f"mcp.{server_name}" in self.enabled_skills:
735
+ return True
736
+
737
+ return False
738
+
739
+ @staticmethod
740
+ def from_dict(config_dict: dict) -> "SkillConfig":
741
+ """Create configuration from dictionary"""
742
+ return SkillConfig(enabled_skills=config_dict.get("enabled_skills"))
743
+
744
+ def to_dict(self) -> dict:
745
+ """Convert to dictionary"""
746
+ return {"enabled_skills": self.enabled_skills}
747
+
748
+
749
+ class RetrievalProcessingConfig:
750
+ """Retrieval system processing configuration"""
751
+
752
+ def __init__(
753
+ self,
754
+ max_text_length: int = 512,
755
+ max_batch_size: int = 16,
756
+ max_documents_per_rerank: int = 100,
757
+ chunk_overlap: int = 50,
758
+ chunk_size: int = 512,
759
+ ):
760
+ self.max_text_length = max_text_length # Maximum length (tokens) for a single text
761
+ self.max_batch_size = max_batch_size # embedding batch size
762
+ self.max_documents_per_rerank = max_documents_per_rerank # Number of documents processed at once by rerank
763
+ self.chunk_overlap = chunk_overlap # Document slice overlap length
764
+ self.chunk_size = chunk_size # Document chunk size
765
+
766
+ @staticmethod
767
+ def from_dict(config_dict: dict) -> "RetrievalProcessingConfig":
768
+ """Create configuration from dictionary"""
769
+ return RetrievalProcessingConfig(
770
+ max_text_length=config_dict.get("max_text_length", 512),
771
+ max_batch_size=config_dict.get("max_batch_size", 16),
772
+ max_documents_per_rerank=config_dict.get("max_documents_per_rerank", 100),
773
+ chunk_overlap=config_dict.get("chunk_overlap", 50),
774
+ chunk_size=config_dict.get("chunk_size", 512),
775
+ )
776
+
777
+ def to_dict(self) -> dict:
778
+ """Convert to dictionary"""
779
+ return {
780
+ "max_text_length": self.max_text_length,
781
+ "max_batch_size": self.max_batch_size,
782
+ "max_documents_per_rerank": self.max_documents_per_rerank,
783
+ "chunk_overlap": self.chunk_overlap,
784
+ "chunk_size": self.chunk_size,
785
+ }
786
+
787
+
788
+ class RetrievalModelConfig:
789
+ """Retrieval model configuration, supports multiple cloud providers"""
790
+
791
+ def __init__(
792
+ self,
793
+ embedding_config: dict = None,
794
+ rerank_config: dict = None,
795
+ cloud: str = None,
796
+ index_path: str = None,
797
+ processing_config: RetrievalProcessingConfig = None,
798
+ ):
799
+ self.embedding_config = embedding_config or {}
800
+ self.rerank_config = rerank_config or {}
801
+ self.cloud = cloud # Specify the cloud provider to use
802
+ self.index_path = index_path or "data/local_retrieval_index.pkl" # Index file path
803
+ # System Processing Configuration
804
+ self.processing_config = processing_config or RetrievalProcessingConfig()
805
+
806
+ @staticmethod
807
+ def from_dict(config_dict: dict) -> "RetrievalModelConfig":
808
+ """Create configuration from dictionary"""
809
+ processing_config = None
810
+ if "processing_config" in config_dict:
811
+ processing_config = RetrievalProcessingConfig.from_dict(
812
+ config_dict["processing_config"]
813
+ )
814
+
815
+ return RetrievalModelConfig(
816
+ embedding_config=config_dict.get("embedding_config", {}),
817
+ rerank_config=config_dict.get("rerank_config", {}),
818
+ cloud=config_dict.get("cloud"),
819
+ index_path=config_dict.get("index_path"),
820
+ processing_config=processing_config,
821
+ )
822
+
823
+ def to_dict(self) -> dict:
824
+ """Convert to dictionary"""
825
+ return {
826
+ "embedding_config": self.embedding_config,
827
+ "rerank_config": self.rerank_config,
828
+ "cloud": self.cloud,
829
+ "index_path": self.index_path,
830
+ "processing_config": self.processing_config.to_dict(),
831
+ }
832
+
833
+
834
+ class ContextEngineerConfig:
835
+ """Context Engineer Configuration"""
836
+
837
+ def __init__(
838
+ self,
839
+ context_config: ContextConfig = None,
840
+ import_mem: bool = False,
841
+ default_strategy: str = "level",
842
+ constraints: ContextConstraints = None,
843
+ strategy_configs: Dict[str, Any] = None,
844
+ tokenizer_backend: str = "auto",
845
+ ):
846
+ self.context_config = context_config
847
+ self.import_mem = import_mem
848
+ self.default_strategy = default_strategy
849
+ self.constraints = constraints or ContextConstraints()
850
+ self.strategy_configs = strategy_configs or {}
851
+ self.tokenizer_backend = tokenizer_backend
852
+
853
+ @staticmethod
854
+ def from_dict(config_dict: dict, base_dir: str = None) -> "ContextEngineerConfig":
855
+ """Create configuration from dictionary
856
+
857
+ Args:
858
+ config_dict: Configuration dictionary
859
+ base_dir: Base directory for resolving relative paths in config_path
860
+ """
861
+ context_config = None
862
+ config_path = config_dict.get("config_path")
863
+ if config_path:
864
+ # Resolve relative path relative to base_dir if provided
865
+ if not os.path.isabs(config_path) and base_dir:
866
+ # If config_path starts with base_dir name, remove the prefix to avoid duplication
867
+ base_dir_name = os.path.basename(base_dir)
868
+ if config_path.startswith(base_dir_name + os.sep):
869
+ config_path = config_path[len(base_dir_name + os.sep):]
870
+ config_path = os.path.join(base_dir, config_path)
871
+ context_config = ContextConfig.from_yaml(config_path)
872
+
873
+ constraints_dict = config_dict.get("constraints", {})
874
+ constraints = ContextConstraints.from_dict(constraints_dict)
875
+ return ContextEngineerConfig(
876
+ context_config=context_config,
877
+ import_mem=config_dict.get("import_mem", False),
878
+ default_strategy=config_dict.get("default_strategy", "level"),
879
+ constraints=constraints,
880
+ strategy_configs=config_dict.get("strategy_configs", {}),
881
+ tokenizer_backend=config_dict.get("tokenizer_backend", "auto"),
882
+ )
883
+
884
+ def to_dict(self) -> dict:
885
+ """Convert to dictionary"""
886
+ data = {
887
+ "import_mem": self.import_mem,
888
+ "default_strategy": self.default_strategy,
889
+ "constraints": self.constraints.to_dict(),
890
+ "strategy_configs": self.strategy_configs,
891
+ "tokenizer_backend": self.tokenizer_backend,
892
+ }
893
+ # Compatible output extended fields
894
+ if hasattr(self, "config_path") and getattr(self, "config_path"):
895
+ data["config_path"] = getattr(self, "config_path")
896
+ return data
897
+
898
+ class GlobalConfig:
899
+ def __init__(
900
+ self,
901
+ default_llm: str = "",
902
+ llmInstanceConfigs: dict = {},
903
+ fast_llm: str = None,
904
+ all_clouds_config: AllCloudsConfig = None,
905
+ vm_config: VMConfig = None,
906
+ context_engineer_config: ContextEngineerConfig = None,
907
+ memory_config: MemoryConfig = None,
908
+ mcp_config: MCPConfig = None,
909
+ skill_config: SkillConfig = None,
910
+ resource_skills: Optional[Dict[str, Any]] = None,
911
+ ontology_config: OntologyConfig = None,
912
+ retrieval_model_config: RetrievalModelConfig = None,
913
+ base_dir: Optional[str] = None,
914
+ ):
915
+ self.default_llm = default_llm
916
+ self.fast_llm = fast_llm if fast_llm else default_llm
917
+ self.all_clouds_config = all_clouds_config
918
+ self.llmInstanceConfigs = llmInstanceConfigs
919
+ self._vm_config = vm_config
920
+ self._context_engineer_config = (
921
+ context_engineer_config or ContextEngineerConfig()
922
+ )
923
+ self._memory_config = memory_config or MemoryConfig()
924
+ self._mcp_config = mcp_config or MCPConfig()
925
+ self._skill_config = skill_config or SkillConfig()
926
+ self._resource_skills = resource_skills
927
+ self._llm_cache = GlobalCacheKVCenter.getCacheMgr(
928
+ "data/cache/", category="llm", expireTimeByDay=7
929
+ )
930
+ self._ontology_config = ontology_config
931
+ self._retrieval_model_config = retrieval_model_config
932
+ self._base_dir = base_dir
933
+
934
+ @property
935
+ def base_dir(self) -> Optional[str]:
936
+ """Get the base directory for resolving relative paths.
937
+
938
+ Returns:
939
+ The directory where the config file is located, or None if not set.
940
+ """
941
+ return self._base_dir
942
+
943
+ @property
944
+ def vm_config(self) -> VMConfig:
945
+ return self._vm_config
946
+
947
+ @property
948
+ def context_engineer_config(self) -> ContextEngineerConfig:
949
+ """[Deprecated alias] Old name, returns MessageCompressor configuration"""
950
+ return self._context_engineer_config
951
+
952
+ @property
953
+ def message_compressor_config(self) -> ContextEngineerConfig:
954
+ """MessageCompressor New Naming Configuration Access Entry"""
955
+ return self._context_engineer_config
956
+
957
+ @property
958
+ def memory_config(self) -> MemoryConfig:
959
+ return self._memory_config
960
+
961
+ @property
962
+ def mcp_config(self) -> MCPConfig:
963
+ return self._mcp_config
964
+
965
+ @property
966
+ def skill_config(self) -> SkillConfig:
967
+ return self._skill_config
968
+
969
+ @property
970
+ def resource_skills(self) -> Optional[Dict[str, Any]]:
971
+ return self._resource_skills
972
+
973
+ @property
974
+ def ontology_config(self) -> OntologyConfig:
975
+ return self._ontology_config
976
+
977
+ @property
978
+ def retrieval_model_config(self) -> RetrievalModelConfig:
979
+ return self._retrieval_model_config
980
+
981
+ def get_model_config(self, llm_name: Optional[str]) -> LLMInstanceConfig:
982
+ if llm_name in self.llmInstanceConfigs:
983
+ return self.llmInstanceConfigs.get(llm_name, "")
984
+ elif not llm_name:
985
+ return self.llmInstanceConfigs.get(self.default_llm, {})
986
+ else:
987
+ available_models = list(self.llmInstanceConfigs.keys())
988
+ raise ValueError(
989
+ f"Model '{llm_name}' not found in configuration.\n"
990
+ f" Available models: {available_models}\n"
991
+ f" Default model: '{self.default_llm}'\n\n"
992
+ f" To fix this, either:\n"
993
+ f" 1. Add '{llm_name}' to the 'llms' section in your global.yaml, or\n"
994
+ f" 2. Remove the model parameter from your .dph file to use the default model, or\n"
995
+ f" 3. Use one of the available models: {available_models}"
996
+ )
997
+
998
+ def get_default_model_config(self) -> LLMInstanceConfig:
999
+ return self.get_model_config(self.default_llm)
1000
+
1001
+ def get_fast_model_config(self) -> LLMInstanceConfig:
1002
+ return self.get_model_config(self.fast_llm)
1003
+
1004
+ def set_llm_cache(self, llm: str, key: Messages, value: Any):
1005
+ self._llm_cache.setValue(llm, key=key.get_messages_as_dict(), value=value)
1006
+
1007
+ def get_llm_cache(self, llm: str, key: Messages):
1008
+ return self._llm_cache.getValue(llm, key=key.get_messages_as_dict())
1009
+
1010
+ def set_llm_cache_by_dict(self, llm: str, key: List[Dict[str, Any]], value: Any):
1011
+ """Set LLM cache using a sanitized dict-list key."""
1012
+ self._llm_cache.setValue(llm, key=key, value=value)
1013
+
1014
+ def get_llm_cache_by_dict(self, llm: str, key: List[Dict[str, Any]]):
1015
+ """Get LLM cache using a sanitized dict-list key."""
1016
+ return self._llm_cache.getValue(llm, key=key)
1017
+
1018
+ @staticmethod
1019
+ def from_dict(config_dict: dict, base_dir: str = None) -> "GlobalConfig":
1020
+ is_new_config_format = "llms" in config_dict and "default" in config_dict
1021
+ if is_new_config_format:
1022
+ default_llm = config_dict.get("default")
1023
+ fast_llm = config_dict.get("fast", None)
1024
+
1025
+ clouds = config_dict.get("clouds", None)
1026
+ all_clouds_config = AllCloudsConfig.from_dict(clouds) if clouds else None
1027
+
1028
+ llms = config_dict.get("llms")
1029
+ llmInstanceConfigs = {}
1030
+ for llm_name, llm_config in llms.items():
1031
+ llmInstanceConfigs[llm_name] = LLMInstanceConfig.from_dict(
1032
+ llm_name, all_clouds_config, llm_config
1033
+ )
1034
+
1035
+ if default_llm not in llmInstanceConfigs:
1036
+ raise ValueError(
1037
+ f"default_llm {default_llm} not found in llmInstanceConfigs"
1038
+ )
1039
+
1040
+ if fast_llm and fast_llm not in llmInstanceConfigs:
1041
+ raise ValueError(f"fast_llm {fast_llm} not found in llmInstanceConfigs")
1042
+
1043
+ vm = config_dict.get("vm", None)
1044
+ vm_config = VMConfig.fromArgs(vm) if vm else None
1045
+
1046
+ context_engineer = config_dict.get("context_engineer", None)
1047
+ context_engineer_config = (
1048
+ ContextEngineerConfig.from_dict(context_engineer, base_dir=base_dir)
1049
+ if context_engineer
1050
+ else None
1051
+ )
1052
+
1053
+ memory = config_dict.get("memory", None)
1054
+ memory_config = MemoryConfig.from_dict(memory) if memory else None
1055
+
1056
+ # Parse MCP configuration
1057
+ mcp = config_dict.get("mcp", None)
1058
+ mcp_config = MCPConfig.from_dict(mcp) if mcp else None
1059
+
1060
+ # Parse skill configuration
1061
+ skill = config_dict.get("skill", None)
1062
+ skill_config = SkillConfig.from_dict(skill) if skill else None
1063
+
1064
+ # ResourceSkillkit configuration (Claude Skill format support)
1065
+ resource_skills = config_dict.get("resource_skills", None)
1066
+
1067
+ ontology = config_dict.get("ontology", None)
1068
+ ontology_config = OntologyConfig.from_dict(ontology) if ontology else None
1069
+
1070
+ # Parse retrieval_model_config
1071
+ retrieval_model = config_dict.get("retrieval_model_config", None)
1072
+ retrieval_model_config = (
1073
+ RetrievalModelConfig.from_dict(retrieval_model)
1074
+ if retrieval_model
1075
+ else None
1076
+ )
1077
+
1078
+ return GlobalConfig(
1079
+ default_llm=default_llm,
1080
+ fast_llm=fast_llm,
1081
+ all_clouds_config=all_clouds_config,
1082
+ llmInstanceConfigs=llmInstanceConfigs,
1083
+ vm_config=vm_config,
1084
+ context_engineer_config=context_engineer_config,
1085
+ memory_config=memory_config,
1086
+ mcp_config=mcp_config,
1087
+ skill_config=skill_config,
1088
+ resource_skills=resource_skills,
1089
+ ontology_config=ontology_config,
1090
+ retrieval_model_config=retrieval_model_config,
1091
+ base_dir=base_dir,
1092
+ )
1093
+ else:
1094
+ model_name = config_dict.get("model_name")
1095
+ llm_instance_config = LLMInstanceConfig.from_dict(
1096
+ model_name, all_clouds_config=None, config_dict=config_dict
1097
+ )
1098
+
1099
+ # The old format also supports context_engineer, memory, and mcp configurations
1100
+ context_engineer = config_dict.get("context_engineer", None)
1101
+ context_engineer_config = (
1102
+ ContextEngineerConfig.from_dict(context_engineer, base_dir=base_dir)
1103
+ if context_engineer
1104
+ else None
1105
+ )
1106
+
1107
+ memory = config_dict.get("memory", None)
1108
+ memory_config = MemoryConfig.from_dict(memory) if memory else None
1109
+
1110
+ mcp = config_dict.get("mcp", None)
1111
+ mcp_config = MCPConfig.from_dict(mcp) if mcp else None
1112
+
1113
+ skill = config_dict.get("skill", None)
1114
+ skill_config = SkillConfig.from_dict(skill) if skill else None
1115
+
1116
+ resource_skills = config_dict.get("resource_skills", None)
1117
+
1118
+ ontology = config_dict.get("ontology", None)
1119
+ ontology_config = OntologyConfig.from_dict(ontology) if ontology else None
1120
+
1121
+ # Parse retrieval_model_config
1122
+ retrieval_model = config_dict.get("retrieval_model_config", None)
1123
+ retrieval_model_config = (
1124
+ RetrievalModelConfig.from_dict(retrieval_model)
1125
+ if retrieval_model
1126
+ else None
1127
+ )
1128
+
1129
+ llmInstanceConfigs = {model_name: llm_instance_config}
1130
+ if "name" in config_dict:
1131
+ llmInstanceConfigs[config_dict["name"]] = llm_instance_config
1132
+ return GlobalConfig(
1133
+ default_llm=model_name,
1134
+ llmInstanceConfigs=llmInstanceConfigs,
1135
+ context_engineer_config=context_engineer_config,
1136
+ memory_config=memory_config,
1137
+ mcp_config=mcp_config,
1138
+ skill_config=skill_config,
1139
+ resource_skills=resource_skills,
1140
+ ontology_config=ontology_config,
1141
+ retrieval_model_config=retrieval_model_config,
1142
+ )
1143
+
1144
+ @staticmethod
1145
+ def from_yaml(yaml_path: str) -> "GlobalConfig":
1146
+ with open(yaml_path, "r", encoding="utf-8") as file:
1147
+ config_dict = yaml.load(file, Loader=yaml.FullLoader)
1148
+ # Get the directory of the YAML file to resolve relative paths
1149
+ base_dir = os.path.dirname(os.path.abspath(yaml_path))
1150
+ return GlobalConfig.from_dict(config_dict, base_dir=base_dir)
1151
+
1152
+ @staticmethod
1153
+ def from_yaml_with_base(
1154
+ base_yaml: str, override_yaml: str = None
1155
+ ) -> "GlobalConfig":
1156
+ """Load from base configuration and override configuration, supporting configuration inheritance and merging.
1157
+
1158
+ Args:
1159
+ base_yaml: Path to the base configuration file (required)
1160
+ override_yaml: Path to the override configuration file (optional), will recursively override the base configuration
1161
+
1162
+ Returns:
1163
+ GlobalConfig: Merged configuration object
1164
+
1165
+ Examples:
1166
+ # Use only the base configuration
1167
+ config = GlobalConfig.from_yaml_with_base("config/global.yaml")
1168
+
1169
+ # Use base configuration + agent-specific configuration
1170
+ config = GlobalConfig.from_yaml_with_base(
1171
+ base_yaml="config/global.yaml",
1172
+ override_yaml="config/alice/agent.yaml"
1173
+ )
1174
+ """
1175
+ # Load base configuration
1176
+ with open(base_yaml, "r", encoding="utf-8") as f:
1177
+ base_dict = yaml.load(f, Loader=yaml.FullLoader)
1178
+
1179
+ # If there is a configuration overlay, perform a deep merge.
1180
+ if override_yaml and os.path.exists(override_yaml):
1181
+ with open(override_yaml, "r", encoding="utf-8") as f:
1182
+ override_dict = yaml.load(f, Loader=yaml.FullLoader)
1183
+ # If the override_yaml file contains only comments or is empty, yaml.load will return None
1184
+ if override_dict:
1185
+ config_dict = GlobalConfig._deep_merge(base_dict, override_dict)
1186
+ else:
1187
+ config_dict = base_dict
1188
+ # Use override_yaml directory as base_dir for relative paths
1189
+ base_dir = os.path.dirname(os.path.abspath(override_yaml))
1190
+ else:
1191
+ config_dict = base_dict
1192
+ # Use base_yaml directory as base_dir for relative paths
1193
+ base_dir = os.path.dirname(os.path.abspath(base_yaml))
1194
+
1195
+ return GlobalConfig.from_dict(config_dict, base_dir=base_dir)
1196
+
1197
+ @staticmethod
1198
+ def _deep_merge(base: dict, override: dict) -> dict:
1199
+ """Deeply merge two dictionaries, with values in override recursively overriding those in base.
1200
+
1201
+ Args:
1202
+ base: Base dictionary
1203
+ override: Override dictionary
1204
+
1205
+ Returns:
1206
+ dict: Merged dictionary (new dictionary, original dictionaries are not modified)
1207
+
1208
+ Note:
1209
+ - Dictionary types are merged recursively
1210
+ - Other types (including lists) are directly overwritten
1211
+ """
1212
+ result = base.copy()
1213
+ for key, value in override.items():
1214
+ if (
1215
+ key in result
1216
+ and isinstance(result[key], dict)
1217
+ and isinstance(value, dict)
1218
+ ):
1219
+ # Recursively merge dictionaries
1220
+ result[key] = GlobalConfig._deep_merge(result[key], value)
1221
+ else:
1222
+ # Direct override (including lists, strings, numbers, etc.)
1223
+ result[key] = value
1224
+ return result
1225
+
1226
+ def to_dict(self) -> dict:
1227
+ """Convert to dictionary"""
1228
+ result = {
1229
+ "default": self.default_llm,
1230
+ }
1231
+
1232
+ # Add fast_llm (if different from default_llm)
1233
+ if self.fast_llm and self.fast_llm != self.default_llm:
1234
+ result["fast"] = self.fast_llm
1235
+
1236
+ # Add clouds configuration
1237
+ if self.all_clouds_config:
1238
+ clouds = {"default": self.all_clouds_config.default_cloud}
1239
+ for cloud_name, cloud_config in self.all_clouds_config.clouds.items():
1240
+ clouds[cloud_name] = {
1241
+ "api": cloud_config.api,
1242
+ "api_key": cloud_config.api_key,
1243
+ }
1244
+ if cloud_config.user_id:
1245
+ clouds[cloud_name]["userid"] = cloud_config.user_id
1246
+ if cloud_config.headers:
1247
+ clouds[cloud_name]["headers"] = cloud_config.headers
1248
+ result["clouds"] = clouds
1249
+
1250
+ # Add llms configuration
1251
+ if self.llmInstanceConfigs:
1252
+ llms = {}
1253
+ for llm_name, llm_instance_config in self.llmInstanceConfigs.items():
1254
+ llm_dict = llm_instance_config.llm_config.to_dict()
1255
+ # If cloud configuration is used, add a cloud reference
1256
+ if self.all_clouds_config:
1257
+ for cloud_name, cloud_config in self.all_clouds_config.clouds.items():
1258
+ if cloud_config == llm_instance_config.cloud_config:
1259
+ llm_dict["cloud"] = cloud_name
1260
+ break
1261
+ llms[llm_name] = llm_dict
1262
+ result["llms"] = llms
1263
+
1264
+ # Add additional configuration
1265
+ if self._vm_config:
1266
+ result["vm"] = self._vm_config.toDict()
1267
+
1268
+ if self._context_engineer_config:
1269
+ result["context_engineer"] = self._context_engineer_config.to_dict()
1270
+
1271
+ if self._memory_config:
1272
+ result["memory"] = self._memory_config.to_dict()
1273
+
1274
+ if self._mcp_config:
1275
+ result["mcp"] = self._mcp_config.to_dict()
1276
+
1277
+ if self._skill_config:
1278
+ result["skill"] = self._skill_config.to_dict()
1279
+
1280
+ if self._resource_skills is not None:
1281
+ result["resource_skills"] = self._resource_skills
1282
+
1283
+ if self._ontology_config:
1284
+ result["ontology"] = self._ontology_config.to_dict()
1285
+
1286
+ if self._retrieval_model_config:
1287
+ result["retrieval_model_config"] = self._retrieval_model_config.to_dict()
1288
+
1289
+ return result