config: [run] verify_clean 可配置无违规两票复核开关

- config.ini [run] verify_clean = yes/no(默认 yes),GUI/CLI 均跟随
- CLI --no-verify 仍可强制关闭;非法值回落默认开启
- 模板/示例/README 同步,新增配置测试(共 36 个全部通过)
This commit is contained in:
yeuimu
2026-09-02 16:39:53 +08:00
parent 0ed5b9d9ff
commit bca1ceeea5
6 changed files with 35 additions and 3 deletions
+1
View File
@@ -78,6 +78,7 @@ model = deepseek-v4-flash-vision-exp
max_tokens = 5000 max_tokens = 5000
[run] [run]
mode = cascade # cascade / doubao(仅豆包)/ deepseek(仅 DeepSeek mode = cascade # cascade / doubao(仅豆包)/ deepseek(仅 DeepSeek
verify_clean = yes # DeepSeek 判无违规后是否追加两票复核(yes/no)
``` ```
运行模式:config.ini `[run] mode` 全局生效(GUI 也跟随);CLI 的 `--mode` 参数可临时覆盖。 运行模式:config.ini `[run] mode` 全局生效(GUI 也跟随);CLI 的 `--mode` 参数可临时覆盖。
+2
View File
@@ -25,5 +25,7 @@ file =
recheck = 无违规,违规不明,检测异常 recheck = 无违规,违规不明,检测异常
# 运行模式:cascade=豆包初筛+DeepSeek复检(默认)/ doubao=仅豆包 / deepseek=仅DeepSeek # 运行模式:cascade=豆包初筛+DeepSeek复检(默认)/ doubao=仅豆包 / deepseek=仅DeepSeek
# 无违规两票复核:DeepSeek 判为无违规的图片是否再追加两票判定(yes/no,默认 yes)
[run] [run]
mode = cascade mode = cascade
verify_clean = yes
+3 -1
View File
@@ -48,6 +48,8 @@ def main(argv=None) -> int:
if args.max_tokens: if args.max_tokens:
cfg.max_tokens = args.max_tokens cfg.max_tokens = args.max_tokens
mode = args.mode or cfg.mode mode = args.mode or cfg.mode
# 复核开关:CLI --no-verify 强制关;否则跟随 config.ini [run] verify_clean
verify = cfg.verify_clean and not args.no_verify
missing = missing_fields(cfg, mode) missing = missing_fields(cfg, mode)
if missing: if missing:
@@ -58,7 +60,7 @@ def main(argv=None) -> int:
try: try:
rows, summary = asyncio.run(run_detection( rows, summary = asyncio.run(run_detection(
args.folder, cfg, mode=mode, verify=not args.no_verify)) args.folder, cfg, mode=mode, verify=verify))
except FileNotFoundError as e: except FileNotFoundError as e:
logger.error("路径或文件不存在:%s", e) logger.error("路径或文件不存在:%s", e)
return 1 return 1
+14 -1
View File
@@ -57,8 +57,10 @@ file =
recheck = 无违规,违规不明,检测异常 recheck = 无违规,违规不明,检测异常
# 运行模式:cascade=豆包初筛+DeepSeek复检(默认)/ doubao=仅豆包 / deepseek=仅DeepSeek # 运行模式:cascade=豆包初筛+DeepSeek复检(默认)/ doubao=仅豆包 / deepseek=仅DeepSeek
# 无违规两票复核:DeepSeek 判为无违规的图片是否再追加两票判定(yes/no,默认 yes)
[run] [run]
mode = cascade mode = cascade
verify_clean = yes
""" """
# 环境变量优先于配置文件 # 环境变量优先于配置文件
@@ -99,6 +101,7 @@ class AppConfig:
recheck_categories: list = field(default_factory=lambda: ["无违规", "违规不明", "检测异常"]) recheck_categories: list = field(default_factory=lambda: ["无违规", "违规不明", "检测异常"])
# 运行 # 运行
mode: str = "cascade" mode: str = "cascade"
verify_clean: bool = True
retries: int = 3 retries: int = 3
@property @property
@@ -154,6 +157,14 @@ def load_config(path: str | None = None) -> AppConfig:
return conv(parser.get(section, key).strip()) return conv(parser.get(section, key).strip())
return None return None
def get_bool(section, key):
if not parser.has_option(section, key):
return None
try:
return parser.getboolean(section, key)
except ValueError:
return None
cfg.ark_api_key = get("ark", "api_key") or cfg.ark_api_key cfg.ark_api_key = get("ark", "api_key") or cfg.ark_api_key
cfg.ark_model = get("ark", "model_id") or cfg.ark_model cfg.ark_model = get("ark", "model_id") or cfg.ark_model
cfg.ark_base_url = get("ark", "base_url") or cfg.ark_base_url cfg.ark_base_url = get("ark", "base_url") or cfg.ark_base_url
@@ -168,6 +179,8 @@ def load_config(path: str | None = None) -> AppConfig:
if raw: if raw:
cfg.recheck_categories = [s.strip() for s in raw.split(",") if s.strip()] cfg.recheck_categories = [s.strip() for s in raw.split(",") if s.strip()]
cfg.mode = normalize_mode(get("run", "mode") or cfg.mode) cfg.mode = normalize_mode(get("run", "mode") or cfg.mode)
if get_bool("run", "verify_clean") is not None:
cfg.verify_clean = get_bool("run", "verify_clean")
for attr, env in ENV_OVERRIDES.items(): for attr, env in ENV_OVERRIDES.items():
val = os.environ.get(env) val = os.environ.get(env)
@@ -188,7 +201,7 @@ def save_config(cfg: AppConfig, path: str | None = None):
"max_tokens": str(cfg.max_tokens)} "max_tokens": str(cfg.max_tokens)}
parser["prompt"] = {"file": cfg.prompt_file or ""} parser["prompt"] = {"file": cfg.prompt_file or ""}
parser["cascade"] = {"recheck": ",".join(cfg.recheck_categories)} parser["cascade"] = {"recheck": ",".join(cfg.recheck_categories)}
parser["run"] = {"mode": cfg.mode} parser["run"] = {"mode": cfg.mode, "verify_clean": "yes" if cfg.verify_clean else "no"}
with ini.open("w", encoding="utf-8") as f: with ini.open("w", encoding="utf-8") as f:
parser.write(f) parser.write(f)
+1 -1
View File
@@ -257,7 +257,7 @@ class App:
try: try:
rows, summary = loop.run_until_complete(run_detection( rows, summary = loop.run_until_complete(run_detection(
folder, cfg, progress=progress, folder, cfg, progress=progress,
stop=self.stop_event.is_set, mode=mode, verify=True)) stop=self.stop_event.is_set, mode=mode, verify=cfg.verify_clean))
logger.info("正在整理输出(分类归档 + 生成报表)…") logger.info("正在整理输出(分类归档 + 生成报表)…")
ts_dir = organize_output(rows, folder, out_dir) ts_dir = organize_output(rows, folder, out_dir)
xlsx = build_report(rows, str(ts_dir), xlsx = build_report(rows, str(ts_dir),
+14
View File
@@ -63,6 +63,20 @@ def test_mode_from_ini(tmp_path):
assert load_config(str(ini)).mode == "cascade" assert load_config(str(ini)).mode == "cascade"
def test_verify_clean_from_ini(tmp_path):
ini = tmp_path / "config.ini"
ini.write_text("[run]\nverify_clean = no\n", encoding="utf-8")
assert load_config(str(ini)).verify_clean is False
ini.write_text("[run]\nverify_clean = yes\n", encoding="utf-8")
assert load_config(str(ini)).verify_clean is True
# 非法值保持默认开启
ini.write_text("[run]\nverify_clean = 也许\n", encoding="utf-8")
assert load_config(str(ini)).verify_clean is True
# 未配置保持默认开启
ini.write_text("[ark]\n", encoding="utf-8")
assert load_config(str(ini)).verify_clean is True
def test_save_and_load_roundtrip(tmp_path): def test_save_and_load_roundtrip(tmp_path):
ini = tmp_path / "config.ini" ini = tmp_path / "config.ini"
cfg = AppConfig(ark_api_key="ark-key", ark_model="ep-xyz", cfg = AppConfig(ark_api_key="ark-key", ark_model="ep-xyz",