feat: 结构化输出按官方规范 + 两段式重试 + DeepSeek 复检开关
- 豆包: response_format.json_schema(strict);DeepSeek: 切 Responses API text.format.json_schema (官方 chat/completions 通道不支持 json_schema),不支持时自动降级 json_object([output] schema) - 重试两段式:传输错误 cfg.retries 次;解析失败/空正文有独立 3 次专用重试,仍失败落 parse_fail - 移除三票复核(decide_final/_verify_clean/verify_clean),DeepSeek 单次判定即终稿 - 新增 [run] deepseek_recheck 开关(默认 no=仅豆包初筛;yes=追加 DeepSeek 复检) - GUI 不再覆盖 config 提示词;prompt 相对/前导斜杠路径按 exe 目录解析 - 报表去「票型/复核」列并同步说明;README/config.example.ini 同步 - 测试新增输出格式、重试、开关与提示词路径用例(50 passed)
This commit is contained in:
+70
-11
@@ -1,7 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""config 模块测试:模板生成、缺项检测、读写回环、提示词路径。"""
|
||||
import violation_detector.config as config_mod
|
||||
from violation_detector.config import (
|
||||
AppConfig, ensure_config, load_config, missing_fields, save_config,
|
||||
AppConfig, effective_mode, ensure_config, load_config, missing_fields,
|
||||
save_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -63,18 +65,57 @@ def test_mode_from_ini(tmp_path):
|
||||
assert load_config(str(ini)).mode == "cascade"
|
||||
|
||||
|
||||
def test_verify_clean_from_ini(tmp_path):
|
||||
def test_output_schema_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("[output]\nschema = on\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).output_schema_mode == "on"
|
||||
ini.write_text("[output]\nschema = off\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).output_schema_mode == "off"
|
||||
ini.write_text("[output]\nschema = auto\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).output_schema_mode == "auto"
|
||||
# 非法值回退 auto;未配置默认 auto
|
||||
ini.write_text("[output]\nschema = 乱来\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).output_schema_mode == "auto"
|
||||
ini.write_text("[ark]\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).verify_clean is True
|
||||
assert load_config(str(ini)).output_schema_mode == "auto"
|
||||
|
||||
|
||||
def test_output_schema_roundtrip(tmp_path):
|
||||
ini = tmp_path / "config.ini"
|
||||
save_config(AppConfig(output_schema_mode="off"), str(ini))
|
||||
assert load_config(str(ini)).output_schema_mode == "off"
|
||||
assert AppConfig().output_schema_mode == "auto"
|
||||
|
||||
|
||||
def test_deepseek_recheck_default_and_ini(tmp_path):
|
||||
# 默认关
|
||||
assert AppConfig().deepseek_recheck is False
|
||||
ini = tmp_path / "config.ini"
|
||||
ini.write_text("[run]\ndeepseek_recheck = yes\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).deepseek_recheck is True
|
||||
ini.write_text("[run]\ndeepseek_recheck = no\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).deepseek_recheck is False
|
||||
# 非法值/未配置保持默认关
|
||||
ini.write_text("[run]\ndeepseek_recheck = 也许\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).deepseek_recheck is False
|
||||
ini.write_text("[ark]\n", encoding="utf-8")
|
||||
assert load_config(str(ini)).deepseek_recheck is False
|
||||
|
||||
|
||||
def test_effective_mode_respects_recheck_switch():
|
||||
on = AppConfig(mode="cascade", deepseek_recheck=True)
|
||||
off = AppConfig(mode="cascade", deepseek_recheck=False)
|
||||
assert effective_mode(on, "cascade") == "cascade"
|
||||
assert effective_mode(off, "cascade") == "doubao"
|
||||
# 开关只影响 cascade;doubao/deepseek 不受影响
|
||||
assert effective_mode(off, "doubao") == "doubao"
|
||||
assert effective_mode(off, "deepseek") == "deepseek"
|
||||
|
||||
|
||||
def test_deepseek_recheck_roundtrip(tmp_path):
|
||||
ini = tmp_path / "config.ini"
|
||||
save_config(AppConfig(deepseek_recheck=True), str(ini))
|
||||
assert load_config(str(ini)).deepseek_recheck is True
|
||||
|
||||
|
||||
def test_save_and_load_roundtrip(tmp_path):
|
||||
@@ -104,3 +145,21 @@ def test_prompt_path_custom(tmp_path):
|
||||
p.write_text("x", encoding="utf-8")
|
||||
cfg = AppConfig(prompt_file=str(p))
|
||||
assert cfg.prompt_path == p
|
||||
|
||||
|
||||
def test_prompt_path_relative_prefers_app_dir(tmp_path, monkeypatch):
|
||||
(tmp_path / "prompts.txt").write_text("x", encoding="utf-8")
|
||||
monkeypatch.setattr(config_mod, "app_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr(config_mod, "resource_dir", lambda: tmp_path)
|
||||
cfg = AppConfig(prompt_file="prompts.txt")
|
||||
assert cfg.prompt_path == tmp_path / "prompts.txt"
|
||||
|
||||
|
||||
def test_prompt_path_leading_slash_treated_relative(tmp_path, monkeypatch):
|
||||
# 无盘符的 /prompts.txt 应视为 exe 同目录的相对文件(Windows 容错)
|
||||
(tmp_path / "prompts.txt").write_text("x", encoding="utf-8")
|
||||
monkeypatch.setattr(config_mod, "app_dir", lambda: tmp_path)
|
||||
monkeypatch.setattr(config_mod, "resource_dir", lambda: tmp_path)
|
||||
for raw in ("/prompts.txt", "\\prompts.txt"):
|
||||
cfg = AppConfig(prompt_file=raw)
|
||||
assert cfg.prompt_path == tmp_path / "prompts.txt", raw
|
||||
|
||||
Reference in New Issue
Block a user