dtflow 0.4.3__tar.gz → 0.5.0__tar.gz

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 (47) hide show
  1. {dtflow-0.4.3 → dtflow-0.5.0}/PKG-INFO +107 -2
  2. {dtflow-0.4.3 → dtflow-0.5.0}/README.md +106 -1
  3. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/__init__.py +34 -1
  4. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/__main__.py +22 -0
  5. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/commands.py +5 -0
  6. dtflow-0.5.0/dtflow/cli/validate.py +152 -0
  7. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/core.py +154 -0
  8. dtflow-0.5.0/dtflow/framework.py +610 -0
  9. dtflow-0.5.0/dtflow/schema.py +508 -0
  10. dtflow-0.5.0/tests/test_framework.py +204 -0
  11. dtflow-0.5.0/tests/test_schema.py +547 -0
  12. {dtflow-0.4.3 → dtflow-0.5.0}/.gitignore +0 -0
  13. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/__init__.py +0 -0
  14. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/clean.py +0 -0
  15. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/common.py +0 -0
  16. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/io_ops.py +0 -0
  17. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/lineage.py +0 -0
  18. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/pipeline.py +0 -0
  19. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/sample.py +0 -0
  20. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/stats.py +0 -0
  21. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/cli/transform.py +0 -0
  22. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/converters.py +0 -0
  23. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/lineage.py +0 -0
  24. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/mcp/__init__.py +0 -0
  25. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/mcp/__main__.py +0 -0
  26. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/mcp/cli.py +0 -0
  27. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/mcp/docs.py +0 -0
  28. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/mcp/server.py +0 -0
  29. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/pipeline.py +0 -0
  30. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/presets.py +0 -0
  31. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/storage/__init__.py +0 -0
  32. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/storage/io.py +0 -0
  33. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/streaming.py +0 -0
  34. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/tokenizers.py +0 -0
  35. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/utils/__init__.py +0 -0
  36. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/utils/display.py +0 -0
  37. {dtflow-0.4.3 → dtflow-0.5.0}/dtflow/utils/field_path.py +0 -0
  38. {dtflow-0.4.3 → dtflow-0.5.0}/pyproject.toml +0 -0
  39. {dtflow-0.4.3 → dtflow-0.5.0}/tests/benchmark_io.py +0 -0
  40. {dtflow-0.4.3 → dtflow-0.5.0}/tests/test_converters.py +0 -0
  41. {dtflow-0.4.3 → dtflow-0.5.0}/tests/test_field_path.py +0 -0
  42. {dtflow-0.4.3 → dtflow-0.5.0}/tests/test_io.py +0 -0
  43. {dtflow-0.4.3 → dtflow-0.5.0}/tests/test_lineage.py +0 -0
  44. {dtflow-0.4.3 → dtflow-0.5.0}/tests/test_pipeline.py +0 -0
  45. {dtflow-0.4.3 → dtflow-0.5.0}/tests/test_streaming.py +0 -0
  46. {dtflow-0.4.3 → dtflow-0.5.0}/tests/test_tokenizers.py +0 -0
  47. {dtflow-0.4.3 → dtflow-0.5.0}/tests/test_transformer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dtflow
3
- Version: 0.4.3
3
+ Version: 0.5.0
4
4
  Summary: A flexible data transformation tool for ML training formats (SFT, RLHF, Pretrain)
5
5
  Project-URL: Homepage, https://github.com/yourusername/DataTransformer
6
6
  Project-URL: Documentation, https://github.com/yourusername/DataTransformer#readme
@@ -129,7 +129,7 @@ dt.filter(lambda x: x.language == "zh")
129
129
  ### 数据验证
130
130
 
131
131
  ```python
132
- # 验证数据,返回不通过的记录列表
132
+ # 简单验证,返回不通过的记录列表
133
133
  errors = dt.validate(lambda x: len(x.messages) >= 2)
134
134
 
135
135
  if errors:
@@ -137,6 +137,53 @@ if errors:
137
137
  print(f"第 {e.index} 行: {e.error}")
138
138
  ```
139
139
 
