feat(ssa): Complete Phase I-IV intelligent dialogue and tool system development

Phase I - Session Blackboard + READ Layer:
- SessionBlackboardService with Postgres-Only cache
- DataProfileService for data overview generation
- PicoInferenceService for LLM-driven PICO extraction
- Frontend DataContextCard and VariableDictionaryPanel
- E2E tests: 31/31 passed

Phase II - Conversation Layer LLM + Intent Router:
- ConversationService with SSE streaming
- IntentRouterService (rule-first + LLM fallback, 6 intents)
- SystemPromptService with 6-segment dynamic assembly
- TokenTruncationService for context management
- ChatHandlerService as unified chat entry
- Frontend SSAChatPane and useSSAChat hook
- E2E tests: 38/38 passed

Phase III - Method Consultation + AskUser Standardization:
- ToolRegistryService with Repository Pattern
- MethodConsultService with DecisionTable + LLM enhancement
- AskUserService with global interrupt handling
- Frontend AskUserCard component
- E2E tests: 13/13 passed

Phase IV - Dialogue-Driven Analysis + QPER Integration:
- ToolOrchestratorService (plan/execute/report)
- analysis_plan SSE event for WorkflowPlan transmission
- Dual-channel confirmation (ask_user card + workspace button)
- PICO as optional hint for LLM parsing
- E2E tests: 25/25 passed

R Statistics Service:
- 5 new R tools: anova_one, baseline_table, fisher, linear_reg, wilcoxon
- Enhanced guardrails and block helpers
- Comprehensive test suite (run_all_tools_test.js)

Documentation:
- Updated system status document (v5.9)
- Updated SSA module status and development plan (v1.8)

Total E2E: 107/107 passed (Phase I: 31, Phase II: 38, Phase III: 13, Phase IV: 25)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-22 18:53:39 +08:00
parent bf10dec4c8
commit 3446909ff7
68 changed files with 11583 additions and 412 deletions

View File

