---
title: "广告混排"
description: "广告混排：六种混排器、默认 multi_risk、品牌安全五档裁定、三类相邻检查与丢弃原因记录"
---

> Documentation Index
> Fetch the complete documentation index at: https://x-algorithm.vergego.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 广告混排

## 这一页回答什么

本页回答四个问题：混排器怎样把广告插进 For You 信息流；广告插在哪个位置；系统怎样计算帖子的品牌安全裁定；混排器在哪些情况下丢弃广告。

## 核心结论

1. **混排发生在外层选择器**。`BlenderSelector` 按 `AdsBlenderType` 参数从六种混排器中选一种（`home-mixer/selectors/blender_selector.rs:61-79`）。旧版只有 `safe_gap` 与 `partition_organic` 两种。
2. **开源默认值是 `multi_risk`**（`home-mixer/params/param.rs:957-962`）。参数取未知值时退回 `PartitionOrganicAdsBlender`（`blender_selector.rs:78`）。
3. **品牌安全裁定从四档变成五档**，新增 `HighRisk`（`home-mixer/models/brand_safety.rs:7-14`）。中风险与高风险帖子都不能紧挨广告（`home-mixer/ads/util.rs:24-29`）。
4. **广告数取三个上限中的最小值**：请求的广告数；按广告间距算出的容量；可作邻居的安全帖数的一半。帖子不足 5 条时，混排器不插广告（`ads/util.rs:9`）。
5. **混排器为每条被丢弃的广告记录原因**：帖子太少、超出间距容量、没有合格邻居、低风险邻居、排除账号、排除关键词、被截断。混排器把原因写入 `query.ad_drops`（`blender_selector.rs:80-82`、`ads/drops.rs:41-89`）。

## 在哪里发生

