跳到主要内容

被你的智能体拙劣重造的特征存储

· 阅读需 11 分钟
Tian Pan
Software Engineer

观察一个客服智能体处理一段对话,数一数它计算了多少次“流失风险”(churn risk)。第一次是在它对工单进行分类时。第二次是在它决定是否提供折扣时。第三次是在它起草升级摘要(escalation summary)时。每一次,它都会重新读取原始订单表,重新运行内联聚合,并生成一个数字。这三个数字并不匹配。没人注意到这一点,因为它们从未被放在一起记录过。

这就是特征工程(feature engineering)。智能体在每一轮对话中都在进行特征工程,而且是用自然语言进行的,其表现甚至不如十年前那些会被你在代码审查(code review)中嘲笑的流水线。

机器学习领域已经解决了这个问题。解决方案被称为特征存储(feature store),它所强制执行的纪律——计算一次特征、为其命名、对其进行版本控制、一致地提供服务——正是当你交给智能体一个数据库工具时,它立即抛弃的纪律。你的智能体并没有避免构建特征流水线。它构建了一个,只不过它构建的是整栋楼里最烂的一个。

智能体就是一个特征流水线,而你却没有审计过它

特征(feature)是一个派生事实:账户时长、订阅层级、终身价值、自上次登录以来的天数、流失风险。这些在你的数据库中都不作为可以直接 SELECT 的列存在。它们是对原始行应用逻辑——连接(join)、开窗函数(window function)、阈值、模型评分——后的输出。将行转化为特征是应用机器学习中最古老且枯燥的工作,而特征存储的存在是因为团队总是在不一致地执行这项工作。

一个回答关于客户问题的智能体也在做同样的工作。它必须这样做。当用户询问“这个客户是否应该获得退款”时,模型无法直接从 ordersevents 表中进行推理;它需要派生事实。因此,在智能体循环的某个地方——在工具调用中、在模型的思维链(chain of thought)中、或者在模型运行时编写的 SQL 字符串中——派生过程发生了。

区别在于,你数据团队的特征流水线经过了审查。有人争论过“活跃”是指 30 天还是 7 天。有人编写了连接。有人将其提交到了版本控制系统。而智能体的特征流水线则完全没有经过这些。它是在推理时由模型编写的,使用的是一种没有类型系统的语言,并且每一轮对话都会被重新编写。

这就是我们需要记住的核心观点:智能体不是在查询特征,它是在没有工厂的情况下现场制造特征。

你默默放弃的四项保证

特征存储不是数据库。它是关于派生数据的一系列保证。在线计算特征的智能体破坏了这全部四项保证。

缓存(Caching)。 特征存储将存储分为离线存储(完整历史记录,用于训练和分析)和在线存储(亚毫秒级缓存,用于实时服务)。在线存储的意义在于,你只需计算一次“流失风险”,之后就可以廉价地读取。在线计算流失风险的智能体没有这种缓存。它在每次问题出现时都要付出全部代价——Token、延迟、数据库负载——而且在一段对话中,这类问题会反复出现。你正在运行最昂贵的特征流水线(以 LLM Token 计价),而且是在冗余地运行。

时间点正确性(Point-in-time correctness)。 这是特征存储最努力实现的保证。如果你问“三周前提交工单时,这位客户的订阅层级是什么”,诚实的答案需要的是那个时间戳的值,而不是今天的值。特征存储通过时间点连接——有时被称为时间旅行(time travel)——来实现这一点,专门为了防止训练数据将未来的信息泄露到过去。读取当前 customers 行的智能体没有“截至日期”的概念。它会愉快地告诉你客户在提交工单时使用的是企业版(Enterprise)计划,因为他们现在是企业版。但当时他们使用的是免费版。智能体的回答不仅仅是一个小错误,它在讨论的时间点上就犯了范畴错误。

共享定义(Shared definitions)。 在特征存储中,“活跃用户”被定义一次,在同一个地方,并由专人负责。每个读取 user_active_30d 的模型和仪表盘都获得相同的逻辑。拥有数据库工具的智能体没有共享定义。工具 A 的提示词说活跃意味着过去 30 天内有登录。三个冲刺周期后由另一位工程师编写的工具 B 的提示词则说活跃意味着过去 7 天内有任何事件。智能体调用了这两个工具,得到了两个答案,并用自然语言化解了矛盾——通常是默默选择其中一个,偶尔是对它们取平均值,而这毫无意义。特征没有规范值,因为它没有规范定义。

确定性(Determinism)。 特征流水线是代码。在相同的输入上运行两次,得到相同的输出。智能体的在线派生存在于模型的推理中,这意味着它对措辞、温度(temperature)、对话历史以及上下文窗口中竞争注意力的任何其他内容都很敏感。用两种方式问“这是否是一个高价值客户”,你可能会得到两个不同的阈值。派生逻辑没有在任何地方被固定下来,因此它在任何地方都不稳定。

“给它一个数据库工具就行了” 只会让情况变得更糟,而不是更好