140
+ ### Schema 验证
141
+
142
+ 使用 Schema 进行结构化数据验证:
143
+
144
+ ```python
145
+ from dtflow import Schema, Field, openai_chat_schema
146
+
147
+ # 使用预设 Schema
148
+ result = dt.validate_schema(openai_chat_schema)
149
+ print(result) # ValidationResult(valid=950, invalid=50, errors=[...])
150
+
151
+ # 自定义 Schema
152
+ schema = Schema({
153
+ "messages": Field(type="list", required=True, min_length=1),
154
+ "messages[*].role": Field(type="str", choices=["user", "assistant", "system"]),
155
+ "messages[*].content": Field(type="str", min_length=1),
156
+ "score": Field(type="float", min=0, max=1),
157
+ })
158
+
159
+ result = dt.validate_schema(schema)
160
+
161
+ # 过滤出有效数据
162
+ valid_dt = dt.validate_schema(schema, filter_invalid=True)
163
+ valid_dt.save("valid.jsonl")
164
+ ```
165
+
166
+ **预设 Schema**:
167
+
168
+ | Schema 名称 | 用途 |
169
+ |------------|------|
170
+ | `openai_chat_schema` | OpenAI messages 格式验证 |
171
+ | `alpaca_schema` | Alpaca instruction/output 格式 |
172
+ | `sharegpt_schema` | ShareGPT conversations 格式 |
173
+ | `dpo_schema` | DPO prompt/chosen/rejected 格式 |
174
+
175
+ **Field 参数**:
176
+
177
+ | 参数 | 说明 | 示例 |
178
+ |------|------|------|
179
+ | `type` | 类型验证 | `"str"`, `"int"`, `"float"`, `"bool"`, `"list"`, `"dict"` |
180
+ | `required` | 是否必填 | `True` / `False` |
181
+ | `min` / `max` | 数值范围 | `min=0, max=1` |
182
+ | `min_length` / `max_length` | 长度范围 | `min_length=1` |
183
+ | `choices` | 枚举值 | `choices=["user", "assistant"]` |
184
+ | `pattern` | 正则匹配 | `pattern=r"^\d{4}-\d{2}-\d{2}$"` |
185
+ | `custom` | 自定义验证 | `custom=lambda x: x > 0` |
186
+
140
187
  ### 数据转换
141
188
 
142
189
  ```python
@@ -286,6 +333,58 @@ dt.transform(to_swift_vlm(images_field="images")).save("swift_vlm.jsonl")
286
333
  # 输出: {"messages": [...], "images": ["/path/to/img.jpg"]}
287
334
  ```
288
335
 
336
+ ### 训练框架一键导出
337
+
338
+ 将数据导出为目标训练框架可直接使用的格式,自动生成配置文件:
339
+
340
+ ```python
341
+ from dtflow import DataTransformer
342
+
343
+ dt = DataTransformer.load("data.jsonl")
344
+
345
+ # 1. 检查框架兼容性
346
+ result = dt.check_compatibility("llama-factory")
347
+ print(result)
348
+ # ✅ 兼容 - LLaMA-Factory (openai_chat)
349
+ # 或
350
+ # ❌ 不兼容 - 错误: xxx
351
+
352
+ # 2. 一键导出到 LLaMA-Factory
353
+ files = dt.export_for("llama-factory", "./llama_ready/")
354
+ # 生成文件:
355
+ # - ./llama_ready/custom_dataset.json # 数据文件
356
+ # - ./llama_ready/dataset_info.json # 数据集配置
357
+ # - ./llama_ready/train_args.yaml # 训练参数模板
358
+
359
+ # 3. 导出到 ms-swift
360
+ files = dt.export_for("swift", "./swift_ready/")
361
+ # 生成: data.jsonl + train_swift.sh
362
+
363
+ # 4. 导出到 Axolotl
364
+ files = dt.export_for("axolotl", "./axolotl_ready/")
365
+ # 生成: data.jsonl + config.yaml
366
+
367
+ # 指定数据集名称
368
+ dt.export_for("llama-factory", "./output/", dataset_name="my_sft_data")
369
+ ```
370
+
371
+ **支持的框架**:
372
+
373
+ | 框架 | 导出内容 | 使用方式 |
374
+ |------|---------|---------|
375
+ | `llama-factory` | data.json + dataset_info.json + train_args.yaml | `llamafactory-cli train train_args.yaml` |
376
+ | `swift` | data.jsonl + train_swift.sh | `bash train_swift.sh` |
377
+ | `axolotl` | data.jsonl + config.yaml | `accelerate launch -m axolotl.cli.train config.yaml` |
378
+
379
+ **自动格式检测**:
380
+
381
+ | 检测到的格式 | 数据结构 |
382
+ |------------|---------|
383
+ | `openai_chat` | `{"messages": [{"role": "user", ...}]}` |
384
+ | `alpaca` | `{"instruction": ..., "output": ...}` |
385
+ | `sharegpt` | `{"conversations": [{"from": "human", ...}]}` |
386
+ | `dpo` | `{"prompt": ..., "chosen": ..., "rejected": ...}` |
387
+
289
388
  ### 其他操作
