ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ChatBI 会话保持与意图继承:当用户追问“那华南区呢”时的上下文补全

ChatBI 会话保持与意图继承:当用户追问“那华南区呢”时的上下文补全 ChatBI 会话保持与意图继承当用户追问“那华南区呢”时的上下文补全周一上午的业务复盘会上大区销售总监在智能问答交互界面输入了一句“上个季度华东区各省份的客单价与毛利表现如何”系统响应迅速三秒内渲染出了一张清晰的柱状堆叠图。紧接着总监在输入框顺手打出四个字“那华南区呢”结果下一秒屏幕上直接弹出一个巨大的红色报错弹窗或者更荒谬的是大模型直接生成了一条针对所有历史年份、不带任何时间范围且没有聚合指标的单表全量扫描SELECT * FROM dim_region WHERE region_name 华南区。总监皱着眉头转过头问我“大喜这 AI 是不是有失忆症我上一句话还在问上季度的客单价和毛利下一句换个区域它怎么连我在算什么都忘了”在 ChatBI 的多轮对话交互中这种现象被称为意图继承断层Intent Inheritance Failure。现实业务场景中人类习惯于极度精简的追问“那上个月呢”时间切片变更度量与维度继承“按周展开看看”粒度下钻时间与过滤条件继承“只看自营的”新增过滤谓词其余上下文全部继承“退款率呢”度量替换维度与过滤条件继承如果系统把每一次输入都当作孤立的无状态单轮问答Stateless Prompting或者粗暴地把历史整段聊天记录塞进 Context 赌大模型自己“悟出来”不仅会吃满 Token 导致响应延迟翻倍更会因大模型的注意力漂移Attention Drift导致生成完全失真的畸形 SQL。一、 多轮对话中的三种典型意图突变构建稳健的上下文补全引擎首先要将用户的追问动作在语义状态机Semantic State Machine中完成精准分类------------------------------------------------------------- | 上一轮会话状态 (Prior State): | | Metrics: [客单价, 毛利] | | Dimensions: [省份] | | Filters: [时间2026Q3, 区域华东区] | ------------------------------------------------------------- | 用户追问输入识别 v ------------------------------------------------------------- | 模式 A: 槽位替换 (Slot Overwrite) | | 典型提问“那华南区呢” - 继承全部上下文仅将 [区域] 替换为华南区| ------------------------------------------------------------- | 模式 B: 维度/度量追加与下钻 (Drill-down / Metric Append) | | 典型提问“加上退款率” - 保留原有度量追加 [退款率] 到查询列表 | ------------------------------------------------------------- | 模式 C: 意图彻底漂移 (Topic Switch / Hard Reset) | | 典型提问“查下张三的员工工号” - 清空全部数仓上下文开启新会话 | -------------------------------------------------------------槽位值就地覆写Slot Overwrite这是最常见的情形。用户更换了某个维度维值从“华东”到“华南”或变更了时间切片从“上季度”到“上月”。此时系统必须保持原有的指标度量客单价、毛利与聚合粒度不变精准替换过滤条件。渐进式下钻与切片Drill-down / Slice用户在原有结论基础上请求更细的颗粒度。例如原先按省份汇总追问“按城市看呢”此时必须执行维度的下钻Drill-down而非简单替换。话题跳跃Topic Switch如果用户突然提问与当前数据分析完全无关的内容上下文继承逻辑必须有能力主动“熔断”否则强行把前序的客单价与财务过滤硬套在人员工号查询上必然引发语法与逻辑的崩塌。二、 架构设计基于 AST 状态合并的会话上下文引擎我们摒弃了“把历史对话原封不动扔给大模型做端到端推导”的黑盒做法改用**结构化语义槽Semantic Slots 差异修补状态机Diff Patching Engine**的显式控制流------------------------------------------------------------- | 用户输入新 Query (那华南区呢) | ------------------------------------------------------------- | v ------------------------------------------------------------- | 意图分类器 (Intent Classifier: 轻量级 LLM / 小参数模型) | | - 判定提问类型: 是延续追问 (Follow-up) 还是全新查询 (New Topic) | ------------------------------------------------------------- | (如果是 Follow-up) v ------------------------------------------------------------- | 语义增量抽取器 (Delta Slot Extractor) | | - 提取本次变动: [Filter: region 华南区] | | - 提取省略信号: (未指定度量、未指定时间) | ------------------------------------------------------------- | v ------------------------------------------------------------- | 状态合并器 (State Merging Engine) | | - 读取 SessionCache 中的上一轮状态快照 | | - 执行覆写规则: region_value: 华东区 - 华南区 | | - 产出完整完备的合并后语义槽 (Resolved Semantic Spec) | ------------------------------------------------------------- | v ------------------------------------------------------------- | 最终生成器 (Text2SQL Generator): 接收完备语义槽生成无歧义 SQL | -------------------------------------------------------------三、 核心实现语义状态继承与合并器代码以下是在我们 ChatBI 会话网关中运行的核心会话补全处理器。代码通过强类型槽位维护历史快照并利用显式策略完成状态继承from dataclasses import dataclass, field from typing import List, Dict, Optional, Any from enum import Enum import json class QueryType(Enum): NEW_QUERY NEW_QUERY FOLLOW_UP FOLLOW_UP TOPIC_SWITCH TOPIC_SWITCH dataclass class SemanticState: metrics: List[str] field(default_factorylist) # 指标列表如 [客单价, 毛利] dimensions: List[str] field(default_factorylist) # 分组维度如 [省份] filters: Dict[str, Any] field(default_factorydict) # 过滤条件字典如 {date_range: 2026Q3, region: 华东区} granularity: Optional[str] None # 时间粒度如 month, day class ConversationalContextManager: def __init__(self): # 实际生产中保存在 Redis此处用内存字典模拟 Session self.session_store: Dict[str, SemanticState] {} def get_last_state(self, session_id: str) - Optional[SemanticState]: return self.session_store.get(session_id) def merge_state(self, session_id: str, delta_slots: Dict[str, Any], query_type: QueryType) - SemanticState: 核心状态机合并逻辑 prev_state self.get_last_state(session_id) # 如果是新话题或没有历史状态直接构建全新状态 if query_type QueryType.NEW_QUERY or prev_state is None: new_state SemanticState( metricsdelta_slots.get(metrics, []), dimensionsdelta_slots.get(dimensions, []), filtersdelta_slots.get(filters, {}), granularitydelta_slots.get(granularity) ) self.session_store[session_id] new_state return new_state # 如果是跟进追问 (Follow-up)执行智能继承与覆写 merged_metrics delta_slots.get(metrics) or prev_state.metrics merged_dimensions delta_slots.get(dimensions) or prev_state.dimensions merged_granularity delta_slots.get(granularity) or prev_state.granularity # 过滤条件的深层合并与覆写 merged_filters dict(prev_state.filters) new_filters delta_slots.get(filters, {}) for k, v in new_filters.items(): merged_filters[k] v # 相同 key 直接更新如 region: 华东区 - 华南区 merged_state SemanticState( metricsmerged_metrics, dimensionsmerged_dimensions, filtersmerged_filters, granularitymerged_granularity ) # 更新 Session 状态 self.session_store[session_id] merged_state return merged_state def generate_reconstructed_prompt(self, state: SemanticState) - str: 将合并后的完整完备状态翻译为无歧义的自包含Self-contained提示词 metric_str , .join(state.metrics) if state.metrics else 默认聚合 dim_str , .join(state.dimensions) if state.dimensions else 全盘汇总 filter_str , .join([f{k}为{v} for k, v in state.filters.items()]) return ( f请生成 SQL统计在【{filter_str}】条件下 f按【{dim_str}】切分计算【{metric_str}】。 )四、 实战场景端到端演练以下是用户连续提问时会话状态机的真实转移过程第一轮用户提问“上个季度华东区各省份的客单价与毛利表现如何”解析状态metrics:[客单价, 毛利]dimensions:[省份]filters:{date_range: 2026Q3, region: 华东区}生成的 SQLSELECT province_name, SUM(pay_amount) / COUNT(DISTINCT order_id) AS avg_order_val, SUM(pay_amount - cost_amount) AS total_gross_profit FROM dws_trade_order_di WHERE dt BETWEEN 2026-07-01 AND 2026-09-30 AND region_name 华东区 GROUP BY province_name第二轮追问用户提问“那华南区呢”模型判定QueryType.FOLLOW_UP抽取出增量delta_slots {filters: {region: 华南区}}。状态机合并后metrics: 保持[客单价, 毛利]dimensions: 保持[省份]filters: 时间仍为2026Q3region覆写为华南区。自包含 SQL 重新生成SELECT province_name, SUM(pay_amount) / COUNT(DISTINCT order_id) AS avg_order_val, SUM(pay_amount - cost_amount) AS total_gross_profit FROM dws_trade_order_di WHERE dt BETWEEN 2026-07-01 AND 2026-09-30 AND region_name 华南区 GROUP BY province_name没有丢失任何度量时间范围死死锁在三季度业务直接拿到最直观的华南数据。五、 架构师工程落地经验总结设置合理的 Session TTL 与滑动窗口业务人员的追问通常发生在几分钟之内。如果一个会话闲置超过 30 分钟再次提问时应主动判定为新会话或者在前端给出友好提示“已为您开启新一轮探索”避免半天前的历史条件幽灵般地污染当前查询。在 UI 界面显式反显继承的条件标签Active Filters Pill当用户追问“那华南区呢”前端渲染结果的顶部必须打出已激活的过滤药丸卡片[时间: 2026Q3] [区域: 华南区] [指标: 客单价, 毛利]并允许用户一键点击“×”移除某个继承条件。显式交互比隐式猜测更让业务信任。消除时间常量的静态僵化如果前一轮继承了“今天”而第二轮追问发生在次日凌晨跨天场景时间过滤器的解析器必须重新以最新时间戳去动态求值切忌直接硬编码死昨天的日期字符串。
返回列表