单轮问数的局限 用户很少一次把问题说全。典型不完整问法:
用户输入
缺失信息
系统应有行为
销售收入多少
时间周期、区域
追问周期,默认全国
华东区留存率
时间周期、指标确认
追问 Q1 还是全年
对比一下
对比对象、指标
追问哪两个区域、哪个指标
和上次一样
依赖会话上下文
读取上一轮参数
若强行用默认值查数,容易答非所问 ;若每轮都当新问题,则体验断裂 。多轮对话设计的核心:显式状态 + 可控澄清 + 可审计的上下文合并 。
会话状态机 1 2 3 4 5 6 7 8 9 10 11 stateDiagram-v2 [*] --> Idle: 新会话 Idle --> Parsing: 收到问题 Parsing --> Clarifying: 缺必要槽位 Parsing --> Executing: 槽位完整 Clarifying --> Parsing: 用户补充 Clarifying --> Idle: 用户放弃/新话题 Executing --> Answered: SQL 成功 Executing --> Clarifying: 执行前再歧义 Answered --> Parsing: 追问/换条件 Answered --> Idle: 显式新会话
槽位(Slot)定义
槽位
必填
来源
示例
metric
是
检索 / 用户确认
销售收入
time_period
是
时间解析 / 追问
2026Q1
region
否
维度解析
华东区
value_type
否
关键词
同比、完成率
granularity
否
默认配置
省级
缺时间周期追问 最常见澄清场景。检测逻辑:
1 2 3 4 def needs_time_clarification (metric: str , question: str ) -> bool : if not metric: return False return not _has_time_period(question)
返回契约(虚构):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "needs_clarification" : true , "fallback_type" : "missing_time_period" , "session_id" : "sess_8f3a2b" , "clarification" : { "needed_fields" : [ "time_period" ] , "questions" : [ "请问需要查询哪个时间周期的销售收入?" ] , "suggested_questions" : [ "华东区 2026 年一季度销售收入" , "华北区 2026 年销售收入" , "2026 年 5 月新增客户数" ] , "partial_context" : { "metric" : "销售收入" , "region" : "华东区" } } }
前端展示:主问题 + 可点击的建议问法 chips,降低用户输入成本。
指标歧义澄清 当检索返回多个同分指标:
1 2 3 4 5 6 7 SELECT metric_code, metric_name, scoreFROM ( SELECT 'M_RETENTION' AS metric_code, '客户留存率' AS metric_name, 8 AS score UNION ALL SELECT 'M_NEW_CUST' , '新增客户数' , 8 ) t;
系统响应:
1 2 3 4 5 6 7 8 9 10 11 12 { "needs_clarification" : true , "fallback_type" : "ambiguous_metric" , "clarification" : { "needed_fields" : [ "metric" ] , "questions" : [ "您想查询的是哪一个指标?" ] , "options" : [ { "code" : "M_RETENTION" , "label" : "客户留存率" , "desc" : "老客户活跃比例" } , { "code" : "M_NEW_CUST" , "label" : "新增客户数" , "desc" : "当期首次下单客户数" } ] } }
产品原则 :歧义时绝不默认选第一个,必须用户确认或点选。
上下文记忆设计 session 存储结构 1 2 3 4 5 6 7 8 9 10 11 CREATE TABLE qa_session ( session_id VARCHAR (64 ) PRIMARY KEY , user_id VARCHAR (64 ), created_at DATETIME, updated_at DATETIME, slots_json JSON, last_metric VARCHAR (64 ), last_region VARCHAR (64 ), last_period VARCHAR (32 ), turn_count INT DEFAULT 0 );
slots_json 虚构示例:
1 2 3 4 5 6 7 8 { "metric" : "客户留存率" , "metric_code" : "M_RETENTION" , "time_period" : "2026Q1" , "region" : "华东区" , "value_fields" : [ "当期值" , "同比" ] , "table_code" : "dws_sales_metric_wide" }
多轮合并规则 1 2 3 4 5 6 7 8 9 flowchart TD A[新一轮用户输入] --> B{是否新话题?} B -->|含全新指标名| C[重置槽位,重新解析] B -->|补充型| D[合并 partial_context] D --> E{槽位仍缺失?} E -->|是| F[继续 Clarifying] E -->|否| G[Executing] B -->|指代型「和上次一样」| H[继承 slots_json] H --> G
新话题检测 启发式:
出现与当前 metric 无关的高分指标词 → 重置
用户点击「新对话」→ 新 session_id
超过 30 分钟无交互 → 软过期,提示是否继续上文
指代与省略
用户说
合并结果
华北区呢
继承 metric=销售收入, period=2026Q1,替换 region
换成同比
继承 metric/region/period,追加 value_type
那留存率呢
替换 metric,保留 region/period(若兼容)
合并代码示意:
1 2 3 4 5 6 7 8 def merge_slots (prev: dict , new_parse: dict , question: str ) -> dict : if _is_new_topic(question, prev): return new_parse merged = {**prev} for key in ("metric" , "time_period" , "region" , "value_fields" ): if new_parse.get(key): merged[key] = new_parse[key] return merged
API 契约 请求 1 2 3 4 5 6 7 8 POST /api/askContent-Type : application/json { "question": "华北区呢", "session_id": "sess_8f3a2b", "clarification_reply": null }
澄清回复 1 2 3 4 5 6 7 8 9 10 POST /api/ask { "question": "", "session_id": "sess_8f3a2b", "clarification_reply": { "field": "time_period", "value": "2026Q1" } }
响应字段
字段
说明
needs_clarification
是否等待用户补充
fallback_type
澄清类型枚举
answer
自然语言结果
sql
可审计 SQL
agent_flow
管道步骤可视化
session_id
会话 ID
前端交互要点 1 2 3 4 5 6 7 8 9 10 11 12 13 sequenceDiagram participant U as 用户 participant UI as 问数抽屉 participant API as /ask U->>UI: 销售收入多少 UI->>API: question + session_id API-->>UI: needs_clarification + 建议问法 UI-->>U: 气泡追问 + chips U->>UI: 点击「华东区 2026Q1 销售收入」 UI->>API: 完整 question API-->>UI: answer + sql + chart UI-->>U: 结果 + 可展开 SQL
澄清气泡与答案气泡样式区分 (如虚线边框)
suggested_questions 渲染为可点击 chip
歧义选项用 el-radio-group 或卡片单选
顶部显示「当前上下文:销售收入 · 华东区 · 待选周期」
日志与可观测性 每轮写入 qa_history:
1 2 3 4 5 6 7 8 9 10 11 INSERT INTO qa_history ( session_id, turn_index, user_question, needs_clarification, fallback_type, slots_snapshot, sql_text, latency_ms ) VALUES ( 'sess_8f3a2b' , 2 , '华北区呢' , 0 , NULL , '{"metric":"销售收入","period":"2026Q1","region":"华北区"}' , 'SELECT ... FROM dws_sales_metric_wide ...' , 342 );
用于分析:澄清率、平均轮次、放弃率。
本章小结
多轮对话 = 槽位状态机 + 合并规则 + 显式澄清,不是无限聊天
缺时间周期与指标歧义是最常见的两类 fallback_type
session_id + slots_json 承载上下文,指代型问法依赖合并而非重新 NLU
澄清 UI 应用建议问法降低摩擦,歧义必须用户确认
思考题
「和昨天一样」在跨天时如何处理?哪些槽位该过期?
若用户第三轮突然问「新增客户数」,应硬切换还是提示确认?
澄清轮次超过 3 次仍不完整,产品应如何兜底?
系列导航
← 返回专题首页