我是AI时代的无业游民,我游荡在现实与意念之间
当广告采集器成为大模型的"眼睛":跨站上下文注入的架构与代价
背景与痛点
过去一年,一个微妙的变化在浏览器与对话式 AI 之间发生:你在电商站点浏览过的商品、在技术论坛停留过的页面、在新闻站滚动过的段落,正以某种"广告采集"的名义,被汇聚进对话模型的上下文里。用户打开 ChatGPT 提问时,它给出的回答已经不只是基于你刚刚输入的那句话,而是叠加了一层来自其他站点的行为画像。
这件事的技术本质并不神秘:跨站追踪早已存在,广告技术栈里的像素、SDK、Cookie 同步、fingerprint 采集已经跑了很多年。真正的新问题是------当这些信号不再只用于投放广告,而是作为大模型的检索上下文进入推理链路时,系统的边界、延迟、隐私与失效模式全部变了。
不解决的代价很直接。对开发者而言,如果你在做 RAG、Agent 或任何带外部上下文的产品,你会遇到三个真实故障:
其一,上下文污染 。跨站采集来的数据噪声极高,用户三小时前点开的一个无关页面,可能让模型在回答当前问题时跑偏。其二,延迟不可控 。广告采集链路通常是异步、最终一致的,当它被接入同步推理请求时,P99 延迟会被这条"旁路"拖垮。其三,合规与信任。一旦用户感知到自己的浏览行为被用于对话,产品信任度会断崖式下跌,且面临 GDPR、CCPA 以及国内《个人信息保护法》下的合规风险。
所以真正要回答的问题不是"能不能做",而是:我们该用什么架构,把跨站信号安全、可控、可解释地注入到模型上下文里?

