Architecture Design: - Add intent recognition and dialogue architecture design (Intent Router + DataContext) - Add tool system planning (4-layer 7-tool fusion solution: READ/INTERACT/THINK/ACT) - Add 4-layer 7-tool implementation mechanism details (Conversation Layer LLM + Node.js orchestration) Development Plan (v1.2): - Create 6-phase development plan (134h/22 days) for intelligent dialogue system - Add 8 architectural constraints (C1-C8): no Function Calling, Postgres-Only cache, streaming output, context guard, Zod dynamic validation - Correct Session Blackboard to use CacheFactory (Postgres-Only, no Redis) Status Updates: - Update SSA module status: QPER complete + dialogue architecture design complete - Update system-level status: add SSA architecture design milestone Other: - R tools minor fixes (chi_square, correlation, logistic_binary, mann_whitney, t_test_paired) - Frontend AIA chat workspace style adjustment Co-authored-by: Cursor <cursoragent@cursor.com>
27 KiB
27 KiB
SSA-Pro 意图识别与对话架构设计
文档版本: v1.0
创建日期: 2026-02-21
文档类型: 架构设计 (Architecture Design)
核心理念: 从"统计参数提取器"升级为"数据感知的智能对话系统"
前置文档: QPER 架构设计方案 V4、理想状态与智能化愿景设计
1. 问题诊断
1.1 当前架构的根本缺陷
当前 QPER 架构中,Q 层(QueryService)的本质是统计分析参数提取器,不是真正的用户意图识别:
当前流程:
用户消息 → QueryService(强制提取 goal/y/x/design)→ Planner → Execute → Reflection
它假设用户发出的每一条消息都是一个分析请求,试图从中提取四维参数(分析目标、结局变量、预测变量、研究设计)。
这导致了三个核心问题:
| # | 问题 | 表现 |
|---|---|---|
| 1 | 无法自由对话 | 用户说"这个数据有什么特点"→ 系统强行匹配到 descriptive,跑描述统计 |
| 2 | 跳过需求梳理 | 用户说"BMI 和血压有关系吗"→ 直接跑相关分析,但用户可能只是在咨询 |
| 3 | LLM 能力浪费 | 只用 LLM 做参数提取,完全没利用 LLM 的知识库和推理能力 |
1.2 用户真实旅程 vs 系统假设
系统假设的用户旅程:
上传数据 → 说出分析需求 → 系统执行 → 查看结果
医生的真实旅程:
上传数据 → 理解数据全貌 → 与AI讨论探索 → 确定分析方案 → 执行分析 → 解读结果
↑ ↑ ↑
当前系统完全跳过了这三个关键阶段
1.3 一句话总结
当前系统只有"接单"能力,没有"对话"能力。QPER 是一个优秀的分析流水线,但它不应该是用户的唯一入口。
2. 设计目标
2.1 核心目标
将 SSA 从"统计分析执行器"升级为"数据感知的统计顾问":
- 用户上传数据后,可以自由与系统对话,了解数据、讨论方案
- 系统基于数据全貌回答问题,像一个了解你数据的统计专家
- 只有当用户明确表达分析意图时,才触发 QPER 流水线
- 分析完成后,用户可以继续讨论结果,获得深入解读
2.2 设计原则
| 原则 | 含义 |
|---|---|
| 对话优先 | 默认是自由对话,分析执行是对话中的特殊行为 |
| 数据感知 | 所有 LLM 调用都携带数据全貌上下文 |
| 渐进深入 | 从数据概览 → PICO 分类 → 变量字典,逐步丰富理解 |
| QPER 不变 | QPER 仍是核心分析引擎,但只在 analyze 意图时触发 |
| 用户主导 | 系统建议而非替用户决定,分析方案需用户确认 |
3. 总体架构
3.1 两层意图 + 数据全貌上下文
┌─────────────────────────────────┐
│ 数据全貌(DataContext) │
│ 持久存在于整个会话生命周期中 │
│ 在对话过程中逐步丰富 │
└──────────┬──────────────────────┘
│
│ 注入
▼
用户消息 ──→ 【意图路由器 Intent Router】──────────────────────────────────
│
├── 💬 自由对话(Chat)
│ LLM(DataContext) → 直接回复
│
├── 🔍 数据探索(Explore)
│ DataProfile + LLM(DataContext) → 直接回复
│
├── 📋 分析咨询(Consult)
│ LLM(DataContext + 统计知识) → 建议方案
│
├── 🎯 分析执行(Analyze)
│ → 进入 QPER 流水线(Q → P → E → R)
│
└── 📊 结果讨论(Discuss)
LLM(DataContext + 分析结果) → 解读回复
3.2 和现有架构的关系
现有 QPER(保持不变)
┌─────────────────────────┐
用户消息 → [Intent Router] → │ Q → P → E → R │
│ │ └─────────────────────────┘
│ │ ↑ 仅 analyze 意图触发
│ │
│ ├── chat → LLM(DataContext) → 回复
│ ├── explore → DataProfile/LLM → 回复
│ ├── consult → LLM(DataContext + 统计知识) → 建议
│ └── discuss → LLM(DataContext + 结果) → 解读
│
└── DataContext(数据全貌,贯穿会话)
关键:不是推翻 QPER,而是在 QPER 前面加一层路由。QPER 仍然是核心分析执行引擎,但它只在用户有明确分析意图时被触发。
4. 数据全貌(DataContext)
4.1 为什么需要数据全貌
当前系统中,DataProfile 只在 Q 层被使用,用于辅助 LLM 提取分析参数。但数据全貌的价值远不止于此 —— 它应该是贯穿整个会话的持久化上下文,让 LLM 在每一次回复中都"了解"用户的数据。
4.2 三层数据全貌模型
DataContext {
┌─────────────────────────────────────────────────────────┐
│ Layer 1: 统计摘要(Statistical Summary) │
│ 来源:DataProfileService 自动生成 │
│ 时机:数据上传后立即生成 │
│ │
│ - totalRows: 200 │
│ - totalCols: 15 │
│ - missingRate: { overall: 3.2%, perColumn: {...} } │
│ - categoricalVars: ["group", "gender", "smoking"] │
│ - continuousVars: ["age", "bmi", "bp_baseline", ...] │
│ - dataStructure: "cross-sectional" │
│ - idLikeVars: ["patient_id"] │
└─────────────────────┬───────────────────────────────────┘
│ 自动
▼
┌─────────────────────────────────────────────────────────┐
│ Layer 2: PICO 分类(Clinical Context) │
│ 来源:LLM 推断 + 用户确认/修正 │
│ 时机:数据探索阶段,LLM 主动推断或用户询问时 │
│ │
│ - population: "高血压患者,年龄 40-70 岁" │
│ - intervention: { │
│ var: "group", │
│ levels: ["treatment", "control"], │
│ description: "新型降压药 vs 安慰剂" │
│ } │
│ - comparator: "安慰剂对照" │
│ - outcomes: [ │
│ { var: "bp_change", type: "continuous", │
│ role: "primary", unit: "mmHg" } │
│ ] │
└─────────────────────┬───────────────────────────────────┘
│ LLM 推断 + 用户确认
▼
┌─────────────────────────────────────────────────────────┐
│ Layer 3: 变量字典(Variable Dictionary) │
│ 来源:LLM 推断 + 用户逐步修正 │
│ 时机:对话过程中逐步丰富 │
│ │
│ - { name: "age", label: "年龄", │
│ type: "continuous", role: "baseline", │
│ unit: "years", description: "入组时年龄" } │
│ - { name: "group", label: "分组", │
│ type: "categorical", role: "intervention", │
│ levels: ["treatment", "control"] } │
│ - { name: "bp_change", label: "血压变化", │
│ type: "continuous", role: "outcome", │
│ unit: "mmHg", description: "治疗后-治疗前" } │
└─────────────────────────────────────────────────────────┘
4.3 数据全貌的生命周期
时间线:
─────────────────────────────────────────────────────────────→
│ │ │ │
上传数据 对话探索 分析执行 结果讨论
│ │ │ │
Layer 1 生成 Layer 2 推断 QPER 使用 解读引用
(自动) Layer 3 丰富 DataContext DataContext
(LLM+用户) (参数更准确) (结论更准确)
4.4 DataContext 接口定义
interface DataContext {
sessionId: string;
uploadedAt: string;
// Layer 1: 自动生成
summary: {
totalRows: number;
totalCols: number;
missingOverview: { overall: number; perColumn: Record<string, number> };
categoricalVars: string[];
continuousVars: string[];
idLikeVars: string[];
dataStructure: 'cross-sectional' | 'longitudinal' | 'repeated-measures' | 'unknown';
sampleSizeWarning?: string;
};
// Layer 2: LLM 推断 + 用户确认
pico: {
population: string | null;
intervention: {
var: string | null;
levels: string[];
description: string | null;
};
comparator: string | null;
outcomes: Array<{
var: string;
type: 'continuous' | 'categorical' | 'time-to-event';
role: 'primary' | 'secondary';
unit?: string;
}>;
confirmed: boolean; // 用户是否已确认
};
// Layer 3: 变量字典
variableDictionary: Array<{
name: string; // 原始列名
label: string; // 中文标签(LLM 推断或用户提供)
type: 'continuous' | 'categorical' | 'ordinal' | 'datetime' | 'id';
role: 'baseline' | 'intervention' | 'outcome' | 'covariate' | 'id' | 'unknown';
unit?: string;
levels?: string[]; // 分类变量的水平
description?: string; // 变量含义
confirmed: boolean; // 用户是否已确认该条目
}>;
// 元数据
lastUpdated: string;
enrichmentHistory: Array<{
timestamp: string;
layer: 1 | 2 | 3;
source: 'auto' | 'llm' | 'user';
description: string;
}>;
}
5. 意图路由器(Intent Router)
5.1 设计理念
意图路由器不是提取分析参数,而是判断用户当前想做什么。它是一个轻量级 LLM 调用(或规则匹配),输出一个意图分类。
5.2 五种意图类型
| 意图 | 触发示例 | 系统行为 | LLM 上下文 |
|---|---|---|---|
| chat | "这个数据有多少样本?" "BMI 一般正常范围是多少?" "临床试验中 P 值多少算显著?" |
直接 LLM 对话 | DataContext |
| explore | "帮我看看各组的样本分布" "哪些变量缺失比较严重?" "能推断出结局指标是哪些吗?" |
DataProfile 增强探索 | DataContext + 统计摘要 |
| consult | "我想比较两组差异,应该用什么方法?" "这个数据适合做什么分析?" "帮我制定一个分析计划" |
LLM 给出分析建议(不执行) | DataContext + 统计知识 |
| analyze | "对 BMI 和血压做相关分析" "比较治疗组和对照组的血压差异" "执行之前建议的分析方案" |
进入 QPER 流水线 | DataContext → Q 层 |
| discuss | "这个 p 值说明什么?" "结果和我预期不一样,为什么?" "需要做哪些敏感性分析?" |
LLM 结果解读 | DataContext + 分析结果 |
5.3 意图识别策略
意图路由器的判断策略(优先级从高到低):
1. 显式触发词
- "执行" "运行" "分析一下" "开始分析" → analyze
- "为什么" "说明什么" "怎么解读" (且有最近结果) → discuss
- "应该用什么方法" "建议" "计划" "方案" → consult
- "帮我看看" "分布" "缺失" "概况" → explore
2. 上下文推断
- 刚上传数据、尚无对话 → 倾向 explore / chat
- 已有分析结果、围绕结果提问 → 倾向 discuss
- 讨论过方法选择、确认执行 → 倾向 analyze
3. 默认兜底
- 无法判断 → chat(最安全的默认值)
- chat 意味着 LLM 携带 DataContext 直接回复,不触发任何流水线
5.4 Intent Router 接口
interface IntentRouterInput {
userMessage: string;
sessionId: string;
dataContext: DataContext | null; // 可能尚未上传数据
recentAnalysisResult: AnalysisRecord | null; // 最近的分析结果(用于判断 discuss)
conversationHistory: Message[]; // 最近 N 条对话(用于上下文推断)
}
interface IntentRouterOutput {
intent: 'chat' | 'explore' | 'consult' | 'analyze' | 'discuss';
confidence: number;
reasoning: string; // LLM 给出的判断理由(用于调试)
suggestedResponse?: string; // chat/explore/consult/discuss 模式下的直接回复
}
5.5 关键设计决策:LLM 路由 vs 规则路由
| 方案 | 优势 | 劣势 |
|---|---|---|
| 纯规则路由 | 快速、确定性强、无 LLM 成本 | 覆盖面有限,边界情况多 |
| 纯 LLM 路由 | 理解力强,边界情况少 | 多一次 LLM 调用,增加延迟和成本 |
| 混合路由(推荐) | 快速覆盖明确场景 + LLM 兜底模糊场景 | 实现略复杂 |
推荐方案:混合路由
- 先走规则匹配(关键词 + 上下文状态)
- 规则无法判断时,调用轻量级 LLM(仅分类,不生成长文)
- LLM 路由可与后续处理并行,减少感知延迟
6. 各意图的处理流程
6.1 Chat — 自由对话
用户: "BMI 在临床研究中一般怎么分类?"
系统处理:
1. Intent Router → chat
2. 构建 LLM Prompt:
- System: "你是一个临床统计顾问。用户上传了一份数据,以下是数据全貌..."
- System: {DataContext 的 JSON 摘要}
- User: "BMI 在临床研究中一般怎么分类?"
3. LLM 直接回复(利用自身知识库)
4. 回复展示在对话区
特点:
- 不触发任何分析流水线
- LLM 带着数据上下文回答,回复更有针对性
- 类似于和一个"读过你数据"的统计专家对话
6.2 Explore — 数据探索
用户: "帮我看看这个数据的缺失情况"
系统处理:
1. Intent Router → explore
2. 从 DataContext.summary 提取缺失率信息
3. 构建 LLM Prompt:
- System: "基于以下数据质量摘要,为用户解读缺失情况"
- System: {缺失率数据}
- User: "帮我看看这个数据的缺失情况"
4. LLM 生成结构化解读(哪些变量缺失严重、是否影响分析、建议处理方式)
5. 可选:生成缺失率可视化(调用 R 引擎)
进阶场景:
用户: "你觉得结局指标可能是哪些?"
→ LLM 基于 DataContext + 临床知识推断 PICO 分类
→ 生成推断结果,等待用户确认
→ 用户确认后更新 DataContext.pico
6.3 Consult — 分析咨询
用户: "我想比较治疗组和对照组的血压差异,应该用什么方法?"
系统处理:
1. Intent Router → consult
2. 构建 LLM Prompt:
- System: "你是统计方法学顾问。基于以下数据全貌,为用户推荐分析方法"
- System: {DataContext}
- System: "可用的分析方法列表: {tools_registry 摘要}"
- User: "我想比较治疗组和对照组的血压差异,应该用什么方法?"
3. LLM 回复:
- 推荐方法(如"建议使用独立样本 T 检验")
- 选择理由("因为结局变量是连续型,两组独立...")
- 前提条件("需要满足正态性和方差齐性")
- 替代方案("如果不满足正态性,可以使用 Wilcoxon 秩和检验")
4. 用户可以继续追问,或说"好,按这个方案执行"
关键区别:
- consult 只给建议,不执行
- 用户确认后再转为 analyze 意图
- 这让用户在执行前充分理解"为什么这样做"
6.4 Analyze — 分析执行(当前 QPER)
用户: "好的,按你说的方案,对血压做 T 检验"
或: "对 BMI 和 bp_change 做相关分析"
系统处理:
1. Intent Router → analyze
2. 进入 QPER 流水线:Q → P → E → R
3. 但此时 QPER 的 Q 层更轻松:
- DataContext 中已有 PICO 分类和变量字典
- consult 阶段已经讨论过方法选择
- Q 层只需确认参数,不需要"猜测"
提升点:
- Q 层可以直接从 DataContext.variableDictionary 获取变量类型和角色
- P 层的决策表匹配更准确(因为变量角色已确认)
- 用户的分析预期更明确(因为经过了 consult 阶段的讨论)
6.5 Discuss — 结果讨论
用户: "p 值 0.03 说明什么?为什么置信区间这么宽?"
系统处理:
1. Intent Router → discuss(检测到最近有分析结果)
2. 构建 LLM Prompt:
- System: "你是统计结果解读专家。以下是用户的数据全貌和最近的分析结果"
- System: {DataContext}
- System: {最近的 AnalysisRecord — 包含步骤结果和结论}
- User: "p 值 0.03 说明什么?为什么置信区间这么宽?"
3. LLM 深入解读结果
4. 可以建议后续分析("建议做一下亚组分析")
特点:
- 不重新跑分析,只是讨论已有结果
- LLM 带着完整的数据和结果上下文回答
- 可以引导用户进入下一轮分析(discuss → consult → analyze)
7. 对话状态机
7.1 用户旅程状态流转
┌──────────┐
┌────────→│ Chat │←───────┐
│ └────┬─────┘ │
│ │ │
│ ▼ │
┌────┴─────┐ ┌────────────┐ │
上传数据 ────────→ │ Explore │──→│ Consult │─────┤
└────┬─────┘ └────┬───────┘ │
│ │ │
│ ▼ │
│ ┌────────────┐ │
│ │ Analyze │ │
│ │ (QPER) │ │
│ └────┬───────┘ │
│ │ │
│ ▼ │
│ ┌────────────┐ │
└────────→│ Discuss │─────┘
└────────────┘
说明:
- 任何状态都可以跳转到 Chat(用户随时可以自由提问)
- Explore → Consult → Analyze 是推荐的"主线",但不强制
- Discuss 之后可以回到任何状态(继续探索、咨询新方案、执行新分析)
- 状态之间没有硬性约束,用户完全自主决定对话方向
7.2 数据全貌的渐进丰富
阶段 1 — 上传完成:
DataContext.summary ← DataProfileService 自动填充
阶段 2 — 探索对话中:
DataContext.pico ← LLM 推断,用户确认
DataContext.variableDictionary ← LLM 推断,用户逐步修正
阶段 3 — 分析执行时:
QPER 的 Q 层直接引用 DataContext(参数提取更准确)
阶段 4 — 结果讨论时:
LLM 引用 DataContext + 分析结果(解读更精准)
8. LLM Prompt 策略
8.1 System Prompt 模板
所有意图类型共享一个基础 System Prompt,差异在于注入的上下文片段:
[基础角色]
你是 SSA-Pro 智能统计分析助手,专注于临床研究统计分析。
你了解用户上传的数据,可以回答关于数据的问题,推荐分析方法,解读分析结果。
[数据全貌 — 始终注入]
用户上传了一份数据集,以下是数据全貌:
{DataContext.summary 的结构化摘要}
[PICO 分类 — 如果已推断]
根据数据特征,推断的 PICO 分类如下:
{DataContext.pico}
[变量字典 — 如果已丰富]
各变量的定义和角色:
{DataContext.variableDictionary}
[意图特定指令 — 按路由结果注入]
- chat: "请基于你的统计知识和用户数据回答问题。不要主动建议分析。"
- explore: "请基于数据摘要为用户解读数据特征。可以推断 PICO 分类。"
- consult: "请推荐合适的分析方法,给出理由和前提条件,不要执行分析。"
- discuss: "以下是最近的分析结果:{结果}。请帮助用户解读。"
[最近分析结果 — discuss 意图时注入]
{AnalysisRecord 摘要}
8.2 Token 成本控制
DataContext 注入 LLM 会增加 token 消耗,需要控制:
| 层 | 预估 Token | 控制策略 |
|---|---|---|
| Layer 1 摘要 | ~200 | 始终注入,成本低 |
| Layer 2 PICO | ~150 | 已推断时注入 |
| Layer 3 变量字典 | ~50/变量 × N | 仅注入相关变量(裁剪策略) |
| 分析结果 | ~500/步骤 | discuss 时注入,限制摘要长度 |
变量字典裁剪策略:
- 变量数 ≤ 20:全部注入
- 变量数 > 20:只注入 confirmed + role ≠ unknown 的变量
- 极端情况(>50 列):只注入 PICO 相关变量
9. 和现有代码的映射
9.1 需要新增的组件
| 组件 | 位置 | 职责 |
|---|---|---|
| IntentRouterService | backend/src/modules/ssa/services/ |
意图分类(规则 + LLM) |
| DataContextService | backend/src/modules/ssa/services/ |
DataContext 生命周期管理 |
| ChatService | backend/src/modules/ssa/services/ |
chat/explore/consult/discuss 的 LLM 对话 |
| DataContext 存储 | prisma schema 或内存 |
按 sessionId 持久化 DataContext |
9.2 需要改造的组件
| 组件 | 当前职责 | 改造方向 |
|---|---|---|
| QueryService | 意图解析 + 参数提取 | 仅保留参数提取,意图分类移到 IntentRouter |
| workflow.routes | /api/ssa/workflow/plan 直接调 QueryService |
新增路由入口,先走 IntentRouter |
| DataProfileService | 生成统计摘要 | 输出同时写入 DataContext.summary |
| 前端 SSAChatPane | 消息 → 调用 workflow API | 消息 → 调用 IntentRouter API → 按意图分发 |
9.3 保持不变的组件
| 组件 | 原因 |
|---|---|
| DecisionTableService | P 层逻辑不变 |
| FlowTemplateService | P 层逻辑不变 |
| WorkflowExecutorService | E 层逻辑不变 |
| ReflectionService | R 层逻辑不变 |
| RClientService | R 引擎调用不变 |
| 所有 R 工具脚本 | R 层不变 |
10. 实施路线建议
Phase 1: DataContext 基础(建议优先)
- 扩展 DataProfileService,上传后自动生成 DataContext.summary
- DataContext 按 sessionId 内存缓存(后续可持久化)
- 前端上传完成后展示数据概览卡片
Phase 2: Intent Router
- 实现 IntentRouterService(先纯规则,后加 LLM 兜底)
- 新增 API 入口
/api/ssa/chat,前端消息统一走此入口 - analyze 意图 → 转发到现有 QPER 流水线
- 其他意图 → ChatService 处理
Phase 3: 数据感知对话
- ChatService 实现 chat / explore / consult / discuss 四种模式
- LLM 调用时注入 DataContext
- 前端对话区支持非分析类回复(纯文本,无卡片)
Phase 4: PICO 推断与变量字典
- explore 对话中 LLM 推断 PICO 分类
- 前端展示 PICO 确认面板
- 变量字典逐步丰富机制
- DataContext 反哺 QPER Q 层
11. 预期效果对比
| 维度 | 当前系统 | 新架构 |
|---|---|---|
| 用户上传数据后 | 等待用户发出分析指令 | 自动展示数据概览,邀请探索 |
| 用户问"这个数据有什么特点" | 强行匹配 descriptive goal | 进入 explore 模式,LLM 解读数据 |
| 用户问"应该做什么分析" | 强行猜测分析类型 | 进入 consult 模式,给出方法建议 |
| 用户确认"按这个方案做" | N/A | 进入 analyze → QPER 执行 |
| 分析完成后用户追问 | 无法处理 | 进入 discuss 模式,解读结果 |
| LLM 利用率 | 仅参数提取 | 全流程知识对话 |
| 数据理解深度 | 仅列名 + 类型 | PICO + 变量字典 + 临床含义 |
| 多轮对话能力 | 无(每条消息独立处理) | 完整对话上下文 + 数据全貌 |
文档版本: v1.0
创建日期: 2026-02-21
下一步: 评审确认后制定详细开发计划