# X For You アルゴリズム — ソースコード完全ナレッジ

> リポジトリ `github.com/xai-org/x-algorithm` を全216ファイル（Rust 139 / Python 68）クローンして精読した実装レベルの知見。
> 観測日 **2026-05-16** / コミット = 2026-05-15 公開版 / ライセンス Apache 2.0。
> ローカル: `~/Documents/x-algorithm-repo/`

---

## 0. 最重要の前提 ―「何が公開され、何が公開されていないか」

ここを誤解すると全部ミスリードする。

| 公開されている | 公開されていない（=ブラックボックスのまま） |
|---|---|
| パイプライン構造・全フィルタ/スコアラ/ソースのロジック | **各エンゲージの実際の重み値**（`FavoriteWeight`等の数値） |
| Grok系transformerのアーキ実装（grok-1移植） | 本番feature switches / decider 設定（`params::FS_PATH`, `params::decider_path()`） |
| ミニ学習済みモデル（128次元4層）の重み | 本番モデルの重み・層数・訓練データ全体 |
| ハッシュ関数・埋め込み構造 | リアルタイム連続学習の中身 |

決定的事実：本番のスコア重みは Rust コードでは `query.params.get(FavoriteWeight)` のように **`xai_feature_switches::Params` 経由で外部から注入**される。その `params` クレート（`xai_home_mixer::params`）も feature-switch ファイルも**リポジトリに含まれていない**（`params.rs` は存在せず `Cargo.toml` すら同梱なし）。

→ **「コードは透明／意思決定（チューニング）は不透明」** はマーケ表現ではなく、コードを読むと構造的にそうなっている。唯一の具体的な重みは後述するOSSデモ用のハードコード値だけ。

---

## 1. システム全体（7ステージ）

```
For You Request
  └─ HOME MIXER（オーケストレーション層 / Rust / gRPC ScoredPostsService）
       1. Query Hydration   ユーザー文脈を並列取得
       2. Candidate Sources Thunder + Phoenix 等を並列取得
       3. Hydration          候補にメタデータ付与（並列）
       4. Pre-Scoring Filter 重複/古い/自分/ブロック/ミュート/既読 除外
       5. Scoring            Phoenix → Weighted → AuthorDiversity → OON
       6. Selection          Top K ソート / Blender で広告挿入
       7. Post-Selection     VF（削除/スパム/暴力）+ 会話重複排除
  └─ Ranked Feed Response
```

実行モデル（`candidate-pipeline/candidate_pipeline.rs`）:
- query hydrator / source / candidate hydrator は **全部 `join_all` で並列**
- side effect は `tokio::spawn` の **撃ちっぱなし（fire-and-forget）並列**
- hydrator/scorer は `Vec<Result<C, String>>` を返す = **1候補のエラーが他を巻き込まない**グレースフル設計

---

## 2. コンポーネント別

### 2.1 Home Mixer（Rust・統括層）
- gRPC `ScoredPostsService` を公開（`home-mixer/scored_posts_server.rs`, `server.rs`, `for_you_server.rs`）
- **Query Hydrator 22種**（`query_hydrators/`）。例: フォロー中ユーザ/topic/starter pack、ブロック・ミュートID、impression bloom filter、served history、相互フォロー、IP、推論gender/demographics、user action sequence（=エンゲージ履歴）
- **Candidate Source 12種**（`sources/`）: `thunder_source`（in-network）, `phoenix_source` / `phoenix_moe_source`（MoE）/ `phoenix_topics_source`, `ads_source`, `who_to_follow_source`, `prompts_source`, `cached_posts_source`, `push_to_home_source`, `tweet_mixer_source`
- **Candidate Hydrator 21種**（`candidate_hydrators/`）: core data, author(gizmoduck), engagement counts, has_media, language code, quote展開, 相互フォローjaccard, video duration, subscription, brand safety(×2), VF
- **Filter 19種**（後述）
- **Scorer**（後述）
- **Selector**: `top_k_score_selector`, `blender_selector`（広告ブレンド）
- **Side Effect 16種**: Redis候補キャッシュ、seen IDをKafka publish、served history更新/truncate、phoenixリクエストキャッシュ、client events Kafka、reranking Kafka、ads injection logging 等

