# SSA-Pro 四层七工具实现机制详解 > **文档版本:** v1.1 > **创建日期:** 2026-02-21 > **最后更新:** 2026-02-21(v1.1 — 新增对话层 LLM 架构说明) > **文档类型:** 架构说明 (Architecture Reference) > **目标读者:** 开发团队 > **前置文档:** `SSA-Pro 工具体系规划方案(团队讨论稿).md`(定义了"做什么"),本文档说明"怎么做" > **核心问题:** 每个工具的内部实现机制是什么?哪些依赖 LLM?哪些依赖规则引擎?哪些是纯计算?**对话层 LLM 如何保证多轮对话的连贯性?** --- ## 1. 三层架构全貌:对话层 LLM 是核心大脑 ### 1.1 架构中的三个层次 系统不是简单的"Node.js 调工具",而是三层协同: ``` ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 第一层:Node.js 编排层(交通指挥) │ │ ──────────────────────────────── │ │ 职责:意图路由 → 工具调度 → 流程控制 → Session 黑板读写 │ │ 特点:确定性、无 LLM、零 Token 成本 │ │ 类比:交通警察,决定车(工具)往哪走,但不理解乘客(用户)在说什么 │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 第二层:对话层 LLM(全局大脑) ← 这是架构的核心 │ │ ──────────────────────────── │ │ 职责:理解用户意图 → 组织工具输出 → 生成连贯自然语言回复 │ │ 特点:全局状态感知、多轮对话记忆、知识推理 │ │ 类比:统计顾问本人,读过你的数据,记得之前聊了什么,知道什么时候该问 │ │ │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 第三层:工具层(专业手脚) │ │ ──────────────────────── │ │ 职责:执行具体任务(统计计算、规则匹配、R 脚本执行) │ │ 特点:无状态函数,输入→输出,不关心对话上下文 │ │ 类比:实验室设备,按按钮出结果,但不知道实验的完整背景 │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 1.2 一次完整交互的真实流程 以用户说 **"帮我看看各组的样本分布"** 为例: ``` 用户消息: "帮我看看各组的样本分布" │ ▼ ┌─ 第一层:Node.js 编排 ──────────────────────────────────────────┐ │ IntentRouter 分类 → explore │ │ 决定调用 get_data_overview(若缓存未命中) │ └──────────────┬──────────────────────────────────────────────────┘ │ 工具调用 ▼ ┌─ 第三层:工具层 ────────────────────────────────────────────────┐ │ get_data_overview → DataProfileService(Python) 返回结构化 JSON │ │ { total_rows: 200, categorical_vars: [...], ... } │ └──────────────┬──────────────────────────────────────────────────┘ │ 结构化输出 ▼ ┌─ 第二层:对话层 LLM ────────────────────────────────────────────┐ │ │ │ System Prompt: │ │ "你是 SSA-Pro 智能统计分析助手... │ │ 用户数据全貌: {DataContext} │ │ 请基于数据摘要为用户解读数据特征。" │ │ │ │ 对话历史: │ │ [user] 上传了数据 xxx.csv │ │ [assistant] 您的数据已上传成功... │ │ [user] 帮我看看各组的样本分布 ← 当前消息 │ │ │ │ 工具输出(注入为上下文): │ │ get_data_overview 返回: { ... } │ │ │ │ → LLM 生成连贯的自然语言回复: │ │ "您的数据共 200 例,分为 treatment 组(103 例)和 │ │ control 组(97 例)。各组分布如下: │ │ - treatment 组平均年龄 54.3 岁(SD=11.2)... │ │ 需要我进一步看某个具体变量吗?" │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` **关键点:工具返回的是结构化 JSON,但用户看到的是自然语言。中间的翻译者就是对话层 LLM。** ### 1.3 对话层 LLM 的六大职责 | # | 职责 | 靠 Node.js 能做到吗? | 说明 | |---|------|:---:|------| | 1 | **自然语言生成** | ❌ | 把结构化 JSON 变成"像人说的话" | | 2 | **多轮对话记忆** | ❌ | "刚才那个变量"→ 理解指的是上一轮讨论的 BMI | | 3 | **上下文推理** | ❌ | 用户说"有没有效"→ 结合 DataContext 推理出想比较什么 | | 4 | **主动引导** | ❌ | 分析完主动问"需要做敏感性分析吗?" | | 5 | **知识融合** | ❌ | 结合统计学知识解释"为什么选 T 检验而不是 ANOVA" | | 6 | **语气和策略** | ❌ | 对医生用临床语言,对统计师用技术语言 | > **结论:Node.js 编排层绝对不够。对话层 LLM 是不可或缺的核心大脑。Node.js 管"做什么",LLM 管"怎么说"和"怎么想"。** --- ## 2. 对话层 LLM 的技术实现 ### 2.1 System Prompt 架构 对话层 LLM 有一个持久的 System Prompt,在整个会话生命周期内持续生效,并根据意图和状态动态组装: ``` ┌─ System Prompt(始终存在)────────────────────────────────────────┐ │ │ │ [基础角色](固定) │ │ 你是 SSA-Pro 智能统计分析助手,专注于临床研究统计分析。 │ │ 你了解用户上传的数据,可以回答关于数据的问题,推荐分析方法, │ │ 解读分析结果。你的风格是专业但易懂的统计顾问。 │ │ │ │ [数据全貌](上传数据后始终注入,~200 Token) │ │ 用户上传了一份数据集: │ │ {DataContext.summary — 行数/列数/缺失率/类型/结构} │ │ │ │ [PICO 分类](推断后注入,~150 Token) │ │ 根据数据特征,推断/确认的 PICO 分类如下: │ │ {DataContext.pico — population/intervention/outcome} │ │ │ │ [变量字典](逐步丰富,~50 Token/变量,裁剪后注入) │ │ 各变量的定义和角色: │ │ {DataContext.variableDictionary — 裁剪策略控制} │ │ │ │ [意图特定指令](每次请求动态切换) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ chat: 请基于统计知识和用户数据回答。不要主动建议分析。│ │ │ │ explore: 请基于数据摘要解读数据特征。可以推断 PICO。 │ │ │ │ consult: 请推荐分析方法,给出理由和前提。不要执行。 │ │ │ │ analyze: 以下是工具执行结果,请向用户说明进展。 │ │ │ │ discuss: 以下是分析结果: {结果}。请帮助用户解读。 │ │ │ │ feedback: 以下是完整 QPER 记录: {trace}。请分析问题。 │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ [工具输出](本轮工具调用的结果,动态注入) │ │ {get_data_overview / method_consult / run_step 等的结构化输出} │ │ │ │ [分析结果](discuss/feedback 时注入,~500 Token/步骤) │ │ {最近的 AnalysisRecord 摘要} │ │ │ └────────────────────────────────────────────────────────────────────┘ ┌─ 对话历史(最近 N 轮,滑动窗口)─────────────────────────────────────┐ │ [user] 上传了 clinical_trial.csv │ │ [assistant] 您的数据已上传,共 200 行 15 列... │ │ [user] 帮我看看各组的样本分布 │ │ [assistant] treatment 组 103 例,control 组 97 例... │ │ [user] BMI 的分布怎样? │ │ [assistant] BMI 均值 25.3,标准差 4.1... │ │ [user] 这两组 BMI 有差异吗?应该怎么分析? ← 当前消息 │ └──────────────────────────────────────────────────────────────────────┘ ``` ### 2.2 对话层 LLM 在每种意图中的角色 | 意图 | Node.js 编排做了什么 | 对话层 LLM 做了什么 | |------|---------------------|-------------------| | **chat** | 几乎不介入(不调工具) | **完全主导**:纯 LLM 对话,基于 DataContext + 统计知识回答 | | **explore** | 调用 get_data_overview 或 get_variable_detail | **翻译 + 解读**:把结构化 JSON 变成易懂的数据解读 | | **consult** | 调用 method_consult(决策表匹配) | **融合 + 解释**:把匹配结果变成完整的方法推荐(带理由、前提、替代) | | **analyze** | 调用 analysis_plan → run_step ×N → write_report | **进度播报 + 过渡衔接**:每步执行后生成进展说明,衔接步骤间的上下文 | | **discuss** | 提取最近的 AnalysisRecord | **深度解读**:回答"p 值说明什么""置信区间为什么宽"等统计学问题 | | **feedback** | 注入 qperTrace 到 LLM | **问题诊断**:分析完整记录,找出问题根因,建议新方案 | ### 2.3 对话连贯性保障机制 ``` ┌─ 保障机制 1:对话历史注入 ────────────────────────────────────────┐ │ │ │ 每次 LLM 调用都携带最近 N 轮对话历史(滑动窗口) │ │ → 用户说"刚才那个变量" → LLM 从历史中知道指的是 BMI │ │ → 用户说"按你说的方案执行" → LLM 从历史中知道之前推荐了 T 检验 │ │ │ │ 窗口大小:根据 Token 预算动态调整 │ │ - 简单对话:保留最近 10 轮 │ │ - 长对话(含分析结果):保留最近 5 轮 + 关键事件摘要 │ │ │ └────────────────────────────────────────────────────────────────────┘ ┌─ 保障机制 2:Session 黑板持久化 ──────────────────────────────────┐ │ │ │ 对话历史有滑动窗口(会丢失早期内容),但关键决策结果不会丢失: │ │ - PICO 确认结果 → 写入 blackboard.confirmedPico → 始终可用 │ │ - 变量角色确认 → 写入 blackboard.variableDictionary → 始终可用 │ │ - 分析方案 → 写入 blackboard.currentPlan → 始终可用 │ │ - 执行结果 → 写入 blackboard.stepResults → 始终可用 │ │ │ │ → 即使对话历史中早期的讨论被裁剪,结论仍在 Session 黑板中 │ │ → LLM 通过 DataContext 注入随时可以引用这些结论 │ │ │ └────────────────────────────────────────────────────────────────────┘ ┌─ 保障机制 3:意图切换时的上下文衔接 ──────────────────────────────┐ │ │ │ 用户从 consult 转到 analyze 时: │ │ │ │ [consult 阶段对话] │ │ user: "我想比较两组血压差异,用什么方法?" │ │ assistant: "建议独立样本 T 检验,理由是..." │ │ user: "好的,按这个方案执行" │ │ │ │ → IntentRouter 识别为 analyze │ │ → Node.js 编排层从 Session 黑板读取 consult 阶段的结论 │ │ → 对话层 LLM 收到完整上下文: │ │ System Prompt(含 DataContext + "用户已确认 T 检验方案") │ │ + 对话历史(包含 consult 阶段的讨论) │ │ + 意图特定指令("正在执行分析,请向用户说明进展") │ │ → LLM 生成衔接回复: │ │ "好的,我来按之前讨论的方案执行。分析计划如下: │ │ 步骤 1: 描述统计(两组基线特征) │ │ 步骤 2: 独立样本 T 检验(比较血压差异) │ │ 确认开始执行吗?" │ │ │ └────────────────────────────────────────────────────────────────────┘ ``` ### 2.4 对话层 LLM 的技术参数 | 参数 | 值 | 说明 | |------|---|------| | 模型 | deepseek-v3 | 全系统统一模型 | | Temperature | 0.7(对话)/ 0.3(报告生成) | 对话允许自然变化,报告追求稳定 | | System Prompt | 持久注入 | 整个会话生命周期内持续生效 | | 对话历史 | 滑动窗口(5-10 轮) | Token 预算内尽量多保留 | | DataContext | 始终注入 | 通过 Session 黑板注入,保证数据感知 | | Prompt 管理 | DB 存储 + Seed 脚本 | 方法学团队可编辑,不写死在代码中 | --- ## 3. 编排层 vs 对话层的职责划分 ### 3.1 什么事 Node.js 编排层做 | 职责 | 说明 | 举例 | |------|------|------| | **意图路由** | 决定这条消息走哪个处理路径 | "比较两组" → analyze | | **工具调度** | 决定调用哪些工具、什么顺序 | analyze → plan → run_step ×N → report | | **Session 黑板读写** | 工具输出写入黑板,工具输入从黑板读 | PICO 写入 → plan 读取 | | **Token 控制** | 注入 LLM 前裁剪上下文 | 变量字典 >20 → 只注入已确认的 | | **data_source 注入** | R 引擎调用前自动注入数据源 | OSS 预签名 URL | | **错误分类** | run_step 出错后决定重试还是报告 | "Column not found" → 可自愈 | | **流程控制** | ask_user 中断/恢复、重试计数 | MAX 2 次 → 停止重试 | ### 3.2 什么事对话层 LLM 做 | 职责 | 说明 | 举例 | |------|------|------| | **自然语言生成** | 把结构化输出变成人话 | JSON → "您的数据共 200 例..." | | **多轮对话连贯** | 理解指代、省略、上下文引用 | "刚才那个" → BMI | | **知识推理** | 基于统计学知识回答专业问题 | "为什么选 T 检验?" | | **PICO 推断** | 基于临床知识推断数据角色 | 推断 outcome 可能是 bp_change | | **方法解释** | 解释决策表的匹配结果 | "选 T 检验是因为..." | | **结果解读** | 深度解释统计结果的含义 | "p=0.03 说明..." | | **主动引导** | 在合适时机引导用户下一步 | "需要做敏感性分析吗?" | | **反思分析** | 分析 QPER 全记录找问题根因 | "样本量不足导致检验效能低" | ### 3.3 协作模型 ``` Node.js 编排层 对话层 LLM ───────────── ────────── "这条消息是 explore 意图" → "调用 get_data_overview" → "工具返回了 JSON 结果" → "把 JSON + DataContext + 对话历史 组装成 Prompt,生成自然语言解读" ← "您的数据共 200 例,分为..." "把回复返回给前端" → ─── 下一轮 ─── "这条消息是 consult 意图" → "调用 method_consult → (DecisionTable 匹配)" "匹配结果: T 检验" → "把匹配结果 + DataContext + 对话历史 组装成 Prompt,生成方法推荐 + 理由" ← "建议独立样本 T 检验,因为..." ``` **核心原则:Node.js 决定"做什么",LLM 决定"怎么说"和"怎么想"。两者缺一不可。** --- ## 4. 意图 → 编排流程 → LLM 角色映射 | 意图 | Node.js 编排流程 | 对话层 LLM 角色 | |------|-----------------|----------------| | **chat** | 几乎不介入 | **完全主导**:直接回答,DataContext 保证数据感知 | | **explore** | 调用 READ 层工具 | **翻译+解读**:JSON → 自然语言数据解读 | | **consult** | 调用 method_consult | **融合+解释**:决策表结果 → 完整方法推荐 | | **analyze** | 调用 THINK+ACT 全链路 | **播报+衔接**:步骤进展说明,上下文衔接 | | **discuss** | 提取分析结果 | **深度解读**:统计学问题的专业解答 | | **feedback** | 注入 QPER 记录 | **诊断+建议**:问题根因分析,新方案建议 | --- ## 5. 四层七工具实现机制总览 ### 5.1 四层七工具实现机制一览 | 层 | 工具 | 实现机制 | LLM 参与 | 核心引擎 | 风险/成本 | |----|------|---------|:---:|---------|----------| | **READ** | `get_data_overview` | Python 计算 + LLM 推断 | ✅ 部分 | DataProfileService (Python) | 零风险 | | **READ** | `get_variable_detail` | 纯 Python 计算 | ❌ | DataProfileService (Python) | 零风险 | | **READ** | `method_consult` | 匹配引擎 + LLM 补充 | ✅ 部分 | DecisionTableService (规则) | 零风险 | | **INTERACT** | `ask_user` | 纯系统机制 | ❌ | Node.js → 前端 → 用户 | 零风险 | | **THINK** | `analysis_plan` | 匹配引擎 + 模板填充 | ❌ | DecisionTable + FlowTemplate | 低风险 | | **ACT** | `run_step` | 纯 R 引擎执行 | ❌ | WorkflowExecutor → R Plumber | 有计算成本 | | **ACT** | `write_report` | LLM 生成 + 槽位注入 | ✅ 核心 | ReflectionService (LLM) | 有 Token 成本 | ### 5.2 辅助机制 | 机制 | 实现方式 | LLM 参与 | |------|---------|:---:| | **意图路由器** | 规则引擎优先 + LLM 兜底(混合路由) | ✅ 部分 | | **反思 — 自动重试** | 错误分类器(规则) + LLM 修正参数 | ✅ 部分 | | **反思 — 手动触发** | LLM 分析完整 QPER 记录 → 新方案 | ✅ 核心 | | **Session 黑板** | Node.js 内存缓存 | ❌ | | **Token 控制** | 代码裁剪函数 | ❌ | | **data_source 注入** | Node.js 编排层自动注入 | ❌ | --- ## 6. READ 层 — 只读,零风险 ### 6.1 get_data_overview > **实现机制:Python 统计计算 + LLM PICO 推断(两步串联)** ``` ┌──────────────────────────────────┐ session_id ──→ │ Step 1: DataProfileService │ ← 纯 Python 计算 │ (Python pandas 统计分析) │ 无 LLM │ 输出:行数/列数/缺失率/类型/分布 │ └──────────────┬───────────────────┘ │ 结构化 JSON ▼ ┌──────────────────────────────────┐ │ Step 2: LLM PICO 推断 │ ← LLM Prompt │ 输入:统计摘要 + 临床知识 │ 纯推理,无工具调用 │ 输出:population / intervention / │ │ outcome 候选 + 置信度 │ └──────────────┬───────────────────┘ │ 合并 ▼ 写入 Session 黑板 ``` **实现细节:** | 环节 | 引擎 | 说明 | |------|------|------| | 统计摘要生成 | **Python(pandas)** | 调用已有 `DataProfileService`,返回结构化 JSON | | PICO 推断 | **LLM Prompt** | 将统计摘要注入 Prompt,LLM 基于临床知识推断 PICO 分类 | | 结果缓存 | **Node.js 内存** | 写入 Session 黑板,后续工具直接读缓存 | **关键约束:** - Step 1(统计计算)和 LLM 完全无关,是确定性结果 - Step 2(PICO 推断)标记为 `inference`,必须经 `ask_user` 确认后才变为 `confirmed` - 已有实现基础:`DataProfileService` 已能生成统计摘要并缓存 --- ### 6.2 get_variable_detail > **实现机制:纯 Python 计算(零 LLM)** ``` session_id + variable_name │ ▼ ┌────────────────────────────────┐ │ DataProfileService │ ← 纯 Python 计算 │ 按需查询单列 │ 无 LLM │ 输出:分布/直方图/异常值/样本值 │ 确定性结果 └────────────────────────────────┘ ``` **实现细节:** | 环节 | 引擎 | 说明 | |------|------|------| | 单列统计分析 | **Python(pandas)** | 计算分布、异常值、直方图分箱等 | **关键约束:** - 整个工具内部没有 LLM 参与,完全是 Python 数值计算 - 是 `get_data_overview` 的按需 drill-down,解决大数据集 Token 爆炸问题 - 需要在 Python 侧新增"单列查询" API(当前 `DataProfileService` 只有全量 profile) --- ### 6.3 method_consult > **实现机制:决策表四维匹配 + LLM 推理补充(双引擎)** ``` research_question + outcome_var + predictors + DataContext │ ├──→ ┌─────────────────────────────────────────┐ │ │ 引擎 A: DecisionTableService │ ← 规则匹配引擎 │ │ 四维匹配 Goal × OutcomeType × │ 无 LLM │ │ PredictorType × Design │ 确定性结果 │ │ 输出:primaryTool + fallbackTool │ │ └──────────────┬──────────────────────────┘ │ │ 匹配结果 │ ▼ └──→ ┌─────────────────────────────────────────┐ │ 引擎 B: LLM 推理补充 │ ← LLM Prompt │ 输入:匹配结果 + DataContext + 统计知识 │ 纯推理 │ 输出: │ │ rationale(选择理由) │ │ prerequisites(前提条件) │ │ limitations(局限性) │ │ alternatives(替代方案 + 适用场景) │ └─────────────────────────────────────────┘ ``` **实现细节:** | 环节 | 引擎 | 说明 | |------|------|------| | 方法选择(选什么) | **DecisionTableService** | 四维规则匹配,JSON 驱动,确定性、可审计 | | 解释说明(为什么选) | **LLM Prompt** | 利用 LLM 统计学知识生成自然语言解释 | **关键约束:** - 核心决策(选什么方法)由规则引擎决定,不由 LLM 决定 — 确保可审计、可配置 - LLM 只负责"润色"和"补充" — 生成人类可读的理由和替代方案说明 - 决策表当前 ~10 条规则,覆盖 7 个 R 工具,JSON 驱动,方法学团队可编辑 - 未来工具扩展到 30+ 时,可在决策表前加一层 RAG Top-K 检索 - 已有实现基础:`DecisionTableService.match()` 已实现四维匹配 --- ## 7. INTERACT 层 — 人机交互,零风险 ### 7.1 ask_user > **实现机制:纯系统机制(Node.js 请求-响应模式,零 LLM)** ``` Node.js 编排层决定需要提问 │ ▼ ┌──────────────────────────────────────────────────┐ │ Step 1: Node.js 生成 ClarificationCard JSON │ ← 无 LLM │ (question + options + context) │ Node.js 代码构造 └──────────────┬───────────────────────────────────┘ │ SSE 推送 / HTTP Response ▼ ┌──────────────────────────────────────────────────┐ │ Step 2: 前端 ClarificationCard 组件渲染 │ ← 前端 React │ 用户看到选择卡片 → 点击选择 → 提交 │ └──────────────┬───────────────────────────────────┘ │ HTTP POST(用户选择结果) ▼ ┌──────────────────────────────────────────────────┐ │ Step 3: Node.js 从 Session 黑板恢复上下文 │ ← 无 LLM │ 将用户选择写入黑板 → 继续后续流程 │ └──────────────────────────────────────────────────┘ ``` **实现细节:** | 环节 | 引擎 | 说明 | |------|------|------| | 卡片生成 | **Node.js 代码** | 根据当前流程上下文构造问题和选项 | | 卡片渲染 | **前端 React** | `ClarificationCard` 组件,支持单选/多选/自由文本 | | 上下文恢复 | **Session 黑板** | 用户响应后,从黑板恢复上下文继续流程 | **关键约束:** - 工具本身完全不涉及 LLM — 它是一个"人机交互通道" - 但"什么时候问、问什么"是由 Node.js 编排层决定的 - 当前实现为"请求-响应模式":Node.js 发送卡片 → 中断等待 → 下一次 HTTP 请求恢复 - 不是 LLM Function Calling 挂起模式(那是 Phase 4+ 的预研方向) - 已有实现基础:`QueryService.generateClarificationCards()` + `ClarificationCard` 前端组件 --- ## 8. THINK 层 — 生成方案,低风险 ### 8.1 analysis_plan > **实现机制:决策表匹配 + 流程模板填充(确定性引擎,零 LLM)** ``` confirmed_question + confirmed_methods + variable_mapping + DataContext │ ▼ ┌──────────────────────────────────────────────────┐ │ Step 1: DecisionTableService.match() │ ← 规则匹配引擎 │ 四维匹配 → 命中规则 → 得到 templateId │ 无 LLM └──────────────┬───────────────────────────────────┘ │ matchResult { templateId, primaryTool, fallbackTool } ▼ ┌──────────────────────────────────────────────────┐ │ Step 2: FlowTemplateService.fill() │ ← 模板引擎 │ 选择模板 → 填充参数 → EPV 防护 → 有序步骤列表 │ 无 LLM │ 输出:[{order, tool_code, params, depends_on}] │ 确定性结果 └──────────────────────────────────────────────────┘ ``` **实现细节:** | 环节 | 引擎 | 说明 | |------|------|------| | 规则匹配 | **DecisionTableService** | 四维匹配(Goal × OutcomeType × PredictorType × Design) | | 模板填充 | **FlowTemplateService** | 从 `flow_templates.json` 加载模板,填入变量名和参数 | | EPV 防护 | **FlowTemplateService** | 检查事件数/自变量比,超限自动截断并生成免责声明 | **关键约束:** - 整个工具内部没有 LLM 参与 — 完全是规则匹配 + 模板填充的确定性计算 - 方法选择在 `method_consult` 阶段已完成并经用户确认,`analysis_plan` 只负责"编排顺序 + 填充参数" - 输出包含确定的 `tool_code` + 完整 `params`,`run_step` 只需傻瓜式执行 - 生成后展示给用户审查,用户确认后才进入 ACT 层 - 已有实现基础:`FlowTemplateService.fill()` + `decision_tables.json` + `flow_templates.json` --- ## 9. ACT 层 — 执行,有成本 ### 9.1 run_step > **实现机制:纯 R 引擎执行(傻瓜式 API 转发,零 LLM)** ``` step_definition { tool_code, params } + session_id │ ▼ ┌──────────────────────────────────────────────────┐ │ Node.js 编排层:自动注入 data_source │ ← 无 LLM │ 从 Session 黑板取 dataOssKey → 生成预签名 URL │ 系统机制 │ 注入 params.data_source = { type:'oss', oss_url } │ └──────────────┬───────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────┐ │ WorkflowExecutorService → RClientService │ │ HTTP POST 给 R Plumber API │ ← 无 LLM │ R 脚本执行统计分析 │ 纯 R 引擎计算 │ 返回 ReportBlock[] + figures + r_code │ └──────────────────────────────────────────────────┘ ``` **实现细节:** | 环节 | 引擎 | 说明 | |------|------|------| | data_source 注入 | **Node.js 编排层** | 从 Session 黑板取 `dataOssKey`,生成预签名 URL 自动注入 | | R 脚本执行 | **R Plumber API** | 接收 tool_code + params → 执行统计计算 → 返回结构化结果 | | 结果标准化 | **R Block Helpers** | R 端输出标准化为 `ReportBlock[]` 格式 | **关键约束:** - 整个工具内部没有 LLM 参与 — 纯粹的 HTTP 转发 + R 引擎执行 - 是"傻瓜式 API 转发器",不做任何方法选择决策 - `data_source` 由 Node.js 编排层自动注入,LLM 和 `analysis_plan` 全程不感知数据文件位置 - 错误处理由编排层接管(双轨反思机制) - 已有实现基础:`WorkflowExecutorService` + `RClientService` + `resolveDataSource()` 全部已实现 --- ### 9.2 write_report > **实现机制:LLM 生成叙述 + 统计量槽位注入 + Zod 校验(LLM 为核心引擎)** #### generate 模式(论文级报告) ``` step_results + DataContext │ ▼ ┌──────────────────────────────────────────────────┐ │ Step 1: 提取 Key Findings │ ← 无 LLM │ 从 stepResults 中提取 p 值、置信区间、效应量等 │ 纯代码提取 │ 生成 {{ slot }} 映射表 │ └──────────────┬───────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────┐ │ Step 2: LLM 生成叙述框架 │ ← LLM 核心 │ Prompt: "根据以下统计结果,生成论文级结论" │ 生成自然语言叙述 │ LLM 输出包含 {{ slot }} 占位符 │ │ LLM 被禁止生成任何数值(反幻觉机制) │ └──────────────┬───────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────┐ │ Step 3: jsonrepair + Zod 校验 + 槽位渲染 │ ← 无 LLM │ 校验 LLM 输出结构完整性 │ 确定性后处理 │ 用真实统计数值替换 {{ slot }} 占位符 │ │ 校验失败 → 降级到 ConclusionGeneratorService │ └──────────────────────────────────────────────────┘ ``` #### interpret 模式(结果解读) ``` user_question + step_results + DataContext │ ▼ ┌──────────────────────────────────────────────────┐ │ LLM 深度解读 │ ← LLM 核心 │ Prompt: DataContext + 分析结果 + 用户具体问题 │ 纯推理 │ 输出:针对用户问题的统计学解读 │ └──────────────────────────────────────────────────┘ ``` **实现细节:** | 环节 | 引擎 | 说明 | |------|------|------| | Key Findings 提取 | **Node.js 代码** | 从 R 输出中提取关键统计量,构建槽位映射表 | | 叙述框架生成 | **LLM (deepseek-v3)** | 生成论文级叙述,用 `{{ slot }}` 占位符替代数值 | | 输出校验 | **jsonrepair + Zod** | 修复 JSON 格式 → Schema 强校验 → 确保结构完整 | | 槽位渲染 | **Node.js 代码** | 用真实统计数值替换占位符 | | 降级兜底 | **ConclusionGeneratorService** | Zod 校验失败时,规则拼接生成基础结论 | **关键约束:** - 这是 7 个工具中 LLM 参与度最高的,LLM 是核心引擎 - 但 LLM 不是"裸奔"的:统计量通过槽位注入从 R 输出渲染,LLM 被禁止生成数值(反幻觉) - 三层防御:`jsonrepair` → `Zod Schema` → `ConclusionGeneratorService`(规则拼接兜底) - 已有实现基础:`ReflectionService` + `ConclusionGeneratorService` 全部已实现 --- ## 10. 辅助机制详解 ### 10.1 意图路由器(IntentRouterService) > **实现机制:规则引擎优先 + LLM 兜底(混合路由)** ``` 用户消息 + Session 上下文 │ ▼ ┌──────────────────────────────────────┐ │ Step 1: 规则匹配器 │ ← 无 LLM │ 关键词匹配 + 上下文状态推断 │ 确定性、零延迟 │ "执行""分析一下" → analyze │ │ "应该用什么方法" → consult │ │ "帮我看看" → explore │ │ "p 值说明什么"(有结果) → discuss │ └──────────────┬───────────────────────┘ │ ├── 命中 → 直接返回意图(不调 LLM) │ └── 未命中 ──→ ┌──────────────────────────┐ │ Step 2: LLM 轻量分类 │ ← LLM Prompt │ 仅做分类,不生成长文 │ 低 Token 成本 │ 输出:intent + confidence │ └──────────────┬───────────┘ │ ├── confidence 足够 → 返回 └── confidence 不足 → 默认 chat ``` **关键约束:** - 规则优先:明确场景走规则,零延迟、零 Token 成本 - LLM 兜底:模糊场景走轻量级 LLM 分类(仅输出 intent + confidence,不生成长文) - 默认安全:无法判断时 → chat(最安全的兜底,不触发任何流水线) ### 10.2 反思机制(编排层逻辑,非工具) #### 自动反思(静默重试) ``` run_step 返回 error │ ▼ ┌──────────────────────────────────────┐ │ 错误分类器 │ ← 无 LLM │ 读取 error_classification.json │ 规则匹配 │ R 报错关键词 → 分类 │ └──────────┬──────────┬────────────────┘ │ │ 可自愈(参数级) 不可自愈(方法级) │ │ ▼ ▼ ┌──────────────┐ ┌──────────────────────────┐ │ LLM 修正参数 │ │ 报告用户 + 建议替代方案 │ │ 注入错误日志 │ │ 不重试 │ │ + DataContext │ └──────────────────────────┘ │ → 重试(MAX 2) │ └──────────────┘ ← LLM Prompt ``` #### 手动反思(用户驱动) ``` 用户: "结果不对" / "换个方法试试" │ ▼ IntentRouter → feedback 意图 │ ▼ ┌──────────────────────────────────────┐ │ LLM 分析完整 QPER 记录 │ ← LLM 核心 │ 注入 qperTrace(滑动窗口摘要) │ 深度推理 │ 分析问题根因 → 生成新方案建议 │ └──────────────┬───────────────────────┘ │ ▼ ask_user → 新 analysis_plan → run_step → write_report ``` ### 10.3 Session 黑板(纯系统基础设施) ``` ┌─ Session Blackboard ─────────────────────────────────────┐ │ sessionId │ │ dataOverview ← get_data_overview 写入 │ │ confirmedPico ← ask_user 确认后写入 │ │ variableDictionary ← 对话中逐步丰富 │ │ dataOssKey ← 上传时写入(run_step 自动注入用) │ │ currentPlan ← analysis_plan 写入 │ │ stepResults ← run_step 每步追加 │ │ report ← write_report 写入 │ │ qperTrace ← 所有工具调用记录(反思用) │ └──────────────────────────────────────────────────────────┘ 实现:纯 Node.js 内存缓存,无 LLM 参与 Token 控制:注入 LLM 前的裁剪函数(纯代码逻辑) ``` --- ## 11. LLM 在架构中的准确定位 ### 11.1 三层分类 ``` ┌─────────────────────────────────────────────────────────────┐ │ 完全不用 LLM 的 │ │ │ │ get_variable_detail — 纯 Python 计算 │ │ ask_user — 纯系统机制(Node.js → 前端 → 用户) │ │ analysis_plan — 纯规则匹配 + 模板填充 │ │ run_step — 纯 R 引擎执行 │ │ Session 黑板 / Token 控制 / data_source 注入 │ │ │ │ → 这些是"管道和基础设施",确定性、可审计、零 Token 成本 │ ├─────────────────────────────────────────────────────────────┤ │ 部分使用 LLM 的 │ │ │ │ get_data_overview — Python 计算(主) + LLM PICO 推断(辅) │ │ method_consult — 决策表匹配(选什么) + LLM(解释为什么选) │ │ 意图路由器 — 规则优先(确定性) + LLM 兜底(模糊场景) │ │ 反思-自动重试 — 规则分类错误 + LLM 修正参数 │ │ │ │ → 这些是"引擎 + LLM 增强",核心决策不依赖 LLM │ ├─────────────────────────────────────────────────────────────┤ │ LLM 为核心的 │ │ │ │ write_report(generate) — LLM 生成叙述(但槽位注入防幻觉) │ │ write_report(interpret) — LLM 深度解读用户问题 │ │ 反思-手动触发 — LLM 分析完整 QPER 记录 │ │ chat / explore 对话 — LLM(DataContext) 直接回复 │ │ │ │ → 这些是"LLM 的主场",充分发挥 LLM 知识和推理能力 │ └─────────────────────────────────────────────────────────────┘ ``` ### 11.2 LLM 调用点清单 | 调用点 | LLM 角色 | 模型 | 防护机制 | |--------|---------|------|---------| | PICO 推断 | 临床知识推理 | deepseek-v3 | ask_user 人工确认 | | method_consult 补充 | 统计学知识解释 | deepseek-v3 | 决策表为主,LLM 只补充说明 | | 意图路由 LLM 兜底 | 意图分类 | deepseek-v3 | 规则优先,LLM 只处理模糊场景 | | write_report(generate) | 论文级叙述生成 | deepseek-v3 | 槽位注入 + Zod 校验 + 规则拼接兜底 | | write_report(interpret) | 结果深度解读 | deepseek-v3 | DataContext 约束回答范围 | | 反思-自动修正 | 参数级错误修复 | deepseek-v3 | MAX 2 次重试限制 | | 反思-手动分析 | QPER 全记录分析 | deepseek-v3 | ask_user 确认新方案 | | chat/explore 对话 | 自由知识对话 | deepseek-v3 | DataContext 注入提供约束 | --- ## 12. 架构哲学总结 ### 12.1 核心协作模型 ``` ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Node.js 编排层(交通指挥) │ │ ┌────────────────────────┐ │ │ │ 意图路由 → 工具调度 │ │ │ │ Session 黑板读写 │ │ │ │ Token 控制 + 流程控制 │ │ │ └──┬──────────────┬──────┘ │ │ │ │ │ │ ┌──────────┘ └──────────┐ │ │ ▼ ▼ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ 工具层(手脚) │ │ 用户 │ │ │ │ │ 结构化输出 │ │ │ │ │ Python 计算 │ ──────────────→ │ ask_user │ │ │ │ DecisionTable │ │ PICO 确认 │ │ │ │ FlowTemplate │ │ 方案审查 │ │ │ │ R 引擎 │ │ 反思决策 │ │ │ │ ErrorClassify │ └────────────────┘ │ │ └────────┬───────┘ │ │ │ 结构化输出 │ │ ▼ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 对话层 LLM(全局大脑) │ │ │ │ │ │ │ │ System Prompt(角色 + DataContext + 意图指令) │ │ │ │ + 对话历史(滑动窗口) │ │ │ │ + 工具输出(结构化 JSON) │ │ │ │ │ │ │ │ → 自然语言生成 → 多轮对话连贯 │ │ │ │ → 知识推理 → 主动引导 │ │ │ │ → PICO 推断 → 结果解读 │ │ │ │ → 方法解释 → 反思分析 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 自然语言回复 → 前端展示 │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 12.2 一句话总结 > **确定性的事交给规则引擎**(决策表、模板、R 脚本、Python 计算) > **需要知识和推理的事交给对话层 LLM**(理解意图、组织回复、知识推理、结果解读) > **需要判断和确认的事交给用户**(PICO 确认、方法选择、方案审查) > **三者通过 Node.js 编排层和 Session 黑板协同工作** ### 12.3 角色定位 | 层 | 角色类比 | 核心职责 | Token 成本 | |----|---------|---------|-----------| | **Node.js 编排层** | 交通指挥 | 决定做什么、走哪条路 | 零 | | **对话层 LLM** | 统计顾问本人 | 理解、推理、表达、引导 | 有(核心开销) | | **工具层** | 实验室设备 | 计算、匹配、执行 | 零(除 write_report) | | **用户** | 最终决策者 | 确认、修正、决定方向 | 零 | **对话层 LLM 是系统的大脑和嘴巴** — 它理解用户在说什么,知道该怎么回答,把工具的结构化输出变成人类能理解的对话。没有它,系统只是一堆互不关联的 API 调用。 --- --- **文档维护者:** SSA 架构团队 **创建日期:** 2026-02-21 **最后更新:** 2026-02-21(v1.1 — 新增对话层 LLM 架构说明,明确三层架构模型) **关联文档:** - `SSA-Pro 工具体系规划方案(团队讨论稿).md` — 工具定义(做什么) - `SSA-Pro 意图识别与对话架构设计.md` — 意图路由 + System Prompt 策略 - `04-开发计划/11-智能对话与工具体系开发计划.md` — 开发落地计划 ### 变更日志 | 版本 | 日期 | 变更内容 | |------|------|---------| | v1.0 | 2026-02-21 | 初版:四层七工具的内部实现机制 | | v1.1 | 2026-02-21 | **新增对话层 LLM 架构**:① 明确三层架构(编排层 + 对话层 LLM + 工具层);② 新增 System Prompt 架构(基础角色 + DataContext 注入 + 意图指令 + 对话历史);③ 新增对话连贯性三大保障机制(对话历史注入 + Session 黑板持久化 + 意图切换上下文衔接);④ 新增编排层 vs 对话层职责划分表 |