订阅更新

Agent的Trace和对话日志进数仓,模型怎么建?

Agent手册 第 13 题 约 10 分钟读完

本文目录
  1. 30秒版回答
  2. 面试官在考什么
  3. 展开一:识别业务过程和粒度
  4. 展开二:事实表设计
  5. 展开三:维度设计
  6. 展开四:特殊问题处理
  7. 展开五:敏感信息分级存储
  8. 实战经验:埋点规范的思路可以直接复用
  9. 追问链
  10. 加分说法 vs 减分说法
  11. 怎么挂到你的项目上
  12. 总结

30秒版回答

核心是按粒度拆事实表,不要做成一张大宽表。我会拆成四到五张事实表:会话事实(一次会话一行)、轮次事实(用户一次提问一行)、模型调用事实(一次模型调用一行,带 Token 和成本)、工具调用事实(一次工具调用一行)、检索事实(一次检索的候选结果,一个候选一行)。它们之间通过 session_id、turn_id、span_id 关联。

维度包括用户、Agent 版本、模型、工具、提示词版本、知识库版本。

几个特殊问题:重试要标记,避免一次提问被算成多次;流式输出要以最终完成事件为准;对话原文要脱敏分级存储,分析用的表只保留脱敏文本或分类标签。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

面试官:「我们的 Agent 每天产生大量对话和调用日志,现在都是一坨 JSON 存在日志系统里,没法分析。如果让你把这些数据建进数仓,你会怎么设计模型?」

这道题是数据开发在 Agent 面试里最能展示专业能力的题,因为它本质上是一道数仓建模题,只是业务对象从“订单”换成了“对话”。

很多算法和应用工程师会把日志直接打平成一张大宽表,结果是:会话数、提问数、模型调用数混在一起,Token 成本算重复,一个问题重试了三次被算成三个问题。粒度不清是这类数据最常见的问题,而粒度设计恰恰是数据开发的基本功。

面试官在考什么

• 能否识别出不同粒度的业务过程:会话、轮次、模型调用、工具调用、检索。

• 事实表和维度表设计是否合理,关联关系是否清晰。

• 是否考虑流式输出、重试、迟到数据等特殊问题。

• 是否考虑敏感信息:对话内容可能包含个人信息和业务机密。

展开一:识别业务过程和粒度

建模第一步永远是梳理业务过程。一次 Agent 交互里有几个层次的“事件”,它们的数量关系是一对多的:

会话 session(用户打开一次对话)
  └─ 轮次 turn(用户的一次提问 + Agent 的最终回答)       1 会话 : N 轮次
       └─ 步骤 span(Agent 内部的一次动作)                1 轮次 : N 步骤
            ├─ 模型调用 llm_call(含 Token、成本、模型)
            ├─ 工具调用 tool_call(含工具名、参数、耗时、结果状态)
            └─ 检索 retrieval(含检索词、过滤条件)
                 └─ 候选 candidate(召回的每个 Chunk 及分数)  1 检索 : N 候选

这和电商里“订单 → 子订单 → 商品明细”的层次结构非常像。如果把它们打平到一张表里,就会出现和“订单金额按商品明细重复求和”一样的问题:会话数被轮次数放大,Token 成本被检索候选数放大。

展开二:事实表设计

事实表粒度核心度量主要外键
fct_agent_session一次会话一行轮次数、时长、是否转人工、满意度user、agent_version、channel
fct_agent_turn一次提问一行端到端延迟、步数、总 Token、总成本、是否成功、反馈session、intent
fct_llm_call一次模型调用一行输入/输出/缓存 Token、成本、首字延迟、总延迟turn、span、model、prompt_version
fct_tool_call一次工具调用一行耗时、是否成功、错误类型、返回大小turn、span、tool
fct_retrieval_candidate一个召回候选一行召回分数、重排分数、最终排名、是否被引用retrieval、chunk、doc_version
-- 模型调用事实表:一次模型调用一行
CREATE TABLE dwd.fct_llm_call (
  call_id            STRING,      -- 主键,来自 span_id
  turn_id            STRING,      -- 所属轮次
  session_id         STRING,
  parent_span_id     STRING,      -- 用于还原调用树(多 Agent 场景)
  model_key          STRING,      -- 关联模型维度:厂商、名称、版本、单价
  prompt_version     STRING,      -- 提示词版本
  purpose            STRING,      -- 规划/生成SQL/总结/评估,用于成本归因
  input_tokens       BIGINT,
  cached_tokens      BIGINT,      -- 命中缓存的输入 Token,单价不同
  output_tokens      BIGINT,
  cost_amount        DECIMAL(18,6),-- 按调用当时的单价计算,冗余存储
  ttft_ms            INT,         -- 首字延迟
  latency_ms         INT,
  finish_reason      STRING,      -- 正常结束/长度截断/工具调用/内容过滤
  is_retry           TINYINT,     -- 是否为重试调用
  event_time         TIMESTAMP,
  dt                 STRING       -- 分区
);

这里有两个值得讲的设计:

• cost_amount 按调用当时的单价冗余存储。模型价格会调整,如果每次查询都用最新单价去乘 Token 数,历史成本就会被改写。这和订单金额要存交易时的价格、而不是实时关联商品价格是一个道理。

