20261013 會議記錄

[!Note]

上週待辦

url-resolver

@MrOrz:Vector search 我先 merge 進 master 了,測試方式與圖中找到的 issue 都寫在 https://github.com/cofacts/rumors-api/pull/388#issuecomment-6035830763

  • 測試過程中發現一個和 vector search 無關的 production bug:attachmentHash 如果有 key 但沒值,會導致 search 失敗(會回傳找不到),修在 https://github.com/cofacts/rumors-api/pull/406
  • knn query 的 highlight 和 count 不正常,修正在 https://github.com/cofacts/rumors-api/pull/407

@MrOrz:我在 staging backfill 了 999 篇訊息,請 AI 測了一下,這是他的結果。有些東西,包含所有 Facebook 都會糊在一起沒鑑別力這件事,可能要注意一下 https://claude.ai/artifact/FzuFW2nJgdzV3dkbsN4V3y?sk=pivoVHsUewGHkzCaOP9FTA

Artifact 內容摘要:向量搜尋 staging 實測(2026-10-08,cofacts/rumors-api#388)</summary>

原始 HTML 存檔(artifact 設為「知道連結即可檢視」):artifacts/20261013-FzuFW2nJgdzV3dkbsN4V3y.html

背景:在 staging(production snapshot)用 backfillVertexEmbeddings --limit 1000 隨機 backfill,成功 999 篇,實際 996 筆向量(TEXT 741,其中 251 篇只有網址;IMAGE 175;VIDEO 80)。模型 gemini-embedding-2、768 維。這是離線實驗:從 airesponses 讀向量、查詢向量直接呼叫 Gemini,在記憶體算 cosine;沒有經過 ES kNN、BM25 或 hybrid 排序。查詢是把池中文章做變形(加「轉傳:」、「這是真的嗎?」、結尾「請大家轉發分享」、去 emoji/標點、去網址、去追蹤參數、取前半/後半/前 50 字;LLM 改寫、一句話摘要、英譯、越南文、簡體、3–5 個關鍵字),看原文是否排第一、cosine 多少、以及最像的非原文文章(背景值)多高。樣本幾乎沒有「同一謠言不同版本」的天然配對,量不到真實 recall。

重點結論

  1. taskType 對 gemini-embedding-2 沒作用:RETRIEVAL_QUERY/RETRIEVAL_DOCUMENT/SEMANTIC_SIMILARITY 算出的向量逐位元相同。官方做法是文字前綴:查詢 task: search result | query: …、文件 title: none | text: …。
  2. 只有網址的文章(佔文字文章 34%)黏在一起:兩個無關的 FB 分享連結 cosine 可達 0.999。RAW 下相似度前 50 組有 46 組是只有網址的文章(33 組連結不同,13 組真重複);改 EXP_ASYM_SEARCH(前綴+連結標題摘要)後降為 28 組(12 組連結不同),並出現 6 組內文真的相似的配對。剩下的幾乎都是 FB share/p、share/r 只拿到登入牆的連結,建議不要為這類文章算向量。
  3. 0.7 門檻擋不住雜訊:RAW 背景值中位數 0.777;全庫 27.9 萬篇時 kNN 實際會變成「取最近 k 篇」。各格式分數尺度不同,門檻需依選定格式重新校準(ASYM_SEARCH 非網址文章背景 p95 約 0.89)。
  4. 文字查不到圖片/影片:圖片 OCR 文字查自己的向量,RAW cosine 中位數 0.62、100% 低於 0.7、排第一比例 0;影片 69% 低於 0.7。ASYM_SEARCH 提高到中位數 0.76、排第一 0.62。kNN 是 bool.filter + minimum_should_match: 0 的硬性過濾,開 hybrid 後文字查詢幾乎不會出現圖片文章,BM25 也救不回來。
  5. 近似重複偵測穩、改寫翻譯中等:轉傳前綴、去標點、簡體、英譯等 cosine 多在 0.85–0.95;只用關鍵字查最弱,RAW 有 26% 低於 0.7。

格式比較(原文排第一的比例):ASYM_SEARCH 幾乎每列都勝過 RAW(圖片 0→0.62、去標點 0.86→0.99、加結尾 0.76→0.94、翻譯改寫約 +0.1)。EXP_ASYM_SEARCH 在查詢仍保留網址時最好(約 0.98–0.99;url_strip_params 從 RAW 0.62 升到 0.96),但查詢沒有網址時(取前半、去標點、LLM 改寫)反而變差。task: fact checking 前綴結果和 search result 差不多、search 稍好。推測「每篇存兩個向量(原文、原文+連結內容)+ nested score_mode: max」可各取所長,尚未測。

其他問題

  • 音訊文章 zUeT4ZwBB0eXeo826-DL backfill 收到 400 INVALID_ARGUMENT,原因未釐清(contentType、videoMetadata.endOffset 或檔案本身);ERROR 不 cache,每次重跑都會再打。
  • airesponses 的 EMBEDDING cache key 只有 docId,改格式/模型/維度後舊向量會被悄悄沿用。
  • 沒有向量的文章在 hybrid 下會消失(kNN 在 bool.filter)。
  • 未登入時 createEmbedding 丟例外,被 catch 後悄悄退回 BM25;線上 A/B 要帶 app 憑證並確認有 query-text:* 紀錄。
  • (待驗證)ES knn 的 k 預設等於 size,first: 10 時可能影響 totalCount 與翻頁。

給 PR 的建議:(1) 拿掉 taskType 改用文字前綴,媒體不加;(2) embedding 輸入加入過濾後的 hyperlinks 標題摘要,查詢端 ListArticles 用 scrapUrls 同樣組裝,解析不出內容的純網址文章不算向量;(3) kNN 不要硬性過濾,至少「kNN 命中或沒有向量」才保留,更好是與 BM25 取聯集再融合(如 RRF);(4) 門檻依選定格式重新校準;(5) cache key 納入設定版本;(6) 修掉音訊 400 再全量 backfill。

交接(YuTin 用 production 備份在本機測):先選定格式(含「兩個向量」比較組)→ 全量 backfill 文字文章與 replies(staging concurrency 2 約 1.6 篇/秒,27.9 萬篇約 48 小時)→ 以「連到同一則 reply(RUMOR/NOT_RUMOR/OPINIONATED,排除通用回覆)的文章群組」當標準答案,比較 BM25 vs hybrid 的 Recall@10/@50 → 驗證 ES 行為(first: 10 vs 50 的 totalCount、無向量文章)→ 媒體測試(壓縮、裁切、截圖、80 秒後內容)→ ListReplies(reference 內網址可能同樣黏在一起)。評估腳本 evalEmbeddings.js(另外提供,只讀 ES)會輸出 summary.json、self.csv、perturb.csv、background.csv、pairs_*.csv、examples.json、queries.jsonl;staging 那輪 6 種格式約 2.1 萬次 embedding 呼叫、27 分鐘。

</details>

Solutions

  • MrOrz
    • attachmentHash bug https://github.com/cofacts/rumors-api/pull/406
    • highlight and count https://github.com/cofacts/rumors-api/pull/407
  • Yutin: https://github.com/cofacts/rumors-api/pull/411

[!Tip] Yutin 會協助把 #407 的 e2e test case 搬去 #411

Embedding 文字要不要加 task 前綴(#411)

需求:任何模態輸入(文字、圖片、影音),都要找出依關聯度混排的圖、文、影音結果;完全相同的文字排第一。

#411 現狀:文字加不對稱前綴,media 不加

  • 文件:title: none | text: …
  • query:task: search result | query: …
  • 原本的 taskType 參數 gemini-embedding-2 不支援,所以拿掉

需求:以綠皮蕉為例

  • query = 影片 + 逐字稿(只能抽到無意義的浮水印)
  • 希望能找到所有相關影片 or 相關圖片訊息

官方文件

  • 純文字「強烈建議」加前綴
  • 文字和 media 合成一個向量時,一般不建議加前綴
    • 多媒體和文字是分開 embed 的 [name=yutin]
    • 多媒體無 embed
    • 文字有 embed
  • 有 task: fact checking | query: … 這組前綴可以用,文件端格式和 search result 相同。gemini-embedding-2 的文件沒有解釋它;舊模型對應的 FACT_VERIFICATION 說明是「找支持或反駁這段說法的證據」
  • 沒說明:純文字 query 加了前綴之後,拿去比沒加前綴的 media 向量會怎樣

問題:#411 量到文字 query 對圖片的 cosine 全部低於 0.7,目前分不出原因是跨模態本來就低,還是前綴讓它變低。

  • 可能不太發生,格式不同的時候就有很大的差異 [name=yutin]
  • 同樣是文字的可能會比較前面,實務上要用文字搜尋到圖片,應該要搜得很精準才對得上 [name=yutin]

選項

  • A. 不加前綴:文字和 media 都送原始輸入,比較基準一致,但文字找文字可能變差
  • B. 維持 #411(search result 前綴)
  • C. 依查詢的目的使用不同的 query 前綴,文件向量不用重算:
    • ListReplies(找能反駁這則訊息的回應):改用 fact checking 前綴,用途吻合
      • 可能要做一個新的 argument 讓 caller 決定是要 search result 還是 fact checking
    • ListArticles(找同一則或相似的謠言):維持 search result 前綴,或依量測結果決定
  • D. 一篇文件存兩個向量(加前綴、不加前綴各一個):成本加倍

建議:在 staging 比較 A/B/C,分開量「文字找文字」、「文字找 media」、「media 找文字」三種情況(C 要另外量 ListReplies 的訊息找回應),挑同一則謠言的不同版本(文字、截圖、影片)當資料。cache 有版本機制,切換選項時只要重算向量,不會互相干擾。

相關但另外處理:要做到混排,還要解決幾件事:

  • 門檻要依模態組合分開設
  • BM25 和 kNN 的分數要正規化之後再合併
  • 完全相同的文字要明確加分

[!Note]

  • 先用 B + test merge
  • yutin 用 production 備份資料 restore 在 local 測搜尋效果
    • 因為需要 re-embed 所以 https://claude.ai/artifact/FzuFW2nJgdzV3dkbsN4V3y?sk=pivoVHsUewGHkzCaOP9FTA 裡面的 embedding 不適用
    • mrorz 提供生出上面 artifact 的 prompt
  • yutin 研究 fact checking prefix 的用途,再來決定是否讓 ListReplies 有 fact checking 搜尋選項

cofacts.ai

https://github.com/orgs/cofacts/projects/12

GGM’s report

GGM

https://cofacts-ai-eval.godgunman.workers.dev/reviews/2026-10-13

MrOrz’s TODOs

  1. url resolution: make https://github.com/cofacts/ai/pull/118 and 123 ready for review
  2. storybook: review and merge https://github.com/cofacts/ai/pull/126
  3. /report: make https://github.com/cofacts/ai/pull/138 and 139 ready for review
  4. plan for 分工, create foundation PRs

https://github.com/cofacts/rumors-api/issues/409

Dataset preparation PR: https://github.com/cofacts/rumors-api/pull/410

Report of the dataset: https://claude.ai/artifact/HYvYtVexD9WBy7f9Br1bdJ?sk=9s-UzTz6xj3z_kQgzTNaCQ

Artifact 內容摘要:Hyperlink validity dataset(2026-10-12,cofacts/rumors-api#409 / cofacts/rumors-api#410)</summary>

artifact 僅分享給組織內成員,故未存原始 HTML;以下為摘要。

這是一個可篩選瀏覽的資料集檢視頁,收錄 url-resolver 從 Cofacts article/reply 的 URL 抓到的內容,以及「標題、內文各自能不能存」的標籤(keepTitle / keepSummary),給 rumors-api#409 的 classifier 測試與 prompt tuning 用;資料與說明在 rumors-api#410。除 curated 外的標籤由 Claude 標註、尚未人工確認。

規模:共 1,206 筆、603 個不同 URL。快照:production(現存資料)545 筆、rerun-20261012(重抓)603 筆、curated(人工測資)58 筆。標註來源:llm-review 872、rule 276、human 58;標為「有爭議」58 筆。

四種存檔決策(標籤分布):

  • 都存(標題與內文都是該 URL 專屬內容):508
  • 只存標題(如 YouTube 標題真、內文為全站說明):104
  • 只存內文(如 FB 標題是「登入或註冊即可查看」、內文是貼文):25
  • 都不存(登入牆、404、封鎖頁、被轉到首頁或列表頁、錯誤頁、沒抓到東西):569

細分類 kind:content 508、empty 139、blocked 130、partial 129、login_wall 116、not_found 113、generic_page 50、error_page 21。

快照比較:production 快照「都存」241/「都不存」221;10/12 重抓「都存」209/「都不存」348,重抓時被擋或抓不到的比例明顯較高。

網域:最多的是 www.facebook.com(64)、youtu.be(36)、m.facebook.com(30)、youtube.com、www.instagram.com(各 22)、udn.com(20),其次 liff.line.me、share.google、www.youtube.com、t.me(各 16)等。facebook 網域 94 筆中 login_wall 65 筆、content 16、partial 13。

每筆欄位含 url、canonical、title、summary、HTTP status、error、標註理由、規則預判(preLabel)與 HTML 特徵(htmlFeatures),可依快照、細分類、是否有爭議、是否有 HTML 特徵篩選。

</details>

  • 這種 decision model 的問題設計(true/false for each category),是否是正確方向?
    • 是 [name=ggm]
  • nonumpa 有請 AI 抓相同 domain 抓 5 筆,後來沒認真看狀況
  • 怕同一個 pattern 出現很多次,所以就一直抓 facebook [name=nonumpa]
    • 1200 個裡面有滿多樣的樣態,即使 facebook 也記錄到不同 [name=mrorz]
    • 在 prompt 裡面有請他呼叫 url-resolver(但我沒確認他是不是真的用 url-resolver、也沒確認是哪個版本) [name=mrorz]

[!Note] 先放著,有人有興趣可以接手 把研究結果 report 補回 issue

AI category 實驗

Design: https://github.com/cofacts/kb/pull/29

Report: https://claude.ai/artifact/AU8oEiuY2QTqNRLfaQs8Dz?sk=40cO38yM9YeNMafGQiIckw#overview

Artifact 內容摘要:Cofacts 主題分類器實測(2026-10-11 ~ 10-12,kb#29)</summary>

artifact 設為「知道連結即可檢視」,但它是 Dashboard 類型(頁面從 artifact 的資料庫動態載入資料),單獨存 HTML 無法離線顯示,故只留摘要。

設定:從 cofacts/ground-truth 20211210_17111 抽 1,000 篇(每類至少補到 60 則正例,愛滋病只有 6 則;其餘隨機),16 個 ListCategories 類別。比較 7 個方案:決策模型 Jev(typesafe/jev, jev-1.13.0)、Clef(@cf/cloudflare/clef 27B)、clef-flash(9B)搭配題目 A/B,以及 Claude Haiku 5.5(effort low/medium)直接輸出分類清單(structured outputs)。決策模型一次送 16 個是/否問題,回傳每類 0–1 機率,門檻事後決定。

題目 A/B:A 是 16 類共用一句通用條件(「目標編輯會不會有興趣處理」);B 逐類寫具體判斷條件(例:詐騙寫出付款、繳押金、提供個資、加陌生帳號、點連結領贈品、假冒政府或企業),只在定義重疊的 4 類(疾病醫藥、COVID-19、農林漁牧、科技資安)加排除條件。B 每篇多約 140 tokens(成本 +3%)。注意:B 是參考 A 在同一樣本上的誤判寫的,分數可能略樂觀,需在未看過的樣本驗證。

方案總覽(規則 F1 = 分數 ≥ 0.7、最多前 2 個、都沒過取最高 1 個)

方案 AUC 規則 F1 逐類門檻 F1 P≥0.9 設定 P / R 每 1 萬篇 USD 延遲 p50(8 併發)
jev-a 0.969 0.698 0.721 0.967 / 0.649 1.70 0.28s
jev-b 0.973 0.748 0.740 0.976 / 0.630 1.76 0.27s
clef-a 0.967 0.680 0.702 0.960 / 0.683 8.21 0.47s
clef-b 0.974 0.750 0.750 0.953 / 0.633 8.49 0.46s
clef-flash-a 0.959 0.671 0.684 0.942 / 0.658 1.30 0.78s
haiku-low – 0.724 – – 5.29 1.87s
haiku-medium – 0.742 – – 5.91 2.55s

結論:最好的方案 micro-F1 都在 0.74–0.75(jev-b、clef-b、haiku-medium),差距不顯著。題目寫法比模型重要:同模型 B 比 A 的 F1 高 0.02–0.07;同用 B,三模型差距 ±0.01。建議 jev-b:準確率打平但最便宜、最快、可調門檻。回填 28 萬篇約 USD 49(jev-b)/237(clef-b)/165(haiku-medium);重跑 17K 約 USD 3。Jev 在 8 併發(約 21 req/s)偶遇 AI Gateway 429,重試一次即成功。

上線策略建議:逐類門檻、以 precision ≥ 0.9 為約束取 recall 最大的門檻;達不到 0.9 的類別先不自動標。若每篇至少要一個標籤:分數 ≥ 0.7、最多前 2 個、保底 1 個;題目 B 下 Jev 與 Clef 可共用 0.7。校準圖顯示各方案以 ground truth 看都偏過度自信,但 ground truth 漏標會低估實際正例率,所以門檻應逐類從資料校準。

逐類(jev-b,max-F1 門檻下):性別議題 F1 0.94、愛滋病 0.92 表現好;疾病醫藥 0.62、中國政治宣傳 0.68、COVID-19 0.72、詐騙 0.73、跨國互動 0.72 較弱;COVID-19、疾病醫藥、跨國互動在 jev-b 下達不到 precision 0.9。「疾病、醫藥」的誤判多半原本是「保健秘訣」或「COVID-19」。

precision 是下限:抽查 jev-a 分數 ≥ 0.8 的誤判 40 則,Claude 依定義判讀 31 則其實合理(不合理的 9 則全是把一般保健文章標成「疾病、醫藥」);抽查「只有題目 B 才給 ≥ 0.9」的誤判 30 則,28 則合理,只是 ground truth 標了別的分類(非人工判讀,僅供量級參考)。

</details>

  • 價錢可忽略,超便宜
  • Micro F1 0.7 是高還是低?
    • 但看「錯誤範例」好像又覺得,model 標得還算合理? [name=mrorz]
  • Jev vs Clef 不相上下
  • 每個 category 的 confidence 分布不同
    • 問題:如果要讓每個 category 有不同的 confidence threshold,要存在哪裡?
      • 如果每個 category 都抓 0.75 之類的,會差很多嗎?
      • 抓個 0.7 好像不差 [name=ggm]
    • category description / prompt 跟著模型來優化嗎?

[!Note]

  • 用 Jev 搭配修改 category description 減少誤判
  • confidence level 可以先抓個 0.75 之類,不用 by category
  • 先放著,有人有興趣可以接手
  • TODO: 把 report 存下來之後補回相關 issue or kb design doc

MozFest

  • 2026/10/28~2026/10/31
  • Booth
  • https://drive.google.com/drive/u/0/folders/1LM2GVo8glJFxy-OnqFsXdIaRS-cupXj4
    • 四節式易拉展,圖面 57cm x 206cm
    • Figma –> Export 出 N 倍 PNG –> 進 Afinity 放置在印刷廠樣板 EPS 檔裡
    • both Submission flow and Query flow

Meeting

  • 10/20(二)8pm 線上
  • 10/29(四)8pm (TPE) 線上 (tentative)

KB update

https://cofacts.github.io/kb/

  • Artifacts will be downloaded and accessible in browser via Github Pages (Built-in Jekyll)
  • Adds no-index header to block search engine crawlers
    • 維持公開查閱
    • 檢索的部分用個人 coding agent 直接 add repo 搜比較好,網頁只是 for better visualization & 人類直接 navigate

上週重點整理(2026-10-06 ~ 2026-10-12)

GitHub 活動

cofacts/rumors-api

cofacts/cofacts-ai-eval

cofacts/rumors-line-bot

cofacts/rumors-deploy

Discord 重點討論

general

  • 只有 20261006 會議記錄的 HackMD bot 通知,沒有人類討論。

Cofacts 正式環境狀態週報(2026-10-06 ~ 2026-10-12)

總結: 整體健康,所有 Cloud Run 服務都是 Ready。10/07~10/08 有一波流量高峰,Elasticsearch CPU 和 site-tw 對外流量都約翻倍,之後已回到正常水準。

GCE(cofacts-prod)

格式為 min | avg | p95 | max

日期 CPU (%) Mem (%) Swap (%)
10-06 9.7 | 18.1 | 24.9 | 50.6 62.4 | 65.3 | 67.8 | 76.4 30.9 | 33.3 | 35.3 | 35.9
10-07 10.8 | 25.8 | 38.5 | 43.5 62.2 | 66.4 | 70.2 | 86.5 31.6 | 33.8 | 35.5 | 40.2
10-08 9.3 | 28.4 | 44.5 | 77.0 62.9 | 65.5 | 67.8 | 69.7 32.9 | 35.1 | 38.9 | 39.9
10-09 8.6 | 16.2 | 22.2 | 41.7 62.2 | 65.2 | 67.2 | 70.2 33.2 | 34.0 | 35.6 | 36.6
10-10 11.0 | 18.5 | 25.8 | 44.8 62.4 | 64.8 | 66.4 | 67.2 33.8 | 35.7 | 37.1 | 37.4
10-11 10.6 | 17.8 | 24.0 | 41.7 63.0 | 65.4 | 67.4 | 78.0 33.9 | 34.9 | 38.4 | 40.9
10-12 11.7 | 19.1 | 26.6 | 79.2 62.2 | 64.8 | 67.1 | 68.5 33.4 | 35.0 | 38.5 | 39.2

目前狀態:

  • 已連續運行 158 天,load 約 1.1。
  • 記憶體用了 20Gi/31Gi,可用 10Gi。
  • Swap 用了 1.3Gi/4Gi。
  • 磁碟使用率 62%,剩 26G。

主要程序

程序 正常範圍(日平均) 穩定度 觀察
Elasticsearch CPU 約 0.27 核;RSS 約 19.2 GB RSS 很穩(std 約 100MB);CPU 在 10/07、10/08 升到 0.43、0.53 核,峰值 0.86 核 高峰和 site-tw 流量暴增同一時段,應該是外部查詢量造成的。記憶體和上週一樣。
node /srv/www/server.js CPU 約 0.17 核;RSS 約 350 MB 波動偏大(std 約 130MB) 10/07 RSS 峰值 1,362 MB,上週最高約 900 MB,同一天 CPU 峰值 0.49 核。之後回到 700~800 MB。
node /srv/www/build/index.js CPU 約 0.13 核;RSS 約 147 MB 平時很穩 10/12 RSS 突然衝到 1,182 MB(上週最高約 860 MB),同時 CPU 峰值 0.33 核,正好對上當天主機 CPU 79% 的尖峰。
Chromium(puppeteer) 每個 renderer 約 300 MB 短暫出現,用完就結束 正常。

Cloud Run

CPU 和記憶體都是相對上限的使用率(1.0 = 100%)。

服務 CPU p50/p95/p99 峰值 Mem p50/p95/p99 峰值 觀察
site-tw 0.14/0.75/0.95 0.80/0.98/1.01 記憶體長期貼著上限,和上週一樣。10/08 CPU p50 升到 0.34,對外流量 8.9 GB,平常約 4.6 GB。
site-staging-tw/en/ja 約 0.01/0.11~0.18/0.34~0.46 0.77~0.82/0.86~0.95/0.90~1.01 記憶體偏高,但這是 staging,影響不大。
line-bot-en/ja、dev-line-bot 約 0.01/0.08~0.16/0.16~0.30 0.40/0.43/0.44 穩定。dev-line-bot 在 10/07 有部署。
cofacts-ai 0.016/0.11/0.32 0.06/0.07/0.07 穩定,負載很低。
line-bot-tw — — 兩週來 request 數都是 0,只有 10/01 部署時有資料。台灣 LINE bot 實際跑在 GCE 上,所以這是正常的。

建議

  1. 10/07~10/08 的流量高峰值得查一下來源。 ES CPU、site-tw 的對外流量和 CPU 在同一時段一起暴增。可以到 Cloudflare 看看是不是有新的爬蟲繞過了現有的 WAF 規則,這也和降低 egress 費用的追蹤有關。
  2. site-tw 的記憶體一直貼著上限(p99 約 101%)。可以考慮調高記憶體上限,或檢查 Cloud Run 有沒有 OOM 重啟的紀錄。
  3. build/index.js 10/12 的 1.2 GB 尖峰是單一事件,先持續觀察,如果再發生再調查。
  4. Swap 穩定在 33~36%,磁碟 62%,目前都不用處理。