### 2.2 Thunder（Rust・in-network即時ストア）
`thunder/posts/post_store.rs`, `thunder/kafka/tweet_events_listener.rs`
- Kafka の `TweetCreateEvent` / `TweetDeleteEvent`（`InNetworkEvent`, `LightPost`）を consume
- ユーザ別に **オリジナル投稿 / リプライ・リポスト / 動画投稿** の3ストアを保持
- `retention_seconds` で保持期間管理。`trim_old_posts()` が「未来時刻でない & `current_time - created_at <= retention_seconds`」だけ残す
- **`start_auto_trim(interval_minutes)`** で定期トリム（内部 interval は別途5秒pollも有）
- 著者あたり上限: `MAX_ORIGINAL_POSTS_PER_AUTHOR` / `MAX_REPLY_POSTS_PER_AUTHOR` / `MAX_VIDEO_POSTS_PER_AUTHOR`、スキャン上限 `MAX_TINY_POSTS_PER_USER_SCAN`（具体値は外部params側で非同梱）
- `DELETE_EVENT_KEY` はトゥームストーン用センチネルuser_id
- 狙い: in-network候補を**外部DBを叩かずサブミリ秒**で返す

### 2.3 Phoenix（Python/JAX・ML本体）
`phoenix/grok.py`（transformer, grok-1移植）, `recsys_model.py`（ranking 680行）, `recsys_retrieval_model.py`（retrieval 388行）, `run_pipeline.py`, `runners.py`

**Retrieval（Two-Tower / `recsys_retrieval_model.py`）**
- User Tower = ranking と同じ transformer アーキ
- Candidate Tower: `enable_linear_proj=True` → **2層MLP（SiLU）射影 → L2正規化**。Falseなら hashembのmean pooling
- User/Candidate を **L2正規化** し **dot product でANN top-K**
- `log_temperature` 学習パラメータ、訓練時 in-batch negatives `N_neg=64`
- `run_pipeline.py`: corpus_repr(537K) @ user_repr → `np.argpartition` で top_k_retrieval（既定 **200**）

**Ranking（Candidate Isolation transformer / `recsys_model.py`）**
- 入力 = User埋め込み[B,1] + History[B,S,D]（post+author+action+product_surface）+ Candidates[B,C,D]
- **アテンションマスク**: User+History は相互双方向 / Candidate は User+History にのみattend / **Candidate同士はattendできず自分のみ（対角）** → スコアがバッチ構成に非依存＝キャッシュ可能
- 出力 `[B, num_candidates, num_actions]` → sigmoid で各アクション確率

### 2.4 Candidate-Pipeline（Rust・再利用フレーム）
trait シグネチャ:
- `Source::fetch`、`Hydrator::hydrate(&self,&Q,&[C])->Vec<Result<C,String>>`（キャッシュ対応の `hydrate_from_cache`/`hydrate_from_client` 派生あり）
- `Filter::filter(&self,&Q,Vec<C>)->FilterResult{kept,removed}`
- `Scorer::score`（`enable(&Q)->bool` でゲート可）
- `Selector::select->SelectResult`、デフォルトは `score(&C)->f64` でソート
- `SideEffect`（async・撃ちっぱなし）
- pipeline は execution/監視とビジネスロジックを分離、独立段は並列、エラーは候補単位で隔離

### 2.5 Grox（Python・コンテンツ理解）
`grox/`。classifiers（content配下）、embedder、generators、summarizer、tasks（task_pub 555行 / task_filters 370行）、plans、schedules、data_loaders（asr_processor=音声書き起こし394行）、lib。
用途: スパム検出、投稿カテゴリ分類、**PTOS（policy）強制**。ranking/filtering に渡す content signal を生成する前処理レイヤ。

---

## 3. スコアリングの実装（核心）

`home-mixer/scorers/ranking_scorer.rs` が現役の実装（`weighted_scorer.rs` は旧/簡易版で同型）。