290
389
 
291
390
  ```python
@@ -361,6 +460,12 @@ dt concat a.jsonl b.jsonl -o merged.jsonl
361
460
 
362
461
  # 数据统计
363
462
  dt stats data.jsonl
463
+
464
+ # 数据验证
465
+ dt validate data.jsonl --preset=openai_chat # 使用预设 schema 验证
466
+ dt validate data.jsonl --preset=alpaca --verbose # 详细输出
467
+ dt validate data.jsonl --preset=sharegpt --filter-invalid -o valid.jsonl # 过滤出有效数据
468
+ dt validate data.jsonl --preset=dpo --max-errors=100 # 限制错误输出数量
364
469
  ```
365
470
 
366
471
  ### 字段路径语法
@@ -53,7 +53,7 @@ dt.filter(lambda x: x.language == "zh")
53
53
  ### 数据验证
54
54
 
55
55
  ```python
56
- # 验证数据,返回不通过的记录列表
56
+ # 简单验证,返回不通过的记录列表
57
57
  errors = dt.validate(lambda x: len(x.messages) >= 2)
58
58
 
59
59
  if errors:
@@ -61,6 +61,53 @@ if errors:
61
61
  print(f"第 {e.index} 行: {e.error}")
62
62
  ```
63
63
 
64
+ ### Schema 验证
65
+
66
+ 使用 Schema 进行结构化数据验证:
67
+
68
+ ```python
69
+ from dtflow import Schema, Field, openai_chat_schema
70
+
71
+ # 使用预设 Schema
72
+ result = dt.validate_schema(openai_chat_schema)
73
+ print(result) # ValidationResult(valid=950, invalid=50, errors=[...])
74
+
75
+ # 自定义 Schema
76
+ schema = Schema({
77
+ "messages": Field(type="list", required=True, min_length=1),
78
+ "messages[*].role": Field(type="str", choices=["user", "assistant", "system"]),
79
+ "messages[*].content": Field(type="str", min_length=1),
80
+ "score": Field(type="float", min=0, max=1),
81
+ })
82
+
83
+ result = dt.validate_schema(schema)
84
+
85
+ # 过滤出有效数据
86
+ valid_dt = dt.validate_schema(schema, filter_invalid=True)
87
+ valid_dt.save("valid.jsonl")
88
+ ```
89
+
90
+ **预设 Schema**:
91
+
92
+ | Schema 名称 | 用途 |
93
+ |------------|------|
94
+ | `openai_chat_schema` | OpenAI messages 格式验证 |
95
+ | `alpaca_schema` | Alpaca instruction/output 格式 |
96
+ | `sharegpt_schema` | ShareGPT conversations 格式 |
97
+ | `dpo_schema` | DPO prompt/chosen/rejected 格式 |
98
+
99
+ **Field 参数**:
100
+
101
+ | 参数 | 说明 | 示例 |
102
+ |------|------|------|
103
+ | `type` | 类型验证 | `"str"`, `"int"`, `"float"`, `"bool"`, `"list"`, `"dict"` |
104
+ | `required` | 是否必填 | `True` / `False` |
105
+ | `min` / `max` | 数值范围 | `min=0, max=1` |
106
+ | `min_length` / `max_length` | 长度范围 | `min_length=1` |
107
+ | `choices` | 枚举值 | `choices=["user", "assistant"]` |
108
+ | `pattern` | 正则匹配 | `pattern=r"^\d{4}-\d{2}-\d{2}$"` |
109
+ | `custom` | 自定义验证 | `custom=lambda x: x > 0` |
110
+
64
111
  ### 数据转换
65
112
 
66
113
  ```python
@@ -210,6 +257,58 @@ dt.transform(to_swift_vlm(images_field="images")).save("swift_vlm.jsonl")
210
257
  # 输出: {"messages": [...], "images": ["/path/to/img.jpg"]}
211
258
  ```
212
259
 
