mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-21 23:41:39 +08:00
fix(pipeline): report_config 布尔 null 时恢复默认 LLM 开关
JSON null 曾使 llm_matrix_group_summaries 等键存在但值为 None,合并默认配置时不被覆盖,bool(None) 关闭第五章矩阵与第六章促销归纳。校验阶段剥离 null,runner 侧统一 merge_report_config_with_defaults;简报构建同步。补充单测。 Made-with: Cursor
This commit is contained in:
parent
31e8482442
commit
48dc40dfa7
@ -14,6 +14,7 @@ from django.conf import settings
|
||||
|
||||
from ..csv.schema import MERGED_FIELD_TO_CSV_HEADER
|
||||
from ..models import PipelineJob
|
||||
from ..serializers import REPORT_CONFIG_BOOL_KEYS
|
||||
|
||||
|
||||
def merge_llm_supplement_with_rules_report(llm_md: str, rules_md: str) -> str:
|
||||
@ -160,7 +161,10 @@ def use_chunked_group_summaries_llm(report_config: dict[str, Any] | None) -> boo
|
||||
"yes",
|
||||
):
|
||||
return False
|
||||
return bool(rc.get("llm_group_summaries_chunk_by_matrix", True))
|
||||
ch = rc.get("llm_group_summaries_chunk_by_matrix")
|
||||
if ch is None:
|
||||
return True
|
||||
return bool(ch)
|
||||
|
||||
|
||||
def get_default_report_config() -> dict[str, Any]:
|
||||
@ -186,6 +190,25 @@ def get_default_report_config() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def merge_report_config_with_defaults(
|
||||
report_config: dict[str, Any] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
合并 ``get_default_report_config``:任务里常见仅含部分键;JSON ``null`` 的布尔开关视为未设置,
|
||||
否则 ``bool(None)`` 会把第五章矩阵/第六章促销等 LLM 归纳整段关掉。
|
||||
"""
|
||||
eff_rc: dict[str, Any] = (
|
||||
dict(report_config) if isinstance(report_config, dict) else {}
|
||||
)
|
||||
for _bk in REPORT_CONFIG_BOOL_KEYS:
|
||||
if eff_rc.get(_bk) is None:
|
||||
eff_rc.pop(_bk, None)
|
||||
for _k, _v in get_default_report_config().items():
|
||||
if _k not in eff_rc:
|
||||
eff_rc[_k] = _v
|
||||
return eff_rc
|
||||
|
||||
|
||||
def get_default_strategy_config() -> dict[str, Any]:
|
||||
"""策略生成页独立默认(与 ``report_config`` 无关),供前端回填与 PATCH 合并。"""
|
||||
return {
|
||||
@ -225,13 +248,7 @@ def write_competitor_analysis_for_run_dir(
|
||||
except json.JSONDecodeError:
|
||||
meta = None
|
||||
|
||||
eff_rc: dict[str, Any] = (
|
||||
dict(report_config) if isinstance(report_config, dict) else {}
|
||||
)
|
||||
# 任务仅保存部分调参时,补齐默认项(含各 llm_* 开关);显式写入的键不被覆盖
|
||||
for _k, _v in get_default_report_config().items():
|
||||
if _k not in eff_rc:
|
||||
eff_rc[_k] = _v
|
||||
eff_rc = merge_report_config_with_defaults(report_config)
|
||||
all_tx = _flat_comment_texts(comment_rows)
|
||||
suggest_path = run_dir / "keyword_suggest_llm.json"
|
||||
suggest_record: dict[str, Any] = {
|
||||
@ -823,6 +840,9 @@ def build_competitor_brief_for_job(
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
eff_final = merge_report_config_with_defaults(
|
||||
eff if isinstance(eff, dict) else None
|
||||
)
|
||||
return jcr.build_competitor_brief(
|
||||
run_dir=base,
|
||||
keyword=kw,
|
||||
@ -830,7 +850,7 @@ def build_competitor_brief_for_job(
|
||||
search_export_rows=search_export_rows,
|
||||
comment_rows=comment_rows,
|
||||
meta=meta,
|
||||
report_config=eff,
|
||||
report_config=eff_final,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -37,25 +37,10 @@ _REPORT_CONFIG_ALLOWED_KEYS = frozenset(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def validate_report_config_body(value: dict) -> dict:
|
||||
if not isinstance(value, dict):
|
||||
raise serializers.ValidationError("须为 JSON 对象")
|
||||
value = dict(value)
|
||||
value.pop("llm_section_bridges", None)
|
||||
# 已废弃字段:静默丢弃,兼容旧任务 JSON
|
||||
value.pop("comment_focus_words", None)
|
||||
value.pop("comment_scenario_groups", None)
|
||||
value.pop("llm_scenario_group_summaries", None)
|
||||
extra = set(value.keys()) - _REPORT_CONFIG_ALLOWED_KEYS
|
||||
if extra:
|
||||
raise serializers.ValidationError(
|
||||
f"未知字段:{', '.join(sorted(extra))}"
|
||||
)
|
||||
if "llm_comment_sentiment" in value and value["llm_comment_sentiment"] is not None:
|
||||
if not isinstance(value["llm_comment_sentiment"], bool):
|
||||
raise serializers.ValidationError("llm_comment_sentiment 须为 true 或 false")
|
||||
for k in (
|
||||
# 与 ``validate_report_config_body`` 中布尔校验一致;``null`` 视为未设置(须让默认 true/false 生效)
|
||||
REPORT_CONFIG_BOOL_KEYS = frozenset(
|
||||
{
|
||||
"llm_comment_sentiment",
|
||||
"llm_matrix_group_summaries",
|
||||
"llm_price_group_summaries",
|
||||
"llm_promo_group_summaries",
|
||||
@ -66,8 +51,29 @@ def validate_report_config_body(value: dict) -> dict:
|
||||
"chapter8_text_mining_probe_live_llm",
|
||||
"chapter8_text_mining_probe_llm_chunked",
|
||||
"chapter8_text_mining_probe_wordcloud",
|
||||
):
|
||||
if k in value and value[k] is not None and not isinstance(value[k], bool):
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def validate_report_config_body(value: dict) -> dict:
|
||||
if not isinstance(value, dict):
|
||||
raise serializers.ValidationError("须为 JSON 对象")
|
||||
value = dict(value)
|
||||
for _bk in REPORT_CONFIG_BOOL_KEYS:
|
||||
if value.get(_bk) is None:
|
||||
value.pop(_bk, None)
|
||||
value.pop("llm_section_bridges", None)
|
||||
# 已废弃字段:静默丢弃,兼容旧任务 JSON
|
||||
value.pop("comment_focus_words", None)
|
||||
value.pop("comment_scenario_groups", None)
|
||||
value.pop("llm_scenario_group_summaries", None)
|
||||
extra = set(value.keys()) - _REPORT_CONFIG_ALLOWED_KEYS
|
||||
if extra:
|
||||
raise serializers.ValidationError(
|
||||
f"未知字段:{', '.join(sorted(extra))}"
|
||||
)
|
||||
for k in REPORT_CONFIG_BOOL_KEYS:
|
||||
if k in value and not isinstance(value[k], bool):
|
||||
raise serializers.ValidationError(f"{k} 须为 true 或 false")
|
||||
for k in (
|
||||
"chapter8_probe_min_texts",
|
||||
|
||||
33
backend/pipeline/tests/test_report_config_bool_null.py
Normal file
33
backend/pipeline/tests/test_report_config_bool_null.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""report_config 中布尔开关为 JSON null 时不应把 LLM 归纳整段关掉。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from pipeline.jd.runner import merge_report_config_with_defaults
|
||||
from pipeline.serializers import validate_report_config_body
|
||||
|
||||
|
||||
def test_validate_report_config_strips_null_bool_flags() -> None:
|
||||
out = validate_report_config_body(
|
||||
{
|
||||
"llm_matrix_group_summaries": None,
|
||||
"llm_promo_group_summaries": None,
|
||||
}
|
||||
)
|
||||
assert "llm_matrix_group_summaries" not in out
|
||||
assert "llm_promo_group_summaries" not in out
|
||||
|
||||
|
||||
@mock.patch("pipeline.jd.runner.get_default_report_config")
|
||||
def test_merge_report_config_null_bool_gets_default(mock_def: mock.MagicMock) -> None:
|
||||
mock_def.return_value = {
|
||||
"llm_matrix_group_summaries": True,
|
||||
"llm_promo_group_summaries": True,
|
||||
"llm_price_group_summaries": False,
|
||||
}
|
||||
merged = merge_report_config_with_defaults(
|
||||
{"llm_matrix_group_summaries": None, "llm_promo_group_summaries": None}
|
||||
)
|
||||
assert merged["llm_matrix_group_summaries"] is True
|
||||
assert merged["llm_promo_group_summaries"] is True
|
||||
assert merged["llm_price_group_summaries"] is False
|
||||
Loading…
x
Reference in New Issue
Block a user