aire.
·8 分鐘

「AI 味」在 X 不是一句評語,是一個欄位

X 公開的程式碼裡,Gemma 會替通過篩選的貼文打一個「AI 味」分數 slop_score,而排序與能見度過濾的三個目錄都沒有命中。真正掛得上處置的是另一個名字 llm_slop_user,命中就貼上 30 天的 SpamHighRecall,該標籤在只對推薦生效的規則裡是直接 drop。

收進

事實分級說明

檔案路徑與行號取自 xai-org/x-algorithm(Apache 2.0)2026 年 8 月 13 日的 HEAD 快照 a389166f。文中兩處負向結果——slopScore 在下游目錄沒有命中、llm_slop_userslopScore 之間沒有接線——是對這份快照的搜尋結果,不是對線上系統的量測。README 自陳,這次公開排除了 Grox 的 prompt 模板與部分 botmaker 規則。

「AI 味」在 X 不是一句評語,是一個欄位。

grox/flows/upa/classifier_banger_initial_screen_gemma.py 第 44 到 52 行,一個叫 BangerInitialScreenParsedOutput 的資料結構裡,有一項 slop_score: int | None,旁邊還有一項 has_minor_score: float | None。打分的是 Gemma,程式碼裡的模型識別字是 GEMMA_UPA

分數會落地。grox/flows/upa/task_write.py 第 164 到 179 行把它寫進 UnifiedPostAnnotations,欄位名 slopScore

有名字、有型別、有寫入點。剩下的問題只有一個:誰在讀它。

打「AI 味」分數的是 Gemma,只跑在通過篩選的貼文上

先把射程圈出來,因為這個分類器不是掛在全站入口上的。

貼文要先進到 PlanInitialBanger 這條流程,才輪得到它。generators.py 第 17 行只從 TOPIC_MIN_TRACTION 這一個來源注入候選;task_filter.py 第 22 行把帶有 ancestors 的回覆排除掉,第 46 到 48 行把受保護帳號排除掉。

所以進到 Gemma 面前的,是進入 PlanInitialBanger 且通過目前這幾道公開 filter 的那一批貼文。「X 用 Gemma 幫每一則貼文打 AI 味分數」這種講法,跟程式碼對不上。

同一次呼叫產出的還有 has_minor_score,一個判斷內容是否涉及未成年的分數。兩個分數並排在同一個資料結構裡。

排序與能見度過濾的目錄裡,slopScore 是零命中

全 repo 搜 slop_score,只有 4 處,而且全部落在 grox/flows/upa/ 這一條產出鏈上:分類器第 50 行與第 152 行、state_initial_banger.py 第 13 行、task_write.py 第 173 行。

四處都是在算它、傳它、寫它。沒有一處在讀它來做決定。

往下游看。home-mixer/(排序)、visibility-filtering/(能見度過濾)、phoenix/(排序模型服務)三個目錄,搜 slopScoreslop,零命中。

零命中這種結果,要先確認偵測器本身沒壞。一個搜錯路徑、搜錯大小寫、或是被排除規則吃掉的指令,回報的也是零命中,而且長得一模一樣。所以同一組指令要先拿已知會命中的字去試:對 home-mixerfavorite,命中 3 個檔案;對 visibility-filteringNSFW,命中 2 個檔案。偵測器是通的,那三個目錄的零命中才有資格當成一個結果來讀。

還有一個很容易看走眼的地方。botmaker 底下的 FeaturesOfUnifiedPostAnnotationsEvent.java 第 68 行確實在讀 UnifiedPostAnnotations,讀的是隔壁那個 hasMinorScore,不是 slopScore。同一個資料結構被下游讀了,被讀走的偏偏是另一個欄位。

射程要標準確:這是「在這份公開快照裡找不到讀取端」,不是「X 沒有在用」。沒讀到不等於線上沒用;反過來也一樣,不能因為找不到就斷言 slopScore 一定沒被使用。X 這次放出來的是策展過的快照,讀取端在不在快照裡,是兩回事。

到這裡,一個看起來很乾淨的結論已經成形了:AI 味被量了,但量出來的分數沒有人拿去排序。

這個結論查到一半就收手的話,下一句很容易接成「所以 AI 味不影響能見度」。而那句話是錯的。

掛得上處置的是 llm_slop_user,不是 slopScore

abuse-enforcement-service/service-lib/rules/enforcement_user.yaml 第 51 到 56 行:

- id: act_add_llm_slop_label
  when: '"llm_slop_user" in score.labels'
  then:
    kind: act_add_labels_v2
    labels: ["SpamHighRecall"]
    ttl_msec: 2592000000

