這頁解釋 seo_etherwan 各份報表背後用了什麼資料、用了什麼演算法、為什麼這樣選。 目前涵蓋兩塊:Link Equity(連結權重 / Tier 分級 / 建議連入連出) 與 LLM Edit-brief(Gemini 3 Pro 改稿 brief 的 prompt / pipeline / 輸入輸出契約)。 切換下方 tab。
把站內每一頁當作節點、把站內所有內鏈當作邊、把 Google Search Console 90 天曝光當作流量證據; 三者疊合後回答一個問題:「下一個內鏈該補在哪、為什麼。」
傳統 SEO 工具看「孤兒清單」(沒有 inbound link 的頁),但「沒人連到」≠「值得補連結」。 孤兒清單裡實際混雜三種根本不同的問題:
只看 inbound 數字無法分辨這三類。把流量證據(GSC)疊上來,才能把優先序對齊到真實業務價值。
規則式(rule-based),不是 ML score。閾值從本站 187/191 個 page_meta_all 內 GSC 命中頁的曝光分佈拍定(p87 / p64 / p20),
未來資料量改變時可在 data/producer/lib/link_equity.py 調 T_HIGH / T_MID / T_LOW。
每篇文章「建議連入 top 5」+「建議連出 top 5」兩欄背後的邏輯。
純本地計算,不打 API、不用 LLM、不用 embedding。
程式碼:data/producer/lib/link_suggestions.py。
| direction | 對 X 的意義 | UI 顯示 | 誰的 imp 進公式 |
|---|---|---|---|
inbound |
Y 應該連到 X(Y 把權重灌給 X) | X 的 article-brief「建議連入」欄 | Y.imp(source 越熱越好) |
outbound |
X 應該連去 Y(X 推薦讀者去 Y) | X 的 article-brief「建議連出」欄 | Y.imp(target 越熱越值得推薦) |
公式對兩個方向完全對稱:兩種情況下 Y.imp 都是「希望對方越熱越好」。
不一樣的只是「兩者是否已連結」要用對方向判讀(inbound 看 Y → X 是否存在;outbound 看 X → Y 是否存在)。
eth-0145(slug = understanding-the-difference-between-layer-2-and-layer-3-switches-explained):
建議連入 top 5 — 該被誰連到本頁:
what-difference-between-layer-2-managed-switches-and-layer-3-managed-switches(共享 6 token)等。
編輯動作:去那些 source 文章正文加 anchor 連到 eth-0145。
建議連出 top 5 — 本頁該連去哪些文章:
eth-0102 (Layer 2 vs 3 FAQ) / eth-0134 (Managed vs Unmanaged) /
eth-0119 (Configuring Layer 3 Routing) 等。編輯動作:在 eth-0145 原文加 anchor 推薦這些延伸閱讀。
圖論上「X 連到 Y」就等於「Y 被 X 連入」— 同一條邊。但編輯成本不同:
所以實務上「持續部署建議連出」是更可持續的工作流: 編輯每天/週打開幾篇 → 看「建議連出 top 5」→ 採納 1–2 條 → 下一篇。 每一條 outbound 採納,自動就是某頁的 inbound +1,全站連結圖隨時間累積。
2026-04-28 V1 ship 時的決策(記在 link_suggestions.py 開頭 + article-internal-link-suggestions-exploration openspec change):
| 因素 | 純本地(V1) | Gemini / LLM |
|---|---|---|
| 成本 | 0 | 716 條建議 × API 費用 |
| 速度 | producer 一次跑 ~10 秒 | 慢 + rate limit |
| 可解釋 | 「共享 4 個 token + 來源頁 imp 12k」可審計 | 「LLM 說 relevant」黑盒 |
| Producer 自動化 | 隨 build_report 自動跑 | 需 LLM job 流程 + retry |
| 準確度 | 字面相似(slug-level) | semantic-level 更準 |
eth-0145 這類沒語意的 ID → 演算法廢switch token 不代表是同主題(一篇講硬體、一篇講配置)下面三條路按複雜度遞增。料齊但沒做 — 先看 V1 在實際使用上多不準,再決定升哪條。
你已有 dashboard-report-seo-embedding-1 容器(Python FastAPI + pgvector + GPU)。
把 191 篇文章內容跑 embedding 灌進 pgvector,演算法改成「target embedding 跟所有 source embedding 算 cosine sim」取 top 5。
解語意盲點,仍是純本地(不連雲端)。
Embedding 找 top 30 → Gemini 讀文章開頭/headings 對 30 條 rerank 取 top 5。準度最高但慢 + 花錢。
探索 change article-internal-link-suggestions-exploration 的 D2 阻塞決策:cluster_id 0/186 沒填。
要先跑分群(k-means on embeddings 或 LLM 標籤),再用 cluster 為 candidate 過濾。
搭 V2 或 V3 都更準,但工程量大。
整份 link equity 報表是這條 pipeline 的最終輸出。一輪 build_report 跑完所有 stage:
┌───────────────────────────────────────────────────────────┐
│ Step 0 Data discovery (人工 + 一次性) │
│ 讀 page_meta_all.json + GSC _p.csv │
│ → 確認 schema / 拍 Tier 閾值 / 列補爬候選 / 確認 prefix │
└────────────────┬──────────────────────────────────────────┘
│
┌─────────┴─────────┐
▼ ▼
┌──────────────┐ ┌────────────────────────┐
│ Step 1 │ │ Step 3 (並行) │
│ Path Alias │ │ Openspec 文件化 │
│ 修復 URL │ │ proposal/design/tasks │
│ rename │ │ 本介紹頁就是這層延伸 │
└──────┬───────┘ └────────────────────────┘
│
▼
┌──────────────────────────────────┐
│ Step 2 GSC-driven 補爬 ✓ │
│ 高曝光缺頁 → crawler seed → 重爬│
│ Phase 1 完成: 28 頁,產品頁/ │
│ about/applications/support │
│ (page_meta_all 191 → 219) │
└──────┬───────────────────────────┘
│
▼
┌──────────────────────────────────┐
│ Step 4 Alias 量產 (Phase 2) ✓ │
│ scripts/audit_orphan_targets.py │
│ 12 候選 → 5 條 high-confidence │
│ 自動加 path_aliases.json,剩餘 │
│ 8 條 jaccard 0.75(不同產品分類,│
│ 非 rename)defer │
└──────┬───────────────────────────┘
│
▼
┌──────────────────────────────────┐
│ Step 5 Link Equity Report 實作 │
│ producer S9 + Go handler + UI │
│ 包含本頁 + /links-report 主表 │
└──────────────────────────────────┘
scripts/crawl_supplement.py(cloudscraper + bs4)2026-04-29 補爬 28 個高曝光產品 / about / applications / support 頁。後續補爬批次也會用同腳本。page, clicks, impressions, ctr, position。共 9,490 rows / 3,609 unique paths。Robert 從 GSC export 手動更新,每月一次。
path_filters.json 過濾的 prefix。理由:報表的單元應該是「能 take action 的頁」。
不能優化的列進來只會是噪音。
| Prefix | 原因 | 怎麼處理 |
|---|---|---|
/sites/default/files/ | PDF 文件 (95k 90d imp) | 無法加 internal link / 改內容做 SEO,源頭排除 |
/us/ /tw/ /jp/ | locale 變體 (1939 頁 / 843k imp) | 不獨立列出,但 GSC 流量已合併到 root(root 行的 imp 是合併數) |
/cdn-cgi/ | Cloudflare 基礎設施 | 不是真實頁面(44 outbound refs 全部來自 email-protection link) |
/print/pdf/ | Drupal print plugin (125 GSC pages) | 跟原頁同內容,不重複收 |
/node/<id> | Drupal raw ID URL (21 頁) | 網站 alias 沒做好的殘餘,本期統一過濾 |
/us/ /tw/ /jp/ 三個 locale 變體的 imp 合併到 global root。
eth-0145 root 自己 ~329k + tw 19k + jp 28k + us 0 = ~376k。
理由:locale 是同一篇文章的多語版本(hreflang 規範下),分開看反而扭曲主題流量。
/support/faq/generic 那種佔位頁本來就沒人搜;
(3) URL 在 GSC 紀錄是別的 path,需要查 GSC raw 比對。
python -m data.producer.build_report 更新一次。本機開發即跑即看;prod 透過 Git+CI/CD 部署 +
scp reports.db。目前不是 cron 自動。
把 article-brief 的「現況分析」(GSC 指標 / AI-friendliness 子分數 / rule-based 建議 / 原文片段)打包成 prompt, 丟給 Gemini 3 Pro 產出一份 markdown 格式的「edit brief」— 4-6 個按 impact × effort 排序的 Change,每個 Change 明示「改哪裡 / Current 怎樣 / New 怎樣」。
accept_brief)
才會進客戶看的 article-brief 頁。模型瞎掰風險靠 prompt 規則 + rule-based baseline 雙重 dampening。
從一篇文章被選中到「客戶在 article-brief 頁看到 LLM 建議」的完整路徑:
┌──────────────────────────────────────────────────────────────────┐
│ Step 1 選文 (CLI flags) │
│ --top-n-impressions / --pool / --article-ids / --only-unmatched │
│ → 從 static/data/briefs/*.json 篩出 N 篇 candidate │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Step 2 Build prompt (per article) │
│ prompts/edit_brief_v1_2.py::build(brief) │
│ ├─ SYSTEM (固定 ~95 行) ──────────────► 角色 / 規則 / 骨架 / 自檢 │
│ └─ USER (動態 ~7000 字) ─────────────► page_data_lite + rules + │
│ 原文截斷 + 結尾指令 │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Step 3 Call Gemini API │
│ client_gemini.py + key from keys/gemini_api_key.json │
│ model = gemini-3.1-pro-preview (default) / 3.1-flash-lite │
│ → markdown 字串(# Edit Brief …) │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Step 4 Persist (3 個地方) │
│ ├─ 檔:data/seo_etherwan/content-drafts/auto-generated/.md │
│ ├─ Postgres: llm_generation_items (job audit + content_md) │
│ └─ JSONL audit: data/seo_etherwan/auto-brief-runs/<run_id>/ │
└────────────────┬─────────────────────────────────────────────────┘
│ ← 人工 review gate
▼
┌──────────────────────────────────────────────────────────────────┐
│ Step 5 accept_brief.py (rename only, no delete) │
│ auto-generated/.md → group-a-edits/.md (promoted) │
│ auto-generated/.md → retired/<ts>-.md (歸檔) │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Step 6 build_report.py S7 │
│ 掃 group-a-edits/*.md → match article_id → upsert advisor_drafts│
│ sqlite: reports.db.advisor_drafts (draft_type='group-a-edit') │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Step 7 Go API serve │
│ GET /api/article/:project/:aid → JOIN advisor_drafts.content_md │
│ → suggestions[].content │
│ GET /api/article/:project/_index → has_llm_draft / has_advisor │
│ _edit flags 給 sidebar 圖示 + filter 用 │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Step 8 Frontend render │
│ article-brief.js 拉 brief JSON → renderBrief() 把 LLM markdown │
│ 渲染進「LLM 建議」section + Current/New diff 兩欄顯示 │
└──────────────────────────────────────────────────────────────────┘
generate_briefs.py
Source: data/producer/generate_briefs.py。
Default 是 --dry-run,沒打 --confirm 永遠不會打 API。
| Flag | 用法 | 適合場景 |
|---|---|---|
--article-id eth-XXXX |
單篇 | 針對特定文章一次性產 brief |
--article-ids id1,id2,id3 |
逗號清單 | 編輯給定的一批 article-id |
--pool hot|winner|cold|... |
按既有分桶 | 批量處理某一桶(producer S7 wave) |
--top-n-impressions N |
按 GSC 90d impressions 取前 N | 高曝光優先 — ROI 最高 |
可疊加 --only-unmatched 跳過已有 LLM brief 的文章(避免重複跑)。
| Flag | Default | 說明 |
|---|---|---|
--model | gemini-2.5-flash | 實際 production 都改用 gemini-3.1-pro-preview |
--prompt-version | v1.2 | v1.0 / v1.1 / v1.2,見「版本歷史」section |
--max-cost-usd | 1.00 | 單次 run 總成本上限。Pro 跑 20 篇要設 5.00 否則會在 ~5 篇後 stop |
--max-output-tokens | 8192 | worst-case 估算用;實際 output 通常只 ~1100 tok / 篇 |
--dry-run (default):印出完整 prompt(截斷顯示)+ 估算 token 數 + 估算 cost。不打 API、不寫 DB、不寫檔。--confirm:真打 API。需 google-genai SDK + keys/gemini_api_key.json + Postgres 連線(job_id 記到 llm_generation_items)。--no-db:跳過 Postgres,只寫 .md 檔(debug 用)。--show-full-prompt:dry-run 時印完整 SYSTEM+USER;省略時 USER 截斷顯示。.venv/bin/python -m data.producer.generate_briefs \ --top-n-impressions 20 \ --only-unmatched \ --model gemini-3.1-pro-preview \ --prompt-version v1.2 \ --max-cost-usd 5.00 \ --confirm \ -v
edit_brief_v1_2.build(brief, numbering) 接一份 brief JSON(從
static/data/briefs/<article_id>.json 讀),輸出 (SYSTEM, USER) 兩段字串給 Gemini。
USER 段組三塊:頁面資料 JSON + rule-based 建議 + 原文截斷 2000 字。
不是把整份 brief JSON 都送進 prompt(會太貴 + 塞滿 context window)。實際只送這幾欄:
| 欄位 | 來源 | 為什麼送 |
|---|---|---|
article_id, url, path, h1, title_tag, meta_description | brief.json 頂層 | 定位用 — LLM 才知道「在改哪一頁」 |
category, pool, word_count | brief.json 頂層 | 內容類型語氣判斷 |
metrics.{gsc_impressions_90d, gsc_avg_position, ctr_actual, aio_brand_primary, target_keyword, ...} | brief.metrics | 讓 LLM 知道「這頁排名 / 競爭狀態 / AIO 品牌」 |
ai_friendliness.{total, signals.{definition / comparison_table / faq / h2_entity_coverage / ...}} | brief.ai_friendliness | 各子分數 + evidence 字串 — LLM 才知道哪個訊號弱要補 |
headings, outbound_links | brief.headings / outbound_links | 結構訊息 — LLM 才能說「在 ## Applications 之前插入」 |
how_to_read | brief.how_to_read | 該頁的 reader profile — 讓 LLM 對齊讀者 |
在 LLM 跑之前,build_report.py S6 stage 已經依規則跑出一批 rule-based 建議
(low-AIO / low-CTR / no-FAQ / position-near-top-10 等),存進 article_suggestions 表。
組 prompt 時用 _rule_bullets() 把這些 baseline 列成 markdown bullets,丟給 LLM 當參考:
- [P1] **Add comparison table for managed vs unmanaged**(30 min) - why: ai_friendliness.comparison_table = 0 - how: 在 ## Applications 之前插入 markdown table
LLM 被允許「採用、擴充、批判替換」這些 baseline,但不可完全忽略。 理由:rule-based 抓得到 deterministic signal(schema gap / AIO miss),LLM 補的是 nuance。
…(已截斷,實際長度 N 字)
Source: data/producer/lib/llm/prompts/edit_brief_v1_2.py。
~95 行,每篇文章都會送同樣這段(fixed prefix,給 cache 命中)。
你是 EtherWAN 的 SEO 改寫顧問,幫 B2B 工業網路設備廠商產出可執行的 edit brief markdown。 **輸出規則(不可違反)** - Output 第一行必須是 `# Edit Brief <編號>: <target_path>`;不要開場白、不要「好的」「以下是」。 - 每份 brief 含 4-6 個 Changes,依 impact × effort 排序;最省力最高效的先。 - 每個 Change 必須有下列四個欄位: **Location:** 明示改哪裡(section anchor / 段落編號 / H2 標題之前或之後) <段落解釋> 30-80 字正文,直接寫;不要用 `{描述…}` 或 `<…>` 這類 meta placeholder **Current:** <貼出現況的段落或元素;新增段落寫「(新段落,建議插入 XX 處)」> **New:** <貼出建議替換後的完整 markdown 片段> - 禁止瞎掰產品規格、溫度範圍、認證編號。若資料缺,寫「需 RD 確認具體規格」。 **輸出骨架** ``` # Edit Brief <編號>: <target_path> **Target keyword:** <主查詢字> **Current AIO:** <aio_brand_primary,若空寫 "none"> **EtherWAN rank:** <gsc_avg_position 四捨五入 #N> **Problem:** <一句話點出為什麼這頁需要改,不超過 40 字> --- ## Change 1: <具體動作,如「改寫 H1」/「新增 FAQ 段」/「補內部連結」> **Location:** <改哪裡 — 例:「H1」/「`## Applications` 之前」/「第一段後」/「頁末 meta description tag」> <30-80 字正文直接寫,解釋為什麼這改動能解 Problem 的某個面向。> **Current:** ``` <現況段落、元素、或字串> ``` **New:** ``` <建議替換後的完整文字或 markdown 片段> ``` ## Change 2: <...> ## Change 3: <...> ... ``` **質量要求** 1. **EtherWAN 工業語氣**:客戶是 engineering procurement / network engineer,不是消費者。避免 `revolutionary` / `cutting-edge` / `best-in-class` / `game-changing`。 2. **具體可執行**:不要「增加內容」「補充資訊」「優化 H1」;必須指名段落位置、具體字串、schema type、錨文字。 3. **術語白名單不翻譯**:PoE switch / managed switch / industrial / DIN-rail / IEC 61850-3 / EN 50155 / hardened / fanless — 英文原字形。 4. **rule-based 建議是 baseline**:可採用、擴充、批判後替換;不要完全複製。 5. 不要 wrap 整段 markdown 在 code fence 裡;Changes 區直接用 heading + markdown。 **不可犯的錯(output 之前自檢)** - ❌ output 第一行 "好的" / "以下是" / "根據您的資料" - ❌ 保留 `{具體動作}` / `<...>` meta placeholder 在輸出 - ❌ 「Change 1: 優化 H1」沒給 Location、沒給 current / new - ❌ 瞎掰規格 ("operating temp -40°C") 若 page_data 無此資訊 - ❌ 用 "我建議" / "您可以考慮" 這種軟勸說;直接命令式 - ❌ 行銷花俏詞彙
Source: data/producer/lib/llm/prompts/edit_brief_v1_0.py::USER_TEMPLATE。
v1.1 / v1.2 改 SYSTEM 沒改 USER。紅色變數是 build() 動態填入的:
# Edit Brief {numbering}: {target_path} # 頁面資料 ```json {page_data_json} ``` # 系統已有的 rule-based 建議(可參考、擴充或批判替換) {rule_suggestions_bullets} # 原文前 2000 字截斷(content.md 完整長度 {content_md_length} 字) {content_md_truncated} --- 請輸出完整的 edit brief markdown。直接寫,無需開場白或解釋。
| 變數 | 填入內容 | 大小 |
|---|---|---|
{numbering} | 固定 AUTO-0001(每篇都一樣,不影響輸出品質) | 9 chars |
{target_path} | brief.path 或 brief.url | ~50 chars |
{page_data_json} | page_data_lite 序列化(見 §4.1) | ~3000 chars |
{rule_suggestions_bullets} | _rule_bullets() 把 article_suggestions 表的 row 渲染成 markdown bullets | ~500 chars |
{content_md_length} | 文章原始 word_count(資訊用,告訴 LLM 截斷比例) | ~5 chars |
{content_md_truncated} | 原文前 2000 字 + 截斷標記 | ≤ 2050 chars |
# Edit Brief AUTO-0001: /support/featured-articles/-slug **Target keyword:** <主查詢字> **Current AIO:** <品牌名 or "none"> **EtherWAN rank:** #N **Problem:** <不超過 40 字一句話> --- ## Change 1: <動作> **Location:** <改哪裡> <30-80 字解釋 why> **Current:** ``` <現況> ``` **New:** ``` <建議> ``` ## Change 2: ... ## Change 3: ... (4-6 個)
| 欄位 | 用途 | 什麼算合格 |
|---|---|---|
| Location | 編輯打開檔案後該滑到哪 | 「H1」/「`## Applications` 之前」/「第一段後」/「頁末 meta description tag」— 不可寫「在文章裡」 |
| 段落解釋 | 為什麼這改動能解 Problem 的某個面向 | 30-80 字、命令式、無 placeholder(不留 {…} 或 <…>) |
| Current | 原文現況片段 | 貼真實字串;新增段落時寫「(新段落,建議插入 XX 處)」 |
| New | 建議替換後完整 markdown | 編輯可直接 copy-paste 進原文的可執行字串 |
實際輸出存在 data/seo_etherwan/content-drafts/group-a-edits/eth-0129.md。
開頭幾行:
# Edit Brief AUTO-0001: /support/featured-articles/implementing-quality-service-prioritizing-network-traffic **Target keyword:** quality of service network traffic **Current AIO:** none **EtherWAN rank:** #11 **Problem:** 雖然有 11 名排名與 7000+ 月曝光,但無 FAQ / 比較表 / 定義段, AI Search 引擎無法擷取,CTR 低於 1%。 --- ## Change 1: 改寫 H1 加 entity + 主查詢字 **Location:** H1 H1 目前 `Implementing Quality of Service` 沒帶 brand / 設備類型 entity, 搜尋 "QoS switch industrial" 時無法被視為 strong topic match。 **Current:** ``` Implementing Quality of Service: Prioritizing Network Traffic ``` **New:** ``` Implementing QoS on Industrial Ethernet Switches: Prioritizing Network Traffic ``` ## Change 2: ...
{具體動作} 或 <...> 在 outputaccept_brief.py
LLM 寫完不會直接上線。auto-generated/ 是 staging 區,編輯人工掃過再 promote 到 group-a-edits/,
build_report 才會撿。理由:避免 LLM 幻覺直接出現在客戶看的頁面。
auto-generated/<aid>.md → group-a-edits/<aid>.mdauto-generated/<aid>.md → retired/<ts>-<aid>.mdgroup-a-edits/ 已有同名 .md:原檔 rename 為 .pre-<ts>.md 保留| Flag | 用法 |
|---|---|
--article-id eth-XXXX | 單篇 promote |
--article-ids id1,id2 | 逗號清單 |
--all | auto-generated/ 內全部 .md 一次 promote |
--dry-run | 只印 plan,不動檔 |
--force | 覆蓋 group-a-edits/ 已存在的同名檔(仍會 rename 舊檔) |
build_report.py 整合(S7)
Producer 第 7 stage 掃 content-drafts/{auto-generated, group-a-edits}/,把每個 .md 檔
match 到 article_id,upsert 進 reports.db.advisor_drafts 表。
CREATE TABLE advisor_drafts ( draft_id INTEGER PRIMARY KEY, article_id TEXT, draft_type TEXT NOT NULL, -- 'auto-generated' | 'group-a-edit' filename TEXT, content_md TEXT NOT NULL, -- 完整 LLM 輸出 markdown accepted INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL );
draft_type 區分兩階段:auto-generated(剛跑完還沒 accept)vs
group-a-edit(已 accept,可上 prod)。Frontend 用這兩個欄位畫
⚡ (LLM draft) 和 👤 (advisor edit) 兩種 sidebar 圖示。
article_briefs 表沒有 brief row,drafts_imported 會列入 skipped_no_match。
已知 edge case:直接 INSERT advisor_drafts 補資料庫 row 是 workaround;root cause fix 待後續 session。
internal/service/reports_db.go::Article(article_id) 從 advisor_drafts 撈 content_md
→ inject 進 brief JSON 的 suggestions[0].content。同檔 linkSuggestionsFor() 從
article_link_suggestions 表撈 inbound/outbound 建議 → 進 internal_link_flow.inbound_suggested。
_index endpoint JOIN 一次 advisor_drafts 拿到每個 article_id 的
has_llm_draft(draft_type='auto-generated')和
has_advisor_edit(draft_type='group-a-edit')兩個 boolean — 給 sidebar
圖示 + 「⚡ 只看已有建議」filter 用。
static/js/article-brief.js::renderBrief() 把 suggestions[0].content 直接餵進
markdown-it render。Brief 還有「Current/New diff renderer」把 **Current:** ... **New:** ...
pattern 自動切兩欄並排顯示,方便 reviewer 比對前後差異。
| Model | Input $/1M tok | Output $/1M tok | 適合 |
|---|---|---|---|
gemini-3.1-pro-preview | ~$2.50 | ~$15.00 | 旗艦品質、單次重要 |
gemini-3.1-flash-lite-preview | $0.075 | $0.300 | 批次最划算 |
gemini-3-flash-preview | 中間 | 中間 | 折衷 |
--dry-run 用 --max-output-tokens 8192(worst case)算成本。
實際 output 通常只 ~1100 tok / 篇(ratio ~0.13),所以實際是估算的 ~20%:
| Model | Dry-run 估 / 篇 | 實際 / 篇 | 20 篇實際總 |
|---|---|---|---|
gemini-3.1-pro-preview | ~$0.10 | ~$0.02 | ~$0.40 |
gemini-3.1-flash-lite-preview | ~$0.025 | ~$0.005 | ~$0.10 |
--max-cost-usd default $1.00 是 per-run cap,不是 per-article。Pro 跑 20 篇要設 5.00(按 dry-run worst case 估),否則 ~5 篇後會被 cap 擋。
THINKING_REQUIRED 白名單,這幾個 model 不送 thinking_budget=0。
所有 prompt source 在 data/producer/lib/llm/prompts/edit_brief_vX_Y.py。
--prompt-version CLI flag 切換。
| 版本 | 主要變動 | 還在用? |
|---|---|---|
v1.0 |
初版。SYSTEM 7 條質量要求 + USER_TEMPLATE 三段組裝(page_data / rule baseline / 截斷原文)。 | 不建議。撞到 placeholder 字串外洩 bug |
v1.1 |
修 v1.0 placeholder leakage(LLM 把 {描述為什麼這改動…} 字面照抄出來)。SYSTEM 範本 placeholder 改成 <角括號> + 顯式說明「不要保留 label」+ 補 good vs bad example。 |
過渡版 |
v1.2 ⭐ |
Default。加 Location 欄位(每 Change 必須明示改哪裡,避免「在文章裡」這種模糊描述);
禁止任何 preamble(output 第一行直接 #);
EtherWAN 工業語氣強化(明列禁用詞);
「不可犯的錯」自檢清單。
|
✅ Production default |
USER_TEMPLATE / build() / estimate_prompt_tokens 三個 v1.0 → v1.2 都沒動,
v1.1 / v1.2 透過 from edit_brief_v1_0 import USER_TEMPLATE, _truncate, _rule_bullets 共用基礎建設。
--max-output-tokens 8192 算 worst case;實際 LLM 寫一份 brief 通常只 ~1100 tok。
所以實際是估算的 ~20%。但 --max-cost-usd 是按 dry-run worst case 估算扣,要把 cap 放寬否則跑到一半會 stop。
accept_brief 的人工 review gate 必走,且 page_data_lite 不送競品比較數據(讓 LLM 沒料可瞎掰)。
data/producer/lib/llm/client_*.py 是 provider abstraction,已有
client_gemini.py,要加 client_anthropic.py 是同 interface。
但 prompt v1.2 是針對 Gemini 3 Pro 的 thinking-mode tuned,換 model 要重新 verify。
data/seo_etherwan/content-drafts/auto-generated/<article_id>.md。
Accept 後移到 group-a-edits/,原檔留在 retired/。
DB 並行寫進 reports.db.advisor_drafts(sqlite,prod 也有這份)+
llm_generation_items(postgres,job audit / cost log,prod 沒這個 DB)。