方案设计
思路与选型
核心思路是把"采集"与"注入"解耦成两条独立链路,中间用一个可审计的上下文仓库做缓冲。采集侧沿用广告技术栈的成熟能力(像素、SDK、事件上报),注入侧则完全由我们控制,走检索增强(RAG)的路径,而不是把原始行为流直接塞进 prompt。
选型上有三个关键决策:
决策一:推送 vs 拉取。 采集侧推送(webhook / 消息队列)实时性好,但会把噪声和突发流量直接打到推理侧;拉取(推理时按需查询)延迟高一点,但可以做过滤、聚合、限流。我们选择拉取为主、推送为辅------低频高价值事件(如"加入购物车")走推送预热,高频低价值事件(如页面滚动)只在拉取时聚合。
决策二:原始事件 vs 画像摘要。 把原始事件列表塞进上下文,token 消耗巨大且噪声高。我们选择在仓库侧做离线聚合生成画像摘要,推理时只取摘要。代价是时效性下降(分钟级),但换来 token 成本下降一个数量级。
决策三:隐式注入 vs 显式引用。 隐式注入(模型自动使用)体验顺滑但不可解释、难合规;显式引用(用户可见"本次回答参考了你的浏览记录")体验略重但可审计。我们选择显式引用为默认,隐式注入需用户显式授权。
明确放弃的方案
我们放弃了"在浏览器端直接把跨站数据拼进 prompt"的写法。看起来能跑,但线上会炸:一是客户端可被篡改,注入内容不可信;二是无法做服务端限流和审计;三是 token 预算完全失控。也放弃了"全量事件实时入 prompt",原因是延迟与成本都不可接受。
| 维度 | 推送实时注入 | 拉取画像摘要(选定) | 客户端拼接 |
|---|---|---|---|
| 延迟 | 低 | 中 | 低 |
| 成本 | 高 | 低 | 中 |
| 可审计 | 差 | 好 | 极差 |
| 合规风险 | 高 | 可控 | 高 |
核心实现
上下文仓库的数据模型
仓库不存原始事件流,而是存按用户+时间窗聚合的画像分片,每个分片带来源域、置信度、TTL。这样做的原因是让注入侧可以做细粒度取舍。
python
from dataclasses import dataclass, field
from typing import Literal
@dataclass
class ProfileShard:
user_id: str
source_domain: str
topic: str # 聚合后的主题标签
confidence: float # 0~1,来自采集侧的置信度
last_seen: int # epoch 秒
ttl: int = 3600 # 过期时间,默认 1 小时
sensitivity: Literal["low", "mid", "high"] = "low"
raw_ref: str | None = None # 指向原始事件的引用,按需展开
def is_fresh(self, now: int) -> bool:
return now - self.last_seen < self.ttl
关键点是 sensitivity 与 ttl:高敏感分片(如医疗、金融相关域)默认不进入推理上下文,即使被拉取也会在注入前被过滤。这是"看起来能跑、线上会炸"的典型点------很多实现只做相关性排序,忘了敏感度分级。
注入侧的检索与预算控制
注入不是"把所有分片拼起来",而是按当前 query 做相关性检索,并在 token 预算内做贪心选择。我们用轻量 embedding 做召回,再用规则做重排。
python
def build_context(query_vec, shards, now, token_budget=800):
candidates = [s for s in shards if s.is_fresh(now) and s.sensitivity != "high"]
# 召回:向量相似度
scored = [(s, cosine(query_vec, embed(s.topic))) for s in candidates]
# 重排:置信度 * 新鲜度衰减
scored.sort(key=lambda x: x[1] * x[0].confidence * decay(x[0], now), reverse=True)
picked, used = [], 0
for shard, score in scored:
cost = estimate_tokens(shard.topic)
if used + cost > token_budget:
break
picked.append(shard)
used += cost
return picked
decay 是时间衰减函数,通常用指数衰减。这里放弃"按分数阈值截断"的写法,因为阈值在不同用户间不稳定,改用预算驱动的贪心,保证 token 上限硬约束。
引用与审计
每次注入都要落审计日志,记录哪些分片被使用、来源域、时间。这既是合规要求,也是线上排障的依据。
python
def log_injection(user_id, picked, request_id):
audit = {
"request_id": request_id,
"user_id": user_id,
"shards": [{"domain": s.source_domain, "topic": s.topic,
"confidence": s.confidence} for s in picked],
}
audit_sink.write(audit) # 异步写入,不阻塞推理
注意审计写入必须异步,否则又会把延迟拖回同步链路。
效果验证
验证分三层:
离线层:构造 1 万条带标注的 query,对比"无跨站上下文"与"有跨站上下文"的回答相关性(用 LLM-as-judge 打分)。预期相关性提升但方差增大,需观察噪声是否可控。
延迟层:压测 P50/P99。拉取链路必须设超时(如 80ms),超时即降级为无跨站上下文,绝不阻塞主推理。这是可复现的:注入一个 200ms 延迟的 mock 仓库,验证 P99 不劣化。
合规层:抽样审计日志,确认高敏感分片从未进入上下文,确认用户可查询和删除自己的画像分片。这一步不能省,否则上线即埋雷。
复现步骤:部署仓库 mock → 灌入合成事件 → 发起带 query 的推理请求 → 检查返回的引用列表与审计日志是否一致。
边界与演进
局限:画像摘要是分钟级聚合,对"刚刚发生"的强时效场景不适用;跨站信号的置信度依赖采集侧质量,采集侧被污染时注入侧无法自救;显式引用会牺牲部分体验顺滑度。
不适用场景:强实时交互(如实时协作)、高敏感行业(医疗、金融)默认应关闭跨站注入,或只允许用户显式授权的白名单域。
下一步优化 :一是引入用户可控的域白名单/黑名单 ,把控制权交还用户;二是把注入决策从规则重排升级为学习式排序 ,用点击/采纳信号做反馈;三是探索联邦式画像,让原始数据不出域,只交换聚合摘要,从架构上降低合规压力。
回到最初的问题:跨站信号进入大模型上下文,本身不是技术难题,难的是在延迟、成本、合规、体验四者之间找到可运营的平衡点。把采集与注入解耦、用画像摘要替代原始事件、用预算驱动替代阈值截断、用显式引用替代隐式注入------这四条决策构成了当前阶段最稳的工程答案。它们不是终点,但至少能让你的系统在线上活下来。