ttl_msec 那串數字的單位是毫秒。2592000000 毫秒換算下來是 30 天。

被掛上的標籤叫 SpamHighRecall,它會導致什麼,答案在另一個檔案裡。visibility-filtering/rules/registry.rs 第 153 行,SPAM_HIGH_RECALL_DROP 就寫在只對推薦生效的那 26 條規則之中。Drop 在這套規則裡的意思很直接:這則內容不會出現在那條管線的輸出裡。

貼文層另有一組。enforcement_post.yaml 第 39 到 44 行,llm_slop_post 掛上 RiskyHighVizReply,一樣 30 天;隔壁第 47 到 52 行,gibberish_post 掛的是 SpamHighRecall,也是 30 天。

關鍵在名字上。llm_slop_user 跟 Gemma 打的 slopScore 不是同一個分數。Gemma 那條鏈的終點是 UnifiedPostAnnotations 裡的一個整數欄位;規則檔這一條讀的是 score.labels 這個集合裡有沒有 llm_slop_user 這個字串。誰算出這個標籤、用什麼算的、跟 Gemma 的整數之間有沒有換算,這份快照裡找不到接線。

兩件事都成立,而且互不牴觸:量出來的 slopScore 在這份程式碼裡找不到排序端的讀取者;同時,帶有 llm_slop_user 的帳號會被掛上一個 30 天的標籤,而該標籤在推薦這條路上是直接丟掉。

同一個規則檔裡還有一條 skip 值得一起看。enforcement_post.yaml 第 33 到 37 行:當 cred.is_high 為真、或 cred.score 大於等於 50.0,而且沒有設定 skip_author_credibility_prechecks 的時候,這條規則回傳 kind: skip,理由欄位寫的是 pagerank_skipped。要照它的身分讀——這是該規則檔裡的一條略過規則,射程就是這個檔案裡的這條規則。

LLM 真正在做事的地方是標記層

grox/config/config.py 第 79 到 92 行列了 12 個模型識別字,包括 grok-4-minigrok-4-1-fast-hedgehog 系列、eapi- 開頭的一整批,以及 eapi-grok-4-5-internal

檔案裡沒有一張「哪個模型負責哪件事」的對照表。名字看起來很有暗示性,但用途只能從呼叫點回頭判讀。〈你叫的名字,和你拿到的東西〉講的正是這個落差:識別字是招牌,招牌背後是什麼,得往下翻。

翻得到呼叫點的有三處。

回覆 spam。reply_spam/constants.py 第 5 行把 GEMMA_2 指到 oai-gemma4-26b-2classifier_simple_reply_scorer.py 第 15 行用它。

PTOS realtime spam。ptos/task_ptos_spam_detection.py 第 23 到 28 行以 oai-gemma4-26b-ptos-realtime 優先取樣,失敗才回退到 grok-4-1-fast-hedgehog-critical。同一個工作有主力模型也有備援模型,兩顆都在。

協同 spam。reply_spam/classifier_coordinated_spam.py 第 25 到 34 行用 Gemma,第 109 到 128 行把整串回覆(含每一則貼文與媒體)送進去,讓模型指出可疑的索引。模型回話之後還有兩層程式碼在收尾:第 57 到 66 行把只命中一則的結果丟掉,第 73 到 101 行在最新那則回覆的作者已經獲得原貼作者回覆時撤銷判定。判定可疑的標準寫在 prompt 裡,prompt 沒有隨程式碼公開。

三處呼叫都落在 grox 的標註流程裡,產物是欄位與標籤。〈Community Notes:同一邊的人一起說好,推不動分數〉裡的 AI Note Writer 是同一種擺法——AI 進得了「寫」那一端,評分權還在別的機制手上。

排序當下確實有一次推論,打給的是 Phoenix

排序服務在線上會發出一次預測請求,這一點不能含糊。home-mixer/scorers/phoenix_scorer.rs 第 93 到 98 行,先 build_prediction_request(...),接著 self.dispatch.predict_with_fallback(query, cluster, request).await

有請求,有回應,有 fallback。排序這條路上確實有模型在跑。

但跑的是 Phoenix。Phoenix 是排序模型,不是語言模型——它吃的是特徵,吐的是動作機率,〈X 推薦權重表:分數一旦翻負,差多少就不重要了〉拆的那張權重表,乘的就是它吐出來的那組機率。