@@ -0,0 +1,277 @@
/**
* SSA Phase II Prompt Seed 脚本
*
* 写入 7 个 Prompt 模板到 capability_schema.prompt_templates:
* 1. SSA_BASE_SYSTEM — 固定角色定义
* 2. SSA_INTENT_CHAT — chat 意图指令
* 3. SSA_INTENT_EXPLORE — explore 意图指令
* 4. SSA_INTENT_CONSULT — consult 意图指令
* 5. SSA_INTENT_ANALYZE — analyze 意图指令
* 6. SSA_INTENT_DISCUSS — discuss 意图指令
* 7. SSA_INTENT_FEEDBACK — feedback 意图指令
*
* 运行: npx tsx scripts/seed-ssa-phase2-prompts.ts
*/
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
interface PromptDef {
code: string;
name: string;
description: string;
variables: string[];
content: string;
modelConfig: Record<string, any>;
}
const PROMPTS: PromptDef[] = [
{
code: 'SSA_BASE_SYSTEM',
name: 'SSA 基础角色定义',
description: 'Phase II — 对话层 LLM 的固定角色 System Prompt始终作为 [1] 段注入',
variables: [],
content: `你是 SSA-Pro 智能统计分析助手,专注于临床研究统计分析领域。
## 你的身份
你是一位经验丰富的生物统计顾问,服务于临床研究人员和医学院师生。你不仅能执行统计分析,更重要的是帮助用户理解数据、选择方法、解读结果。
## 核心能力
1. **数据理解** — 解读数据结构、变量类型、缺失模式、异常值和分布特征
2. **方法推荐** — 根据研究设计和数据特征推荐合适的统计方法,说明前提条件和替代方案
3. **结果解读** — 用通俗易懂的语言解释 p 值、置信区间、效应量等统计概念
4. **PICO 识别** — 识别研究的人群、干预、对照和结局变量
## 沟通原则
- 使用中文回复
- 语言专业但不晦涩,避免不必要的术语堆砌
- 分点作答,条理清晰
- 对不确定的内容如实说明,不编造数据或结论
- 回复简洁聚焦,不要过度发散
- 当用户的问题涉及其数据时,优先引用数据上下文中的实际信息`,
modelConfig: { model: 'deepseek-v3', temperature: 0.7, maxTokens: 2000 },
},
{
code: 'SSA_INTENT_CHAT',
name: 'SSA chat 意图指令',
description: 'Phase II — chat 意图的指令段,注入 System Prompt [6] 位置',
variables: [],
content: `## 当前任务:自由对话
用户正在与你进行普通对话(可能是统计学问题、数据理解问题、或闲聊)。
规则:
1. 基于统计知识和上方的数据上下文(如有)直接回答
2. 不要主动建议"帮你执行分析",除非用户明确要求
3. 如果问题与用户数据相关,引用数据上下文中的具体信息
4. 如果问题超出统计分析范围,礼貌说明并引导回统计话题
5. 回复简洁,不超过 300 字`,
modelConfig: { model: 'deepseek-v3', temperature: 0.7, maxTokens: 1500 },
},
{
code: 'SSA_INTENT_EXPLORE',
name: 'SSA explore 意图指令',
description: 'Phase II — explore 意图的指令段,用于数据探索解读',
variables: [],
content: `## 当前任务:数据探索
用户想了解数据的特征和质量状况。
规则:
1. 基于上方的数据摘要信息数据概览、变量列表、PICO 推断),帮用户解读数据
2. 重点关注:缺失模式、异常值、变量类型、分布特征、样本量
3. 如果发现数据质量问题,主动提醒并建议处理方式
4. 可以推断 PICO 结构,但标注为"AI 推断,请确认"
5. 不要执行统计分析,仅做描述性解读
6. 使用编号列表组织回答,便于阅读`,
modelConfig: { model: 'deepseek-v3', temperature: 0.7, maxTokens: 2000 },
},
{
code: 'SSA_INTENT_CONSULT',
name: 'SSA consult 意图指令',
description: 'Phase II — consult 意图的指令段Phase III 正式启用)',
variables: [],
content: `## 当前任务:方法咨询
用户在咨询应该使用什么统计方法。
规则:
1. 根据数据特征(变量类型、分布、样本量)和研究目的推荐统计方法
2. 必须说明:推荐方法、选择理由、前提条件(如正态性要求)
3. 提供至少一个替代方案(如非参数替代)
4. 不要直接执行分析,等待用户确认方案后再执行
5. 如果信息不足以做出推荐,主动追问缺少的关键信息`,
modelConfig: { model: 'deepseek-v3', temperature: 0.7, maxTokens: 2000 },
},
{
code: 'SSA_INTENT_ANALYZE',
name: 'SSA analyze 意图指令',
description: 'Phase II — analyze 意图的指令段,用于播报 QPER 执行进度',
variables: [],
content: `## 当前任务:分析执行播报
系统正在执行统计分析(通过 QPER 引擎),你的任务是向用户简要说明进展。
规则:
1. 如果提供了工具执行结果,用通俗语言向用户解释关键发现
2. 避免复制粘贴原始 R 输出提炼核心信息p 值、效应量、置信区间)
3. 使用用户能理解的语言,必要时解释统计术语
4. 回复控制在 200 字以内,详细结果可在分析报告中查看
5. 如果执行出错,简要说明原因并建议解决方案`,
modelConfig: { model: 'deepseek-v3', temperature: 0.5, maxTokens: 1000 },
},
{
code: 'SSA_INTENT_DISCUSS',
name: 'SSA discuss 意图指令',
description: 'Phase II — discuss 意图的指令段Phase V 正式启用)',
variables: [],
content: `## 当前任务:结果讨论
用户想深入讨论已有的分析结果。
规则:
1. 基于上方注入的分析结果,帮助用户深入解读
2. 解释统计量的含义(如 p 值的正确解读、置信区间的意义)
3. 讨论结果的临床意义(不仅是统计显著性)
4. 指出分析的局限性和注意事项
5. 如果用户提出合理质疑,认真分析并给出专业回应
6. 避免给出超出数据支撑范围的结论`,
modelConfig: { model: 'deepseek-v3', temperature: 0.7, maxTokens: 2000 },
},
{
code: 'SSA_INTENT_FEEDBACK',
name: 'SSA feedback 意图指令',
description: 'Phase II — feedback 意图的指令段Phase V 正式启用)',
variables: [],
content: `## 当前任务:分析反馈与改进
用户对之前的分析结果不满意或有改进建议。
规则:
1. 认真分析用户的反馈,理解不满意的具体原因
2. 如果上方提供了 QPER 执行记录,从中诊断问题(方法选择不当?参数错误?数据问题?)
3. 提出具体改进方案(换统计方法、调整参数、处理异常值等)
4. 改进方案必须可执行、有理据
5. 如果是数据本身的问题(样本量不足、变量不合适等),如实告知`,
modelConfig: { model: 'deepseek-v3', temperature: 0.7, maxTokens: 2000 },
},
{
code: 'SSA_INTENT_ROUTER',
name: 'SSA 意图路由分类器',
description: 'Phase II — 轻量级意图分类 PromptLLM 兜底,<500 tokens',
variables: [],
content: `你是一个意图分类器。根据用户消息和会话状态,判断用户的意图类型。
## 可选意图
| 意图 | 含义 | 典型示例 |
|------|------|----------|
| chat | 普通对话、统计知识问答 | "BMI 正常范围是多少?" |
| explore | 探索数据特征、了解数据概况 | "帮我看看各组样本分布" |
| consult | 咨询分析方法、请求推荐 | "我应该用什么方法比较两组差异?" |
| analyze | 要求执行统计分析 | "对 BMI 和血压做相关分析" |
| discuss | 讨论已有分析结果 | "这个 p 值说明什么?" |
| feedback | 对结果不满意、要求改进 | "结果不对,换个方法试试" |
## 分类规则
1. 如果用户消息同时匹配多个意图选择最具体的analyze > consult > explore > chat
2. 如果无法确定,输出 chat最安全的兜底
3. discuss 和 feedback 仅在"有分析结果"时才适用
## 输出格式
以 JSON 格式输出,只输出 JSON不要输出其他内容
{"intent": "chat", "confidence": 0.9}`,
modelConfig: { model: 'deepseek-v3', temperature: 0.1, maxTokens: 100 },
},
];
async function upsertPrompt(def: PromptDef): Promise<void> {
const existing = await prisma.prompt_templates.findUnique({
where: { code: def.code },
});
if (existing) {
const latestVersion = await prisma.prompt_versions.findFirst({
where: { template_id: existing.id },
orderBy: { version: 'desc' },
});
const activeVersion = await prisma.prompt_versions.findFirst({
where: { template_id: existing.id, status: 'ACTIVE' },
});
if (activeVersion && activeVersion.content === def.content) {
console.log(` ⏭️ ${def.code} 内容未变化,跳过`);
return;
}
const newVersion = (latestVersion?.version ?? 0) + 1;
await prisma.prompt_versions.updateMany({
where: { template_id: existing.id, status: 'ACTIVE' },
data: { status: 'ARCHIVED' },
});
await prisma.prompt_versions.create({
data: {
template_id: existing.id,
version: newVersion,
content: def.content,
model_config: def.modelConfig,
status: 'ACTIVE',
changelog: `Phase II v${newVersion}: ${def.description}`,
created_by: 'system-seed',
},
});
console.log(`${def.code} 更新到 v${newVersion}`);
} else {
const template = await prisma.prompt_templates.create({
data: {
code: def.code,
name: def.name,
module: 'SSA',
description: def.description,
variables: def.variables,
},
});
await prisma.prompt_versions.create({
data: {
template_id: template.id,
version: 1,
content: def.content,
model_config: def.modelConfig,
status: 'ACTIVE',
changelog: `Phase II v1.0: ${def.description}`,
created_by: 'system-seed',
},
});
console.log(`${def.code} 创建成功 (id=${template.id})`);
}
}
async function main() {
console.log('🚀 开始写入 SSA Phase II Prompt 模板...\n');
for (const def of PROMPTS) {
console.log(`📝 处理 ${def.code} (${def.name})...`);
await upsertPrompt(def);
}
console.log(`\n✅ 全部 ${PROMPTS.length} 个 Prompt 模板写入完成!`);
}
main()
.catch(e => {
console.error('❌ 写入失败:', e);
process.exit(1);
})
.finally(() => prisma.$disconnect());