diff --git a/README.md b/README.md index 5ba8bac..c78dce6 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ model = deepseek-v4-flash-vision-exp max_tokens = 5000 [run] mode = cascade # cascade / doubao(仅豆包)/ deepseek(仅 DeepSeek) +verify_clean = yes # DeepSeek 判无违规后是否追加两票复核(yes/no) ``` 运行模式:config.ini `[run] mode` 全局生效(GUI 也跟随);CLI 的 `--mode` 参数可临时覆盖。 diff --git a/config.example.ini b/config.example.ini index d8b660e..5b84837 100644 --- a/config.example.ini +++ b/config.example.ini @@ -25,5 +25,7 @@ file = recheck = 无违规,违规不明,检测异常 # 运行模式:cascade=豆包初筛+DeepSeek复检(默认)/ doubao=仅豆包 / deepseek=仅DeepSeek +# 无违规两票复核:DeepSeek 判为无违规的图片是否再追加两票判定(yes/no,默认 yes) [run] mode = cascade +verify_clean = yes diff --git a/src/violation_detector/cli.py b/src/violation_detector/cli.py index bcd1b37..8480206 100644 --- a/src/violation_detector/cli.py +++ b/src/violation_detector/cli.py @@ -48,6 +48,8 @@ def main(argv=None) -> int: if args.max_tokens: cfg.max_tokens = args.max_tokens 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) if missing: @@ -58,7 +60,7 @@ def main(argv=None) -> int: try: 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: logger.error("路径或文件不存在:%s", e) return 1 diff --git a/src/violation_detector/config.py b/src/violation_detector/config.py index 3ecbc03..924c8d3 100644 --- a/src/violation_detector/config.py +++ b/src/violation_detector/config.py @@ -57,8 +57,10 @@ file = recheck = 无违规,违规不明,检测异常 # 运行模式:cascade=豆包初筛+DeepSeek复检(默认)/ doubao=仅豆包 / deepseek=仅DeepSeek +# 无违规两票复核:DeepSeek 判为无违规的图片是否再追加两票判定(yes/no,默认 yes) [run] mode = cascade +verify_clean = yes """ # 环境变量优先于配置文件 @@ -99,6 +101,7 @@ class AppConfig: recheck_categories: list = field(default_factory=lambda: ["无违规", "违规不明", "检测异常"]) # 运行 mode: str = "cascade" + verify_clean: bool = True retries: int = 3 @property @@ -154,6 +157,14 @@ def load_config(path: str | None = None) -> AppConfig: return conv(parser.get(section, key).strip()) 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_model = get("ark", "model_id") or cfg.ark_model 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: cfg.recheck_categories = [s.strip() for s in raw.split(",") if s.strip()] 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(): val = os.environ.get(env) @@ -188,7 +201,7 @@ def save_config(cfg: AppConfig, path: str | None = None): "max_tokens": str(cfg.max_tokens)} parser["prompt"] = {"file": cfg.prompt_file or ""} 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: parser.write(f) diff --git a/src/violation_detector/gui.py b/src/violation_detector/gui.py index 7707732..965af2a 100644 --- a/src/violation_detector/gui.py +++ b/src/violation_detector/gui.py @@ -257,7 +257,7 @@ class App: try: rows, summary = loop.run_until_complete(run_detection( 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("正在整理输出(分类归档 + 生成报表)…") ts_dir = organize_output(rows, folder, out_dir) xlsx = build_report(rows, str(ts_dir), diff --git a/tests/test_config.py b/tests/test_config.py index 728f8cc..d664c28 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -63,6 +63,20 @@ def test_mode_from_ini(tmp_path): 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): ini = tmp_path / "config.ini" cfg = AppConfig(ark_api_key="ark-key", ark_model="ep-xyz",