home-mixer/ 裡有 12 個檔案提到 Grok 或 Gemma,逐檔看過,沒有一處在請求 Grok 或 Gemma 推論。它們出現的方式只有三種:向 store 取值、放進請求、比對標籤鍵。

followed_grok_topics 是被放進預測請求的一個特徵

三種讀取裡,最能說明整篇文章在講什麼的是這一種。

home-mixer/util/phoenix_request.rs 第 50 行,把 followed_grok_topics 放進要送給 Phoenix 的預測請求裡。

Grok 的產物是以特徵的身分進入排序,不是以呼叫的身分進入。排序服務拿到的是一組已經算好的主題資料,不是一個等著模型回話的連線。這一行就是整套架構最短的證據。

另外兩處是同一個形狀。query_hydrators/followed_grok_topics_query_hydrator.rs 第 28 行執行 self.client.get_followed_grok_topics(query.user_id).await,向 store 取值。models/brand_safety.rs 第 48 行做的是 labels.contains_key(&SafetyLabelType::GROK_SFA),看一個標籤鍵在不在。

回覆評分有一把 0 到 3 的量表,量表本身沒公開

標記層裡有一個位置特別敏感,因為它算的是每一則回覆的分數。

grox/flows/reply_spam/classifier_reply_ranking.py 第 21 行與第 30 到 53 行:一個叫 ReplyScorer 的類別,用 VisionSampler,也就是 Grok 的視覺模型,替回覆評分。

state_reply_ranking.py 第 6 到 8 行宣告的 schema 只有 score: float,沒有寫範圍。範圍在另一個地方,寫在執行期檢查裡。同一支分類器第 163 到 169 行:

if not (0 <= score <= 3):
    raise ValueError(f"Score {score} outside the 0-3 rubric")

rubric 是程式碼自己用的字,中文叫評分量表。分數落在 0 到 3 之外就丟例外,不是統計上的觀察,是程式碼自己設下的護欄。第 48 到 51 行 metrics 的分桶邊界 [0.0, 1.0, 2.0, 3.0] 跟它一致。

範圍在,級距不在。grox/flows/ptos/prompts.py 第 11 行的註解把理由寫得很白:

As stated in README for the open source repo, prompts are excluded to reduce gameability of the system.

該檔用 FileSystemLoader 去載 templates 目錄底下的 .j2 檔,而 grox/flows/ptos/ 底下只有 .py,沒有 templates 目錄。載入器留著,模板拿掉了。

所以一則回覆拿到 1 分還是 3 分、每一級各自代表什麼,這份快照答不了。X 沒有含糊帶過這件事,它把「為什麼不給」直接寫在註解裡:降低系統被操弄的可能。

它不是在評分,是在打安全牌〉講過一個模型打出來的分數要怎麼讀。這裡連讀的機會都沒有——分數的形狀公開了,判準沒有。

能見度靠的是確定性規則,先命中先掉

標籤都貼好之後,決定內容出不出得去的不是模型,是一張規則表。

visibility-filtering/rules/registry.rs 第 102 到 130 行是 base_home_rules(),28 條。第 140 到 166 行再加 26 條,合計 54 條。timeline_home_policy() 用的是 base 那 28 條,timeline_home_recommendations_policy() 用的是 28 加 26。

跑法寫在 rules/mod.rs 第 86 到 105 行。規則逐條過,遇到 Drop 立刻回傳、不再往下跑,並記下是哪一條規則做的決定。Interstitial(中介警示)的行為不一樣:它不短路,只記住第一個命中,然後繼續往下跑完。

多出來的 26 條裡,有些名字看了就知道在管什麼:DO_NOT_AMPLIFY_NON_FOLLOWER_USER_DROPNSFW_AVATAR_IMAGE_USER_DROPNSFW_BANNER_IMAGE_USER_DROP。後面兩條掛在帳號層——頭像與橫幅,跟單則貼文寫了什麼無關。前面提過的 SPAM_HIGH_RECALL_DROP 也在這一批裡面。

講到這裡要停住。filter_tweets.rs 第 28 到 34 行只把請求帶進來的 safety level 原樣映射成規則集,至於上游是誰、依什麼條件決定套哪一套,這份快照裡看不到呼叫端。能講的是「規則長這樣」,不是「觸及結果會變成怎樣」。

不過規則表本身的形狀已經夠清楚了:判斷交給模型,處置交給規則。模型在標註階段產出欄位與標籤,規則在能見度這一關做確定性的取捨,先命中先掉,而且會把是誰決定的記下來。出了問題可以回查是哪一條規則、哪一個標籤,不必去問一個模型當時在想什麼。

模型產欄位,執行層只讀欄位

