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.
- dataagent/__init__.py +37 -0
- dataagent/utils/__init__.py +12 -0
- dataagent/utils/builder_utils.py +322 -0
- dataagent/utils/cli/rich_renderer.py +896 -0
- dataagent/utils/cli/terminal_input.py +132 -0
- dataagent/utils/compression_utils.py +462 -0
- dataagent/utils/constants.py +647 -0
- dataagent/utils/converter/__init__.py +21 -0
- dataagent/utils/converter/graph_summary.py +236 -0
- dataagent/utils/converter/ir_converter_constants.py +188 -0
- dataagent/utils/converter/ir_message_consumer.py +499 -0
- dataagent/utils/converter/result_ir_converter.py +865 -0
- dataagent/utils/dag_utils.py +145 -0
- dataagent/utils/env_file_loader.py +255 -0
- dataagent/utils/env_utils.py +35 -0
- dataagent/utils/fix_md_image_path.py +300 -0
- dataagent/utils/formatting_utils.py +525 -0
- dataagent/utils/import_utils.py +228 -0
- dataagent/utils/info_utils.py +44 -0
- dataagent/utils/log/__init__.py +70 -0
- dataagent/utils/log/config.py +54 -0
- dataagent/utils/log/configs/default.yaml +49 -0
- dataagent/utils/log/configs/dev.yaml +18 -0
- dataagent/utils/log/configs/prod.yaml +19 -0
- dataagent/utils/log/dataagent_logger.py +452 -0
- dataagent/utils/messages_utils.py +611 -0
- dataagent/utils/parsing_utils.py +403 -0
- dataagent/utils/runtime_paths.py +437 -0
- datagallery-0.1.0.dev20260829.dist-info/METADATA +288 -0
- datagallery-0.1.0.dev20260829.dist-info/RECORD +33 -0
- datagallery-0.1.0.dev20260829.dist-info/WHEEL +5 -0
- datagallery-0.1.0.dev20260829.dist-info/licenses/LICENSE +201 -0
- 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
|