mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-22 08:01:34 +08:00
feat(pipeline): remove llm section bridges from report generation
Made-with: Cursor
This commit is contained in:
parent
c244a2902e
commit
f994555f9f
@ -33,6 +33,3 @@ CSRF_TRUSTED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
# MA_SKIP_LLM_KEYWORD_SUGGEST=1
|
||||
# MA_ENABLE_LLM_COMMENT_SENTIMENT=1
|
||||
# MA_SKIP_LLM_COMMENT_SENTIMENT=1
|
||||
# 各章标题后插入大模型「衔接分析」(任务 report_config.llm_section_bridges 或本项设为 1)
|
||||
# MA_ENABLE_LLM_SECTION_BRIDGES=1
|
||||
# MA_SKIP_LLM_SECTION_BRIDGES=1
|
||||
|
||||
@ -1787,7 +1787,6 @@ def build_competitor_markdown(
|
||||
"- **用途/场景**:对每条评价独立判断是否命中预设场景词;一条可计入多个场景,统计的是「提及该场景的评价条数」而非用户数。",
|
||||
"- **用户画像(第八章)**:正负面粗判含**口语短语**级摘录;关注词与场景**仅按细类**以条形图展示(场景图为**占该细类有效文本比例 %**);见 §8.3~8.4。",
|
||||
"- **细类划分(§5~§8)**:**仅**依据合并表 ``detail_category_path``;该列为空或无法解析出可读细类段的 SKU **不参与**竞品矩阵与按细类评价统计(相关评价条亦**不进入**按细类图表)。",
|
||||
"- **各章衔接(可选)**:若任务配置 ``llm_section_bridges``(或部署侧环境变量启用),则在「## 一」至「## 九」各章二级标题后插入大模型撰写的**衔接分析**段落,便于阅读过渡;**定量结论仍以正文表格与摘要 JSON 为准**。",
|
||||
"- **检索结果规模**:来自京东 PC 搜索返回的「结果条数」类指标,表示平台侧申报的匹配数量级,**不等于**动销、库存或独立 SKU 数。",
|
||||
"",
|
||||
"### 1.4 主要局限",
|
||||
|
||||
@ -6,7 +6,6 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@ -71,25 +70,6 @@ def merge_llm_report_with_rules_charts(llm_md: str, rules_md: str) -> str:
|
||||
return merge_llm_supplement_with_rules_report(llm_md, rules_md)
|
||||
|
||||
|
||||
def inject_section_bridges_into_markdown(md: str, bridges: dict[str, str]) -> str:
|
||||
"""
|
||||
在「## 一、」…「## 九、」各章标题行之后插入 ``#### 衔接分析(大模型)`` 段落。
|
||||
自第九章向前替换,避免多次插入导致偏移错位。
|
||||
"""
|
||||
out = md
|
||||
for key in "九八七六五四三二一":
|
||||
content = (bridges.get(key) or "").strip()
|
||||
if not content:
|
||||
continue
|
||||
pat = re.compile(rf"^(## {key}、[^\n]*)\n", re.MULTILINE)
|
||||
|
||||
def _repl(m: re.Match[str], _c: str = content) -> str:
|
||||
return m.group(0) + "\n#### 衔接分析(大模型)\n\n" + _c + "\n\n"
|
||||
|
||||
out = pat.sub(_repl, out, count=1)
|
||||
return out
|
||||
|
||||
|
||||
def _flat_comment_texts(comment_rows: list[dict[str, str]]) -> list[str]:
|
||||
"""全部非空评价正文(与报告统计同源)。"""
|
||||
out: list[str] = []
|
||||
@ -169,7 +149,6 @@ def get_default_report_config() -> dict[str, Any]:
|
||||
jcr, _ = _jd_crawler_modules()
|
||||
return {
|
||||
"llm_comment_sentiment": True,
|
||||
"llm_section_bridges": True,
|
||||
"llm_matrix_group_summaries": True,
|
||||
"llm_comment_group_summaries": True,
|
||||
"llm_price_group_summaries": True,
|
||||
@ -528,48 +507,6 @@ def write_competitor_analysis_for_run_dir(
|
||||
llm_comment_groups_section_md=llm_comment_gr_md or None,
|
||||
)
|
||||
|
||||
bridge_record: dict[str, Any] = {
|
||||
"schema_version": 1,
|
||||
"attempted": False,
|
||||
}
|
||||
skip_bridge = os.environ.get(
|
||||
"MA_SKIP_LLM_SECTION_BRIDGES", ""
|
||||
).strip().lower() in ("1", "true", "yes")
|
||||
env_bridge = os.environ.get(
|
||||
"MA_ENABLE_LLM_SECTION_BRIDGES", ""
|
||||
).strip().lower() in ("1", "true", "yes")
|
||||
want_bridge = bool(eff_rc.get("llm_section_bridges")) or env_bridge
|
||||
if want_bridge and not skip_bridge:
|
||||
from .llm_generate import (
|
||||
generate_section_bridges_llm,
|
||||
split_competitor_report_for_bridges,
|
||||
)
|
||||
|
||||
parts = split_competitor_report_for_bridges(md)
|
||||
if parts:
|
||||
bridge_record["attempted"] = True
|
||||
try:
|
||||
bridges = generate_section_bridges_llm(
|
||||
keyword=kw, brief=brief_final, sections=parts
|
||||
)
|
||||
bridge_record["keys_received"] = sorted(bridges.keys())
|
||||
md = inject_section_bridges_into_markdown(md, bridges)
|
||||
bridge_record["ok"] = True
|
||||
except Exception as e:
|
||||
bridge_record["ok"] = False
|
||||
bridge_record["error"] = str(e)
|
||||
else:
|
||||
bridge_record["skipped"] = "no_h2_sections_matched"
|
||||
elif skip_bridge:
|
||||
bridge_record["skipped"] = "MA_SKIP_LLM_SECTION_BRIDGES"
|
||||
elif not want_bridge:
|
||||
bridge_record["skipped"] = "not_enabled"
|
||||
|
||||
(run_dir / "section_bridge_llm.json").write_text(
|
||||
json.dumps(bridge_record, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
out_md = run_dir / "competitor_analysis.md"
|
||||
out_md.write_text(md, encoding="utf-8")
|
||||
return run_dir
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
- §6 后:``generate_price_group_summaries_llm``
|
||||
- §8.2:``generate_comment_sentiment_analysis_llm``
|
||||
- §8末细类评价:``generate_comment_group_summaries_llm``
|
||||
- 各章衔接(一~九):``generate_section_bridges_llm``(基于无 LLM 插入的规则稿切分)
|
||||
- §8.5 类全文补充(独立长文):``generate_competitor_report_markdown_llm``
|
||||
|
||||
cd backend
|
||||
@ -124,7 +123,7 @@ def main() -> None:
|
||||
"--only",
|
||||
type=str,
|
||||
default="",
|
||||
help="逗号分隔子集:sentiment,matrix,price,comment_groups,bridges,report_supplement",
|
||||
help="逗号分隔子集:sentiment,matrix,price,comment_groups,report_supplement",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preview-chars",
|
||||
@ -144,7 +143,6 @@ def main() -> None:
|
||||
"matrix",
|
||||
"price",
|
||||
"comment_groups",
|
||||
"bridges",
|
||||
"report_supplement",
|
||||
}
|
||||
if not only:
|
||||
@ -166,8 +164,6 @@ def main() -> None:
|
||||
generate_competitor_report_markdown_llm,
|
||||
generate_matrix_group_summaries_llm,
|
||||
generate_price_group_summaries_llm,
|
||||
generate_section_bridges_llm,
|
||||
split_competitor_report_for_bridges,
|
||||
)
|
||||
|
||||
if "sentiment" in only:
|
||||
@ -256,18 +252,8 @@ def main() -> None:
|
||||
if not args.live:
|
||||
print(f" payload groups={len(pl_cg)}", flush=True)
|
||||
|
||||
brief = jcr.build_competitor_brief(
|
||||
run_dir=run_dir,
|
||||
keyword=keyword,
|
||||
merged_rows=merged,
|
||||
search_export_rows=search_rows,
|
||||
comment_rows=comment_rows,
|
||||
meta=meta,
|
||||
report_config=eff_rc,
|
||||
)
|
||||
|
||||
if "bridges" in only:
|
||||
md_base = jcr.build_competitor_markdown(
|
||||
if "report_supplement" in only:
|
||||
brief = jcr.build_competitor_brief(
|
||||
run_dir=run_dir,
|
||||
keyword=keyword,
|
||||
merged_rows=merged,
|
||||
@ -275,29 +261,7 @@ def main() -> None:
|
||||
comment_rows=comment_rows,
|
||||
meta=meta,
|
||||
report_config=eff_rc,
|
||||
llm_sentiment_section_md=None,
|
||||
llm_matrix_section_md=None,
|
||||
llm_price_groups_section_md=None,
|
||||
llm_comment_groups_section_md=None,
|
||||
)
|
||||
parts = split_competitor_report_for_bridges(md_base)
|
||||
|
||||
def _br() -> str:
|
||||
m = generate_section_bridges_llm(
|
||||
keyword=keyword, brief=brief, sections=parts
|
||||
)
|
||||
return json.dumps(m, ensure_ascii=False, indent=2)
|
||||
|
||||
_run_one(
|
||||
"各章衔接 bridges(JSON 键一~九)",
|
||||
_br,
|
||||
live=args.live,
|
||||
preview_chars=min(args.preview_chars, 1200),
|
||||
)
|
||||
if not args.live:
|
||||
print(f" sections keys={sorted(parts.keys())}", flush=True)
|
||||
|
||||
if "report_supplement" in only:
|
||||
|
||||
def _rp() -> str:
|
||||
return generate_competitor_report_markdown_llm(brief, keyword)
|
||||
|
||||
@ -17,7 +17,6 @@ from .models import (
|
||||
_REPORT_CONFIG_ALLOWED_KEYS = frozenset(
|
||||
{
|
||||
"llm_comment_sentiment",
|
||||
"llm_section_bridges",
|
||||
"llm_matrix_group_summaries",
|
||||
"llm_price_group_summaries",
|
||||
"llm_comment_group_summaries",
|
||||
@ -31,6 +30,8 @@ _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)
|
||||
extra = set(value.keys()) - _REPORT_CONFIG_ALLOWED_KEYS
|
||||
if extra:
|
||||
raise serializers.ValidationError(
|
||||
@ -39,9 +40,6 @@ def validate_report_config_body(value: dict) -> dict:
|
||||
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")
|
||||
if "llm_section_bridges" in value and value["llm_section_bridges"] is not None:
|
||||
if not isinstance(value["llm_section_bridges"], bool):
|
||||
raise serializers.ValidationError("llm_section_bridges 须为 true 或 false")
|
||||
for k in (
|
||||
"llm_matrix_group_summaries",
|
||||
"llm_price_group_summaries",
|
||||
@ -61,7 +59,6 @@ _ARTIFACT_FILES: tuple[tuple[str, str], ...] = (
|
||||
("comments", "comments_flat.csv"),
|
||||
("detail_ware", "detail_ware_export.csv"),
|
||||
("report", "competitor_analysis.md"),
|
||||
("section_bridge_llm", "section_bridge_llm.json"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -17,7 +17,6 @@ function splitTriggers(text) {
|
||||
*/
|
||||
const REPORT_CONFIG_PASSTHROUGH_BOOL_KEYS = [
|
||||
'llm_comment_sentiment',
|
||||
'llm_section_bridges',
|
||||
'llm_matrix_group_summaries',
|
||||
'llm_price_group_summaries',
|
||||
'llm_comment_group_summaries',
|
||||
|
||||
@ -278,7 +278,7 @@ watch(
|
||||
<h3 class="report-config-title">报告里的评价统计怎么算</h3>
|
||||
<p class="hint-top report-config-hint">
|
||||
关注词、场景词组、外部市场表等<strong>可以不改</strong>:留空并保存即沿用内置规则。大模型相关布尔项(如
|
||||
<code>llm_comment_sentiment</code>、<code>llm_section_bridges</code>)不再单独占勾选框:若任务里已有,会在保存时保留;要改请展开「高级 JSON」。
|
||||
<code>llm_comment_sentiment</code>)不再单独占勾选框:若任务里已有,会在保存时保留;要改请展开「高级 JSON」。
|
||||
</p>
|
||||
<div class="report-config-actions">
|
||||
<button
|
||||
@ -315,7 +315,7 @@ watch(
|
||||
<summary>高级:用 JSON 编辑(一般不需要)</summary>
|
||||
<p class="rc-help">
|
||||
打开时会根据上面表单生成内容;改完后点「写回表单」再保存。可在此加入
|
||||
<code>llm_comment_sentiment</code>、<code>llm_section_bridges</code>、<code>llm_matrix_group_summaries</code>
|
||||
<code>llm_comment_sentiment</code>、<code>llm_matrix_group_summaries</code>
|
||||
等布尔字段(须为 <code>true</code>/<code>false</code>)。页顶「重新生成报告」默认已使用
|
||||
<code>generator:"llm"</code>;若只要规则稿请勾选「本次仅用规则引擎」。
|
||||
</p>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user