
智能体轨迹压缩成自动机这个话题在我最近一次框架选型实测里直接派上了用场。把一批 agent 运行轨迹压缩成自动机之后我发现一个非常直观的结论行为更多由框架决定而不是由模型参数或提示词细节决定。这篇文章把怎么做、需要看哪些参数、哪些地方最容易踩坑按我实际落地的顺序拆一遍。适合准备做 agent 框架选型、多智能体调试或者想用自动化方法分析 agent 行为稳定性的开发者看。1. 轨迹压缩成自动机之前先搞清楚这三个概念1.1 轨迹是行为序列不是日志文本在智能体场景里一条轨迹是指一次任务从开始到结束的完整行为序列通常包含用户输入、模型调用、工具调用、观察结果、中间推理、最终答案这些元素。普通日志是按时间顺序刷出来的文本单看一条还好几十条上百条叠在一起很难说清楚“这个 agent 到底是怎么做事的”。轨迹强调顺序和因果。先调用搜索工具再给结论和先给结论再调用搜索工具日志里都能写出来但在行为语义上是完全不同的。前者是“查了再说”后者是“编完再补依据”。如果你只看日志文本这两者可能混在一起但一旦转成轨迹序列差别立刻显现。我一般会先把轨迹统一成动作序列每个动作只保留“类型 关键对象”不保留具体内容。比如用户问了一个问题中间动作就是 USER_INPUT、LLM_CALL、TOOL_SEARCH、OBSERVATION、LLM_CALL、FINAL_ANSWER。至于问题里的具体实体名称、搜索关键词先剥掉。这一步很重要因为自动机建模的是行为结构不是业务内容。1.2 自动机是轨迹的行为骨架自动机在这里就是一张有向图节点是状态边是转移关系。把一批轨迹叠在一起相同的动作模式合并成一个状态转移上标记出现频次就得到自动机。它的价值不是复述某一条轨迹而是把一组轨迹的共同结构抽出来。假设你收集了 100 条成功轨迹其中 80 条都是“先调用搜索工具再调用代码工具最后输出答案”自动机上就能看到一条主通道从起始状态经过搜索状态、代码状态再到终止状态转移频次很高。剩下 20 条走了别的分支。这个骨架比 100 条日志直观得多。实现上常见做法是从前缀树开始合并。先把所有轨迹按动作符号化建一棵 trie再把结构等价、语义等价的状态合并最后按转移频次剪枝。如果只想做行为模式匹配也可以借用 AC 自动机那套多模式匹配思路去扫描哪些高频动作片段反复出现。但我不建议一上来就上复杂方法先用前缀树加状态合并够用且容易解释。1.3 框架是轨迹结构的第一个约束条件智能体框架解决的核心问题是把模型调用、工具调用、记忆读写、任务分发这些环节串起来。串的方式不同行为空间就不同。这句话是理解“行为更多由框架决定”的关键。框架不是透明的执行器它本身就是行为空间的边界。你在一个不允许循环的框架里永远压不出带循环的自动机你在一个默认重试 3 次的框架里自动机上大概率会出现一条反复调用同一工具的分支。所以在分析轨迹之前先把框架的控制流结构弄清楚否则很容易把框架造成的现象误判成模型或提示词的问题。2. 为什么行为更多由框架决定从自动机形态看2.1 框架先定义了路径模型只做局部选择不同类型框架压缩出来的自动机形态差异非常明显。偏工作流平台的框架比如 Dify 这类agent 行为是用节点图定义的开始节点、LLM 节点、工具节点、结束节点连线固定。轨迹基本是线性或少量分支不太可能出现“无限循环调用自己”这种结构因为图本身限制了路径。压缩出来的自动机主干清晰节点少分支少确定性高。典型 ReAct 风格的 agent 框架循环结构是内建的思考、行动、观察、再思考。轨迹天然会出现重复的“THINK → ACTION → OBSERVATION”环环数取决于任务复杂度和最大迭代次数。自动机上会表现为明显的自环或回边这是框架设定好的不是模型随机跑出来的。多智能体框架更不一样。轨迹里会出现跨智能体消息比如 AGENT_A_TO_AGENT_B状态图会分出很多子图消息传递顺序往往由框架的调度策略决定。你甚至能通过自动机直接看到哪个智能体是消息中心哪个智能体是边缘节点。把这些自动机放在一起看主干结构几乎就是框架控制流图的实例化。模型能力、提示词、温度这些因素更多是在同一个结构里做局部选择很难突破框架给定的路径。2.2 实测对比换框架比换提示词更容易改变骨架我做过一个对比实验同一个模型同一组任务分别在偏工作流框架和偏自由 ReAct 风格框架里跑然后各自压缩自动机。结果很明显。工作流框架的自动机接近一条线性管道节点顺序固定几乎不存在回边。ReAct 框架的自动机则带明显的循环回溯分支失败时会弹回之前的思考状态重新选路。两者骨架完全不同。反过来在同一个框架里换提示词、调温度自动机主干几乎不变变的最多只是某些分支上的动作组合和失败回退次数。这说明一个问题分析 agent 行为时框架是最高优先级的变量。如果你看到某个 agent 频繁出现某类行为先别急着改提示词先看看是不是框架把路径限制成这样或者框架默认就循环执行了 N 轮。很多所谓的“行为问题”本质是“框架配置问题”。3. 环境与数据准备没有干净的轨迹压缩无从谈起3.1 轨迹来源平台日志、埋点、trace 和回放要压缩自动机第一步是拿到足够多、字段完整的轨迹。常见来源有四种。第一agent 平台自带的运行日志或调试面板导出。Dify、Coze 这类平台一般都能看到单条任务的事件流直接导出或用接口拉取。第二自己业务代码里埋点。在每次 LLM 调用、工具调用、消息传递的地方写日志记录会话 ID、步骤序号、动作类型、时间戳。这是最灵活的方式也是我推荐长期使用的方式。第三通过可观测性工具收集 trace 数据。如果你的服务已经接了 OpenTelemetry 之类的链路追踪agent 调用链天然就是轨迹只需要做一次格式转换。第四测试集回放。把一组固定问题反复跑记录每次的行为序列。这种方式适合做对比实验因为输入可控。我建议至少收集 50 到 100 条有效轨迹再开始压缩。少于 20 条压缩出来的自动机基本都是单路径看不出行为分布意义不大。样本要同时覆盖成功和失败两种结果因为失败路径往往是框架问题最明显的暴露点。3.2 统一轨迹格式先转 JSON Lines不同框架日志格式差异很大直接拿来分析会乱。建议统一转成 JSON Lines 格式每个事件一行。下面是我常用的字段结构你可以按实际日志调整{session_id: task_001, step: 1, actor: USER, action_type: INPUT, detail: 用户问题, timestamp: 1700000000, status: success} {session_id: task_001, step: 2, actor: AGENT, action_type: LLM_CALL, detail: 模型调用, timestamp: 1700000001, status: success} {session_id: task_001, step: 3, actor: TOOL, action_type: TOOL_CALL, detail: search, timestamp: 1700000002, status: success} {session_id: task_001, step: 4, actor: SYSTEM, action_type: OBSERVATION, detail: 搜索结果摘要, timestamp: 1700000003, status: success} {session_id: task_001, step: 5, actor: AGENT, action_type: FINAL_ANSWER, detail: 最终回复, timestamp: 1700000004, status: success}字段含义session_id哪一次任务step第几步用于还原顺序actorUSER、AGENT、TOOL、SYSTEMaction_typeINPUT、LLM_CALL、TOOL_CALL、OBSERVATION、FINAL_ANSWER、ERRORdetail动作的补充描述压缩阶段一般不用排查时再看timestamp时间戳判断重试间隔和循环耗时statussuccess、failed、timeout用于区分正常路径和失败路径。最终压缩只用到会话 ID、步骤、actor、动作类型、工具名这几个字段但其他字段在排查时很关键不要提前删掉。3.3 工具链选择pandas、networkx、graphviz 足够直接用 Python 就能做不需要重型平台。我通常只依赖几个库pandas 做数据清洗networkx 建图和统计转移graphviz 或 pyvis 出图再加一个 json 或 yaml 做配置。没有固定版本要求按你的 Python 环境正常安装即可。如果你的轨迹量到了几万条可以把 pandas 换成 polars处理速度会快不少。但第一次做完全没必要先把方法跑通再考虑性能优化。注意这一步最容易忽视的是埋点规范。如果不同模块记录的工具名不统一比如 search_web、web_search、google_search 混着出现后面清洗会非常痛苦。先花半小时把命名统一比任何算法都重要。4. 从轨迹到自动机的完整流程4.1 符号化把轨迹变成动作序列这一步的目标是把每条轨迹变成一行符号数组。先按 session_id 分组按时间戳排序再映射成符号。比如一条轨迹用户输入 → 模型调用 → 调用搜索工具 → 返回结果 → 模型调用 → 最终答案符号化之后就是USER_INPUT, LLM_CALL, TOOL_SEARCH, OBSERVATION, LLM_CALL, FINAL_ANSWER如果工具调用有多个参数我只保留 tool_name参数留到原始日志里查。原因前面说过自动机建模的是行为结构不是参数细节。参数一旦进入状态状态数会爆炸。代码示意如下字段名以你自己日志为准symbols [] for event in session_events: if event[actor] USER: symbols.append(USER_INPUT) elif event[actor] AGENT and event[action_type] LLM_CALL: symbols.append(LLM_CALL) elif event[actor] AGENT and event[action_type] FINAL_ANSWER: symbols.append(FINAL_ANSWER) elif event[actor] TOOL: symbols.append(fTOOL_{event[detail].upper()}) elif event[actor] SYSTEM and event[action_type] OBSERVATION: symbols.append(OBSERVATION) elif event[status] in (failed, timeout): symbols.append(ERROR)特别注意不要把 ERROR 和 OBSERVATION 混在一起。出错状态必须单独保留因为自动机里是否出现 ERROR 状态以及错误之后回到哪个状态是判断框架稳定性的核心指标。4.2 建前缀树并合并等价状态有了符号序列集合第一步是建前缀树。把每条序列按顺序插入树中相同前缀共享路径每个节点记录经过它的轨迹数量。这一步的直观作用是直接看到所有轨迹的共同开头是什么。比如大部分轨迹都是 USER_INPUT → LLM_CALL 开头说明框架统一先做一次模型调用而不是直接调工具。如果有些轨迹是 USER_INPUT → TOOL_CALL 开头说明框架支持跳过模型直接执行工具这个分支值得关注。建完树之后做状态合并。合并原则不是看状态名一样而是看语义等价。比如 LLM_CALL 后接 TOOL_SEARCH和 LLM_CALL 后接 TOOL_WEB_FETCH如果你只是分析框架行为这两个搜索类工具状态可以合并成 TOOL_RETRIEVAL。合不合并取决于你想分析到什么粒度。如果你分析的是动作模式的共现可以先用序列挖掘方法找出高频动作片段再把高频片段作为自动机的候选子路径。这个思路在数据量大时更快但解释性稍微弱一点。第一次做我建议老老实实用前缀树加人工确认的合并规则。4.3 统计转移、剪枝和出图状态合并完成后统计所有相邻转移的次数。比如从状态 A 到状态 B 出现了 80 次从 A 到 C 出现了 20 次就在图上画两条边标上权重。这一步建议输出两个东西转移表CSV 格式包含 from、to、count、ratio方便后续用脚本过滤和排序可视化图networkx 生成标注主路径和分支方便人眼判断。判断主路径的方式很简单从起始状态开始沿着每个节点上转移比例最高的边走直到终止状态。这条主路径就是框架下最常见的行为模式。分支多不多看每个非终止节点是不是都存在明显的次要转移。剪枝时不要只按绝对次数砍。比如总共只有 50 条轨迹出现 2 次的边占比 4%这种边界信息有时能暴露框架的异常分支。我建议先保留所有边出图时再按最小支持度过滤不要把原始转移表里的数据也删掉。5. 四个关键参数与三套判断标准5.1 参数表支持度、合并阈值、截断长度、状态集合压缩自动机没有唯一算法参数不同结果差异很大。我常用这几个参数参数作用建议初值说明最小支持度转移频次低于该阈值的边不画出5% 或 3 次太低会有一堆噪声分支太高会丢掉关键边界行为状态合并阈值决定哪些状态可以合并先按动作类型精确合并不要一上来就做语义合并最大轨迹长度超过 N 步的轨迹是否截断按框架最大迭代轮数定防止异常长尾干扰结构是否允许未知状态是否允许自动机出现未定义状态建议允许未知状态是观察新行为的窗口最小支持度最影响美观状态合并阈值最影响结构最大轨迹长度最影响环的判断固定状态集合最影响扩展性。这四个参数要分开调不要同时乱改。5.2 覆盖率、确定性和主路径占比怎么用四个指标我每次必看覆盖率自动机上的主路径和主要分支能覆盖多少原始轨迹。低于 70%说明状态抽象或阈值有问题骨架没有反映真实行为。确定性给定一个状态它的下一转移是否主要集中在一条边上。如果某状态后面均匀分到 5 条边说明这个位置行为非常不稳定需要放大看是框架随机还是提示词导致。环的数量和位置出现自环是正常的比如 ReAct 的多轮迭代。但如果某个非预期节点出现大量自环比如反复调用同一个工具多半是框架循环配置或工具返回异常。主路径占比主路径覆盖轨迹的比例。比例高说明行为稳定比例低说明行为分散可能是任务多样性太大也可能是框架没有收敛。这四个指标不是越高越好。覆盖率想拉高就把状态合并得粗一点想凸显细节就降低合并粒度。关键是你得明确这次压缩的目的是看主干还是看异常。5.3 参数调优的先后顺序不要一上来就调语义合并。我建议的顺序是先用精确动作类型压一版看主路径和覆盖率再决定是否合并同类型工具最后才是引入内容哈希或语义相似度。每调一步重新看覆盖率、确定性和主路径占比。三个指标同时变差说明抽象过度了。注意状态合并阈值和最小支持度是两个不同概念。前者决定状态是否等价后者决定边是否保留。只调后者不调前者通常只能去掉细枝末节不能改变结构。6. 常见问题和排查顺序6.1 样本太少压缩结果全是单路径现象自动机只有一条从头到尾的线分支几乎没有。先别高兴这不代表框架很稳定很可能是样本量太小还没来得及出现分支。排查链路先看样本数量少于 30 条直接补数据再看任务多样性如果 30 条都是同一个问题压缩结果自然单薄最后看是否过滤了失败轨迹如果只保留成功轨迹失败回退分支会被过滤掉。我一般会先把成功和失败轨迹分开压缩一次。成功轨迹的自动机看主流程失败轨迹的自动机看崩溃点两个结合起来才能定位问题。6.2 状态爆炸先查符号化再查归一化现象自动机节点几百个根本没法看。最常见原因是把不该进状态的内容放进去了比如把具体参数、完整输入文本、工具返回值当成了状态标识。排查链路先确认符号化时是否只保留 actor 加 action_type 加 tool_name再确认是否对不同工具名做了归一化比如 search_web、web_search、google_search 要合并成一个状态然后看是否插入了内容哈希如果插了去掉再看如果还是爆炸改用最小支持度过滤低频节点。90% 的状态爆炸都是前两步造成的很少需要动用复杂算法。6.3 环太多区分标准循环和失败重试现象图里到处都是回边画出来像蜘蛛网。这通常不是状态合并问题而是 agent 在真实任务里反复重试或反复决策。排查链路先看具体是哪些边形成环是 THINK → ACTION → OBSERVATION 这种标准推理循环还是 ACTION → ERROR → ACTION 这种重试循环标准循环看框架最大迭代轮数是否合理默认 10 轮不代表任务需要 10 轮重试循环看工具调用是否稳定很多环是工具超时或返回格式错误导致模型反复重来检查每条环的轨迹耗时和 token 消耗判断是否值得优化。这里最容易误判的是把失败重试的环当成正常迭代。区分方法很直接看环上的转移是否经过 ERROR 或 TOOL_ERROR 状态。经过了就是重试没经过才是正常推理循环。6.4 框架差异看不出来比较低频分支和边界行为现象跑了两套框架压缩出来的自动机主路径高度相似。先检查是不是两个框架本质上用了同一种 agent 循环范式。比如两个框架都内置了 ReAct 循环主路径当然类似。这时候要比较的不是主路径而是分支和边界行为比较起始状态一个框架是否先做系统指令注入另一个是否直接调模型比较结束状态失败时是否走统一的 ERROR 状态还是直接返回半成品比较多智能体场景下的消息边框架 A 是串行转消息框架 B 是直接调用函数自动机形态会不同把阈值降低到 2%看低频分支的差异。低频分支才是框架细节的暴露区。主路径决定框架的“大类”低频分支和错误处理决定框架的“细节”。选型时两个都要看。7. 这套方法的适用边界和落地建议7.1 适合做框架选型、回归测试和稳定性分析这套方法更适合做横向对比和稳定性分析而不是单点性能调优。典型场景包括框架选型两个 agent 框架用同一模型同一测试集各自压缩出自动机直接比较行为结构和失败分支回归测试修改框架配置或升级框架后重新压缩轨迹看主路径和覆盖率是否变化提示词分析确认在同一个框架内提示词改动是否真的改变了行为结构还是只在局部改了几个分支多智能体调度分析看智能体之间消息传递的路径和频率分布判断是否存在消息风暴或单点瓶颈。7.2 不适合单条调试和严格自动机学习如果只是单条任务调试或者任务数量很少直接看日志更快不用压缩。自动机是统计结构的工具样本太少时输出没有说服力。如果你想追求自动机本身的数学完备性需要严格的自动机学习算法和验证集那本文的前缀树合并方法不够严谨只能算行为骨架。另外如果轨迹里动作类型没有统一规范工具名五花八门第一步清洗就要花大量时间。先做好埋点规范比选任何算法都重要。7.3 落地前先做好埋点和输出规范最后留几个我自己的经验点。第一先跑通最小样例。不要一开始就收集几千条轨迹先拿 20 条左右把符号化、建树、出图整个链路跑通确认输出格式自己看得懂再扩大样本。第二输出目录和命名要做好。每次压缩都生成一个带时间戳的转移表 CSV 和自动机图否则改了几次参数之后你根本不知道哪张图对应哪组参数。第三大部分“行为怪异”问题先看框架配置再看模型和提示词。按我的经验轨迹压缩出来的异常环、异常分支十次有七八次是框架的循环次数、超时设置、工具调用策略造成的。智能体轨迹压缩成自动机说到底不是要把 agent 变成一台机器而是让你跳出单条日志从结构上看见 agent 的行为规律。框架决定骨架模型和提示词决定细节这个顺序先立住后续的调试和选型都会省力很多。