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:
hub-gif 2026-04-23 09:49:25 +08:00
parent 3dac5b95de
commit 991dec3c4b
3 changed files with 387 additions and 200 deletions

View File

@ -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]
若未指定 --matrix-index则默认收窄到第一个矩阵分组选第一个细类等效
# 仅磁盘 run_dir无需 PipelineJobjob_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
@ -14,12 +28,12 @@ import json
import os
import sys
from pathlib import Path
from typing import Any
# Django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "market_assistant.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
def _strategy_decisions_empty() -> dict:
def _strategy_decisions_empty() -> dict[str, Any]:
return {
"product_role": "",
"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:
import django
@ -53,12 +74,11 @@ def main() -> int:
from pipeline.jd.runner import build_competitor_brief_for_job
from pipeline.llm.generate_strategy import (
STRATEGY_SYSTEM,
STRATEGY_USER_PREFIX,
_omit_ch8_probe_wordchart_fields,
_truncate_strategy_narrative,
resolve_strategy_draft_llm_input_snapshot,
_min_strategy_completion_tokens,
)
from pipeline.llm.llm_client import estimate_chat_input_tokens
from pipeline.models import JobStatus, PipelineJob
from pipeline.reporting.brief_compact import compact_brief_for_llm
from pipeline.reporting.brief_strategy_scope import (
filter_brief_for_strategy_matrix_group,
list_matrix_groups_for_api,
@ -67,13 +87,16 @@ def main() -> int:
load_report_matrix_group_evidence_markdown,
)
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.add_argument("--job-id", type=int, default=None)
p = argparse.ArgumentParser(description=__doc__)
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(
"--matrix-index",
type=int,
@ -85,40 +108,89 @@ def main() -> int:
action="store_true",
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(
"-o",
"--output",
type=str,
type=Path,
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()
backend_dir = Path(__file__).resolve().parents[2]
repo_root = backend_dir.parent
default_out = (
default_docs_out = (
repo_root / "docs" / "planning" / "策略生成-LLM全量输入快照.md"
)
out_path = Path(args.output) if args.output else default_out
job_id = args.job_id
if job_id:
job = PipelineJob.objects.filter(pk=job_id).first()
run_dir_s: str
kw: str
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:
job = (
PipelineJob.objects.filter(status=JobStatus.SUCCESS)
.exclude(run_dir="")
.order_by("-id")
.first()
)
if not job:
print("无可用成功任务(需 run_dir 非空)", file=sys.stderr)
return 1
jid = args.job_id
if jid:
job = PipelineJob.objects.filter(pk=jid).first()
else:
job = (
PipelineJob.objects.filter(status=JobStatus.SUCCESS)
.exclude(run_dir="")
.order_by("-id")
.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(
job.run_dir,
job.keyword,
run_dir_s,
kw,
report_config=rc,
)
matrix_groups = list_matrix_groups_for_api(brief)
@ -140,91 +212,106 @@ def main() -> int:
gen_at = timezone.now().isoformat()
sd = _strategy_decisions_empty()
rules_md = build_strategy_draft_markdown(
job_id=job.id,
keyword=job.keyword,
brief=brief,
business_notes="",
generated_at_iso=gen_at,
strategy_decisions=sd,
report_config=rc,
)
if args.decisions_json is not None:
dp = args.decisions_json.expanduser().resolve()
if not dp.is_file():
print(f"decisions-json 不存在: {dp}", file=sys.stderr)
return 1
try:
loaded = json.loads(dp.read_text(encoding="utf-8"))
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()
evidence_md = ""
evidence_src = "none"
if scoped_label:
evidence_md, evidence_src = load_report_matrix_group_evidence_markdown(
job.run_dir,
run_dir_s,
scoped_label,
)
compact_max = 80_000
excerpt_max = 24_000
compact = compact_brief_for_llm(brief, max_chars=compact_max)
if report_uses_chapter8_text_mining_probe(rc):
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(evidence_md.strip(), ev_max)
if evidence_md
else ""
payload, user_body, tier_note = resolve_strategy_draft_llm_input_snapshot(
job_id=job_id,
keyword=kw,
brief=brief,
business_notes=(args.business_notes or "").strip(),
generated_at_iso=gen_at,
strategy_decisions=sd,
report_strategy_excerpt=excerpt_raw or None,
report_matrix_group_evidence_md=evidence_md.strip() or None,
report_config=rc,
)
payload: dict = {
"job_id": job.id,
"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)
min_comp = _min_strategy_completion_tokens()
est_in = estimate_chat_input_tokens(STRATEGY_SYSTEM, 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)
lines: list[str] = [
"# 策略生成 · 大模型一次调用的「全量输入」快照",
"# 独立策略稿 · 大模型一次调用的「全量输入」快照",
"",
"> **生成方式**:本机 `pipeline.demos.dump_strategy_llm_input_md` 按与 "
"`generate_strategy_draft_markdown_llm` 相同的 payload 组装逻辑导出。",
"> **与线上一致性**:与真实接口相比,表单字段此处均为空默认;"
"你只要把当时提交的 `strategy_decisions` / `business_notes` 代入即与线上等价。",
"> **生成方式**`pipeline.demos.dump_strategy_llm_input_md` 调用 "
"`resolve_strategy_draft_llm_input_snapshot`,与 ``generate_strategy_draft_markdown_llm`` "
"首档通过的 ``payload`` / ``user`` 一致(不请求网关)。",
"> **与线上一致**:将当时 POST 的 `strategy_decisions`、`business_notes`、`strategy_matrix_group_index` "
"与本脚本参数对齐即可复现。",
"",
"## 快照元数据",
"",
f"- **任务 ID**{job.id}",
f"- **关键词**{job.keyword}",
f"- **run_dir**`{job.run_dir}`",
f"- **任务 IDpayload.job_id**{job_id}",
f"- **关键词**{kw}",
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"- **本任务可选细类(节选)**{group_names[:20]}{'' if len(group_names) > 20 else ''}",
f"- **第九章节选来源**{excerpt_src},约 {len(ex)} 字符",
f"- **细类报告节选来源**{evidence_src},约 {len(gm)} 字符",
f"- **选用档位**{tier_note}",
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"- **User 消息字符数**{len(user_body)}",
f"- **合计约**{full_chars} 字符",
f"- **estimate_chat_input_tokens项目内启发式**{est_in}",
f"- **structured_brief 序列化长度**{sb_json_len}",
f"- **rules_draft_markdownpayload 内)字符数**{rd_len}",
"",
"---",
"",

View File

@ -45,6 +45,7 @@ from .generate_strategy import (
STRATEGY_USER_PREFIX,
generate_strategy_draft_markdown_llm,
generate_strategy_opportunities_llm,
resolve_strategy_draft_llm_input_snapshot,
strategy_decisions_substantive,
)
from .llm_client import call_llm as _call_llm
@ -81,6 +82,7 @@ __all__ = [
"generate_section_bridges_llm",
"generate_strategy_draft_markdown_llm",
"generate_strategy_opportunities_llm",
"resolve_strategy_draft_llm_input_snapshot",
"split_competitor_report_for_bridges",
"strategy_decisions_substantive",
]

View File

@ -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=6000excerpt_max=1000"
"rules_draft 截断上限 28000网关仍可能报错"
)
return payload, user, note
def generate_strategy_draft_markdown_llm(
*,
job_id: int,
@ -218,119 +417,18 @@ def generate_strategy_draft_markdown_llm(
``report_matrix_group_evidence_md``按所选矩阵细类从 ``competitor_analysis.md`` 抽取的第五第八章大模型小节摘录
``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,
keyword=keyword,
brief=brief,
business_notes=business_notes,
generated_at_iso=generated_at_iso,
strategy_decisions=strategy_decisions,
report_strategy_excerpt=report_strategy_excerpt,
report_matrix_group_evidence_md=report_matrix_group_evidence_md,
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)
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)
return call_llm(STRATEGY_SYSTEM, user)
STRATEGY_OPPORTUNITIES_SYSTEM = (