### 3.1 重み付き合成
```
combined = Σ ( wᵢ × P(actionᵢ) )
```
対象アクション（コード内の実フィールド名）:
`favorite, reply, retweet, photo_expand, click, profile_click, vqv(動画品質視聴),
share, share_via_dm, share_via_copy_link, dwell, quote, quoted_click, quoted_vqv,
cont_dwell_time(連続滞在秒), cont_click_dwell_time, follow_author`
ネガティブ: `not_interested, block_author, mute_author, report, not_dwelled`

各 wᵢ は `query.params.get(FavoriteWeight)` … で **feature switch から実行時注入**（値は非同梱）。

### 3.2 オフセット（負スコアの扱い）— `offset_score()`
```
total_sum == 0      → max(combined, 0)
combined  <  0      → (combined + negative_sum) / total_sum × NEGATIVE_SCORES_OFFSET
それ以外            → combined + NEGATIVE_SCORES_OFFSET
```
`negative_sum = -(not_interested+block+mute+report+not_dwelled)`、`total_sum = positive_sum + negative_sum`。負を線形リスケールして下限側に潰す設計。

### 3.3 VQV（動画品質視聴）ゲート
`video_duration_ms > min_video_duration_ms` のときだけ VQV 重みを適用（短尺動画はVQV寄与ゼロ）。`quoted_vqv` も同様（`enable_quoted_vqv_duration_check`）。

### 3.4 著者多様性 — `apply_author_diversity()`
スコア降順に走査、著者ごとに出現位置 `position` をカウントし倍率を掛ける:
```
multiplier(position) = (1 - floor) × decay_factor^position + floor
```
`AuthorDiversityDecay` / `AuthorDiversityFloor` は feature switch。**同一著者が2回目以降に指数減衰、floorで下げ止まり**。1人がフィードを占有できない。

### 3.5 OON（out-of-network 抑制）— `effective_oon_weight()`
in-network はそのまま、**out-of-network は係数を掛けて減衰**:
- `topic_ids` 指定リクエスト → `TopicOonWeightFactor`
- 新規ユーザ（アカウント年齢 < `NewUserAgeThresholdSecs` **かつ** フォロー数 ≥ `NEW_USER_MIN_FOLLOWING`）→ `NEW_USER_OON_WEIGHT_FACTOR`
- それ以外 → `OonWeightFactor`

最終: `final = (weighted → author_diversity) ×(OON係数 if out-of-network)`。

### 3.6 ★唯一の具体的重み（OSSデモ限定 / `run_pipeline.py:355`）
本番値は非公開だが、同梱デモパイプラインのハードコードはこれ:
```
weighted = P(fav)×1.0 + P(reply)×0.5 + P(retweet)×0.3 + P(dwell)×0.2
```
アクションenumインデックス（`run_pipeline.py` / proto `ActionName`）:
`1=favorite, 4=reply, 5=quote, 6=retweet, 11=dwell, 13=video_quality_view`。
※これはデモ用であって本番チューニングではない点に注意。それでも「reply は fav の半分、RT は1/3」という**相対感の参考値**にはなる。

---

## 4. フィルタ全19種（`home-mixer/filters/`）

**Pre-Scoring**
| ファイル | 役割 |
|---|---|
| `drop_duplicates_filter` | post_id重複除去 |
| `core_data_hydration_filter` | core metadata取得失敗を除去 |
| `age_filter` | `duration_since_creation(tweet_id) <= max_age` 超過を除去（max_ageは注入） |
| `self_tweet_filter` | 自分の投稿除去 |
| `retweet_deduplication_filter` | 同一内容RTを重複排除 |
| `ineligible_subscription_filter` | アクセス不可の課金コンテンツ除去 |
| `previously_seen_posts_filter` / `_backup_filter` | 既読除去 |
| `previously_served_posts_filter` | セッション内served除去 |
| `muted_keyword_filter` | ミュートKW含む投稿除去 |
| `author_socialgraph_filter` | ブロック/ミュート著者除去 |
| `topic_ids_filter` / `new_user_topic_ids_filter` | topicベース絞り |
| `video_filter` | 動画条件 |

**Post-Selection**
| `vf_filter` / `ancillary_vf_filter` | 削除/スパム/暴力/グロを除去（Visibility Filtering） |
| `dedup_conversation_filter` | 同一会話スレッドの複数枝を重複排除 |

`age_filter` は tweet_id（Snowflake）から生成時刻を逆算して年齢判定するのが実装ポイント。

