这一页回答什么
本页回答四个问题:混排器怎样把广告插进 For You 信息流;广告插在哪个位置;系统怎样计算帖子的品牌安全裁定;混排器在哪些情况下丢弃广告。
核心结论
- 混排发生在外层选择器。
BlenderSelector按AdsBlenderType参数从六种混排器中选一种(home-mixer/selectors/blender_selector.rs:61-79)。旧版只有safe_gap与partition_organic两种。 - 开源默认值是
multi_risk(home-mixer/params/param.rs:957-962)。参数取未知值时退回PartitionOrganicAdsBlender(blender_selector.rs:78)。 - 品牌安全裁定从四档变成五档,新增
HighRisk(home-mixer/models/brand_safety.rs:7-14)。中风险与高风险帖子都不能紧挨广告(home-mixer/ads/util.rs:24-29)。 - 广告数取三个上限中的最小值:请求的广告数;按广告间距算出的容量;可作邻居的安全帖数的一半。帖子不足 5 条时,混排器不插广告(
ads/util.rs:9)。 - 混排器为每条被丢弃的广告记录原因:帖子太少、超出间距容量、没有合格邻居、低风险邻居、排除账号、排除关键词、被截断。混排器把原因写入
query.ad_drops(blender_selector.rs:80-82、ads/drops.rs:41-89)。
在哪里发生
外层 ForYouCandidatePipeline 的 BlenderSelector 先把 FeedItem 按类型分桶,再选混排器:
// 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 等模块在混排之后插入。详见 候选选择与成型。
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)按顺序判定:
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)。
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 项,末项是广告则去掉"]
- 前置条件:没有广告或帖子少于 5 条时,只返回帖子,广告记
TooFewPosts(:46-57)。 - 间距容量:
spacing_cap = (n − 1) / spacing.requested(:59-63)。compute_spacing取前 4 条广告insert_position的最小正差;差值不小于 3 时采用,否则用默认间距 3(util.rs:54-75)。 - 安全帖名额:
safe_budget = min(广告数, spacing_cap, safe 帖数 / 2)(:65-73)。每个广告占用两条安全帖作邻居。 - 高容忍广告的额外通道:风险容忍为
BsrHigh的广告可以放在两条中风险帖之间,名额是中风险帖数的一半(:127-151)。这条通道只做账号与关键词检查。 - 普通通道:从安全帖第
组号 × group_size条开始取上下两条,依次做低风险、账号、关键词检查。命中任一检查时,混排器丢弃该广告,下一条广告再尝试同一组(:153-180)。 - 交错:剩余的安全、中风险、高风险帖按分数降序作填充,均分到各三元组之后(
:211-252)。 - 收尾:截断到
RESULT_SIZE35 项(广告也计数)。末项是广告时去掉该广告。被截掉的广告记为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——AdsBlendertraithome-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—— 裁定计算与作者兜底
相关页面
- 候选选择与成型 ——
BlenderSelector的完整步骤 - home-mixer 编排层 —— 外层流水线与
AdsSource - 过滤流水线 —— 帖子侧可见性过滤,与品牌安全裁定不同
- Grox 内容分类器 —— Grok 安全标签的产出方
- 标签规则 —— 其他安全标签的产出方
- 系统架构总览 —— 广告在整体请求路径中的位置