260
+ ### 训练框架一键导出
261
+
262
+ 将数据导出为目标训练框架可直接使用的格式,自动生成配置文件:
263
+
264
+ ```python
265
+ from dtflow import DataTransformer
266
+
267
+ dt = DataTransformer.load("data.jsonl")
268
+
269
+ # 1. 检查框架兼容性
270
+ result = dt.check_compatibility("llama-factory")
271
+ print(result)
272
+ # ✅ 兼容 - LLaMA-Factory (openai_chat)
273
+ # 或
274
+ # ❌ 不兼容 - 错误: xxx
275
+
276
+ # 2. 一键导出到 LLaMA-Factory
277
+ files = dt.export_for("llama-factory", "./llama_ready/")
278
+ # 生成文件:
279
+ # - ./llama_ready/custom_dataset.json # 数据文件
280
+ # - ./llama_ready/dataset_info.json # 数据集配置
281
+ # - ./llama_ready/train_args.yaml # 训练参数模板
282
+
283
+ # 3. 导出到 ms-swift
284
+ files = dt.export_for("swift", "./swift_ready/")
285
+ # 生成: data.jsonl + train_swift.sh
286
+
287
+ # 4. 导出到 Axolotl
288
+ files = dt.export_for("axolotl", "./axolotl_ready/")
289
+ # 生成: data.jsonl + config.yaml
290
+
291
+ # 指定数据集名称
292
+ dt.export_for("llama-factory", "./output/", dataset_name="my_sft_data")
293
+ ```
294
+
295
+ **支持的框架**:
296
+
297
+ | 框架 | 导出内容 | 使用方式 |
298
+ |------|---------|---------|
299
+ | `llama-factory` | data.json + dataset_info.json + train_args.yaml | `llamafactory-cli train train_args.yaml` |
300
+ | `swift` | data.jsonl + train_swift.sh | `bash train_swift.sh` |
301
+ | `axolotl` | data.jsonl + config.yaml | `accelerate launch -m axolotl.cli.train config.yaml` |
302
+
303
+ **自动格式检测**:
304
+
305
+ | 检测到的格式 | 数据结构 |
306
+ |------------|---------|
307
+ | `openai_chat` | `{"messages": [{"role": "user", ...}]}` |
308
+ | `alpaca` | `{"instruction": ..., "output": ...}` |
309
+ | `sharegpt` | `{"conversations": [{"from": "human", ...}]}` |
310
+ | `dpo` | `{"prompt": ..., "chosen": ..., "rejected": ...}` |
311
+
213
312
  ### 其他操作
214
313
 
215
314
  ```python
@@ -285,6 +384,12 @@ dt concat a.jsonl b.jsonl -o merged.jsonl
285
384
 
286
385
  # 数据统计
287
386
  dt stats data.jsonl
387
+
388
+ # 数据验证
389
+ dt validate data.jsonl --preset=openai_chat # 使用预设 schema 验证
390
+ dt validate data.jsonl --preset=alpaca --verbose # 详细输出
391
+ dt validate data.jsonl --preset=sharegpt --filter-invalid -o valid.jsonl # 过滤出有效数据
392
+ dt validate data.jsonl --preset=dpo --max-errors=100 # 限制错误输出数量
288
393
  ```
289
394
 
290
395
  ### 字段路径语法
@@ -4,6 +4,7 @@ DataTransformer: 简洁的数据格式转换工具
4
4
  核心功能:
5
5
  - DataTransformer: 数据加载、转换、保存
6
6
  - presets: 预设转换模板 (openai_chat, alpaca, sharegpt, dpo_pair, simple_qa)
7
+ - schema: 数据结构验证 (Schema, Field)
7
8
  - tokenizers: Token 统计和过滤
8
9
  - converters: HuggingFace/OpenAI 等格式转换