---

## 5. 広告ブレンド（`home-mixer/ads/`）

`util.rs` の実定数:
```rust
MIN_POSTS_FOR_ADS  = 5      // 5投稿未満なら広告挿入しない
MIN_REQUESTED_GAP  = 3
DEFAULT_SPACING    = { requested: 3, min: 2 }   // 広告間隔 理想3・最低2
```
- `safe_gap_blender.rs`: brand-safety的に安全な「gap」だけを探し、理想位置(`insert_position`)から最も近い安全gapに広告を割付。先頭4広告のみ位置調整。
- `has_avoid()`: `BrandSafetyVerdict::MediumRisk` の隣は広告回避（センシティブ境界を尊重）
- `partition_organic_blender.rs`: organicと広告を分離してからマージ
- `RESULT_SIZE` は params 注入

→ 広告は「最低5件以上のフィードで・最低2件間隔・brand-safety的に安全な隙間にだけ」入る。

---

## 6. モデル設定値（同梱ミニモデル / `phoenix/README.md` + `run_pipeline.py`）

| 項目 | 値 |
|---|---|
| 埋め込み次元 | 128（本番はより広い） |
| transformer層 | 4（本番はより深い） |
| アテンションヘッド | 4 |
| key_size | 32 |
| widening_factor | 2.0 |
| attn_output_multiplier | 0.125 |
| history長 | 127 |
| candidate長 | 64 |
| user/item/author vocab | 各 1,000,000 |
| エンティティ毎ハッシュ数 | 2 |
| action種 | 19 |
| product_surface_vocab | 16（既定） |
| post_age粒度 | 60分（既定） |

- ハッシュ: 線形合同（int64 wrap）。unified table を `pad=65` 起点に user→item→author 領域連結。`id==0` はpad(0)に落とす
- 同梱corpus = `sports_corpus.npz`（6時間窓のSports topic **約537K post**）。デモ履歴 `example_sequence.json` = NFL/NBA/NHL の3件
- frozen checkpoint（本番は連続学習、これはその一時スナップショット）
- 実行: `uv run run_pipeline.py --artifacts_dir artifacts/oss-phoenix-artifacts`（artifactsは~3GB Git LFS、retrieval/ranker各 model_params 3MB + embedding_tables 1.4GB）

---

## 7. So What ―運用・分析にどう効くか

1. **「Xアルゴ解析記事」を鵜呑みにしない根拠ができた**: 重み数値は非公開。"replyが最強で重み◯◯" 系の断言はコード上裏が取れない（相対比はデモ値 fav1.0/reply0.5/RT0.3/dwell0.2 まで）。
2. **構造的に効くレバーは確定情報**: ①ネガティブ(block/mute/report/not_interested)は negative_sum 経由で全体を強く押し下げる ②同一著者連投は指数減衰（連投より別アカ/別著者の広がりが効く）③out-of-networkは常時係数で減衰＝**フォロー外リーチは構造的にハンデ**、ただし新規ユーザ/topic指定時は係数が別。
3. **動画**: 一定尺(min_video_duration)未満はVQV重みが効かない＝極端な短尺は動画視聴シグナルで損する設計。
4. **広告下限**: フィード5件未満／間隔2未満では広告が入らない＝低エンゲージ薄いフィードは広告機会も減る（プロダクト示唆）。
5. **透明性の正体**: 「コードは公開／チューニング(feature switch)は非公開」。EU規制対応の文脈で見ると、"説明可能性"の核（重みと連続学習データ）は依然ブラックボックス。

---

## 参照（一次）
- リポジトリ: https://github.com/xai-org/x-algorithm （ローカル `~/Documents/x-algorithm-repo/`）
- 主要ファイル: `home-mixer/scorers/ranking_scorer.rs`, `home-mixer/ads/util.rs`, `phoenix/recsys_model.py`, `phoenix/recsys_retrieval_model.py`, `phoenix/run_pipeline.py`, `thunder/posts/post_store.rs`, `candidate-pipeline/candidate_pipeline.rs`, `README.md`, `phoenix/README.md`
- 告知ツイート（X API v2検証済）: https://x.com/elonmusk/status/2055277918633562153
