别把 userId 交给内容 Agent:聚合优先的数据契约

如果内容分析服务的第一版接口长这样,后面通常会越来越难收拾:

ts 复制代码
interface ReaderEvent {
  userId: string;
  contentId: string;
  channel: string;
  action: "view" | "like" | "collect";
  occurredAt: string;
  device?: string;
  region?: string;
}

它很灵活。Agent 可以按用户、设备、地区、时间随意切片,今天做选题复盘,明天做用户画像,后天再把原始轨迹塞进提示词。

问题也正在这里:一个服务原本只想回答"哪类文章值得继续写",却顺手获得了识别个人、拼接轨迹和跨用途使用数据的能力。对于人少、维护能力有限的小团队,这不是免费能力,而是一笔持续的安全与治理债务。

GitHub 在 2026 年 9 月 4 日发布了新的 Star history REST API endpoint。官方给出的能力是:返回带时间戳的历史 Star 数量,但不暴露具体 stargazer 身份。此前相关 stargazer 列表接口已收紧访问,用于保护用户隐私。

这里不讨论 GitHub 内部用了什么匿名化算法------官方资料没有这样说明。值得借鉴的是 API 边界:调用方要的是增长曲线,就给增长曲线,不必顺带给一份身份清单。

从输出开始设计,而不是从事件开始

先假设 Agent 最终只需要生成这种复盘:

json 复制代码
{
  "pillar": "agent-architecture",
  "window": "72h",
  "samples": 6,
  "medianViews": 128,
  "medianCollections": 9,
  "confidence": "low",
  "note": "样本较少,仅用于下一轮选题"
}

它要比较内容支柱,需要文章维度的窗口快照;它要表达不确定性,需要样本数;它不需要任何一个读者的身份。

因此数据链路可以反过来设计:

text 复制代码
Decision → Metric contract → Aggregate job → Ephemeral input

而不是:

text 复制代码
Collect everything → Store forever → Let Agent decide later

事件只是临时输入

如果平台或自有站点确实以事件形式提供数据,可以让事件只活在聚合窗口中:

ts 复制代码
type EphemeralEvent = {
  eventId: string;
  contentId: string;
  channel: string;
  action: "view" | "like" | "collect" | "comment";
  occurredAt: string;
  expiresAt: string;
};

type MetricSnapshot = {
  contentId: string;
  channel: string;
  window: "24h" | "72h";
  windowEndsAt: string;
  sampledAt: string;
  status: "available" | "not_due" | "unavailable";
  values?: Partial<Record<EphemeralEvent["action"], number>>;
  schemaVersion: 1;
};

EphemeralEvent 没有 userId,因为这项决策根本用不到它。若另一个业务确实需要身份,应该新建带独立目的、权限和保留期的契约,而不是污染内容复盘服务。

聚合任务也不应该在窗口未结束时抢跑:

ts 复制代码
function buildSnapshot(now: Date, endsAt: Date): MetricSnapshot["status"] {
  if (now < endsAt) return "not_due";
  return "available";
}

这行看似普通,却能挡住一种常见的数据污染:23 小时 48 分的当前总量,被自动化任务写成"24 小时数据"。

0 不是 unavailable

做跨平台内容复盘时,最危险的默认值可能就是 0。

一个平台公开显示 0 次收藏,是事实;页面没加载出来、指标暂不提供、登录过期导致取数失败,都不是 0。如果 schema 只允许数字,Agent 会把缺失当表现差,从而给出错误选题建议。

所以快照至少要保留状态和来源:

json 复制代码
{
  "contentId": "post_xxx",
  "window": "24h",
  "status": "unavailable",
  "sampledAt": "2026-09-06T20:45:30+08:00",
  "source": "public_page",
  "reason": "metric_not_exposed"
}

这里的 reason 应该是白名单枚举,不要把 Cookie、页面正文或异常堆栈直接带给 Agent。

小样本必须允许"不回答"

去掉 userId 以后,过细的维度仍可能把人重新找出来。一个只有一次互动的分钟级、地区级、设备级桶,匿名性非常有限。

可以在聚合出口增加抑制规则:

ts 复制代码
function releaseBucket<T extends { count: number }>(bucket: T, min = 10) {
  if (bucket.count < min) {
    return { status: "suppressed" as const, count: null };
  }
  return { status: "available" as const, count: bucket.count };
}

