datagallery 0.1.0.dev20260829__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 (33) hide show
  1. dataagent/__init__.py +37 -0
  2. dataagent/utils/__init__.py +12 -0
  3. dataagent/utils/builder_utils.py +322 -0
  4. dataagent/utils/cli/rich_renderer.py +896 -0
  5. dataagent/utils/cli/terminal_input.py +132 -0
  6. dataagent/utils/compression_utils.py +462 -0
  7. dataagent/utils/constants.py +647 -0
  8. dataagent/utils/converter/__init__.py +21 -0
  9. dataagent/utils/converter/graph_summary.py +236 -0
  10. dataagent/utils/converter/ir_converter_constants.py +188 -0
  11. dataagent/utils/converter/ir_message_consumer.py +499 -0
  12. dataagent/utils/converter/result_ir_converter.py +865 -0
  13. dataagent/utils/dag_utils.py +145 -0
  14. dataagent/utils/env_file_loader.py +255 -0
  15. dataagent/utils/env_utils.py +35 -0
  16. dataagent/utils/fix_md_image_path.py +300 -0
  17. dataagent/utils/formatting_utils.py +525 -0
  18. dataagent/utils/import_utils.py +228 -0
  19. dataagent/utils/info_utils.py +44 -0
  20. dataagent/utils/log/__init__.py +70 -0
  21. dataagent/utils/log/config.py +54 -0
  22. dataagent/utils/log/configs/default.yaml +49 -0
  23. dataagent/utils/log/configs/dev.yaml +18 -0
  24. dataagent/utils/log/configs/prod.yaml +19 -0
  25. dataagent/utils/log/dataagent_logger.py +452 -0
  26. dataagent/utils/messages_utils.py +611 -0
  27. dataagent/utils/parsing_utils.py +403 -0
  28. dataagent/utils/runtime_paths.py +437 -0
  29. datagallery-0.1.0.dev20260829.dist-info/METADATA +288 -0
  30. datagallery-0.1.0.dev20260829.dist-info/RECORD +33 -0
  31. datagallery-0.1.0.dev20260829.dist-info/WHEEL +5 -0
  32. datagallery-0.1.0.dev20260829.dist-info/licenses/LICENSE +201 -0
  33. datagallery-0.1.0.dev20260829.dist-info/top_level.txt +1 -0