9
10
  """
@@ -26,6 +27,23 @@ from .converters import ( # LLaMA-Factory 扩展; ms-swift
26
27
  )
27
28
  from .core import DataTransformer, DictWrapper, TransformError, TransformErrors
28
29
  from .presets import get_preset, list_presets
30
+ from .schema import (
31
+ Field,
32
+ Schema,
33
+ ValidationError,
34
+ ValidationResult,
35
+ alpaca_schema,
36
+ dpo_schema,
37
+ openai_chat_schema,
38
+ sharegpt_schema,
39
+ validate_data,
40
+ )
41
+ from .framework import (
42
+ CompatibilityResult,
43
+ check_compatibility,
44
+ detect_format,
45
+ export_for,
46
+ )
29
47
  from .storage import load_data, sample_file, save_data
30
48
  from .streaming import StreamingTransformer, load_sharded, load_stream, process_shards
31
49
  from .tokenizers import (
@@ -42,7 +60,7 @@ from .tokenizers import (
42
60
  token_stats,
43
61
  )
44
62
 
45
- __version__ = "0.4.3"
63
+ __version__ = "0.5.0"
46
64
 
47
65
  __all__ = [
48
66
  # core
@@ -53,6 +71,21 @@ __all__ = [
53
71
  # presets
54
72
  "get_preset",
55
73
  "list_presets",
74
+ # schema
75
+ "Schema",
76
+ "Field",
77
+ "ValidationResult",
78
+ "ValidationError",
79
+ "validate_data",
80
+ "openai_chat_schema",
81
+ "alpaca_schema",
82
+ "dpo_schema",
83
+ "sharegpt_schema",
84
+ # framework
85
+ "CompatibilityResult",
86
+ "check_compatibility",
87
+ "detect_format",
88
+ "export_for",
56
89
  # storage
57
90
  "save_data",
58
91
  "load_data",
@@ -18,6 +18,7 @@ Commands:
18
18
  clean 数据清洗
19
19
  run 执行 Pipeline 配置文件
20
20
  history 显示数据血缘历史
21
+ validate 使用 Schema 验证数据格式
21
22
  mcp MCP 服务管理(install/uninstall/status)
22
23
  logs 日志查看工具使用说明
23
24
  """
@@ -40,6 +41,7 @@ from .cli.commands import stats as _stats
40
41
  from .cli.commands import tail as _tail
41
42
  from .cli.commands import token_stats as _token_stats
42
43
  from .cli.commands import transform as _transform
44
+ from .cli.commands import validate as _validate
43
45
 
44
46
  # 创建主应用
