chore: 忽略离线部署包,提交安全加固、数据库初始化与文档

- .gitignore: 忽略 docker/offline 离线部署包(镜像/运行时等大文件)
- 安全加固: 新增 compute/api/security.py 及各端安全测试,补充 docs/security-hardening.md
- 数据库: 新增完整初始化 SQL 与 docs/database-config.md
- 数据转换与评测: 修复类型检查、增强校验并补充测试
- Docker 配置与环境变量更新

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
wuyongtao
2026-08-07 09:24:35 +08:00
parent e397bcc2ca
commit 75cc105ebc
24 changed files with 1850 additions and 50 deletions

View File

@@ -15,27 +15,58 @@ class LlamaFactoryCommand:
def _load_dataset_preview(path: Path) -> list[dict[str, Any]]:
"""Load a preview of JSON/JSONL records from a dataset file.
Content-sniffs instead of trusting the extension so that BOM-prefixed files,
JSONL files containing a single JSON array, and mislabeled extensions all work.
"""
if not path.exists():
return []
text = path.read_text(encoding="utf-8", errors="replace").strip()
text = path.read_text(encoding="utf-8-sig", errors="replace").strip()
if not text:
return []
if path.suffix.lower() == ".jsonl":
items: list[dict[str, Any]] = []
for line in text.splitlines()[:20]:
line = line.strip()
if not line:
continue
value = json.loads(line)
if isinstance(value, dict):
items.append(value)
return items
value = json.loads(text)
try:
value = json.loads(text)
except json.JSONDecodeError:
value = None
if isinstance(value, list):
return [item for item in value[:20] if isinstance(item, dict)]
if isinstance(value, dict):
return [value]
return []
items: list[dict[str, Any]] = []
for line in text.splitlines()[:20]:
line = line.strip()
if not line:
continue
try:
parsed = json.loads(line)
except json.JSONDecodeError:
continue
if isinstance(parsed, list):
items.extend(item for item in parsed[:20] if isinstance(item, dict))
elif isinstance(parsed, dict):
items.append(parsed)
if len(items) >= 20:
break
return items[:20]
def _required_columns_for(formatting: str, columns: dict[str, Any]) -> list[str]:
"""Required data columns per dataset format.
Mirrors LLaMA-Factory's leniency: optional columns (e.g. ``input`` / ``query``
in Alpaca) are never required, only fields the format structurally needs.
"""
fmt = str(formatting or "").lower()
if fmt == "sharegpt":
return [str(columns.get("messages") or "messages")]
if fmt in {"dpo", "rm", "kto", "ppo"}:
return [str(columns[key]) for key in ("chosen", "rejected") if columns.get(key)]
if fmt in {"cpt", "pt", "pretrain"}:
return [str(columns.get("prompt") or columns.get("text") or "text")]
# alpaca family: prompt (instruction) + response (output) required,
# query (input) / history are optional and common to omit in jsonl datasets.
return [str(columns[key]) for key in ("prompt", "response") if columns.get(key)]
def _validate_dataset_columns(config: dict[str, Any]) -> list[str]:
@@ -51,7 +82,7 @@ def _validate_dataset_columns(config: dict[str, Any]) -> list[str]:
file_name = item.get("file_name")
file_names = file_name if isinstance(file_name, list) else [file_name]
columns = item.get("columns") if isinstance(item.get("columns"), dict) else {}
required_columns = [str(value) for value in columns.values() if value]
required_columns = _required_columns_for(str(item.get("formatting") or ""), columns)
for name in file_names:
if not name:
continue