1. 当 Trace 多到人眼看不完聚类就成了刚需做 Agent 开发的朋友大概率都经历过这个阶段本地跑几个 case 的时候Trace 面板一拉每一步的输入输出、工具调用、耗时、token 消耗看得清清楚楚调试起来很舒服。可一旦上线或者做一轮批量评测情况就完全变了——一天几万甚至几十万条 Session每条 Session 里又有几十个 Span你打开可视化面板翻到第三页就已经不知道自己在看什么了。我最早做 Agent 可观测性的时候踩的就是这个坑。当时团队做了一个客服场景的 Agent上线第一周收集了大概 8 万条 Session。老板问了一个特别朴素的问题用户到底在拿它干什么哪些场景表现好哪些场景翻车我盯着 Trace 列表看了整整一个下午最后只能给出几个拍脑袋的个例完全没有说服力。问题不在于数据不够而在于数据太多、太碎、太异构人眼根本没法从海量 Trace 里抽象出行为模式。这就是智能聚类要解决的核心问题把海量、零散、非结构化的 Agent Trace通过 Embedding 和聚类算法自动归并成若干类可解释的行为模式让你从看单条 Trace升级到看行为分布。它适合所有在做 Agent 开发、评测、运维、安全分析的人尤其是那些已经过了 Demo 阶段、开始面对真实流量的人。这篇文章我会把整套思路拆开讲Trace 和 Session 的数据模型怎么设计、Embedding 到底该 embed 什么、聚类算法怎么选、聚类结果怎么解释、以及我在实际项目里踩过的那些坑。不讲空理论全部围绕能落地来写。2. 先搞清楚要聚类的对象Trace、Session、Span 到底是什么关系2.1 三层数据模型别把 Session 和 Trace 混为一谈很多人一开始就搞混了这几个概念导致后面 Embedding 的粒度选错聚类结果一塌糊涂。我先把它们理清楚。在典型的 Agent 可观测体系里数据是分层的Session会话一次完整的用户交互过程从用户发起请求到 Agent 给出最终答复。一个 Session 可能包含多轮对话。Trace追踪一次请求链路的技术性记录通常一个 Session 对应一个或多个 Trace。Trace 是技术视角的容器。Span跨度Trace 里的最小单元一次 LLM 调用、一次工具调用、一次检索都是一个 Span。用生活化的类比Session 是你去医院看了一次病Trace 是这次看病的完整病历流水Span 是挂号、问诊、化验、开药每一个具体环节。为什么要强调这个因为聚类的粒度决定了你能得到什么洞察聚类粒度聚合对象能回答的问题适用场景Span 级单次工具/LLM 调用哪类工具调用容易失败工具优化Trace 级单次请求链路哪类任务链路最长/最贵性能优化Session 级完整多轮会话用户到底想干什么产品/行为分析我个人的经验是做理解 Agent 行为和表现Session 级聚类是主战场Trace 级聚类是补充。因为用户意图往往要跨多轮才能体现只看单轮 Trace 会丢失上下文。2.2 一条 Session 里哪些字段值得被 Embedding确定了粒度接下来要决定把什么变成向量。这是整个流程里最关键的一步选错了后面全白搭。一条 Session 通常包含这些信息用户的多轮输入文本Agent 的多轮输出文本调用的工具序列tool call sequence每步的耗时、token 数、状态码最终是否成功、是否有用户负反馈元数据时间、渠道、用户 ID、模型版本不是所有字段都适合 Embedding。文本类字段用户输入、Agent 输出天然适合结构化字段耗时、状态码更适合作为聚类后的分析维度而不是聚类输入。我一般会构造一个复合文本表示把一条 Session 压缩成一段可 Embedding 的文本类似这样def session_to_text(session): user_turns .join([t.content for t in session.turns if t.role user]) tool_seq - .join([s.tool_name for s in session.spans if s.type tool]) return f用户意图: {user_turns[:500]}\n工具链路: {tool_seq}注意这里我做了两件事一是截断用户输入避免超长文本稀释语义二是把工具序列也拼进去。为什么因为很多 Agent 的行为差异不体现在文本上而体现在它调用了哪些工具、以什么顺序调用。两个用户问的问题很像但一个触发了检索、一个直接回答这本身就是两种行为模式。提示工具序列拼接时建议用固定的分隔符如-并且对工具名做归一化避免同一个工具因为命名不一致被当成两个。2.3 为什么不能直接对原始 Trace JSON 做 Embedding我见过有人图省事直接把整条 Trace 的 JSON 序列化后丢给 Embedding 模型。实测下来效果很差原因有三个第一JSON 里大量字段是技术噪音trace_id、时间戳、span_id这些对语义毫无贡献反而会干扰向量表示。第二JSON 的结构化符号括号、引号、冒号会占用大量 token稀释真正有意义的文本。第三Embedding 模型是在自然语言上训练的对 JSON 这种半结构化文本的语义捕捉能力很弱。正确的做法是先做特征工程再 Embedding。把结构化信息转成自然语言描述或者干脆分成两路文本走 Embedding结构化特征走单独的数值聚类最后做融合。这个思路后面第 4 节会详细展开。3. Embedding 选型不是越贵越好而是越懂你的领域越好3.1 通用 Embedding 模型在 Agent Trace 上的真实表现Embedding 模型排行网上到处都是但那些榜单基本都是在通用语义相似度任务上评的跟你的 Agent Trace 场景未必对得上。我实测过几个主流方向说说真实感受。通用大模型 Embedding比如各家 API 提供的通用 embedding 接口开箱即用语义理解能力强对用户输入这种自然语言处理得很好。缺点是贵而且对 Agent 领域特有的术语工具名、内部黑话理解一般。开源中小模型比如 BGE 系列、GTE 系列可以本地部署成本低中文支持不错。缺点是维度固定长文本处理能力有限需要自己做分块。领域微调模型如果你有标注数据比如人工标过的 Session 分类可以拿通用模型做微调。效果最好但成本最高一般团队到不了这一步。我的建议是先用通用 API 模型跑通流程验证聚类有没有价值再考虑降本或微调。别一上来就纠结模型选型流程跑不通模型再好也没用。3.2 维度、成本、延迟的三角权衡选 Embedding 模型绕不开三个指标维度、成本、延迟。它们互相制约。维度方面常见的有 768、1024、1536、3072 等。维度越高表达能力越强但存储和计算成本也越高。做 Session 聚类我实测 1024 维基本够用1536 维是舒适区3072 维对聚类任务来说边际收益很低——因为聚类本身就是在降维找结构输入维度太高反而容易受噪声影响。成本方面按 token 计费的 API 模型一条 Session 如果压缩到 500 token10 万条 Session 就是 5000 万 token这个量级要算清楚预算。本地模型虽然省 API 费但要算 GPU 成本。延迟方面如果是离线批量聚类延迟不敏感如果要实时给每条新 Session 打标签那 Embedding 延迟就很重要了。我一般会做一个简单的成本估算表方案单条成本10万条成本延迟适用阶段通用 API 大模型高高中验证期开源本地模型低摊薄后低低规模化微调模型中中低成熟期3.3 一个容易被忽略的点Embedding 的归一化这个坑我踩过。不同 Embedding 模型输出的向量有的已经做了 L2 归一化有的没有。如果你混用不同来源的向量或者没注意归一化聚类结果会严重偏移。判断方法很简单算一下向量的模长。如果模长都接近 1说明已归一化如果差异很大就要手动归一化。做余弦相似度聚类时务必确保所有向量在同一尺度上。我一般会在入库前统一做一次 L2 归一化省得后面出问题。import numpy as np def l2_normalize(vecs): norms np.linalg.norm(vecs, axis1, keepdimsTrue) norms[norms 0] 1e-10 return vecs / norms4. 聚类算法怎么选K-Means 不是万能药4.1 先降维还是先聚类顺序很重要拿到一堆高维向量第一反应往往是先降维再聚类。但这里有个顺序问题降维是为了可视化还是为了聚类如果是为了可视化画个散点图给人看那 UMAP 或 t-SNE 都行降到 2 维或 3 维。但要注意降维后的距离关系已经失真不能拿降维后的结果去做聚类否则聚类质量会大打折扣。如果是为了聚类正确顺序是先在高维空间聚类再降维可视化。因为聚类算法在高维空间能捕捉到更完整的结构降维只是为了让你看得见。我见过有人用 t-SNE 降到 2 维再跑 K-Means结果聚出来的类完全没有业务意义。这就是顺序搞反了。4.2 K-Means、HDBSCAN、层次聚类的适用边界不同聚类算法适合不同场景我列个对比算法是否需要预设类数能否发现噪声适合的类形状Agent Trace 场景K-Means是否球形类数已知、分布均匀HDBSCAN否是任意类数未知、有离群点层次聚类否否任意小数据集、要层次结构谱聚类是否任意图结构数据做 Agent Trace 聚类我最推荐HDBSCAN。原因很实际你事先根本不知道用户有多少种行为模式而且总有一批四不像的 Session比如用户乱输、Agent 报错这些应该被识别为噪声而不是硬塞进某个类。K-Means 会强行把每个点都分到某个类导致噪声污染真实类。但 HDBSCAN 也有缺点参数敏感尤其是min_cluster_size调不好要么全是一个类要么全是噪声。我的经验是min_cluster_size从总样本量的 1% 到 2% 开始试然后根据结果调整。4.3 类数到底怎么定肘部法之外的实战判断如果你非要用 K-Means那类数 K 怎么定就是绕不开的问题。教科书会告诉你用肘部法或轮廓系数但实战中这两个指标经常给出模棱两可的答案。我的做法是指标 业务双验证。先用轮廓系数扫一遍 K 的范围比如 5 到 50找出几个候选值然后对每个候选 K 跑一次聚类人工看几个类的代表样本判断这个类是不是有业务意义。指标好但业务上说不通的类直接否掉。举个真实例子有一次轮廓系数告诉我 K12 最好但我看了聚类结果发现其中 4 个类其实是同一个业务场景的细分合并后 K8 反而更清晰。指标是参考业务是裁判。5. 让聚类结果说人话从向量簇到行为标签5.1 每个簇的代表样本怎么挑聚类跑完你得到一堆簇 ID但这对业务方毫无意义。下一步是给每个簇画像。最直接的方法是挑代表样本。怎么挑离簇中心最近的若干条。但要注意如果用的是 HDBSCAN簇中心的概念不直接适用可以用簇内所有点的均值作为近似中心。def pick_representatives(embeddings, labels, cluster_id, top_n5): mask labels cluster_id cluster_vecs embeddings[mask] center cluster_vecs.mean(axis0) dists np.linalg.norm(cluster_vecs - center, axis1) idx np.argsort(dists)[:top_n] return np.where(mask)[0][idx]挑出代表样本后人工读一遍就能大致总结出这个簇在讲什么。这一步目前还很难完全自动化但可以用 LLM 辅助把代表样本喂给大模型让它生成一句行为描述。实测下来LLM 生成的描述有 70% 左右可以直接用剩下的需要人工修正。5.2 用 LLM 给簇自动打标签的正确姿势让 LLM 打标签关键在于提示词的设计。直接问这些 Session 属于什么类型效果一般因为 LLM 不知道你的业务分类体系。我的做法是分两步第一步让 LLM 从代表样本里抽取共同特征用户意图、工具链路、结果状态第二步基于这些特征生成一个简短标签。提示词大概长这样以下是同一类 Agent 会话的代表样本请总结它们的共同行为模式。 样本1: ... 样本2: ... 请输出1) 用户核心意图 2) 典型工具链路 3) 一个不超过10字的标签这样出来的标签质量明显更高。另外标签要可迭代第一轮生成的标签可能比较粗糙可以拿全部样本再让 LLM 精修一轮。5.3 标签体系要能挂上业务指标光有标签还不够标签必须能跟业务指标挂钩否则聚类就只是自嗨。我一般会做一张簇-指标对照表把每个簇的样本量、成功率、平均耗时、平均 token 消耗、负反馈率都算出来。这样一眼就能看出哪个行为模式是高频但低质的哪个是低频但高价值的。簇 ID行为标签样本占比成功率平均耗时负反馈率0简单问答35%96%1.2s2%1多轮检索22%78%8.5s12%2工具链失败8%31%15s45%3意图不明15%60%3s20%有了这张表优化方向就非常明确了簇 2 成功率只有 31%负反馈率 45%这就是重点排查对象。你可以进一步下钻看这个簇里工具调用失败的具体原因。6. 我在实际项目里踩过的坑和对应解法6.1 坑一Session 长度差异巨大导致聚类偏移Agent 的 Session 长度差异可以非常夸张有的就一轮问答有的能聊几十轮。如果直接把整条 Session 的文本拼起来做 Embedding长 Session 的向量会被大量内容平均掉短 Session 的向量则很集中导致聚类结果被长度主导而不是被语义主导。我的解法是分层处理对超长 Session 做摘要后再 Embedding或者干脆按轮次切分对每一轮单独 Embedding 后再做 Session 级聚合比如取平均或加权平均。实测下来先摘要再 Embedding 的效果更稳定。6.2 坑二工具名不统一同一个工具被聚成两类这个坑特别隐蔽。比如你的 Agent 里有个搜索工具有时候叫web_search有时候叫search有时候叫google_search。Embedding 时它们会被当成不同的东西导致本该属于同一类的 Session 被拆开。解法是做工具名归一化维护一张别名映射表把所有变体映射到统一名称。这个表要定期维护因为工具会不断新增。6.3 坑三聚类结果不稳定每次跑都不一样K-Means 对初始中心敏感HDBSCAN 对参数敏感导致每次跑出来的聚类结果都不太一样。这在需要追踪行为变化趋势的场景下是致命的——你没法比较这周和上周的分布。解法有两个一是固定随机种子保证可复现二是用增量聚类把历史簇中心保存下来新数据来了先分配到已有簇只有明显偏离的才新建簇。这样分布就是连续可比的。6.4 坑四把聚类当成终点而不是起点最常见的误区是聚类跑完、标签打完项目就结束了。但实际上聚类只是把海量数据压缩成可理解的几个类别真正的价值在于基于这些类别去做优化。我一般会把聚类结果接入三个下游一是评测针对每个簇设计专门的测试用例二是告警某个簇的占比突然飙升就触发预警三是产品迭代高频低质的簇就是优化优先级最高的地方。7. 从离线聚类到在线行为监控的演进思路离线聚类跑通之后很自然会想能不能实时监控 Agent 的行为分布我的做法是离线建库、在线匹配。离线阶段把历史 Session 聚成若干簇保存每个簇的中心向量和标签。在线阶段每条新 Session 来了先做 Embedding然后计算它到各个簇中心的距离分配到最近的簇。如果距离超过阈值说明这是一个新行为先标记为待观察积累到一定量后再触发一次离线聚类。这样既保证了实时性又能持续发现新行为模式。阈值怎么定我一般用历史数据的距离分布取 95 分位数作为阈值这样只有真正偏离的行为才会被标记。这套机制跑起来之后你对 Agent 的理解就从事后分析变成了实时感知。哪个行为模式在增长、哪个在衰退、有没有新行为冒出来一目了然。这才是智能聚类真正的价值所在——它不是一个分析工具而是一个持续理解 Agent 行为的感知系统。最后分享一个我个人的小习惯每次聚类结果出来后我都会随机抽 20 条 Session 人工读一遍看看聚类有没有把明显不同的东西混在一起。这个动作花不了多少时间但能帮你及时发现 Embedding 或聚类参数的问题。机器再智能也需要人的眼睛做最后一道校验。