• purpose 字段标记这次调用的用途(规划、生成 SQL、总结、评估)。没有它,你只知道总成本,不知道钱花在哪个环节。第 14 题的成本归因就靠它。

展开三:维度设计

• dim_agent_version:Agent 名称、版本、上线时间、包含的提示词和工具版本。版本对比分析的基础。

• dim_model:模型厂商、名称、版本、上下文长度、各类 Token 单价及生效时间(用拉链表管理价格变化)。

• dim_tool:工具名称、所属系统、是否有副作用、负责人。

• dim_prompt_version:提示词版本、变更说明、变更人。

• dim_user:用户、部门、角色。注意和业务系统的用户维度做一致性关联,才能分析“哪些部门在用、用得怎么样”。

展开四:特殊问题处理

问题现象处理方式
重试一次提问因超时重试三次,被算成三次提问turn 以用户提问为粒度;模型调用打 is_retry 标记,指标区分“提问数”和“调用数”
流式输出一个回答拆成很多个流式事件以完成事件为准生成一行;中途断开的标记为未完成
迟到数据反馈(点赞/点踩)可能几天后才到反馈单独建事实表,按 turn_id 关联;会话表的满意度按累计快照更新
多 Agent 嵌套子 Agent 的调用挂在主 Agent 下面用 parent_span_id 还原调用树,成本可按根节点汇总
大字段提示词和输出很长原文存对象存储或冷表,事实表只存哈希、长度和引用

展开五:敏感信息分级存储

对话内容可能包含用户的个人信息、公司的业务机密、甚至密码。这部分数据不能像普通日志一样随便开放查询。我会分三级存储:

• L1 原文层:完整对话原文,加密存储,访问严格审批,保留期短,仅用于必要的问题排查。

• L2 脱敏层:自动识别并替换手机号、邮箱、身份证号等敏感信息后的文本,用于评测和质量分析。

• L3 分析层:只有结构化字段和分类标签(意图、主题、是否成功),对分析人员开放。

这个思路和我们在数仓里做用户明细数据的分级管理是一样的。第 16 题会专门讲 Agent 场景的合规。

实战经验:埋点规范的思路可以直接复用

我在某出海硬件品牌做埋点治理时,最大的教训是:数据采集阶段的规范比事后清洗重要得多。当时 1000 多个事件命名混乱、属性含义不一致,后期治理花了很大力气,最终精简到 100 多个标准化事件。

Agent 日志也一样。我会推动应用侧在埋点阶段就遵循统一规范:每个事件必须带 session_id、turn_id、span_id、parent_span_id、版本信息和时间戳,字段命名统一。业界也有在可观测性标准里为大模型调用定义的通用语义约定,可以作为字段规范的参考,比每个团队自己发明一套格式要好得多。

追问链

追问一:“对话数”这个指标,你怎么定义?

我会先反问:业务说的“对话数”到底是会话数、提问数还是模型调用数?这三个数可能差一个数量级。我会在指标平台里分别定义:会话数(用户发起的会话个数)、提问数(用户的提问轮次,不含重试)、模型调用数(含 Agent 内部的多步调用和重试)。看板上明确标注口径,避免不同团队拿着不同的数吵架。

追问二:Agent 版本迭代很快,怎么做版本间的效果对比?

关键是每条事实数据都带上 agent_version 和 prompt_version,然后在轮次事实表上按版本分组对比成功率、步数、成本、延迟、反馈。注意要控制流量分布的差异,比如新版本只在某个部门灰度,不能直接和全量的旧版本比,要在同一用户群体上对比。

追问三:数据量很大,实时还是离线?

分场景。成本监控、异常告警需要实时或准实时,可以用流式计算做分钟级聚合;效果分析、版本对比、归因统计用离线 T+1 就够了。明细数据量大的检索候选表,可以只保留一定时间的全量数据,历史数据只保留被引用的候选和聚合结果。

加分说法 vs 减分说法

✔ 加分说法✘ 减分说法
按粒度拆事实表:会话/轮次/模型调用/工具/检索把日志打平成一张大宽表
用 span_id 和 parent_span_id 还原调用树会话数、提问数、调用数混用
成本按调用时单价冗余存储用最新单价重算历史成本
重试、流式、迟到反馈分别处理对话原文直接开放查询
对话原文分级存储,分析层只用脱敏数据不区分重试调用

怎么挂到你的项目上

话术模板:“Agent 日志建模我会按粒度拆成会话、轮次、模型调用、工具调用、检索候选几张事实表,用 span_id 还原调用树。成本按调用当时的单价冗余存储,重试单独标记,避免提问数被放大。对话原文分三级存储。我之前做埋点治理的经验是:采集规范比事后清洗重要,所以我会推动应用侧统一事件字段。”

总结

Agent 日志建模本质上是一道粒度设计题。会话、轮次、调用、检索是一对多的层次关系,拆开建事实表才能避免指标放大。再加上成本冗余、重试标记、流式和迟到处理、敏感数据分级,这道题就能体现出数据开发扎实的建模功底。

下一篇聊钱:大模型账单一个月翻了三倍,成本怎么归因、怎么压下来?

你们的 AI 应用日志现在存在哪里?能回答“上周一共花了多少钱、花在哪”吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器