标准的反应是:行吧,给智能体一个 run_sql 工具,让它直接查询数据仓库。这看起来像是进步,实则相反。

run_sql 工具将特征逻辑重定位到模型在运行时编写的字符串中。那个字符串现在就是你的特征定义。而它是存放定义最糟糕的地方。它不在版本控制中。没有经过评审。没有经过测试。它在每次调用时都会重新生成,且略有不同——不同的列别名,不同的 WHERE 子句,这次的 JOIN 悄悄去掉了区域为 null 的客户,而下次则没有。特征的边界——什么算作流失,什么窗口定义为“近期”——正在被一个你无法检查的过程在每次调用时重新划定。

你并没有赋予智能体访问特征的权限。你是在要求它用 SQL 一遍又一遍地重新发明特征,并将其埋在没人阅读的工具调用参数中,从而使这种重新发明变得隐形。

这就是为什么智能体悄悄地成为了公司里最糟糕的特征流水线。它们结合了最高的单次推导成本(你在付钱给前沿模型来编写聚合逻辑)、最低的可复现性(逻辑每次运行都会变)以及最大的爆炸半径(同一个未经测试的推导同时影响分流、定价和客户收到的实际消息)。一个至少会大声报错的糟糕特征流水线还更好些。而这个流水线是在用自信的段落悄悄地失败。

像服务模型一样,为智能体提供特征

解决方法并不稀奇。那就是像对待任何其他特征消费者一样对待智能体。

生产环境中的模型不会通过编写 SQL 来计算客户的终身价值。它调用在线存储并获取 customer_ltv——一个命名的特征,具有定义、所有者、版本和保证一致的值。智能体也应该做完全相同的事情。

具体来说,这意味着智能体的工具不是 run_sql(query),而是 get_customer_features(customer_id),它返回一个由命名特征组成的有类型结构体:account_age_daysplan_tierltvchurn_risk_scoretickets_last_90ddays_since_last_login。每一个名称都解析为代码中的单一定义,经过评审,并由一个流水线计算。智能体消费特征,而不是编写特征。

这一举措一次性买回了所有四项保证:

  • 在线存储缓存这些值,因此对话中的重复读取成本低且结果完全相同。
  • 存储负责处理时点连接(point-in-time join),因此“截至工单日期”是一个参数,而不是一场意外。
  • 定义是共享的,因此每个工具和每一轮对话看到的都是相同的 churn_risk_score
  • 计算是确定性的代码,因此可以进行单元测试,其变更是一个可评审的 diff,而不是被埋没的提示词修改。

这对合规性也有益处。当你通过存储提供特征时,你可以对智能体在决策时看到的精确特征向量进行快照,并将其附加到审计日志中。“智能体为什么拒绝退款”变得可以回答:这是提供给它的七个命名特征,包含数值和时间戳。将其与从模型即兴创作的 SQL 字符串中重建决策进行比较。

在哪里划清界限

这并不意味着智能体永远不应该接触原始数据。你构建智能体的初衷就是为了处理开放式问题,而特征存储只知道你预先定义的特征。如果分析师提出了一个真正新颖的、一次性的问题,仍然需要智能体直接探索数据仓库。

界限在于重复性和后果。当满足以下两种情况之一时,一个事实就理应成为命名特征:它在大多数轮次中都被请求,或者它影响具有实际后果的决策——资格、定价、退款、升级。重复性意味着缓存会有回报。后果意味着你无法承受推导是非确定性且未经测试的。

寻找这些特征的一个实用方法是:拿出一周的智能体追踪记录,寻找重复的聚合操作。在 orders 表上相同的 GROUP BY,相同的 90 天窗口,相同的流失启发式算法,出现在数百个工具调用中,每次 SQL 都略有不同——这就是一个急需被提升的特征。给它一个名字,给它一个所有者,给它一个定义,并把它放在有类型的工具之后。将真正长尾的新颖问题留给直接查询。

迁移是渐进式的。你第一天不需要一个特征存储产品;你只需要一个包含命名的、经过测试的特征函数的模块,以及一个调用它们的工具。纪律比供应商更重要。

智能体时代不断重新学习的廉价数据教训

每一波应用机器学习(ML)都会以艰难的方式重新发现同样的事情:模型很少是问题所在,数据管道才是。特征存储之所以存在,是因为十年来不断有团队发布的模型与自己的仪表盘不一致,将未来信息泄露到训练集中,并耗费数月的工程时间来为同一个数字维护四个流水线。

智能体正直接走回那个泥潭,而文本界面掩盖了溅起的水花。工具调用中的内联聚合看起来不像是未经评审的特征流水线,它看起来像是智能体在提供帮助。但派生事实就是派生事实,无论它是通过 Spark 任务计算的,还是由语言模型在句中即兴创作的——而这两者中,只有一个可以被命名、版本化、测试和信任。

你的智能体已经构建了一个特征存储。唯一悬而未决的问题是,你是打算继续让它在每一轮对话中都构建一个烂的,还是交给它一个好的,让它回到你真正雇佣它去完成的工作上。

References:Let's stay in touch and Follow me for more thoughts and updates