把前面每一段的位置攤開來看,同一個形狀重複了很多次。

Gemma 打 slop_score,寫進 UnifiedPostAnnotations。Grok 的視覺模型替回覆打分,分數落成一個受 0 到 3 護欄約束的 float。Grok 的主題資料落成 followed_grok_topics。安全政策的判定落成 SafetyLabelType 底下的標籤鍵。處置規則要用的時候,讀的是 score.labels 這個字串集合。

每一個 LLM 的產物,最後都變成一個名字固定、型別固定、可以被寫進儲存層的東西。到了排序與能見度這一側,程式碼做的動作全部是讀取與比對:取值、放進請求、看鍵在不在、逐條比對規則。

這樣切開來,有幾件事會變便宜。標註可以離線批次跑,慢一點沒關係,貴一點也還算得過來;排序的延遲不再綁著模型服務的延遲;模型換版之後,舊欄位還在,可以重算也可以先不動;排序邏輯要調,改的是讀欄位那一段,不必重跑任何模型。反過來,把模型掛在請求路徑上,這幾件事的成本會一起往上跳。

另一半是處置端。X 在能見度這一關用的是列舉出來的規則,不是一個打分模型:54 條規則、先命中先掉、記下是哪一條決定的。判斷可以模糊,處置要能追。這兩件事被放在不同的層,各自用適合自己的工具。

自己在做 AI 應用的時候,這條線就是最實際的一個問法:手上這個模型呼叫,是在生產資料,還是在做決定?兩者要放的位置不一樣。

給非工程師的一句話

要判斷一個系統有沒有在管某件事,最容易踩的坑是只查一個名字。X 的程式碼裡,「AI 味」有 Gemma 打的 slopScore,也有規則檔認的 llm_slop_user。查前者,查到的是四處產出、下游零命中;查後者,查到的是命中就掛 30 天標籤、在推薦那條管線直接被丟掉。同一件事、兩個名字、兩個方向相反的答案,而它們之間在這份程式碼裡並沒有接線。

常見問題

X 有沒有在判斷貼文的「AI 味」?

程式碼裡有兩個不同的東西都指向這件事。一個是 Gemma 打的 slop_score,會寫進 UnifiedPostAnnotations 的 slopScore 欄位,只跑在進入 PlanInitialBanger 且通過目前公開 filter 的那批貼文上。另一個是 abuse-enforcement-service 規則檔裡的 llm_slop_user 標籤,命中就掛上 SpamHighRecall、時效 30 天。兩者在這份公開快照裡找不到接線,不是同一個分數。

slopScore 高的貼文會被降低觸及嗎?

這份快照答不了。home-mixer、visibility-filtering、phoenix 三個目錄搜 slopScore 與 slop 都是零命中;同一組指令對 home-mixer 搜 favorite 命中 3 個檔案、對 visibility-filtering 搜 NSFW 命中 2 個檔案,所以是真的沒有命中,不是搜尋方式壞掉。沒有讀取端不等於線上沒有使用,反過來也不能斷言它一定沒被使用。另外要注意能見度那一側有 llm_slop_user 這條路,命中就掛 SpamHighRecall,而 SPAM_HIGH_RECALL_DROP 在只對推薦生效的那 26 條規則裡是直接 drop。

X 排序的當下會呼叫 Grok 嗎?

排序服務在線上確實會發出一次預測請求,phoenix_scorer.rs 第 93 到 98 行看得到,但打給的是 Phoenix,而 Phoenix 是排序模型,不是語言模型。home-mixer 裡提到 Grok 或 Gemma 的 12 個檔案逐檔看過,沒有一處在請求 Grok 或 Gemma 推論。典型的用法是 phoenix_request.rs 第 50 行把 followed_grok_topics 放進預測請求裡,Grok 的產物以特徵的身分進入排序。

回覆評分的 0 到 3 分是怎麼打出來的?

打分的是 Grok 的視覺模型,classifier_reply_ranking.py 用 VisionSampler 取分。0 到 3 不只是 metrics 的分桶邊界,同一支程式第 163 到 169 行有執行期檢查,分數超出範圍會丟出錯誤,訊息原文寫的是 outside the 0-3 rubric。但每一個級距各代表什麼,寫在 .j2 prompt 模板裡,而模板沒有隨程式碼一起公開,prompts.py 的註解寫明理由是降低系統被操弄的可能。

📚 收進你的工具

For AI Reading Era

把這篇文章交給你日常用的工具——做研究、整理筆記,或當 AI 的 context。

 
延伸閱讀