10 只是示例,不是通用合规阈值。真正的 min 应由数据敏感度、业务规模和可关联性决定。对于数据量本来就小的一人公司,更好的办法常常是直接砍掉地区、设备等低价值维度。

Agent 看到 suppressed 后应停止下钻,而不是换一种分组继续逼近同一批人。

给 Agent 的是任务视图,不是数据库权限

数据权限按"人类 / AI"二分不够用。写作 Agent、复盘 Agent、发布 Agent 的工作不同,能看到的数据也应不同。

yaml 复制代码
views:
  review_agent:
    allow:
      - content_metrics_24h
      - content_metrics_72h
      - topic_pillar
      - cover_style
    deny:
      - raw_events
      - viewer_identity
      - cross_account_join

  publishing_agent:
    allow:
      - current_draft
      - current_assets
      - publish_checklist
    deny:
      - analytics_raw_store
      - unrelated_projects

每次调用还应记录 viewVersion、用途、读取条数和输出结论。这样口径变化后,才能解释为什么两次复盘结果不一致。

一个最小的可观测记录可以是:

ts 复制代码
type AgentDataAccessLog = {
  jobId: string;
  role: "review_agent" | "publishing_agent";
  purpose: string;
  viewVersion: string;
  rowCount: number;
  accessedAt: string;
};

它记录"哪项工作读了哪个聚合视图",而不是再复制一份原始用户数据。

四个容易忽略的失败分支

第一,聚合任务失败。不要删除原始输入后才发现快照没写成功;可以采用"快照提交成功 → 标记输入可清理"的两阶段状态。

第二,平台指标延迟。窗口结束不代表数据立刻稳定,允许设置小幅采样延迟,并保留实际 sampledAt。

第三,口径升级。新增指标或修改去重规则时提升 schemaVersion,不要把不同版本直接放在一条趋势线上。

第四,样本太少。系统要能输出"证据不足",而不是为了完成任务强行给标题或封面风格排出胜负。

这对一人公司有什么意义

小团队最缺的通常不是更多原始数据,而是口径稳定、能重复运行的判断流程。少存一个身份字段,意味着少一类权限、少一个泄露面、少一段需要解释的生命周期。

这也是我们做 Tipkay 时坚持岗位拆分的原因。Tipkay 面向一人公司、小微企业和小团队,不同垂类 AI 员工分别处理内容复盘、写作、配图和发布准备。一个岗位能完成工作所需的数据,未必应该自动共享给另一个岗位。按任务给最小视图,比把整套数据塞进一个万能 Agent 更容易长期维护。

一个人的生意,也能有一支专业团队。但专业团队的标志之一,就是有人负责什么、能看什么、什么时候必须停下来,都有清楚边界。

最后

数据最小化不是拒绝分析,而是逼迫系统把问题说清楚。

如果要判断选题,就保存可比的选题快照;如果要判断标题,就记录同窗口的标题实验;如果一个决策不需要知道读者是谁,就别让 userId 进入它的默认契约。

能用趋势回答的问题,不要靠追踪个人来解决。

资料:

相关推荐
ndz2 小时前
在 ModelFold 使用 Claude:无需代理,稳定可靠的日常开发体验
人工智能·api
adinnet20262 小时前
研发提效实战:AI智能体接手代码审查、 文档生成和技术沉淀
人工智能·小艾智能体·ai研发助手·代码审查自动化
寂寞的船2 小时前
【Unity知识速递】第5期 Unity 6.2 深度解析:AI Assistant 登场,World Space UI 与 Sentis 全面升级
人工智能·ui·unity
starrysky8103 小时前
Hermes Agent 升级 v0.23.0:工具恢复、长会话与分层验收
人工智能
武子康3 小时前
语音聊了十分钟,为什么不能拿转写分钟算费用?
人工智能·深度学习·agent
武天旭4 小时前
AI安全--AI大模型的隐私保护解析
人工智能·深度学习·安全·机器学习
aifannihao4 小时前
AI 学习第 5 课:手推逻辑回归与交叉熵,L2 把可分数据的权重按住
人工智能·机器学习·逻辑回归·numpy·学习路线
IHaveADiscovery4 小时前
无痛让AI由Markdown转Word:Pandoc模板了解一下
人工智能·word·markdown
慢慢来不要急4 小时前
Anthropic 不用 Git 了,GitHub 也在为智能体重建存储 · 11-06
人工智能·开发工具
宅小年4 小时前
为什么你的 Grok Bot 总走偏?教你写一个真正好用的 Bot 模板
人工智能