mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-25 01:34:47 +08:00
feat(pipeline): 独立策略稿 LLM 入参快照与 resolve 复用
抽取 resolve_strategy_draft_llm_input_snapshot 与 _build_strategy_draft_llm_payload_and_user,与 generate_strategy_draft_markdown_llm 首档一致。增强 dump_strategy_llm_input_md:支持 --run-dir、表单 JSON、默认落盘到 run_dir;Django 设置改为 config.settings。 Made-with: Cursor
This commit is contained in:
parent
3dac5b95de
commit
991dec3c4b
@ -1,11 +1,25 @@
|
|||||||
"""
|
"""
|
||||||
导出与「策略大模型润色」一次调用一致的完整输入,生成 Markdown(供对照)。
|
导出「独立策略稿 · 大模型润色」一次调用与生产一致的完整入参(不请求网关)。
|
||||||
|
|
||||||
用法(在项目 backend 目录)::
|
与 ``generate_strategy_draft_markdown_llm`` / ``resolve_strategy_draft_llm_input_snapshot``
|
||||||
|
使用相同的截断阶梯与 ``payload`` 字段(含 ``strategy_decisions_substantive``)。
|
||||||
|
|
||||||
|
用法(在 backend 目录)::
|
||||||
|
|
||||||
|
# 按数据库任务(默认取最近成功任务;可指定 job-id)
|
||||||
python -m pipeline.demos.dump_strategy_llm_input_md [--job-id 12] [--matrix-index 0]
|
python -m pipeline.demos.dump_strategy_llm_input_md [--job-id 12] [--matrix-index 0]
|
||||||
|
|
||||||
若未指定 --matrix-index,则默认收窄到第一个矩阵分组(与「选第一个细类」等效)。
|
# 仅磁盘 run_dir(无需 PipelineJob;job_id 写 0 进 JSON,仅影响底稿抬头占位)
|
||||||
|
python -m pipeline.demos.dump_strategy_llm_input_md --run-dir \"D:/.../pipeline_runs/某批次\"
|
||||||
|
|
||||||
|
# 与线上一致:从文件载入当时提交的 strategy_decisions
|
||||||
|
python -m pipeline.demos.dump_strategy_llm_input_md --run-dir \"...\" --decisions-json decisions.json
|
||||||
|
|
||||||
|
# 指定输出
|
||||||
|
python -m pipeline.demos.dump_strategy_llm_input_md --run-dir \"...\" -o path/to/snap.md
|
||||||
|
|
||||||
|
# 只打印摘要、不写文件
|
||||||
|
python -m pipeline.demos.dump_strategy_llm_input_md --run-dir \"...\" --no-md
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -14,12 +28,12 @@ import json
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
# Django
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "market_assistant.settings")
|
|
||||||
|
|
||||||
|
|
||||||
def _strategy_decisions_empty() -> dict:
|
def _strategy_decisions_empty() -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"product_role": "",
|
"product_role": "",
|
||||||
"time_horizon": "",
|
"time_horizon": "",
|
||||||
@ -43,6 +57,13 @@ def _strategy_decisions_empty() -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_decisions(base: dict[str, Any], overlay: dict[str, Any] | None) -> dict[str, Any]:
|
||||||
|
out = dict(base)
|
||||||
|
if isinstance(overlay, dict):
|
||||||
|
out.update(overlay)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
import django
|
import django
|
||||||
|
|
||||||
@ -53,12 +74,11 @@ def main() -> int:
|
|||||||
from pipeline.jd.runner import build_competitor_brief_for_job
|
from pipeline.jd.runner import build_competitor_brief_for_job
|
||||||
from pipeline.llm.generate_strategy import (
|
from pipeline.llm.generate_strategy import (
|
||||||
STRATEGY_SYSTEM,
|
STRATEGY_SYSTEM,
|
||||||
STRATEGY_USER_PREFIX,
|
resolve_strategy_draft_llm_input_snapshot,
|
||||||
_omit_ch8_probe_wordchart_fields,
|
_min_strategy_completion_tokens,
|
||||||
_truncate_strategy_narrative,
|
|
||||||
)
|
)
|
||||||
|
from pipeline.llm.llm_client import estimate_chat_input_tokens
|
||||||
from pipeline.models import JobStatus, PipelineJob
|
from pipeline.models import JobStatus, PipelineJob
|
||||||
from pipeline.reporting.brief_compact import compact_brief_for_llm
|
|
||||||
from pipeline.reporting.brief_strategy_scope import (
|
from pipeline.reporting.brief_strategy_scope import (
|
||||||
filter_brief_for_strategy_matrix_group,
|
filter_brief_for_strategy_matrix_group,
|
||||||
list_matrix_groups_for_api,
|
list_matrix_groups_for_api,
|
||||||
@ -67,13 +87,16 @@ def main() -> int:
|
|||||||
load_report_matrix_group_evidence_markdown,
|
load_report_matrix_group_evidence_markdown,
|
||||||
)
|
)
|
||||||
from pipeline.reporting.report_strategy_excerpt import load_report_strategy_excerpt
|
from pipeline.reporting.report_strategy_excerpt import load_report_strategy_excerpt
|
||||||
from pipeline.reporting.strategy_draft import (
|
|
||||||
build_strategy_draft_markdown,
|
|
||||||
report_uses_chapter8_text_mining_probe,
|
|
||||||
)
|
|
||||||
|
|
||||||
p = argparse.ArgumentParser()
|
p = argparse.ArgumentParser(description=__doc__)
|
||||||
p.add_argument("--job-id", type=int, default=None)
|
src = p.add_mutually_exclusive_group()
|
||||||
|
src.add_argument("--job-id", type=int, default=None, help="PipelineJob 主键")
|
||||||
|
src.add_argument(
|
||||||
|
"--run-dir",
|
||||||
|
type=Path,
|
||||||
|
default=None,
|
||||||
|
help="运行目录(与 job.run_dir 相同结构;与 --job-id 二选一)",
|
||||||
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--matrix-index",
|
"--matrix-index",
|
||||||
type=int,
|
type=int,
|
||||||
@ -85,40 +108,89 @@ def main() -> int:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="与 --matrix-index -1 相同:不收窄 brief、不抽细类报告节选",
|
help="与 --matrix-index -1 相同:不收窄 brief、不抽细类报告节选",
|
||||||
)
|
)
|
||||||
|
p.add_argument(
|
||||||
|
"--decisions-json",
|
||||||
|
type=Path,
|
||||||
|
default=None,
|
||||||
|
help="覆盖 strategy_decisions 的 JSON 对象文件(与线上一致时传入)",
|
||||||
|
)
|
||||||
|
p.add_argument(
|
||||||
|
"--business-notes",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
help="与接口 business_notes 一致的业务备注",
|
||||||
|
)
|
||||||
|
p.add_argument(
|
||||||
|
"--snapshot-job-id",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="写入 payload.job_id(仅 --run-dir 时有效;默认 0)",
|
||||||
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"-o",
|
"-o",
|
||||||
"--output",
|
"--output",
|
||||||
type=str,
|
type=Path,
|
||||||
default=None,
|
default=None,
|
||||||
help="输出 .md 路径(默认:docs/planning/策略生成-LLM全量输入快照.md)",
|
help="输出 .md 路径(默认:run_dir/strategy_draft_llm_input_snapshot.md 或 docs/planning/…)",
|
||||||
|
)
|
||||||
|
p.add_argument(
|
||||||
|
"--no-md",
|
||||||
|
action="store_true",
|
||||||
|
help="不写入 Markdown,仅打印控制台摘要",
|
||||||
)
|
)
|
||||||
args = p.parse_args()
|
args = p.parse_args()
|
||||||
|
|
||||||
backend_dir = Path(__file__).resolve().parents[2]
|
backend_dir = Path(__file__).resolve().parents[2]
|
||||||
repo_root = backend_dir.parent
|
repo_root = backend_dir.parent
|
||||||
default_out = (
|
default_docs_out = (
|
||||||
repo_root / "docs" / "planning" / "策略生成-LLM全量输入快照.md"
|
repo_root / "docs" / "planning" / "策略生成-LLM全量输入快照.md"
|
||||||
)
|
)
|
||||||
out_path = Path(args.output) if args.output else default_out
|
|
||||||
|
|
||||||
job_id = args.job_id
|
run_dir_s: str
|
||||||
if job_id:
|
kw: str
|
||||||
job = PipelineJob.objects.filter(pk=job_id).first()
|
rc: dict[str, Any] | None
|
||||||
|
job_id: int
|
||||||
|
|
||||||
|
if args.run_dir is not None:
|
||||||
|
run_dir_p = args.run_dir.expanduser().resolve()
|
||||||
|
if not run_dir_p.is_dir():
|
||||||
|
print(f"run_dir 不存在: {run_dir_p}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
rc_path = run_dir_p / "effective_report_config.json"
|
||||||
|
meta_path = run_dir_p / "run_meta.json"
|
||||||
|
if not rc_path.is_file() or not meta_path.is_file():
|
||||||
|
print("缺少 effective_report_config.json 或 run_meta.json", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
rc = json.loads(rc_path.read_text(encoding="utf-8"))
|
||||||
|
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
||||||
|
kw = (meta.get("keyword") or "").strip()
|
||||||
|
if not kw:
|
||||||
|
print("run_meta 无 keyword", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
run_dir_s = str(run_dir_p)
|
||||||
|
job_id = int(args.snapshot_job_id) if args.snapshot_job_id is not None else 0
|
||||||
else:
|
else:
|
||||||
job = (
|
jid = args.job_id
|
||||||
PipelineJob.objects.filter(status=JobStatus.SUCCESS)
|
if jid:
|
||||||
.exclude(run_dir="")
|
job = PipelineJob.objects.filter(pk=jid).first()
|
||||||
.order_by("-id")
|
else:
|
||||||
.first()
|
job = (
|
||||||
)
|
PipelineJob.objects.filter(status=JobStatus.SUCCESS)
|
||||||
if not job:
|
.exclude(run_dir="")
|
||||||
print("无可用成功任务(需 run_dir 非空)", file=sys.stderr)
|
.order_by("-id")
|
||||||
return 1
|
.first()
|
||||||
|
)
|
||||||
|
if not job:
|
||||||
|
print("无可用任务:请指定 --job-id 或 --run-dir", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
run_dir_s = job.run_dir
|
||||||
|
kw = job.keyword
|
||||||
|
rc = job.report_config if isinstance(job.report_config, dict) else None
|
||||||
|
job_id = job.id
|
||||||
|
|
||||||
rc = job.report_config if isinstance(job.report_config, dict) else None
|
|
||||||
brief = build_competitor_brief_for_job(
|
brief = build_competitor_brief_for_job(
|
||||||
job.run_dir,
|
run_dir_s,
|
||||||
job.keyword,
|
kw,
|
||||||
report_config=rc,
|
report_config=rc,
|
||||||
)
|
)
|
||||||
matrix_groups = list_matrix_groups_for_api(brief)
|
matrix_groups = list_matrix_groups_for_api(brief)
|
||||||
@ -140,91 +212,106 @@ def main() -> int:
|
|||||||
|
|
||||||
gen_at = timezone.now().isoformat()
|
gen_at = timezone.now().isoformat()
|
||||||
sd = _strategy_decisions_empty()
|
sd = _strategy_decisions_empty()
|
||||||
rules_md = build_strategy_draft_markdown(
|
if args.decisions_json is not None:
|
||||||
job_id=job.id,
|
dp = args.decisions_json.expanduser().resolve()
|
||||||
keyword=job.keyword,
|
if not dp.is_file():
|
||||||
brief=brief,
|
print(f"decisions-json 不存在: {dp}", file=sys.stderr)
|
||||||
business_notes="",
|
return 1
|
||||||
generated_at_iso=gen_at,
|
try:
|
||||||
strategy_decisions=sd,
|
loaded = json.loads(dp.read_text(encoding="utf-8"))
|
||||||
report_config=rc,
|
except json.JSONDecodeError as e:
|
||||||
)
|
print(f"decisions-json 非合法 JSON: {e}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if not isinstance(loaded, dict):
|
||||||
|
print("decisions-json 根须为 JSON 对象", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
sd = _merge_decisions(sd, loaded)
|
||||||
|
|
||||||
excerpt_raw, excerpt_src = load_report_strategy_excerpt(job.run_dir)
|
excerpt_raw, excerpt_src = load_report_strategy_excerpt(run_dir_s)
|
||||||
excerpt_raw = (excerpt_raw or "").strip()
|
excerpt_raw = (excerpt_raw or "").strip()
|
||||||
|
|
||||||
evidence_md = ""
|
evidence_md = ""
|
||||||
evidence_src = "none"
|
evidence_src = "none"
|
||||||
if scoped_label:
|
if scoped_label:
|
||||||
evidence_md, evidence_src = load_report_matrix_group_evidence_markdown(
|
evidence_md, evidence_src = load_report_matrix_group_evidence_markdown(
|
||||||
job.run_dir,
|
run_dir_s,
|
||||||
scoped_label,
|
scoped_label,
|
||||||
)
|
)
|
||||||
|
|
||||||
compact_max = 80_000
|
payload, user_body, tier_note = resolve_strategy_draft_llm_input_snapshot(
|
||||||
excerpt_max = 24_000
|
job_id=job_id,
|
||||||
compact = compact_brief_for_llm(brief, max_chars=compact_max)
|
keyword=kw,
|
||||||
if report_uses_chapter8_text_mining_probe(rc):
|
brief=brief,
|
||||||
compact = dict(compact)
|
business_notes=(args.business_notes or "").strip(),
|
||||||
_omit_ch8_probe_wordchart_fields(compact)
|
generated_at_iso=gen_at,
|
||||||
ex = (
|
strategy_decisions=sd,
|
||||||
_truncate_strategy_narrative(excerpt_raw, excerpt_max)
|
report_strategy_excerpt=excerpt_raw or None,
|
||||||
if excerpt_raw
|
report_matrix_group_evidence_md=evidence_md.strip() or None,
|
||||||
else ""
|
report_config=rc,
|
||||||
)
|
|
||||||
ev_max = min(24_000, max(3_000, excerpt_max + excerpt_max // 2))
|
|
||||||
gm = (
|
|
||||||
_truncate_strategy_narrative(evidence_md.strip(), ev_max)
|
|
||||||
if evidence_md
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
payload: dict = {
|
min_comp = _min_strategy_completion_tokens()
|
||||||
"job_id": job.id,
|
est_in = estimate_chat_input_tokens(STRATEGY_SYSTEM, user_body)
|
||||||
"keyword": job.keyword,
|
|
||||||
"generated_at_iso": gen_at,
|
|
||||||
"strategy_decisions": sd,
|
|
||||||
"business_notes": "",
|
|
||||||
"structured_brief": compact,
|
|
||||||
"rules_draft_markdown": rules_md,
|
|
||||||
"report_strategy_excerpt": ex,
|
|
||||||
"report_matrix_group_evidence_md": gm,
|
|
||||||
"chapter8_text_mining_probe": bool(
|
|
||||||
report_uses_chapter8_text_mining_probe(rc)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
if report_uses_chapter8_text_mining_probe(rc):
|
|
||||||
payload["structured_brief_omission_note"] = (
|
|
||||||
"已启用第八章文本挖掘(探针为主):structured_brief 已省略「关注词/场景子串计数」、按细类 feedback 中的 focus_keyword_hits/scenarios_top、"
|
|
||||||
"`strategy_hints` 等;全量 brief 已**不再**包含 ``comment_sentiment_lexicon``。**不得**再以星级子集预设口语短语计数或预设场景占比作为论据。"
|
|
||||||
"用户与评论侧须依报告 §8 文本挖掘归纳及 `report_matrix_group_evidence_md`;**促销、满减、券价差**须与报告第六章、`price_promotion_signals` 及下方 `report_strategy_excerpt`(第九章)对齐,不得省略报告已写明的活动建议。"
|
|
||||||
)
|
|
||||||
|
|
||||||
user_body = STRATEGY_USER_PREFIX + json.dumps(payload, ensure_ascii=False)
|
|
||||||
full_chars = len(STRATEGY_SYSTEM) + len(user_body)
|
full_chars = len(STRATEGY_SYSTEM) + len(user_body)
|
||||||
|
rd = payload.get("rules_draft_markdown")
|
||||||
|
rd_len = len(rd) if isinstance(rd, str) else 0
|
||||||
|
sb = payload.get("structured_brief")
|
||||||
|
sb_json_len = len(json.dumps(sb, ensure_ascii=False)) if sb else 0
|
||||||
|
|
||||||
|
print("run_dir:", run_dir_s)
|
||||||
|
print("job_id (payload):", job_id)
|
||||||
|
print("keyword:", kw)
|
||||||
|
print("tier:", tier_note)
|
||||||
|
print("MA_STRATEGY_MIN_COMPLETION_TOKENS:", min_comp)
|
||||||
|
print("strategy_decisions_substantive:", payload.get("strategy_decisions_substantive"))
|
||||||
|
print("matrix scope:", f"{matrix_index} → 「{scoped_label}」" if scoped_label else "未收窄")
|
||||||
|
print("report_strategy_excerpt:", excerpt_src, "raw chars:", len(excerpt_raw))
|
||||||
|
print("report_matrix_group_evidence:", evidence_src, "chars in payload:", len(payload.get("report_matrix_group_evidence_md") or ""))
|
||||||
|
print("STRATEGY_SYSTEM chars:", len(STRATEGY_SYSTEM))
|
||||||
|
print("user chars:", len(user_body))
|
||||||
|
print("total chars:", full_chars)
|
||||||
|
print("estimate_chat_input_tokens:", est_in)
|
||||||
|
print("structured_brief JSON len:", sb_json_len)
|
||||||
|
print("rules_draft_markdown len (in payload):", rd_len)
|
||||||
|
|
||||||
|
if args.no_md:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.output is not None:
|
||||||
|
out_path = args.output.expanduser().resolve()
|
||||||
|
elif args.run_dir is not None:
|
||||||
|
out_path = args.run_dir.expanduser().resolve() / "strategy_draft_llm_input_snapshot.md"
|
||||||
|
else:
|
||||||
|
out_path = default_docs_out
|
||||||
|
|
||||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
lines: list[str] = [
|
lines: list[str] = [
|
||||||
"# 策略生成 · 大模型一次调用的「全量输入」快照",
|
"# 独立策略稿 · 大模型一次调用的「全量输入」快照",
|
||||||
"",
|
"",
|
||||||
"> **生成方式**:本机 `pipeline.demos.dump_strategy_llm_input_md` 按与 "
|
"> **生成方式**:`pipeline.demos.dump_strategy_llm_input_md` 调用 "
|
||||||
"`generate_strategy_draft_markdown_llm` 相同的 payload 组装逻辑导出。",
|
"`resolve_strategy_draft_llm_input_snapshot`,与 ``generate_strategy_draft_markdown_llm`` "
|
||||||
"> **与线上一致性**:与真实接口相比,表单字段此处均为空默认;"
|
"首档通过的 ``payload`` / ``user`` 一致(不请求网关)。",
|
||||||
"你只要把当时提交的 `strategy_decisions` / `business_notes` 代入即与线上等价。",
|
"> **与线上一致**:将当时 POST 的 `strategy_decisions`、`business_notes`、`strategy_matrix_group_index` "
|
||||||
|
"与本脚本参数对齐即可复现。",
|
||||||
"",
|
"",
|
||||||
"## 快照元数据",
|
"## 快照元数据",
|
||||||
"",
|
"",
|
||||||
f"- **任务 ID**:{job.id}",
|
f"- **任务 ID(payload.job_id)**:{job_id}",
|
||||||
f"- **关键词**:{job.keyword}",
|
f"- **关键词**:{kw}",
|
||||||
f"- **run_dir**:`{job.run_dir}`",
|
f"- **run_dir**:`{run_dir_s}`",
|
||||||
f"- **矩阵分组**:{matrix_index if matrix_index is not None and matrix_index >= 0 else '未收窄(全部分类)'}{f' → 「{scoped_label}」' if scoped_label else ''}",
|
f"- **矩阵分组**:{matrix_index if matrix_index is not None and matrix_index >= 0 else '未收窄(全部分类)'}{f' → 「{scoped_label}」' if scoped_label else ''}",
|
||||||
f"- **本任务可选细类(节选)**:{group_names[:20]}{'…' if len(group_names) > 20 else ''}",
|
f"- **本任务可选细类(节选)**:{group_names[:20]}{'…' if len(group_names) > 20 else ''}",
|
||||||
f"- **第九章节选来源**:{excerpt_src},约 {len(ex)} 字符",
|
f"- **选用档位**:{tier_note}",
|
||||||
f"- **细类报告节选来源**:{evidence_src},约 {len(gm)} 字符",
|
f"- **strategy_decisions_substantive**:{payload.get('strategy_decisions_substantive')!r}",
|
||||||
|
f"- **第九章节选来源**:{excerpt_src}",
|
||||||
|
f"- **细类报告节选来源**:{evidence_src}",
|
||||||
|
f"- **MA_STRATEGY_MIN_COMPLETION_TOKENS**:{min_comp}",
|
||||||
f"- **System 字符数**:{len(STRATEGY_SYSTEM)}",
|
f"- **System 字符数**:{len(STRATEGY_SYSTEM)}",
|
||||||
f"- **User 消息字符数**:{len(user_body)}",
|
f"- **User 消息字符数**:{len(user_body)}",
|
||||||
f"- **合计约**:{full_chars} 字符",
|
f"- **合计约**:{full_chars} 字符",
|
||||||
|
f"- **estimate_chat_input_tokens(项目内启发式)**:{est_in}",
|
||||||
|
f"- **structured_brief 序列化长度**:{sb_json_len}",
|
||||||
|
f"- **rules_draft_markdown(payload 内)字符数**:{rd_len}",
|
||||||
"",
|
"",
|
||||||
"---",
|
"---",
|
||||||
"",
|
"",
|
||||||
|
|||||||
@ -45,6 +45,7 @@ from .generate_strategy import (
|
|||||||
STRATEGY_USER_PREFIX,
|
STRATEGY_USER_PREFIX,
|
||||||
generate_strategy_draft_markdown_llm,
|
generate_strategy_draft_markdown_llm,
|
||||||
generate_strategy_opportunities_llm,
|
generate_strategy_opportunities_llm,
|
||||||
|
resolve_strategy_draft_llm_input_snapshot,
|
||||||
strategy_decisions_substantive,
|
strategy_decisions_substantive,
|
||||||
)
|
)
|
||||||
from .llm_client import call_llm as _call_llm
|
from .llm_client import call_llm as _call_llm
|
||||||
@ -81,6 +82,7 @@ __all__ = [
|
|||||||
"generate_section_bridges_llm",
|
"generate_section_bridges_llm",
|
||||||
"generate_strategy_draft_markdown_llm",
|
"generate_strategy_draft_markdown_llm",
|
||||||
"generate_strategy_opportunities_llm",
|
"generate_strategy_opportunities_llm",
|
||||||
|
"resolve_strategy_draft_llm_input_snapshot",
|
||||||
"split_competitor_report_for_bridges",
|
"split_competitor_report_for_bridges",
|
||||||
"strategy_decisions_substantive",
|
"strategy_decisions_substantive",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -200,6 +200,205 @@ STRATEGY_USER_PREFIX = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_strategy_draft_llm_payload_and_user(
|
||||||
|
*,
|
||||||
|
job_id: int,
|
||||||
|
keyword: str,
|
||||||
|
generated_at_iso: str,
|
||||||
|
strategy_decisions: dict[str, Any],
|
||||||
|
business_notes: str,
|
||||||
|
brief: dict[str, Any],
|
||||||
|
report_config: dict[str, Any] | None,
|
||||||
|
rules_md: str,
|
||||||
|
excerpt_raw: str,
|
||||||
|
group_evidence_raw: str,
|
||||||
|
compact_max: int,
|
||||||
|
excerpt_max: int,
|
||||||
|
rules_max: int | None,
|
||||||
|
) -> tuple[dict[str, Any], str]:
|
||||||
|
compact = compact_brief_for_llm(brief, max_chars=compact_max)
|
||||||
|
if report_uses_chapter8_text_mining_probe(report_config):
|
||||||
|
compact = dict(compact)
|
||||||
|
_omit_ch8_probe_wordchart_fields(compact)
|
||||||
|
ex = (
|
||||||
|
_truncate_strategy_narrative(excerpt_raw, excerpt_max) if excerpt_raw else ""
|
||||||
|
)
|
||||||
|
ev_max = min(24_000, max(3_000, excerpt_max + excerpt_max // 2))
|
||||||
|
gm = (
|
||||||
|
_truncate_strategy_narrative(group_evidence_raw, ev_max)
|
||||||
|
if group_evidence_raw
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
if rules_max is None:
|
||||||
|
rd = rules_md
|
||||||
|
else:
|
||||||
|
rd = _truncate_rules_draft_md(rules_md, rules_max)
|
||||||
|
payload: dict[str, Any] = {
|
||||||
|
"job_id": job_id,
|
||||||
|
"keyword": keyword,
|
||||||
|
"generated_at_iso": generated_at_iso,
|
||||||
|
"strategy_decisions": strategy_decisions,
|
||||||
|
"strategy_decisions_substantive": strategy_decisions_substantive(
|
||||||
|
strategy_decisions
|
||||||
|
),
|
||||||
|
"business_notes": business_notes,
|
||||||
|
"structured_brief": compact,
|
||||||
|
"rules_draft_markdown": rd,
|
||||||
|
"report_strategy_excerpt": ex,
|
||||||
|
"report_matrix_group_evidence_md": gm,
|
||||||
|
"chapter8_text_mining_probe": bool(
|
||||||
|
report_uses_chapter8_text_mining_probe(report_config)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
if report_uses_chapter8_text_mining_probe(report_config):
|
||||||
|
payload["structured_brief_omission_note"] = (
|
||||||
|
"已启用第八章文本挖掘(探针为主):structured_brief 已省略「关注词/场景子串计数」、按细类 feedback 中的 focus_keyword_hits/scenarios_top、"
|
||||||
|
"``strategy_hints`` 等;报告已**不再**输出 ``comment_sentiment_lexicon``(星级子集预设口语短语)及同口径图。**不得**再以这类子串计数、短语条形图或预设场景占比作为论据。"
|
||||||
|
"用户与评论侧须依报告 §8 文本挖掘归纳及 `report_matrix_group_evidence_md`;**促销、满减、券价差**须与报告第六章、`price_promotion_signals` 及 brief 已给字段一致;若 `report_strategy_excerpt` 非空则勿与其明显矛盾。**默认**节选为空,勿编造「报告策略长文已写明的」具体活动规则。"
|
||||||
|
)
|
||||||
|
raw = json.dumps(payload, ensure_ascii=False)
|
||||||
|
if len(raw) > 500_000:
|
||||||
|
payload["rules_draft_markdown"] = _truncate_rules_draft_md(rd, 200_000)
|
||||||
|
raw = json.dumps(payload, ensure_ascii=False)
|
||||||
|
return payload, STRATEGY_USER_PREFIX + raw
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_strategy_draft_llm_input_snapshot(
|
||||||
|
*,
|
||||||
|
job_id: int,
|
||||||
|
keyword: str,
|
||||||
|
brief: dict[str, Any],
|
||||||
|
business_notes: str,
|
||||||
|
generated_at_iso: str,
|
||||||
|
strategy_decisions: dict[str, Any],
|
||||||
|
report_strategy_excerpt: str | None = None,
|
||||||
|
report_matrix_group_evidence_md: str | None = None,
|
||||||
|
report_config: dict[str, Any] | None = None,
|
||||||
|
) -> tuple[dict[str, Any], str, str]:
|
||||||
|
"""
|
||||||
|
复现 ``generate_strategy_draft_markdown_llm`` 在**首档通过** ``_strategy_prompt_ok_for_call`` 时的
|
||||||
|
``payload`` 与完整 ``user`` 字符串(不请求网关)。
|
||||||
|
|
||||||
|
返回 ``(payload, user, tier_note)``;若所有档位均未通过,与生产一致仍返回最后一档兜底组装的
|
||||||
|
``(payload, user, tier_note)``(``tier_note`` 标明可能仍会由网关报错)。
|
||||||
|
"""
|
||||||
|
rules_md = build_strategy_draft_markdown(
|
||||||
|
job_id=job_id,
|
||||||
|
keyword=keyword,
|
||||||
|
brief=brief,
|
||||||
|
business_notes=business_notes,
|
||||||
|
generated_at_iso=generated_at_iso,
|
||||||
|
strategy_decisions=strategy_decisions,
|
||||||
|
report_config=report_config,
|
||||||
|
for_llm_input=True,
|
||||||
|
)
|
||||||
|
excerpt_raw = (report_strategy_excerpt or "").strip()
|
||||||
|
group_evidence_raw = (report_matrix_group_evidence_md or "").strip()
|
||||||
|
sys_prompt = STRATEGY_SYSTEM
|
||||||
|
min_comp = _min_strategy_completion_tokens()
|
||||||
|
min_comp_relaxed = max(256, min_comp // 2)
|
||||||
|
|
||||||
|
for cap_brief, cap_excerpt, cap_rules in (
|
||||||
|
(80_000, 24_000, None),
|
||||||
|
(64_000, 20_000, None),
|
||||||
|
(48_000, 17_000, None),
|
||||||
|
(36_000, 14_000, None),
|
||||||
|
(28_000, 11_000, None),
|
||||||
|
(22_000, 9_000, None),
|
||||||
|
(18_000, 7_000, None),
|
||||||
|
(14_000, 5_000, None),
|
||||||
|
(12_000, 4_000, 220_000),
|
||||||
|
(10_000, 3_500, 180_000),
|
||||||
|
(10_000, 3_000, 150_000),
|
||||||
|
(9_000, 2_500, 120_000),
|
||||||
|
(8_000, 2_000, 100_000),
|
||||||
|
(8_000, 2_000, 70_000),
|
||||||
|
):
|
||||||
|
payload, user = _build_strategy_draft_llm_payload_and_user(
|
||||||
|
job_id=job_id,
|
||||||
|
keyword=keyword,
|
||||||
|
generated_at_iso=generated_at_iso,
|
||||||
|
strategy_decisions=strategy_decisions,
|
||||||
|
business_notes=business_notes,
|
||||||
|
brief=brief,
|
||||||
|
report_config=report_config,
|
||||||
|
rules_md=rules_md,
|
||||||
|
excerpt_raw=excerpt_raw,
|
||||||
|
group_evidence_raw=group_evidence_raw,
|
||||||
|
compact_max=cap_brief,
|
||||||
|
excerpt_max=cap_excerpt,
|
||||||
|
rules_max=cap_rules,
|
||||||
|
)
|
||||||
|
if _strategy_prompt_ok_for_call(
|
||||||
|
sys_prompt, user, min_completion_tokens=min_comp
|
||||||
|
):
|
||||||
|
rules_note = (
|
||||||
|
"未截断"
|
||||||
|
if cap_rules is None
|
||||||
|
else f"rules_draft_markdown 截断上限 {cap_rules} 字"
|
||||||
|
)
|
||||||
|
note = (
|
||||||
|
"首档(标准 completion 阈值):"
|
||||||
|
f"structured_brief max_chars={cap_brief},"
|
||||||
|
f"report_strategy_excerpt / 节选侧 excerpt_max={cap_excerpt},"
|
||||||
|
f"{rules_note}。"
|
||||||
|
)
|
||||||
|
return payload, user, note
|
||||||
|
|
||||||
|
for cap_brief, cap_excerpt, cap_rules in (
|
||||||
|
(10_000, 2_000, 55_000),
|
||||||
|
(8_000, 1_500, 45_000),
|
||||||
|
(7_000, 1_200, 35_000),
|
||||||
|
):
|
||||||
|
payload, user = _build_strategy_draft_llm_payload_and_user(
|
||||||
|
job_id=job_id,
|
||||||
|
keyword=keyword,
|
||||||
|
generated_at_iso=generated_at_iso,
|
||||||
|
strategy_decisions=strategy_decisions,
|
||||||
|
business_notes=business_notes,
|
||||||
|
brief=brief,
|
||||||
|
report_config=report_config,
|
||||||
|
rules_md=rules_md,
|
||||||
|
excerpt_raw=excerpt_raw,
|
||||||
|
group_evidence_raw=group_evidence_raw,
|
||||||
|
compact_max=cap_brief,
|
||||||
|
excerpt_max=cap_excerpt,
|
||||||
|
rules_max=cap_rules,
|
||||||
|
)
|
||||||
|
if _strategy_prompt_ok_for_call(
|
||||||
|
sys_prompt, user, min_completion_tokens=min_comp_relaxed
|
||||||
|
):
|
||||||
|
note = (
|
||||||
|
"首档(relaxed completion 阈值):"
|
||||||
|
f"structured_brief max_chars={cap_brief},"
|
||||||
|
f"excerpt_max={cap_excerpt},"
|
||||||
|
f"rules_draft 截断上限 {cap_rules}。"
|
||||||
|
)
|
||||||
|
return payload, user, note
|
||||||
|
|
||||||
|
payload, user = _build_strategy_draft_llm_payload_and_user(
|
||||||
|
job_id=job_id,
|
||||||
|
keyword=keyword,
|
||||||
|
generated_at_iso=generated_at_iso,
|
||||||
|
strategy_decisions=strategy_decisions,
|
||||||
|
business_notes=business_notes,
|
||||||
|
brief=brief,
|
||||||
|
report_config=report_config,
|
||||||
|
rules_md=rules_md,
|
||||||
|
excerpt_raw=excerpt_raw,
|
||||||
|
group_evidence_raw=group_evidence_raw,
|
||||||
|
compact_max=6_000,
|
||||||
|
excerpt_max=1_000,
|
||||||
|
rules_max=28_000,
|
||||||
|
)
|
||||||
|
note = (
|
||||||
|
"所有标准/relaxed 档位均未通过 ``_strategy_prompt_ok_for_call``,"
|
||||||
|
"与生产一致使用兜底档:structured_brief max_chars=6000,excerpt_max=1000,"
|
||||||
|
"rules_draft 截断上限 28000(网关仍可能报错)。"
|
||||||
|
)
|
||||||
|
return payload, user, note
|
||||||
|
|
||||||
|
|
||||||
def generate_strategy_draft_markdown_llm(
|
def generate_strategy_draft_markdown_llm(
|
||||||
*,
|
*,
|
||||||
job_id: int,
|
job_id: int,
|
||||||
@ -218,119 +417,18 @@ def generate_strategy_draft_markdown_llm(
|
|||||||
``report_matrix_group_evidence_md``:按所选矩阵细类从 ``competitor_analysis.md`` 抽取的第五~第八章大模型小节摘录(见
|
``report_matrix_group_evidence_md``:按所选矩阵细类从 ``competitor_analysis.md`` 抽取的第五~第八章大模型小节摘录(见
|
||||||
``reporting.report_matrix_group_evidence.load_report_matrix_group_evidence_markdown``);用于与收窄后的 ``structured_brief`` 一并支撑策略叙事。
|
``reporting.report_matrix_group_evidence.load_report_matrix_group_evidence_markdown``);用于与收窄后的 ``structured_brief`` 一并支撑策略叙事。
|
||||||
"""
|
"""
|
||||||
rules_md = build_strategy_draft_markdown(
|
_payload, user, _tier = resolve_strategy_draft_llm_input_snapshot(
|
||||||
job_id=job_id,
|
job_id=job_id,
|
||||||
keyword=keyword,
|
keyword=keyword,
|
||||||
brief=brief,
|
brief=brief,
|
||||||
business_notes=business_notes,
|
business_notes=business_notes,
|
||||||
generated_at_iso=generated_at_iso,
|
generated_at_iso=generated_at_iso,
|
||||||
strategy_decisions=strategy_decisions,
|
strategy_decisions=strategy_decisions,
|
||||||
|
report_strategy_excerpt=report_strategy_excerpt,
|
||||||
|
report_matrix_group_evidence_md=report_matrix_group_evidence_md,
|
||||||
report_config=report_config,
|
report_config=report_config,
|
||||||
for_llm_input=True,
|
|
||||||
)
|
)
|
||||||
excerpt_raw = (report_strategy_excerpt or "").strip()
|
return call_llm(STRATEGY_SYSTEM, user)
|
||||||
group_evidence_raw = (report_matrix_group_evidence_md or "").strip()
|
|
||||||
sys_prompt = STRATEGY_SYSTEM
|
|
||||||
min_comp = _min_strategy_completion_tokens()
|
|
||||||
min_comp_relaxed = max(256, min_comp // 2)
|
|
||||||
|
|
||||||
def _payload_and_user(
|
|
||||||
*,
|
|
||||||
compact_max: int,
|
|
||||||
excerpt_max: int,
|
|
||||||
rules_max: int | None,
|
|
||||||
) -> str:
|
|
||||||
compact = compact_brief_for_llm(brief, max_chars=compact_max)
|
|
||||||
if report_uses_chapter8_text_mining_probe(report_config):
|
|
||||||
compact = dict(compact)
|
|
||||||
_omit_ch8_probe_wordchart_fields(compact)
|
|
||||||
ex = (
|
|
||||||
_truncate_strategy_narrative(excerpt_raw, excerpt_max)
|
|
||||||
if excerpt_raw
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
ev_max = min(24_000, max(3_000, excerpt_max + excerpt_max // 2))
|
|
||||||
gm = (
|
|
||||||
_truncate_strategy_narrative(group_evidence_raw, ev_max)
|
|
||||||
if group_evidence_raw
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
if rules_max is None:
|
|
||||||
rd = rules_md
|
|
||||||
else:
|
|
||||||
rd = _truncate_rules_draft_md(rules_md, rules_max)
|
|
||||||
payload: dict[str, Any] = {
|
|
||||||
"job_id": job_id,
|
|
||||||
"keyword": keyword,
|
|
||||||
"generated_at_iso": generated_at_iso,
|
|
||||||
"strategy_decisions": strategy_decisions,
|
|
||||||
"strategy_decisions_substantive": strategy_decisions_substantive(
|
|
||||||
strategy_decisions
|
|
||||||
),
|
|
||||||
"business_notes": business_notes,
|
|
||||||
"structured_brief": compact,
|
|
||||||
"rules_draft_markdown": rd,
|
|
||||||
"report_strategy_excerpt": ex,
|
|
||||||
"report_matrix_group_evidence_md": gm,
|
|
||||||
"chapter8_text_mining_probe": bool(
|
|
||||||
report_uses_chapter8_text_mining_probe(report_config)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
if report_uses_chapter8_text_mining_probe(report_config):
|
|
||||||
payload["structured_brief_omission_note"] = (
|
|
||||||
"已启用第八章文本挖掘(探针为主):structured_brief 已省略「关注词/场景子串计数」、按细类 feedback 中的 focus_keyword_hits/scenarios_top、"
|
|
||||||
"``strategy_hints`` 等;报告已**不再**输出 ``comment_sentiment_lexicon``(星级子集预设口语短语)及同口径图。**不得**再以这类子串计数、短语条形图或预设场景占比作为论据。"
|
|
||||||
"用户与评论侧须依报告 §8 文本挖掘归纳及 `report_matrix_group_evidence_md`;**促销、满减、券价差**须与报告第六章、`price_promotion_signals` 及 brief 已给字段一致;若 `report_strategy_excerpt` 非空则勿与其明显矛盾。**默认**节选为空,勿编造「报告策略长文已写明的」具体活动规则。"
|
|
||||||
)
|
|
||||||
raw = json.dumps(payload, ensure_ascii=False)
|
|
||||||
if len(raw) > 500_000:
|
|
||||||
payload["rules_draft_markdown"] = _truncate_rules_draft_md(rd, 200_000)
|
|
||||||
raw = json.dumps(payload, ensure_ascii=False)
|
|
||||||
return STRATEGY_USER_PREFIX + raw
|
|
||||||
|
|
||||||
for cap_brief, cap_excerpt, cap_rules in (
|
|
||||||
(80_000, 24_000, None),
|
|
||||||
(64_000, 20_000, None),
|
|
||||||
(48_000, 17_000, None),
|
|
||||||
(36_000, 14_000, None),
|
|
||||||
(28_000, 11_000, None),
|
|
||||||
(22_000, 9_000, None),
|
|
||||||
(18_000, 7_000, None),
|
|
||||||
(14_000, 5_000, None),
|
|
||||||
(12_000, 4_000, 220_000),
|
|
||||||
(10_000, 3_500, 180_000),
|
|
||||||
(10_000, 3_000, 150_000),
|
|
||||||
(9_000, 2_500, 120_000),
|
|
||||||
(8_000, 2_000, 100_000),
|
|
||||||
(8_000, 2_000, 70_000),
|
|
||||||
):
|
|
||||||
user = _payload_and_user(
|
|
||||||
compact_max=cap_brief,
|
|
||||||
excerpt_max=cap_excerpt,
|
|
||||||
rules_max=cap_rules,
|
|
||||||
)
|
|
||||||
if _strategy_prompt_ok_for_call(
|
|
||||||
sys_prompt, user, min_completion_tokens=min_comp
|
|
||||||
):
|
|
||||||
return call_llm(sys_prompt, user)
|
|
||||||
|
|
||||||
for cap_brief, cap_excerpt, cap_rules in (
|
|
||||||
(10_000, 2_000, 55_000),
|
|
||||||
(8_000, 1_500, 45_000),
|
|
||||||
(7_000, 1_200, 35_000),
|
|
||||||
):
|
|
||||||
user = _payload_and_user(
|
|
||||||
compact_max=cap_brief,
|
|
||||||
excerpt_max=cap_excerpt,
|
|
||||||
rules_max=cap_rules,
|
|
||||||
)
|
|
||||||
if _strategy_prompt_ok_for_call(
|
|
||||||
sys_prompt, user, min_completion_tokens=min_comp_relaxed
|
|
||||||
):
|
|
||||||
return call_llm(sys_prompt, user)
|
|
||||||
|
|
||||||
user = _payload_and_user(compact_max=6_000, excerpt_max=1_000, rules_max=28_000)
|
|
||||||
return call_llm(sys_prompt, user)
|
|
||||||
|
|
||||||
|
|
||||||
STRATEGY_OPPORTUNITIES_SYSTEM = (
|
STRATEGY_OPPORTUNITIES_SYSTEM = (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user