mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-21 23:41:39 +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]
|
||||
|
||||
若未指定 --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
|
||||
|
||||
@ -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"- **任务 ID(payload.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_markdown(payload 内)字符数**:{rd_len}",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
|
||||
@ -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",
|
||||
]
|
||||
|
||||
@ -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(
|
||||
*,
|
||||
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 = (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user