AssistantAgent 电商分析 Agent 系列 02:多轮追问为什么不能只靠聊天记录
背景
上一篇我写的是,怎么把一个电商分析 Agent 从问答 Demo 往最小业务闭环推进。那条主线解决的是“单次业务问题能不能跑起来”:用户问 GMV,系统能查数;用户问为什么跌了,系统能沿着区域、订单结构、用户规模、品类、漏斗、退款这条链继续分析。
但当主链跑通之后,新的问题很快出现:真实业务人员不会每次都把问题重新说完整。也就是说,第一篇解决的是“系统能不能分析”,这一篇要解决的是“连续追问时,系统会不会丢参数”。
更常见的对话可能是这样:
用户:昨天 GMV 怎么样?
系统:昨天 GMV 为 3841.30。
用户:那华东呢?
系统:继承“昨天 + GMV”,只把范围切到华东。
用户:那女装呢?
系统:继续继承时间和分析上下文,只切到品类。
用户:那退款率呢?
系统:继承上一轮范围,把指标切到退款率。
如果系统只把这些句子当成普通聊天记录,它当然可以把历史上下文塞给模型,让模型自己猜。但在业务分析里,这种“猜”并不稳定。因为“那华东呢”缺了指标和时间,“那退款率呢”缺了范围,“活跃用户怎么样”本身还可能有口径歧义。模型猜对一次不难,难的是每次都能用同一套业务规则猜对,并且出了问题还能定位。
所以,多轮对话在这个项目里被收敛成一个更小的问题:业务型 Agent 不需要先追求长记忆,而是先要有最小业务记忆。
聊天记录有用,但不够
很多人一开始做多轮对话,会自然想到“把历史消息存起来”。这当然有用,但对电商分析 Agent 来说还不够。
原因很简单:业务追问不是普通闲聊,它里面隐含的是参数继承。
如果没有这层结构化继承,下面这类对话很容易出问题:
用户:昨天 GMV 怎么样?
用户:那华东呢?
第二句话里的“华东”本身并没有说明要看 GMV、订单量、退款率,还是 root cause。模型可以根据上一轮聊天去猜,但系统层面并不知道它到底继承了什么。下一次如果用户先问的是“退款率怎么样”,再问“那华东呢”,同一句追问的含义就变了。
“那华东呢”真正的意思不是让系统回忆上一句话,而是:
沿用上一轮的指标、日期和问题类型,只替换 region = 华东。
“那女装呢”真正的意思也不是简单接着聊,而是:
沿用上一轮的时间和范围,把分析维度切到 category = 女装。
“那退款率呢”则更特殊,它不是换区域,也不是换品类,而是把当前上下文里的指标切到退款率。如果系统只是保存聊天全文,模型可能能看懂;但如果要让这件事稳定、可测试、可复用,就不能只靠模型临场发挥。
这就是多轮理解需要拆成结构化状态的原因。
什么是“最小业务记忆”
在这个项目里,我没有一开始做复杂的长期记忆,也没有做完整的用户画像。当前最小业务记忆只记几类和分析链强相关的东西:
session_state:
last_intent
last_metric
last_date
last_region
last_category
pending_clarification
这些字段看起来很少,但刚好覆盖了电商运营追问里最容易丢的参数。
last_intent 记录上一轮是标准问数、区域对比、用户分析,还是 root cause。last_metric 记录上一轮看的是 GMV、订单量、DAU、活跃买家还是退款率。last_date 解决“那昨天呢”“那前天呢”这种时间延续。last_region 和 last_category 解决“那华东呢”“那女装呢”这种维度追问。pending_clarification 则解决系统已经提问、用户下一句只回答“DAU”或“活跃买家”的情况。
这层设计的重点不是让系统记得更多,而是只记会影响业务口径的参数。因为业务分析里真正危险的不是忘了闲聊内容,而是把日期、指标、区域或品类继承错了。
指标词典:先把用户的话收敛到稳定口径
多轮记忆的前提,是系统知道用户说的词到底对应哪个指标。
比如用户可能说“成交额”“销售额”“流水”“营业额”,但在当前项目里它们都应该收敛到 GMV。用户说“订单量”,在交易分析场景里默认理解成支付订单数。用户说“活跃用户”,就不能直接拍脑袋,因为它可能指 DAU,也可能指活跃买家。
所以项目里补了一层 metric_dictionary.yaml,把口语表达、业务定义、计算口径和常见混淆放在一起。它不是为了做一个很大的术语库,而是先把高频指标的口径稳定下来。
例如:
gmv:
aliases: ["成交额", "销售额", "流水", "营业额"]
business_meaning: "用于衡量平台最核心的交易规模"
dau:
aliases: ["日活", "活跃用户", "活跃人数"]
disambiguation_required: true
这一步很关键。因为如果指标词没有先收敛,多轮记忆就会变成“记住了一堆不稳定的自然语言”。而业务型 Agent 需要的是:用户怎么说都可以,但系统内部最好落到稳定的指标 ID 和计算口径。
semantic model:让追问有业务解释规则
指标词典解决的是“这个词是什么指标”,但还不够。多轮追问还需要知道“这个追问该继承什么、替换什么”。
这就是 semantic_model.yaml 的作用。它不是完整的数据语义层,而是当前项目的第一版业务语义模型。里面沉淀了几类简单但很实用的规则:
conversation_follow_up_rules:
- pattern: "那华东呢 / 那华南呢"
interpretation: "继承上一轮的指标、日期和问题类型,只替换 region。"
- pattern: "那女装呢 / 那家电呢"
interpretation: "继承上一轮的指标、日期和区域,只切换到 category_l1。"
- pattern: "那退款率呢"
interpretation: "继承上一轮的日期和 scope,把指标切换为 refund_rate。"
这层规则让系统的行为更像一个初级数据分析师:不是每次都重新理解全部问题,而是知道哪些信息应该沿用,哪些信息应该替换。
比如用户先问“昨天 GMV 多少”,再问“那华东呢”,系统不应该把“华东”当成一个孤立问题,而应该理解成“昨天华东 GMV 多少”。如果再追问“那退款率呢”,系统应该继承当前日期和范围,把指标切到退款率,而不是重新回到大盘或者默认日期。
这也是多轮业务记忆和普通聊天上下文最大的区别:它不是为了让对话更自然,而是为了让业务参数不丢。
歧义澄清不是失败,而是防止乱猜
一开始我会觉得,用户问了问题,系统最好马上回答。但做业务分析时,这个想法不完全对。
有些问题如果直接回答,反而是不负责任的。比如:
用户:活跃用户怎么样?
系统:您说的活跃用户,是指 DAU(日活)还是活跃买家?
用户:DAU
系统:继续按 DAU 口径回答。
这里的重点不是“系统会提问”,而是“澄清后能继续回答”。如果系统只会问一句“你指哪个”,但用户回答后链路断掉,那它还是没有完成业务任务。当前项目里会把待澄清问题存进 session state,用户下一句回答 DAU 或活跃买家时,系统会接回原来的意图、日期和范围,继续执行分析。
这件事看起来很小,但对业务型 Agent 很重要。因为很多业务词天然有歧义:活跃用户、转化率、订单量、退款率,都可能对应不同口径。系统可以有默认值,但不能在关键口径上一直偷偷猜。
这里踩过的坑
第一个坑,是把多轮对话理解成“把历史消息塞得更长”。这在通用聊天里可能够用,但在数据分析里不稳。因为系统真正需要继承的是结构化参数,不是整段聊天文本。
第二个坑,是过早追求完整语义层。刚开始很容易想把所有指标、维度、业务域都设计完整,但这会让项目变重。更稳的做法是先覆盖当前主链最需要的 GMV、订单、用户、区域、品类、漏斗和退款,先保证高频追问能跑通。
第三个坑,是以为澄清就是“不知道”。更准确地说,澄清是一种业务安全机制。尤其在指标口径不明确时,系统主动问清楚,比给一个看似流畅但口径错误的答案更有价值。
第四个坑,是多轮链路必须进入测试。像“昨天 GMV 多少 -> 那华东呢 -> 那女装呢 -> 那退款率呢”这种链,如果只靠手工试,很容易后面改代码时悄悄坏掉。所以我把这些链也放进启动验证里,让系统每次启动时检查多轮追问是否还能接住。
当前边界
这套最小业务记忆还不是完整的企业级对话系统。它目前主要解决的是电商经营分析里的短链追问,不解决长期用户偏好、跨天跨会话记忆,也没有覆盖所有复杂指标口径。
另外,metric_dictionary.yaml 和 semantic_model.yaml 也只是第一版。它们现在更像一个轻量业务语义层,用来把高频表达收敛到稳定口径,而不是一个完整的数据治理平台。
但这个阶段的价值已经很清楚:系统不再只是“记得用户刚才说了什么”,而是开始知道“当前分析任务还缺什么参数、哪些参数应该继承、哪些口径不能乱猜”。