外层 [ForYouCandidatePipeline](https://x-algorithm.vergego.ai/concepts/home-mixer-orchestration/) 的 `BlenderSelector` 先把 `FeedItem` 按类型分桶，再选混排器：

```rust
// home-mixer/selectors/blender_selector.rs:61-81(节选)
let blender: &dyn AdsBlender = match query.params.get(AdsBlenderType).as_str() {
    "partition_organic_low_risk" => &self.partition_organic_blender,
    "safe_gap" => &self.safe_gap_blender,
    "multi_risk" => &self.multi_risk_blender,
    "slot_spread" => &self.slot_spread_blender,
    "least_movement" => &self.least_movement_blender,
    "time_gap" => { /* 用 AdsTimeGap* 参数构造 TimeGapAdsBlender */ }
    _ => &self.partition_organic_blender,
};
let mut blended = blender.blend(posts, ads, &mut drops);
query.ad_drops.extend(drops);
```

Prompt、Who to Follow、视频轮播、PushToHome 等模块在混排之后插入。详见 [候选选择与成型](https://x-algorithm.vergego.ai/concepts/candidate-selection/)。

`AdsBlender` trait（`ads/mod.rs:28-52`）采用模板方法模式：`blend()` 先记录帖子品牌安全裁定与广告风险等级的分布，再调用实现类的 `blend_inner_with_drops()`。

时序“关注”页使用另一个混排器 `FollowingAdBlender`。`FollowingBlenderSelector` 调用它（`selectors/following_blender_selector.rs:36-37`）。它以会话单元为单位插入广告（`ads/following_ad_blender.rs:76-96`）。本页只讲 For You。

## 品牌安全裁定

内层流水线在选后水合阶段计算裁定，只计算入选候选。`AdsBrandSafetyVfHydrator` 读取帖子的安全标签，再调用 `compute_verdict`（`candidate_hydrators/ads_brand_safety_vf_hydrator.rs:110-118`）。安全标签客户端的超时为 500ms，每批最多查询 100 条（`server.rs:831-838`）。

`BrandSafetyVerdict` 五档（`models/brand_safety.rs:7-14`）：

| 档位 | 值 | 对广告的含义 |
|------|----|--------------|
| `Unspecified` | 0 | 未裁定 |
| `Safe` | 1 | 可作广告邻居 |
| `LowRisk` | 2 | 可作邻居，但低容忍广告拒绝 |
| `MediumRisk` | 3 | 不能作普通广告的邻居 |
| `HighRisk` | 4 | 不能作任何广告的邻居 |

`compute_verdict()`（`brand_safety.rs:103-131`）按顺序判定：

```mermaid
flowchart TB
    A[帖子安全标签] --> H{"含高风险标签?"}
    H -->|是| HR[HighRisk]
    H -->|否| M{"含中风险标签?"}
    M -->|是| MR[MediumRisk]
    M -->|否| G{"有 GROK_SFA 或<br/>GROK_NSFA_LIMITED?"}
    G -->|否| MR
    G -->|是| P{"要求 PTOS 复审<br/>且未通过?"}
    P -->|是| MR
    P -->|否| L{"含低风险标签?"}
    L -->|是| LR[LowRisk]
    L -->|否| SF[Safe]
```

- **高风险标签 6 个**：`NSFW_HIGH_PRECISION`、`NSFW_HIGH_RECALL`、`GORE_AND_VIOLENCE_HIGH_PRECISION`、`PDNA`、`EGREGIOUS_NSFW`、`SOFT_NSFW`（`brand_safety.rs:73-80`）。
- **中风险标签 10 个**：如 `NSFA_HIGH_PRECISION`、`DO_NOT_AMPLIFY`、`MALICIOUS_URL`、`NSFA_COMMUNITY_NOTE`、`GROK_NSFA`、`NSFW_TEXT`（`brand_safety.rs:82-93`）。
- **低风险标签 3 个**：`NSFA_LIMITED_INVENTORY`、`GROK_NSFA_LIMITED`、`NSFA_HIGH_RECALL`（`brand_safety.rs:95-99`）。
- **PTOS 复审**：`tweet_id ≥ PTOS_CUTOFF_TWEET_ID`（2_054_275_414_225_846_272）且没有 `PTOS_REVIEWED` 标签即不通过（`brand_safety.rs:101`、`:190-192`）。雪花 ID 随时间递增。因此这条规则等价于“截止时刻之后发布的帖子须经复审”。

**作者兜底**是新增机制。`EnableAdsAuthorBrandSafetyFallback` 默认 true（`param.rs:971-976`）。帖子没有任何 Grok 标签时，水合器按原帖作者拉取作者级品牌安全标签，把它当作帖子的 Grok 标签，再计算一次裁定（`ads_brand_safety_vf_hydrator.rs:28-58`、`:136-148`）。启用作者兜底时，水合器关闭 PTOS 复审要求（`:111`）。

`EnableAdsBrandSafetyVerdictV2` 默认 false（`param.rs:964-969`）。打开后，带 V2 版 Grok 标签的帖子用 `compute_verdict_v2` 的标签表，其余仍走 V1（`brand_safety.rs:158-188`）。

帖子转成 `ScoredPost` 时，系统把缺失的裁定视为 `MediumRisk`（`home-mixer/scored_posts_server.rs:134-137`）。

## 三类相邻检查

所有混排器共用 `ads/util.rs` 的检查函数。检查函数从广告的 `ad_adjacency_control` 字段读取相邻控制规则。

| 检查 | 触发条件 | 丢弃原因 |
|------|----------|----------|
| `low_risk_block` | 广告风险容忍为 `BsrLow` 或 `BsrIas`，且上下邻居有一条 `LowRisk`（`util.rs:112-128`） | `LowRiskNeighbour` |
| `handle_block` | 邻居的作者、被转发作者、被引用作者或任一祖先作者在广告的排除账号列表里（`util.rs:130-149`） | `ExcludedHandle` |
| `keyword_block` | 广告的排除关键词经分词后出现在邻居正文中（`util.rs:151-174`） | `ExcludedKeyword` |

旧版 `handle_block` 只检查帖子作者。新版还检查被转发作者、被引用作者与祖先作者。

## 默认策略：MultiRiskAdsBlender

`multi_risk` 把帖子分为安全（含 LowRisk）、中风险、高风险三桶，用“帖-广告-帖”三元组插广告（`ads/multi_risk_blender.rs:38-260`）。

```mermaid
flowchart TB
    A[帖子序列] --> B["分桶:safe / medium / high"]
    B --> C["safe_budget = min(广告数, 间距容量, safe 数 / 2)"]
    C --> D{"广告是否 BsrHigh?"}
    D -->|"是,且有中风险帖对"| E["用两条中风险帖夹住<br/>只做账号与关键词检查"]
    D -->|否| F["从 safe 分组取上下两条<br/>做三类相邻检查"]
    E --> T[三元组]
    F -->|通过| T
    F -->|命中| X["丢弃并记录原因"]
    T --> G["三元组与剩余帖子交错<br/>剩余帖子按分数降序"]
    G --> H["截断到 35 项,末项是广告则去掉"]
```

1. **前置条件**：没有广告或帖子少于 5 条时，只返回帖子，广告记 `TooFewPosts`（`:46-57`）。
2. **间距容量**：`spacing_cap = (n − 1) / spacing.requested`（`:59-63`）。`compute_spacing` 取前 4 条广告 `insert_position` 的最小正差；差值不小于 3 时采用，否则用默认间距 3（`util.rs:54-75`）。
3. **安全帖名额**：`safe_budget = min(广告数, spacing_cap, safe 帖数 / 2)`（`:65-73`）。每个广告占用两条安全帖作邻居。
4. **高容忍广告的额外通道**：风险容忍为 `BsrHigh` 的广告可以放在两条中风险帖之间，名额是中风险帖数的一半（`:127-151`）。这条通道只做账号与关键词检查。
5. **普通通道**：从安全帖第 `组号 × group_size` 条开始取上下两条，依次做低风险、账号、关键词检查。命中任一检查时，混排器丢弃该广告，下一条广告再尝试同一组（`:153-180`）。
6. **交错**：剩余的安全、中风险、高风险帖按分数降序作填充，均分到各三元组之后（`:211-252`）。
7. **收尾**：截断到 `RESULT_SIZE` 35 项（广告也计数）。末项是广告时去掉该广告。被截掉的广告记为 `Truncated`（`:254`、`drops.rs:76-89`）。

高风险帖不作广告邻居，但作为填充帖出现在信息流里。

## 其他策略

| 参数值 | 混排器 | 做法 |
|--------|--------|------|
| `partition_organic_low_risk` 及未知值 | `PartitionOrganicAdsBlender` | 旧默认策略。`has_avoid`（中风险或高风险）的帖子进不安全桶，广告数上限 `min(广告数, 间距容量, 安全帖数 / 2)`，三类检查后组成三元组（`partition_organic_blender.rs:21-200`）。没有高容忍广告通道 |
| `safe_gap` | `SafeGapAdsBlender` | 找两侧都不需回避的间隙，按理想间距把广告分配到最近的合格间隙（`safe_gap_blender.rs:25-107`、`util.rs:39-52`） |
| `slot_spread` | `SlotSpreadAdsBlender` | 先按间距把广告槽均匀铺开，再给每个槽找合格邻居（`slot_spread_blender.rs:45-114`） |
| `least_movement` | `LeastMovementAdsBlender` | 先筛出至少有两条兼容邻居的广告，再求一种让帖子位置移动最少的布局；首个广告位的偏移代价权重 50，其余为 2（`least_movement_blender.rs:13-14`、`:46-128`） |
| `time_gap` | `TimeGapAdsBlender` | 用模型预测的停留时长衡量广告间隔：每条帖子的停留时长截到 `[0.5t, 2t]`，t 默认 4.0 秒；两条广告之间的帖子停留时长合计须达到 t × 间距，且至少 3 条帖子（`time_gap_blender.rs:19-35`、`:193-246`、`param.rs:977-1000`） |

## 丢弃原因与统计

`DroppedAd` 记录广告 ID、丢弃原因、阻挡它的帖子 ID、命中的账号或关键词（`ads/drops.rs:41-74`）。选择阶段把这些记录写入 `query.ad_drops`。外层的广告注入日志 side effect 负责上报（`for_you_candidate_pipeline.rs:241-260`）。

各混排器还上报三类指标（`ads/health_metrics.rs`）：`enforcement`（三类检查各丢了多少）、`slot_outcome`（提供的槽位中填了多少）、`serving_limitation`（广告数受哪个上限约束）。

## 设计决策

| 决策 | 理由 |
|------|------|
| 混排放在外层选择器 | 帖子已排好序，混排是成品组装 |
| 六种混排器由参数切换 | 可灰度比较不同混排策略；未知值退回旧默认，配置写错也能出广告 |
| 品牌安全只对入选候选计算 | 标签查询有成本，落选候选不需要 |
| 无 Grok 标签时默认中风险 | 未经内容理解的帖子保守处理；作者兜底减少新帖被误判的数量 |
| 高容忍广告可夹在中风险帖之间 | 广告主接受更高风险时，多一条投放通道 |
| 丢弃原因逐条记录 | 广告未投放时可追溯到具体帖子、账号或关键词 |

## FAQ

**Q：为什么帖子不足 5 条就不插广告？**
A：`MIN_POSTS_FOR_ADS = 5`（`ads/util.rs:9`）。帖子太少时插入广告，会让广告占比过高。6 种 For You 混排器都把这个常量传给混排函数（例如 `ads/multi_risk_blender.rs:25`、`ads/partition_organic_blender.rs:17`）。

**Q：广告会不会把帖子挤出信息流？**
A：会。`multi_risk` 截断到 35 项时广告也计数（`multi_risk_blender.rs:254`）。被挤出的帖子计入 `non_selected`（`blender_selector.rs:95-100`）。

## 源码锚点

- `home-mixer/selectors/blender_selector.rs:61-82` —— 混排器选择
- `home-mixer/ads/mod.rs:28-52` —— `AdsBlender` trait
- `home-mixer/ads/util.rs:9-75` —— 帖数下限、间距、回避判定
- `home-mixer/ads/util.rs:112-174` —— 三类相邻检查
- `home-mixer/ads/multi_risk_blender.rs:38-260` —— 默认混排策略
- `home-mixer/models/brand_safety.rs:7-199` —— 裁定档位与判定逻辑
- `home-mixer/candidate_hydrators/ads_brand_safety_vf_hydrator.rs:28-148` —— 裁定计算与作者兜底

## 相关页面

- [候选选择与成型](https://x-algorithm.vergego.ai/concepts/candidate-selection/) —— `BlenderSelector` 的完整步骤
- [home-mixer 编排层](https://x-algorithm.vergego.ai/concepts/home-mixer-orchestration/) —— 外层流水线与 `AdsSource`
- [过滤流水线](https://x-algorithm.vergego.ai/concepts/filtering-pipeline/) —— 帖子侧可见性过滤，与品牌安全裁定不同
- [Grox 内容分类器](https://x-algorithm.vergego.ai/concepts/grox-classifiers/) —— Grok 安全标签的产出方
- [标签规则](https://x-algorithm.vergego.ai/concepts/labeling-rules/) —— 其他安全标签的产出方
- [系统架构总览](https://x-algorithm.vergego.ai/concepts/system-architecture/) —— 广告在整体请求路径中的位置

Source: https://x-algorithm.vergego.ai/concepts/ads-blending/index.mdx