45
47
  app = typer.Typer(
@@ -211,6 +213,26 @@ def history(
211
213
  _history(filename, json)
212
214
 
213
215
 
216
+ # ============ 验证命令 ============
217
+
218
+
219
+ @app.command()
220
+ def validate(
221
+ filename: str = typer.Argument(..., help="输入文件路径"),
222
+ preset: Optional[str] = typer.Option(
223
+ None, "--preset", "-p", help="预设 Schema: openai_chat, alpaca, dpo, sharegpt"
224
+ ),
225
+ output: Optional[str] = typer.Option(None, "--output", "-o", help="输出有效数据的文件路径"),
226
+ filter: bool = typer.Option(
227
+ False, "--filter", "-f", help="过滤无效数据并保存"
228
+ ),
229
+ max_errors: int = typer.Option(20, "--max-errors", help="最多显示的错误数量"),
230
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="显示详细信息"),
231
+ ):
232
+ """使用预设 Schema 验证数据格式"""
233
+ _validate(filename, preset, output, filter, max_errors, verbose)
234
+
235
+
214
236
  # ============ 工具命令 ============
215
237
 
216
238
 
@@ -33,6 +33,9 @@ from .pipeline import run
33
33
  # 血缘追踪命令
34
34
  from .lineage import history
35
35
 
36
+ # 验证命令
37
+ from .validate import validate
38
+
36
39
  __all__ = [
37
40
  # 采样
38
41
  "sample",
@@ -53,4 +56,6 @@ __all__ = [
53
56
  "run",
54
57
  # 血缘
55
58
  "history",
59
+ # 验证
60
+ "validate",
56
61
  ]
@@ -0,0 +1,152 @@
1
+ """
2
+ CLI Schema 验证命令
3
+ """
4
+
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from ..schema import (
9
+ Schema,
10
+ Field,
11
+ alpaca_schema,
12
+ dpo_schema,
13
+ openai_chat_schema,
14
+ sharegpt_schema,
15
+ )
16
+ from ..storage.io import load_data, save_data
17
+ from .common import _check_file_format
18
+
19
+
20
+ # 预设 Schema 映射
21
+ PRESET_SCHEMAS = {
22
+ "openai_chat": openai_chat_schema,
23
+ "openai-chat": openai_chat_schema,
24
+ "chat": openai_chat_schema,
25
+ "alpaca": alpaca_schema,
26
+ "dpo": dpo_schema,
27
+ "dpo_pair": dpo_schema,
28
+ "sharegpt": sharegpt_schema,
29
+ }
30
+
31
+
32
+ def validate(
33
+ filename: str,
34
+ preset: Optional[str] = None,
35
+ output: Optional[str] = None,
36
+ filter_invalid: bool = False,
37
+ max_errors: int = 20,
38
+ verbose: bool = False,
39
+ ) -> None:
40
+ """
41
+ 使用 Schema 验证数据文件。
42
+
43
+ Args:
44
+ filename: 输入文件路径
45
+ preset: 预设 Schema 名称 (openai_chat, alpaca, dpo, sharegpt)
46
+ output: 输出文件路径(保存有效数据)
47
+ filter_invalid: 过滤无效数据并保存
48
+ max_errors: 最多显示的错误数量
49
+ verbose: 显示详细信息
50
+
51
+ Examples:
52
+ dt validate data.jsonl --preset=openai_chat
53
+ dt validate data.jsonl --preset=alpaca -o valid.jsonl
54
+ dt validate data.jsonl --preset=chat --filter
55
+ """
56
+ filepath = Path(filename)
57
+
58
+ if not filepath.exists():
59
+ print(f"错误: 文件不存在 - {filename}")
60
+ return
61
+
62
+ if not _check_file_format(filepath):
63
+ return
64
+
65
+ # 确定 Schema
66
+ if preset is None:
67
+ # 列出可用的预设
68
+ print("请指定预设 Schema (--preset):")
69
+ print()
70
+ for name in ["openai_chat", "alpaca", "dpo", "sharegpt"]:
71
+ print(f" --preset={name}")
72
+ print()
73
+ print("示例:")
74
+ print(f" dt validate {filename} --preset=openai_chat")
75
+ return
76
+
77
+ preset_lower = preset.lower().replace("-", "_")
78
+ if preset_lower not in PRESET_SCHEMAS:
79
+ print(f"错误: 未知的预设 Schema '{preset}'")
80
+ print(f"可用预设: {', '.join(['openai_chat', 'alpaca', 'dpo', 'sharegpt'])}")
81
+ return
82
+
83
+ schema = PRESET_SCHEMAS[preset_lower]()
84
+
85
+ # 加载数据
86
+ try:
87
+ data = load_data(str(filepath))
88
+ except Exception as e:
89
+ print(f"错误: 无法读取文件 - {e}")
90
+ return
91
+
92
+ if not data:
93
+ print("文件为空")
94
+ return
95
+
96
+ total = len(data)
97
+ print(f"验证文件: {filepath.name}")
98
+ print(f"预设 Schema: {preset}")
99
+ print(f"总记录数: {total}")
100
+ print()
101
+
102
+ # 验证
103
+ valid_data = []
104
+ invalid_count = 0
105
+ error_samples = []
106
+
107
+ for i, item in enumerate(data):
108
+ result = schema.validate(item)
109
+ if result.valid:
110
+ valid_data.append(item)
111
+ else:
112
+ invalid_count += 1
113
+ if len(error_samples) < max_errors:
114
+ error_samples.append((i, result))
115
+
116
+ valid_count = len(valid_data)
117
+ valid_ratio = valid_count / total * 100 if total > 0 else 0
118
+
119
+ # 输出结果
120
+ if invalid_count == 0:
121
+ print(f"✅ 全部通过! {valid_count}/{total} 条记录有效 (100%)")
122
+ else:
123
+ print(f"⚠️ 验证结果: {valid_count}/{total} 条有效 ({valid_ratio:.1f}%)")
124
+ print(f" 无效记录: {invalid_count} 条")
125
+ print()
126
+
127
+ # 显示错误示例
128
+ print(f"错误示例 (最多显示 {max_errors} 条):")
129
+ print("-" * 60)
130
+
131
+ for idx, result in error_samples:
132
+ print(f"[第 {idx} 行]")
133
+ for err in result.errors[:3]: # 每条记录最多显示 3 个错误
134
+ print(f" - {err}")
135
+ if len(result.errors) > 3:
136
+ print(f" ... 还有 {len(result.errors) - 3} 个错误")
137
+ print()
138
+
139
+ # 保存有效数据
140
+ if output or filter_invalid:
141
+ output_path = output or str(filepath).replace(
142
+ filepath.suffix, f"_valid{filepath.suffix}"
143
+ )
144
+ save_data(valid_data, output_path)
145
+ print(f"✅ 有效数据已保存: {output_path} ({valid_count} 条)")
146
+
147
+ # 详细模式:显示 Schema 定义
148
+ if verbose:
149
+ print()
150
+ print("Schema 定义:")
151
+ print("-" * 40)
152
+ print(schema)