mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-24 17:21:40 +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 ..csv.schema import MERGED_FIELD_TO_CSV_HEADER
|
||||||
from ..models import PipelineJob
|
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:
|
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",
|
"yes",
|
||||||
):
|
):
|
||||||
return False
|
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]:
|
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]:
|
def get_default_strategy_config() -> dict[str, Any]:
|
||||||
"""策略生成页独立默认(与 ``report_config`` 无关),供前端回填与 PATCH 合并。"""
|
"""策略生成页独立默认(与 ``report_config`` 无关),供前端回填与 PATCH 合并。"""
|
||||||
return {
|
return {
|
||||||
@ -225,13 +248,7 @@ def write_competitor_analysis_for_run_dir(
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
meta = None
|
meta = None
|
||||||
|
|
||||||
eff_rc: dict[str, Any] = (
|
eff_rc = merge_report_config_with_defaults(report_config)
|
||||||
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
|
|
||||||
all_tx = _flat_comment_texts(comment_rows)
|
all_tx = _flat_comment_texts(comment_rows)
|
||||||
suggest_path = run_dir / "keyword_suggest_llm.json"
|
suggest_path = run_dir / "keyword_suggest_llm.json"
|
||||||
suggest_record: dict[str, Any] = {
|
suggest_record: dict[str, Any] = {
|
||||||
@ -823,6 +840,9 @@ def build_competitor_brief_for_job(
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
eff_final = merge_report_config_with_defaults(
|
||||||
|
eff if isinstance(eff, dict) else None
|
||||||
|
)
|
||||||
return jcr.build_competitor_brief(
|
return jcr.build_competitor_brief(
|
||||||
run_dir=base,
|
run_dir=base,
|
||||||
keyword=kw,
|
keyword=kw,
|
||||||
@ -830,7 +850,7 @@ def build_competitor_brief_for_job(
|
|||||||
search_export_rows=search_export_rows,
|
search_export_rows=search_export_rows,
|
||||||
comment_rows=comment_rows,
|
comment_rows=comment_rows,
|
||||||
meta=meta,
|
meta=meta,
|
||||||
report_config=eff,
|
report_config=eff_final,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -37,25 +37,10 @@ _REPORT_CONFIG_ALLOWED_KEYS = frozenset(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 与 ``validate_report_config_body`` 中布尔校验一致;``null`` 视为未设置(须让默认 true/false 生效)
|
||||||
def validate_report_config_body(value: dict) -> dict:
|
REPORT_CONFIG_BOOL_KEYS = frozenset(
|
||||||
if not isinstance(value, dict):
|
{
|
||||||
raise serializers.ValidationError("须为 JSON 对象")
|
"llm_comment_sentiment",
|
||||||
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 (
|
|
||||||
"llm_matrix_group_summaries",
|
"llm_matrix_group_summaries",
|
||||||
"llm_price_group_summaries",
|
"llm_price_group_summaries",
|
||||||
"llm_promo_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_live_llm",
|
||||||
"chapter8_text_mining_probe_llm_chunked",
|
"chapter8_text_mining_probe_llm_chunked",
|
||||||
"chapter8_text_mining_probe_wordcloud",
|
"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")
|
raise serializers.ValidationError(f"{k} 须为 true 或 false")
|
||||||
for k in (
|
for k in (
|
||||||
"chapter8_probe_min_texts",
|
"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