dataagent/__init__.py ADDED
@@ -0,0 +1,37 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ============================================================================
13
+ """dataagent 包入口。
14
+
15
+ 注意:这里保持轻量,避免在 import dataagent 时就触发大量依赖初始化/循环导入。
16
+ 需要 DataAgent 等对象时,通过属性懒加载获取。
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ __version__ = "0.1.0"
22
+ __author__ = "DataAgent Team"
23
+
24
+ from typing import Any
25
+
26
+
27
+ def __getattr__(name: str) -> Any:
28
+ if name in ("DataAgent", "AgentBuilder", "load_agent_from_config", "BaseDataAgent"):
29
+ from dataagent.interface.sdk import AgentBuilder, BaseDataAgent, DataAgent, load_agent_from_config
30
+
31
+ return {
32
+ "DataAgent": DataAgent,
33
+ "AgentBuilder": AgentBuilder,
34
+ "BaseDataAgent": BaseDataAgent,
35
+ "load_agent_from_config": load_agent_from_config,
36
+ }[name]
37
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,12 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ============================================================================
@@ -0,0 +1,322 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ # ============================================================================
13
+ import shutil
14
+ from contextlib import suppress
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ import yaml
19
+
20
+ from dataagent.utils.runtime_paths import dataagent_home, dataagent_package_path
21
+
22
+ _AGENT_PRESET_PATHS = {
23
+ "deep_analyze": ("core", "flex", "examples", "deep_analyze.yaml"),
24
+ }
25
+
26
+
27
+ def _require_supported_agent_type(agent_type: Any, *, source: str | Path) -> str:
28
+ """Return a builder preset name or raise a configuration error."""
29
+ if not isinstance(agent_type, str):
30
+ raise ValueError(
31
+ f"Failed to resolve `agent_type` from config: {source}. "
32
+ "Please ensure `AGENT_CONFIG.agent_type` is a string."
33
+ )
34
+ if agent_type not in _AGENT_PRESET_PATHS:
35
+ supported_values = ", ".join(sorted(_AGENT_PRESET_PATHS))
36
+ raise ValueError(
37
+ f"Unsupported `agent_type`: {agent_type!r} from config: {source}. Supported values are: {supported_values}."
38
+ )
39
+ return agent_type
40
+
41
+
42
+ def get_agent_type(config: Any, *, source: str | Path) -> str:
43
+ """从配置中提取 agent_type。"""
44
+ try:
45
+ # agent_type 区别于现存 YAML 配置文件中的 type。agent_type 可选值:deep_analyze
46
+ agent_type = config["AGENT_CONFIG"]["agent_type"]
47
+ except (TypeError, KeyError):
48
+ raise ValueError(
49
+ f"Failed to resolve `agent_type` from config: {source}. Please ensure `AGENT_CONFIG.agent_type` exists."
50
+ ) from None
51
+ return _require_supported_agent_type(agent_type, source=source)
52
+
53
+
54
+ def resolve_example_yaml_path(*, agent_type: str) -> Path:
55
+ """按 agent_type 返回预制 Agent 示例 YAML 的源路径。"""
56
+ agent_type = _require_supported_agent_type(agent_type, source="agent_type")
57
+ return dataagent_package_path(*_AGENT_PRESET_PATHS[agent_type])
58
+
59
+
60
+ def _output_path(output_dir: Path, filename: str) -> Path:
61
+ output_dir = output_dir.resolve()
62
+ path = (output_dir / filename).resolve()
63
+ if not path.is_relative_to(output_dir):
64
+ raise ValueError(f"Builder output path escapes the output directory: {filename!r}.")
65
+ return path
66
+
67
+
68
+ def get_original_yaml_path(*, agent_type: str, output_dir: Path) -> Path:
69
+ """按 agent_type 生成 original YAML 文件并返回路径。"""
70
+ example_config_path = resolve_example_yaml_path(agent_type=agent_type)
71
+
72
+ original_yaml_path = _output_path(output_dir, f"original_{agent_type}_config.yaml")
73
+ if original_yaml_path.exists():
74
+ with suppress(OSError):
75
+ original_yaml_path.unlink()
76
+ shutil.copy2(example_config_path, original_yaml_path)
77
+ return original_yaml_path
78
+
79
+
80
+ def write_temp_yaml_path(*, agent_type: str, config_dict: dict, output_dir: Path) -> Path:
81
+ """生成 temp YAML 文件并返回路径。"""
82
+ temp_yaml_path = _output_path(output_dir, f"temp_{agent_type}_config.yaml")
83
+ if temp_yaml_path.exists():
84
+ with suppress(OSError):
85
+ temp_yaml_path.unlink()
86
+ with temp_yaml_path.open("w", encoding="utf-8") as temp_file:
87
+ yaml.safe_dump(config_dict, temp_file, allow_unicode=True, sort_keys=False)
88
+ return temp_yaml_path
89
+
90
+
91
+ def merge_yaml_cfg_val(original_value: Any, new_value: Any) -> Any:
92
+ """递归合并 YAML 配置。
93
+
94
+ 合并规则(相同 key 追加字段):
95
+ - dict + dict:递归合并;同 key 继续向下追加
96
+ - list + list:列表拼接追加
97
+ - 其他类型:使用 temp(new_value) 的值覆盖 original(original_value)
98
+ """
99
+ if isinstance(original_value, dict) and isinstance(new_value, dict):
100
+ merged_value = dict(original_value)
101
+ for key, value in new_value.items():
102
+ if key in merged_value:
103
+ merged_value[key] = merge_yaml_cfg_val(merged_value[key], value)
104
+ else:
105
+ merged_value[key] = value
106
+ return merged_value
107
+ if isinstance(original_value, list) and isinstance(new_value, list):
108
+ return [*original_value, *new_value]
109
+ return new_value
110
+
111
+
112
+ def replace_set_scenario_fields_with_original_scenario(
113
+ *,
114
+ original_config: dict[str, Any],
115
+ temp_config: dict[str, Any],
116
+ ) -> dict[str, Any]:
117
+ """按增量方式将 set_scenario 追加到 original SCENARIO。"""
118
+ temp_scenario = temp_config.get("SCENARIO", {})
119
+ if temp_scenario is None:
120
+ temp_scenario = {}
121
+ if not isinstance(temp_scenario, dict):
122
+ return temp_config
123
+
124
+ temp_chat = temp_scenario.get("chat", {})
125
+ if temp_chat is None:
126
+ temp_chat = {}
127
+ if not isinstance(temp_chat, dict):
128
+ return temp_config
129
+
130
+ original_scenario = original_config.get("SCENARIO", {})
131
+ if original_scenario is None:
132
+ original_scenario = {}
133
+ if not isinstance(original_scenario, dict):
134
+ original_scenario = {}
135
+
136
+ original_chat = original_scenario.get("chat", {})
137
+ if original_chat is None:
138
+ original_chat = {}
139
+ if not isinstance(original_chat, dict):
140
+ original_chat = {}
141
+
142
+ merged_scenario = merge_yaml_cfg_val(original_scenario, temp_scenario)
143
+ if not isinstance(merged_scenario, dict):
144
+ return temp_config
145
+ merged_chat = merged_scenario.get("chat", {})
146
+ if merged_chat is None:
147
+ merged_chat = {}
148
+ if not isinstance(merged_chat, dict):
149
+ merged_chat = {}
150
+
151
+ for field_name in ("instructions", "constraints"):
152
+ temp_value = temp_chat.get(field_name)
153
+ if not isinstance(temp_value, str) or not temp_value:
154
+ continue
155
+ original_value = original_chat.get(field_name)
156
+ if isinstance(original_value, str) and original_value:
157
+ merged_chat[field_name] = f"{original_value}{temp_value}"
158
+ else:
159
+ merged_chat[field_name] = temp_value
160
+
161
+ merged_scenario["chat"] = merged_chat
162
+ updated_temp_config = dict(temp_config)
163
+ updated_temp_config["SCENARIO"] = merged_scenario
164
+ return updated_temp_config
165
+
166
+
167
+ def get_merge_yaml_path(
168
+ *,
169
+ agent_type: str,
170
+ output_dir: Path,
171
+ original_yaml_path: Path,
172
+ temp_yaml_path: Path,
173
+ ) -> Path:
174
+ """合并 original/temp YAML 文件,生成 merged YAML 并返回路径。"""
175
+ merged_yaml_path = _output_path(output_dir, f"merged_{agent_type}_config.yaml")
176
+ if merged_yaml_path.exists():
177
+ with suppress(OSError):
178
+ merged_yaml_path.unlink()
179
+
180
+ original_config: Any = {}
181
+ if original_yaml_path.exists():
182
+ with original_yaml_path.open("r", encoding="utf-8") as original_file:
183
+ original_config = yaml.safe_load(original_file) or {}
184
+
185
+ with temp_yaml_path.open("r", encoding="utf-8") as temp_file:
186
+ temp_config = yaml.safe_load(temp_file) or {}
187
+
188
+ if isinstance(original_config, dict) and isinstance(temp_config, dict):
189
+ # 将 set_scenario 增量追加到 original SCENARIO。
190
+ temp_config = replace_set_scenario_fields_with_original_scenario(
191
+ original_config=original_config,
192
+ temp_config=temp_config,
193
+ )
194
+
195
+ # 合并 original/temp YAML 配置。
196
+ merged_config = merge_yaml_cfg_val(original_config, temp_config)
197
+ with merged_yaml_path.open("w", encoding="utf-8") as merged_file:
198
+ yaml.safe_dump(merged_config, merged_file, allow_unicode=True, sort_keys=False)
199
+ return merged_yaml_path
200
+
201
+
202
+ def remove_sensitive_info_of_output_yamls(*, output_dir: Path) -> None:
203
+ """将 output 目录下 YAML 文件内容脱敏后覆盖写回。"""
204
+
205
+ def _mask_sensitive_fields(data: Any) -> Any:
206
+ if isinstance(data, dict):
207
+ masked_data: dict[Any, Any] = {}
208
+ for key, value in data.items():
209
+ if key in {"base_url", "api_key"}:
210
+ if isinstance(value, str):
211
+ if value:
212
+ masked_data[key] = "*" * len(value)
213
+ else:
214
+ masked_data[key] = "******"
215
+ else:
216
+ masked_data[key] = "******"
217
+ else:
218
+ masked_data[key] = _mask_sensitive_fields(value)
219
+ return masked_data
220
+ if isinstance(data, list):
221
+ masked_list: list[Any] = []
222
+ for item in data:
223
+ masked_list.append(_mask_sensitive_fields(item))
224
+ return masked_list
225
+ return data
226
+
227
+ for yaml_file_path in sorted(output_dir.glob("*.yaml")):
228
+ try:
229
+ with yaml_file_path.open("r", encoding="utf-8") as yaml_file:
230
+ yaml_content = yaml.safe_load(yaml_file) or {}
231
+ except (OSError, yaml.YAMLError):
232
+ continue
233
+ masked_yaml_content = _mask_sensitive_fields(yaml_content)
234
+ try:
235
+ with yaml_file_path.open("w", encoding="utf-8") as yaml_file:
236
+ yaml.safe_dump(masked_yaml_content, yaml_file, allow_unicode=True, sort_keys=False)
237
+ except (OSError, yaml.YAMLError):
238
+ continue
239
+
240
+
241
+ def get_final_yaml(*, agent_type: str, config_dict: dict) -> Path:
242
+ """在 ``~/.dataagent/.builder/output`` 下生成 original/temp/merged YAML,并返回 merged 路径供加载。"""
243
+ agent_type = _require_supported_agent_type(agent_type, source="config_dict")
244
+ output_dir = dataagent_home() / ".builder" / "output"
245
+ output_dir.mkdir(parents=True, exist_ok=True)
246
+
247
+ try:
248
+ # 1) 从包内示例复制到 output 目录,作为合并基底
249
+ original_yaml_path = get_original_yaml_path(
250
+ agent_type=agent_type,
251
+ output_dir=output_dir,
252
+ )
253
+ # 2) 将本次构建参数写入临时 YAML,作为增量配置输入
254
+ temp_yaml_path = write_temp_yaml_path(
255
+ agent_type=agent_type,
256
+ config_dict=config_dict,
257
+ output_dir=output_dir,
258
+ )
259
+ # 3) 合并原始配置与临时配置,生成 merged YAML(仅存在于 output 目录,不写回包内示例)
260
+ merged_yaml_path = get_merge_yaml_path(
261
+ agent_type=agent_type,
262
+ output_dir=output_dir,
263
+ original_yaml_path=original_yaml_path,
264
+ temp_yaml_path=temp_yaml_path,
265
+ )
266
+ return merged_yaml_path
267
+ finally:
268
+ # 无论 get_final_yaml 是否成功,output 目录下已经生成的 YAML 都会被统一脱敏后覆盖写回
269
+ remove_sensitive_info_of_output_yamls(output_dir=output_dir)
270
+
271
+
272
+ def normalize_actions(
273
+ value: list[dict[str, Any]] | None,
274
+ field_name: str,
275
+ ) -> list[dict[str, Any]] | None:
276
+ """规范化 TOOLS 下列表型 action 配置,并做字段校验。"""
277
+ required_fields_map: dict[str, tuple[str, ...]] = {
278
+ "skills": ("user", "builtin"),
279
+ }
280
+ if value is None:
281
+ return None
282
+ if not isinstance(value, list):
283
+ raise ValueError(f"`{field_name}` must be a list[dict] or None.")
284
+ if field_name not in required_fields_map:
285
+ raise ValueError(f"Unsupported action field: {field_name!r}.")
286
+
287
+ required_fields = required_fields_map[field_name]
288
+ normalized_items: list[dict[str, Any]] = []
289
+ for index, item in enumerate(value):
290
+ if not isinstance(item, dict):
291
+ raise ValueError(f"`{field_name}[{index}]` must be a dict.")
292
+
293
+ normalized_item = dict(item)
294
+ for required_field in required_fields:
295
+ field_value = normalized_item.get(required_field)
296
+ if not isinstance(field_value, str) or not field_value.strip():
297
+ raise ValueError(f"`{field_name}[{index}].{required_field}` must be a non-empty string.")
298
+
299
+ if field_name == "mcp":
300
+ config = normalized_item.get("config")
301
+ if config is None:
302
+ normalized_item["config"] = {}
303
+ elif not isinstance(config, dict):
304
+ raise ValueError(f"`{field_name}[{index}].config` must be a dict.")
305
+
306
+ normalized_items.append(normalized_item)
307
+
308
+ return normalized_items
309
+
310
+
311
+ def normalize_skill_allowlists(value: list[str] | None) -> dict[str, list[str]] | None:
312
+ """规范化 TOOLS.skills allowlist 配置。"""
313
+ if value is None:
314
+ return None
315
+ if not isinstance(value, list):
316
+ raise ValueError("`skills` must be a list[str] or None.")
317
+ res = {"builtin": [], "user": []}
318
+ for name in value:
319
+ if not isinstance(name, str) or not name.strip():
320
+ raise ValueError(f"`skills[{name}]` must be a non-empty string.")
321
+ res["user"].append(name.strip())
322
+ return res