单轮问数的局限

用户很少一次把问题说全。典型不完整问法:

用户输入 缺失信息 系统应有行为
销售收入多少 时间周期、区域 追问周期,默认全国
华东区留存率 时间周期、指标确认 追问 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, score
FROM (
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/ask
Content-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 应用建议问法降低摩擦,歧义必须用户确认

思考题

  1. 「和昨天一样」在跨天时如何处理?哪些槽位该过期?
  2. 若用户第三轮突然问「新增客户数」,应硬切换还是提示确认?
  3. 澄清轮次超过 3 次仍不完整,产品应如何兜底?

系列导航

主题
01 架构全景
02 Text-to-SQL
03 RAG 指标库
04 本篇
05 安全校验
06 Vue3 工作台
07 演示验证
08 反馈闭环

← 返回专题首页