Files
AIclinicalresearch/docs/02-通用能力层/06-R统计引擎/01-R统计引擎架构与部署指南.md
HaHafeng c681155de2 docs(r-engine): Update R statistics engine guide v1.6 with structured error handling
- Update execute-code error response format with new fields: error_type, error_line, error_context, console_output
- Document withCallingHandlers for warnings/messages capture in sandbox
- Add error code reference table (E001-E101 + E_EXEC, 20+ pattern matching)
- Document format_agent_error() structured error extraction for CoderAgent auto-repair
- Fix caller description: ReviewerAgent paused, CoderAgent is the actual caller
- Clarify difference between /skills (map_r_error) and /execute-code (format_agent_error) error handling
- Update directory structure descriptions for plumber.R and error_codes.R

Made-with: Cursor
2026-03-07 22:38:28 +08:00

47 KiB
Raw Permalink Blame History

R 统计引擎架构与部署指南

版本: v1.6
更新日期: 2026-03-07
维护者: SSA-Pro 开发团队 / ASL 循证工具箱团队
状态: 生产就绪13 工具 + Block-based 标准化输出 + Agent 代码执行端点 + 结构化错误处理)


📋 目录

  1. 概述
  2. 架构设计
  3. Docker 镜像构建
  4. 部署指南
  5. API 参考
  6. 开发指南
  7. 运维指南
  8. 常见问题
  9. 测试指南

1. 概述

1.1 什么是 R 统计引擎

R 统计引擎是平台的专用统计计算服务,基于 Docker 容器化部署,提供:

  • 🧮 严谨的统计分析能力T 检验、方差分析、回归等)
  • 🛡️ 统计护栏(正态性检验、方差齐性检验等)
  • 📊 可视化输出Base64 编码的图表)
  • 📝 可复现代码生成APA 格式的 R 脚本)

1.2 定位

┌─────────────────────────────────────────────────────────────┐
│                      业务模块层                              │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                     │
│  │ SSA-Pro │  │  ASL    │  │  其他   │                     │
│  │ 智能统计 │  │Meta分析 │  │  模块   │                     │
│  └────┬────┘  └────┬────┘  └─────────┘                     │
├───────┼──────────┼──────────────────────────────────────────┤
│       ▼          ▼       通用能力层                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              R 统计引擎 (Docker)                     │   │
│  │  • /health              健康检查                     │   │
│  │  • /api/v1/tools        工具列表                     │   │
│  │  • /api/v1/skills       技能执行QPER 管线)        │   │
│  │  • /api/v1/execute-code 代码执行Agent 通道)       │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

1.3 技术栈

组件 版本 说明
R 4.3.3 统计计算核心
plumber 1.2.1 REST API 框架
ggplot2 最新 数据可视化
car 3.1-2 高级统计检验
dplyr/tidyr 最新 数据处理
gtsummary 最新 基线特征表生成Phase Deploy 新增)
gt/broom 最新 表格渲染/模型整理Phase Deploy 新增)
scales/gridExtra 最新 坐标轴格式化/多图排版Phase Deploy 新增)
meta 8.2.1 Meta 分析引擎ASL 工具 5 新增 — metagen/metabin/metacont
Docker 24+ 容器化部署

2. 架构设计

2.1 Brain-Hand 模型

R 统计引擎采用 Brain-Hand 分离架构

┌──────────────────┐          ┌──────────────────┐
│    Node.js       │          │    R Docker      │
│    (Brain)       │          │    (Hand)        │
├──────────────────┤          ├──────────────────┤
│ • 业务逻辑       │  HTTP    │ • 统计计算       │
│ • 认证鉴权       │ ───────> │ • 数据处理       │
│ • OSS 签名       │          │ • 图表生成       │
│ • 结果解释       │ <─────── │ • 代码生成       │
└──────────────────┘  JSON    └──────────────────┘

2.2 数据传输协议

支持两种数据传输方式:

方式 条件 字段
inline 数据 < 2MB data_source.data (JSON)
oss 数据 >= 2MB data_source.oss_url (预签名 URL)
// 方式 1: inline
{
  "data_source": {
    "type": "inline",
    "data": [{"group": "A", "value": 10}, ...]
  }
}

// 方式 2: oss (预签名 URL)
{
  "data_source": {
    "type": "oss",
    "oss_url": "https://bucket.oss.com/data.csv?signature=xxx"
  }
}

2.2.1 inline 数据格式详解

R 数据加载器 (utils/data_loader.R) 支持两种 JSON 数据格式:

格式 说明 示例
行格式 JSON 对象数组,每个对象是一行 [{"sex": 1, "age": 25}, {"sex": 2, "age": 30}]
列格式 JSON 对象,每个属性是一列 {"sex": [1, 2], "age": [25, 30]}

推荐:使用行格式,与 JavaScript/TypeScript 的数据处理习惯一致。

Node.js 调用示例:

// 推荐行格式Array of Objects
const data = [
  { sex: 1, age: 25, bmi: 22.5 },
  { sex: 2, age: 30, bmi: 24.1 },
  // ...
];

const response = await axios.post('http://localhost:8082/api/v1/skills/ST_T_TEST_IND', {
  data_source: {
    type: 'inline',
    data: data  // 直接传入数组
  },
  params: {
    group_var: 'sex',
    value_var: 'age'
  }
});

2.3 安全设计

安全措施 实现方式
非特权用户 USER appuser
路径遍历防护 tool_code 正则白名单 ^[A-Z][A-Z0-9_]*$
OSS 密钥隔离 Node.js 生成预签名 URLR 无需持有密钥
健康检查 Docker HEALTHCHECK

3. Docker 镜像构建

3.1 完整 Dockerfile

FROM rocker/r-ver:4.3

LABEL maintainer="dev-team@aiclinicalresearch.com"
LABEL version="1.0.1"
LABEL description="SSA-Pro R Statistics Service"

# 安装系统依赖(包括 R 包编译所需的库)
RUN apt-get update && apt-get install -y \
    libcurl4-openssl-dev \
    libssl-dev \
    libxml2-dev \
    libsodium-dev \
    zlib1g-dev \
    libnlopt-dev \
    liblapack-dev \
    libblas-dev \
    gfortran \
    pkg-config \
    cmake \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 直接安装 R 包(含 Phase Deploy + ASL Meta 分析依赖)
RUN R -e "install.packages(c( \
    'plumber', \
    'jsonlite', \
    'ggplot2', \
    'glue', \
    'dplyr', \
    'tidyr', \
    'base64enc', \
    'yaml', \
    'car', \
    'httr', \
    'scales', \
    'gridExtra', \
    'gtsummary', \
    'gt', \
    'broom', \
    'meta' \
), repos='https://cloud.r-project.org/', Ncpus=2)"

# 安全加固:创建非特权用户
RUN useradd -m -s /bin/bash appuser

WORKDIR /app

# 复制应用代码
COPY plumber.R plumber.R
COPY utils/ utils/
COPY tools/ tools/
COPY tests/ tests/

# 设置目录权限
RUN chown -R appuser:appuser /app

# 切换到非特权用户
USER appuser

EXPOSE 8080

# 环境变量
ENV DEV_MODE="false"

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

# 启动服务
CMD ["R", "-e", "plumber::plumb('plumber.R')$run(host='0.0.0.0', port=8080)"]

3.2 系统依赖说明

依赖包 用途
libcurl4-openssl-dev httr 包HTTP 请求)
libssl-dev openssl 包(加密)
libxml2-dev xml2 包XML 解析)
libsodium-dev sodium 包(加密)
zlib1g-dev httpuv 包Web 服务器)
libnlopt-dev nloptr 包(优化算法)
liblapack-dev 线性代数计算
libblas-dev 基础线性代数
gfortran Fortran 编译器(部分 R 包需要)
cmake nloptr 包构建
curl 健康检查

3.3 构建命令

# 本地构建
cd r-statistics-service
docker build -t ssa-r-statistics:1.0.1 .

# 查看镜像
docker images ssa-r-statistics

# 预期输出
REPOSITORY         TAG       IMAGE ID       CREATED         SIZE
ssa-r-statistics   1.0.1     xxxxxxxxxxxx   x minutes ago   1.81GB

3.4 构建时间参考

阶段 耗时
基础镜像下载 ~2 分钟(首次)
系统依赖安装 ~1 分钟
R 包安装16 个,含 gtsummary/gt/meta ~15 分钟
总计 ~17 分钟

4. 部署指南

4.1 开发环境

使用 docker-compose

# r-statistics-service/docker-compose.yml
services:
  ssa-r-service:
    build: .
    container_name: ssa-r-statistics
    ports:
      - "8082:8080"  # 主机8082 → 容器8080REDCap占用8080/8081
    environment:
      - DEV_MODE=true
    volumes:
      # 开发环境挂载:支持热重载
      - ./plumber.R:/app/plumber.R  # ⚠️ 重要API 入口也需要挂载
      - ./tools:/app/tools
      - ./utils:/app/utils
      - ./tests:/app/tests
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

启动命令:

cd r-statistics-service
docker-compose up -d

4.1.1 热重载机制详解

文件类型 热重载支持 说明
tools/*.R 自动 DEV_MODE=true 时每次请求重新加载
utils/*.R ⚠️ 需重启 服务启动时加载,修改后需 docker-compose restart
plumber.R ⚠️ 需重启 API 路由定义,修改后需 docker-compose restart

最佳实践:

  • 开发新工具时,只需修改 tools/ 目录,无需重启
  • 修改 utils/plumber.R 后,执行 docker-compose restart
  • 添加新的 API 端点后,需要 docker-compose up -d --force-recreate

4.2 生产环境 (SAE)

# SAE 配置
容器镜像: registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.1
实例规格: 2 vCPU, 4 GB
最小实例: 1
最大实例: 5
端口: 8080

环境变量:
  DEV_MODE: "false"

4.3 环境变量

变量 默认值 说明
DEV_MODE false 开发模式(启用热重载,每次请求重新加载工具脚本)

说明:开发环境和生产环境都使用真实 OSS无需 Mock 数据。

  • 开发环境:ai-clinical-data-dev bucket
  • 生产环境:ai-clinical-data bucket

4.4 端口配置

环境 主机端口 容器端口 说明
开发环境 8082 8080 避免与 REDCap 8080/8081 冲突
生产环境 (SAE) 8080 8080 云端无端口冲突

注意Node.js 后端通过 R_SERVICE_URL 环境变量配置 R 服务地址,默认值为 http://localhost:8082


5. API 参考

5.1 健康检查

GET /health

响应:

{
  "status": "ok",
  "timestamp": "2026-02-19 08:00:00",
  "version": "1.0.1",
  "dev_mode": true,
    "tools_loaded": 13
}

5.2 工具列表

GET /api/v1/tools

响应:

{
  "status": "ok",
  "tools": [
    "anova_one",
    "baseline_table",
    "chi_square",
    "correlation",
    "descriptive",
    "fisher",
    "linear_reg",
    "logistic_binary",
    "mann_whitney",
    "meta_analysis",
    "t_test_ind",
    "t_test_paired",
    "wilcoxon"
  ],
  "count": 13
}

已实现的统计工具13 个)

Phase 2A 基础工具7 个)

tool_code 名称 场景
ST_T_TEST_IND 独立样本 T 检验 两组连续变量比较(正态)
ST_MANN_WHITNEY Mann-Whitney U 两组连续变量比较(非参数)
ST_T_TEST_PAIRED 配对 T 检验 前后对比
ST_CHI_SQUARE 卡方检验 分类变量关联
ST_CORRELATION 相关分析 Pearson/Spearman 相关
ST_LOGISTIC_BINARY 二元 Logistic 回归 多因素分析(二分类结局)
ST_DESCRIPTIVE 描述性统计 基线表、数据概况

Phase Deploy 新增工具5 个)

tool_code 名称 场景
ST_FISHER Fisher 精确检验 小样本/稀疏列联表(卡方替代)
ST_ANOVA_ONE 单因素方差分析 三组及以上均值比较(含 Kruskal-Wallis 降级)
ST_WILCOXON Wilcoxon 符号秩检验 配对非参数检验(配对 T 替代)
ST_LINEAR_REG 线性回归 连续结局多因素分析
ST_BASELINE_TABLE 基线特征表(复合工具) 基于 gtsummary 的一键式基线表生成

ASL 循证工具箱新增1 个)

tool_code 名称 场景 调用方
ST_META_ANALYSIS Meta 分析引擎 HR / 二分类 / 连续型 Meta 分析,生成森林图 + 漏斗图 ASL 工具 5

跨模块复用说明: ST_META_ANALYSIS 由 ASL 模块(工具 5Meta 分析引擎)引入,后端代理位于 backend/src/modules/asl/meta-analysis/,使用 inline 数据协议Meta 分析通常 5-30 个研究,数据量极小)。该工具同样可被 SSA 或其他模块复用。

5.3 执行技能

POST /api/v1/skills/{tool_code}
Content-Type: application/json

请求体:

{
  "data_source": {
    "type": "inline",
    "data": [...]
  },
  "params": {
    "group_var": "group",
    "value_var": "value"
  },
  "guardrails": {
    "check_normality": true
  }
}

成功响应:

{
  "status": "success",
  "message": "分析完成",
  "warnings": null,
  "results": {
    "method": "Welch Two Sample t-test",
    "statistic": -5.196,
    "df": 5.98,
    "p_value": 0.002,
    "p_value_fmt": "p = .002"
  },
  "plots": ["data:image/png;base64,..."],
  "trace_log": [...],
  "reproducible_code": "..."
}

错误响应:

{
  "status": "error",
  "error_code": "E001",
  "error_type": "business",
  "message": "列名 'xxx' 在数据中不存在",
  "user_hint": "请检查变量名是否拼写正确"
}

5.4 JIT 护栏检查Phase 2A 新增)

在执行核心统计工具前,调用此端点检验统计假设(正态性、方差齐性等)。

POST /api/v1/guardrails/jit
Content-Type: application/json

请求体:

{
  "data_source": {
    "type": "inline",
    "data": [...]
  },
  "tool_code": "ST_T_TEST_IND",
  "params": {
    "group_var": "sex",
    "value_var": "age"
  }
}

响应:

{
  "status": "success",
  "checks": [
    {
      "check_name": "正态性检验 (组: 1)",
      "passed": true,
      "p_value": 0.234,
      "recommendation": "满足正态性"
    },
    {
      "check_name": "方差齐性检验 (Levene)",
      "passed": false,
      "p_value": 0.012,
      "recommendation": "建议使用 Welch 校正"
    }
  ],
  "suggested_tool": "ST_MANN_WHITNEY",
  "can_proceed": true,
  "all_checks_passed": false
}

使用场景:

  • 工作流执行器在调用核心统计方法前,先调用 JIT 护栏
  • 根据 suggested_tool 自动切换到更合适的方法
  • checks 结果展示给用户

5.5 Agent 通道:执行任意 R 代码v1.5 双通道架构新增)

POST /api/v1/execute-code
Content-Type: application/json

请求体:

{
  "code": "blocks <- list()\nblocks[[1]] <- make_markdown_block('Hello', title='Test')\nlist(status='success', report_blocks=blocks)",
  "session_id": "xxx-xxx",
  "timeout": 120
}
参数 类型 必填 说明
code string 要执行的 R 代码(可使用 block_helpers.R 的所有辅助函数)
session_id string 会话 ID用于日志追踪
timeout number 超时秒数,默认 120最大 120

成功响应:

{
  "status": "success",
  "result": {
    "status": "success",
    "report_blocks": [
      {"type": "markdown", "content": "Hello", "title": "Test"}
    ]
  },
  "console_output": [],
  "duration_ms": 42
}

错误响应v1.6 结构化错误):

{
  "status": "error",
  "error_code": "E001",
  "error_type": "business",
  "message": "[E001] object 'blood_pressure' not found (约第 12 行)\n[Warnings] NAs introduced by coercion",
  "user_hint": "请检查变量名是否拼写正确 | object 'blood_pressure' not found",
  "error_line": 12,
  "error_context": "     9| df$group <- as.factor(df$group)\n    10| \n    11| # 执行 T 检验\n>>> 12| result <- t.test(blood_pressure ~ group, data = df)\n    13| \n    14| # 构建结果",
  "console_output": ["Loading required package: ggplot2", "Warning: NAs introduced by coercion"],
  "duration_ms": 123
}
错误响应字段 类型 说明
error_code string 错误分类码(E001-E007 业务错误,E100-E101 系统错误,E_EXEC 未分类运行时错误)
error_type string business(可由 LLM 自动修复)或 system(需人工介入)或 runtime(未分类)
message string 结构化友好消息,含错误码 + 原始信息 + 行号 + 警告摘要
user_hint string 面向 LLM 的修复建议 + 原始错误拼接
error_line number? 出错代码行号(从 R 错误信息中提取,可能为 null
error_context string? 出错行前后 3 行的代码上下文,>>> 标记出错行(可能为 null
console_output string[] R 控制台完整输出stdout + warnings + messages 合并)

错误码速查(error_codes.R 定义20+ 模式匹配):

错误码 类型 典型触发 LLM 修复建议
E001 business object 'xxx' not found, undefined columns 检查变量名拼写
E002 business non-numeric argument, cannot coerce, contrasts can be applied only to factors 检查数据类型
E003 business 分组变量水平数不匹配 检查分组变量取值
E004 business need at least 2 observations, sample size must be 数据量不足
E005 business singular matrix, rank deficient, singular gradient 移除共线变量
E006 business did not converge 调整模型参数
E007 business 方差为零 该列值全部相同
E100 system subscript out of bounds, missing value where TRUE/FALSE needed 需人工排查
E101 system could not find function, no package called 缺少 R 包
E_EXEC runtime 未匹配到已知模式 返回原始错误信息

沙箱安全机制:

  • 代码在 new.env(parent = globalenv()) 隔离环境中执行
  • setTimeLimit 强制超时CPU + 挂钟时间 ≤ 120 秒)
  • withCallingHandlers 捕获 warnings 和 messages不中断执行错误由外层 tryCatch 捕获
  • console_output 合并三种来源:capture.output stdout + 收集的 warnings + 收集的 messages
  • 错误时调用 format_agent_error() 提取行号、代码上下文、错误分类和修复建议
  • 可访问 block_helpers.Rdata_loader.R 中的所有辅助函数
  • 由 Node.js CodeRunnerService 自动注入 inputdf 数据变量

调用方: SSA 模块 Agent 通道(CodeRunnerService.tsChatHandlerService.ts),用于执行 LLM CoderAgent 生成的 R 代码。

/api/v1/skills/{tool_code} 的区别: /skills 端点执行预制的统计工具(白名单限制),/execute-code 端点执行任意 R 代码(由 LLM CoderAgent 生成)。两者的错误处理也不同:/skills 使用 map_r_error() 做简单映射,/execute-code 使用 format_agent_error() 返回结构化错误(含行号和上下文),便于 CoderAgent 自动修复重试。

5.6 复合工具示例基线特征表Phase Deploy

POST /api/v1/skills/ST_BASELINE_TABLE
Content-Type: application/json

请求体:

{
  "data_source": {
    "type": "inline",
    "data": [
      {"group": "Drug", "age": 45, "sex": "M", "sbp": 130, "bmi": 24.5, "smoking": "Yes"},
      {"group": "Placebo", "age": 47, "sex": "F", "sbp": 128, "bmi": 23.8, "smoking": "No"}
    ]
  },
  "params": {
    "group_var": "group",
    "analyze_vars": ["age", "sex", "sbp", "bmi", "smoking"]
  }
}

成功响应(核心字段):

{
  "status": "success",
  "results": {
    "n_total": 30,
    "n_groups": 2,
    "n_variables": 5,
    "significant_vars": ["sbp"],
    "method_info": [
      {"variable": "age", "method": "Wilcoxon rank sum test"},
      {"variable": "sex", "method": "Fisher's exact test"}
    ]
  },
  "report_blocks": [
    {
      "type": "table",
      "headers": ["Characteristic", "Drug, N = 15", "Placebo, N = 15", "p-value"],
      "rows": [["age", "49 (42, 55)", "47 (41, 54)", "0.6"]],
      "title": "基线特征表 (按 group 分组)",
      "metadata": { "is_baseline_table": true, "group_var": "group", "has_p_values": true }
    }
  ]
}

特点: ST_BASELINE_TABLE 是复合工具,基于 gtsummary::tbl_summary() 自动判断变量类型(连续/分类、选择统计方法T 检验/Mann-Whitney/卡方/Fisher输出标准三线表。report_blocks[0].metadata.is_baseline_table = true 触发前端特殊渲染P 值标星、rowspan 合并行)。

5.7 Meta 分析示例ASL 工具 5 — v1.4 新增)

POST /api/v1/skills/ST_META_ANALYSIS
Content-Type: application/json

请求体HR 数据类型):

{
  "data_source": {
    "type": "inline",
    "data": [
      {"study_id": "Gandhi 2018", "hr": 0.49, "lower_ci": 0.38, "upper_ci": 0.64},
      {"study_id": "Socinski 2018", "hr": 0.56, "lower_ci": 0.45, "upper_ci": 0.70},
      {"study_id": "West 2019", "hr": 0.60, "lower_ci": 0.45, "upper_ci": 0.80}
    ]
  },
  "params": {
    "data_type": "hr",
    "model": "random"
  }
}

支持的 3 种数据类型:

data_type 必需列 R 函数 效应指标
hr study_id, hr, lower_ci, upper_ci meta::metagen() HR
dichotomous study_id, events_e, total_e, events_c, total_c meta::metabin() OR / RR / RD
continuous study_id, mean_e, sd_e, n_e, mean_c, sd_c, n_c meta::metacont() MD

params 参数:

参数 类型 默认值 说明
data_type string 必填 "hr" / "dichotomous" / "continuous"
model string "random" "random" (DerSimonian-Laird) / "fixed"
effect_measure string 自动 仅二分类有效:"OR" / "RR" / "RD"

成功响应(核心字段):

{
  "status": "success",
  "results": {
    "pooled_effect": 0.5948,
    "pooled_lower": 0.5255,
    "pooled_upper": 0.6733,
    "pooled_pvalue": 0.0,
    "i_squared": 15.4,
    "tau_squared": 0.0031,
    "q_statistic": 4.73,
    "q_pvalue": 0.316,
    "k_studies": 3,
    "effect_measure": "HR",
    "model": "Random Effects"
  },
  "plots": [
    "data:image/png;base64,...(森林图)",
    "data:image/png;base64,...(漏斗图)"
  ],
  "report_blocks": [
    {"type": "key_value", "title": "Meta-Analysis Summary", "items": [...]},
    {"type": "image", "title": "Forest Plot", "data": "..."},
    {"type": "image", "title": "Funnel Plot", "data": "..."},
    {"type": "markdown", "title": "Heterogeneity Assessment", "content": "..."}
  ]
}

重要实现细节 — 对数尺度反变换: meta R 包对比值类效应量HR / OR / RR的内部计算在对数尺度上进行,即 TE.random 返回的是 log(HR) 而非 HR。meta_analysis.R 在结果提取阶段对比值类指标自动执行 exp() 反变换,确保 API 返回临床可读的效应量(如 HR = 0.59 而非 log(HR) = -0.52。连续型指标MD无需反变换。


6. 开发指南

6.1 添加新工具

  1. tools/ 目录创建 R 脚本:
# tools/my_analysis.R

#' @tool_code ST_MY_ANALYSIS
#' @name 我的分析工具
#' @version 1.0.0
#' @description 工具描述
#' @author SSA-Pro Team

library(glue)
library(ggplot2)
library(base64enc)

run_analysis <- function(input) {
  # ===== 初始化日志 =====
  logs <- c()
  log_add <- function(msg) { logs <<- c(logs, paste0("[", Sys.time(), "] ", msg)) }
  
  # ===== 数据加载 =====
  log_add("开始加载输入数据")
  df <- tryCatch(
    load_input_data(input),
    error = function(e) {
      log_add(paste("数据加载失败:", e$message))
      return(NULL)
    }
  )
  
  if (is.null(df)) {
    return(make_error(ERROR_CODES$E100_INTERNAL_ERROR, details = "数据加载失败"))
  }
  log_add(glue("数据加载成功: {nrow(df)} 行, {ncol(df)} 列"))
  
  # ===== 参数提取 =====
  p <- input$params
  my_var <- p$my_var
  
  # ===== 参数校验 =====
  if (!(my_var %in% names(df))) {
    return(make_error(ERROR_CODES$E001_COLUMN_NOT_FOUND, col = my_var))
  }
  
  # ===== 护栏检查 =====
  guardrail_results <- list()
  warnings_list <- c()
  
  sample_check <- check_sample_size(nrow(df), min_required = 10, action = ACTION_WARN)
  guardrail_results <- c(guardrail_results, list(sample_check))
  
  guardrail_status <- run_guardrail_chain(guardrail_results)
  if (guardrail_status$status == "blocked") {
    return(list(status = "blocked", message = guardrail_status$reason, trace_log = logs))
  }
  
  # ===== 核心计算 =====
  log_add("执行分析...")
  # result <- your_analysis_function(df, ...)
  
  # ===== 构建 report_blocks必须 =====
  blocks <- list()
  
  # Block: 检验结果key_value
  blocks[[length(blocks) + 1]] <- make_kv_block(
    list("方法" = "Your Method", "统计量" = "1.234", "P 值" = format_p_value(0.05)),
    title = "检验结果"
  )
  
  # Block: 图表image
  plot_base64 <- tryCatch({
    p <- ggplot(df, aes(x = df[[my_var]])) + geom_histogram() + theme_minimal()
    tmp_file <- tempfile(fileext = ".png")
    ggsave(tmp_file, p, width = 7, height = 5, dpi = 100)
    base64_str <- base64encode(tmp_file)
    unlink(tmp_file)
    paste0("data:image/png;base64,", base64_str)
  }, error = function(e) NULL)
  
  if (!is.null(plot_base64)) {
    blocks[[length(blocks) + 1]] <- make_image_block(plot_base64, title = "分析图表")
  }
  
  # Block: 结论markdown
  blocks[[length(blocks) + 1]] <- make_markdown_block("分析结论...", title = "结论摘要")
  
  # ===== 生成可复现代码 =====
  reproducible_code <- glue('
# SSA-Pro 自动生成代码
# 工具: 我的分析工具
# 时间: {Sys.time()}
# ================================

df <- read.csv("data.csv")
# 你的分析代码...
')
  
  # ===== 返回结果 =====
  log_add("分析完成")
  
  return(list(
    status = "success",
    message = "分析完成",
    warnings = if (length(warnings_list) > 0) warnings_list else NULL,
    results = list(
      # 统计结果(使用 jsonlite::unbox 保证单值不被包装成数组)
      statistic = jsonlite::unbox(1.234),
      p_value = jsonlite::unbox(0.05),
      p_value_fmt = format_p_value(0.05)
    ),
    report_blocks = blocks,  # ⚠️ 必须!前端 DynamicReport 依赖此字段渲染
    plots = if (!is.null(plot_base64)) list(plot_base64) else list(),
    trace_log = logs,
    reproducible_code = as.character(reproducible_code)
  ))
}
  1. 开发模式:修改 tools/ 下的文件后,无需重启,下次请求自动加载

  2. 测试:

curl -X POST http://localhost:8082/api/v1/skills/ST_MY_ANALYSIS \
  -H "Content-Type: application/json" \
  -d '{"data_source": {"type": "inline", "data": [{"x": 1}, {"x": 2}]}, "params": {"my_var": "x"}}'

6.2 工具命名规范

项目 规范
文件名 小写下划线:t_test_ind.R
tool_code 大写下划线:ST_T_TEST_IND
入口函数 固定名称:run_analysis

6.3 结果格式规范

return(list(
  status = "success" | "error" | "blocked",
  message = "...",
  warnings = c("...") | NULL,
  results = list(
    # 统计结果(使用 jsonlite::unbox() 保证单值不被包装成数组)
  ),
  report_blocks = list(
    # Block-based 标准化输出Phase E+ 协议),前端 DynamicReport.tsx 统一渲染
    # 支持 4 种 Block 类型markdown / table / image / key_value
    # 通过 utils/block_helpers.R 的辅助函数构建
  ),
  plots = list(
    "data:image/png;base64,..."
  ),
  trace_log = c("..."),
  reproducible_code = "..."
))

6.4 Block-based 输出协议Phase E+ 标准)

所有工具必须通过 utils/block_helpers.R 构建 report_blocks[],前端 DynamicReport.tsx 根据 block.type 统一渲染。

辅助函数 Block 类型 用途
make_markdown_block(content, title) markdown 文本结论、方法说明
make_table_block(headers, rows, title, footnote, metadata) table 统计结果表、系数表、事后比较表
make_table_block_from_df(df, title, footnote, digits) table 从 data.frame 快速构建表格
make_image_block(base64_data, title, alt) image 图表base64 编码 PNG
make_kv_block(items, title) key_value 检验统计量、模型拟合指标

示例:

blocks <- list()
blocks[[length(blocks) + 1]] <- make_kv_block(
  list("检验方法" = "Welch t-test", "统计量" = "t = -2.35", "P 值" = "p = .021"),
  title = "检验结果"
)
blocks[[length(blocks) + 1]] <- make_image_block(plot_base64, title = "组间比较箱线图")
blocks[[length(blocks) + 1]] <- make_markdown_block("两组差异具有统计学意义...", title = "结论")

6.5 各工具参数快速参考

调用 POST /api/v1/skills/{tool_code} 时,params 对象需要的字段速查。

tool_code 必需参数 可选参数
ST_T_TEST_IND group_var, value_var guardrails.check_normality
ST_MANN_WHITNEY group_var, value_var
ST_T_TEST_PAIRED before_var, after_var guardrails.check_normality
ST_CHI_SQUARE var1, var2
ST_CORRELATION var_x, var_y method ("auto" / "pearson" / "spearman")
ST_LOGISTIC_BINARY outcome_var, predictors (数组)
ST_DESCRIPTIVE variables (数组) group_var
ST_FISHER var1, var2
ST_ANOVA_ONE group_var, value_var guardrails.check_normality
ST_WILCOXON before_var, after_var
ST_LINEAR_REG outcome_var, predictors (数组) confounders (数组)
ST_BASELINE_TABLE group_var analyze_vars (数组,不传则自动全选)
ST_META_ANALYSIS data_type (hr / dichotomous / continuous) model (random / fixed)、effect_measure (二分类: OR / RR / RD)

6.6 R 语言陷阱速查(从实际 Bug 中总结)

Phase Deploy 开发中实际踩过的坑,后续开发者必读。每条附真实错误信息和修复方法。

陷阱 1JSON 数组参数在 R 中是 list,不是 character 向量

错误信息: invalid subscript type 'list'

原因: plumber 解析 JSON ["age", "sex", "bmi"]R 拿到的是 list("age", "sex", "bmi"),不是 c("age", "sex", "bmi")。对 list 做 %in%[ 等操作都会报错。

# ❌ 错误:直接使用 JSON 传入的数组参数
analyze_vars <- p$analyze_vars
missing <- analyze_vars[!(analyze_vars %in% names(df))]  # 报错!

# ✅ 正确:先转换为字符向量
analyze_vars <- as.character(unlist(p$analyze_vars))
missing <- analyze_vars[!(analyze_vars %in% names(df))]  # 正常

影响范围: 所有接收数组参数的工具(predictorsvariablesanalyze_varsconfounders)。

陷阱 2list() 中不能用表达式做键名

错误信息: unexpected '='

原因: R 的 list() 构造器只接受字面量作为名称,不接受 paste0()glue() 等函数调用。

# ❌ 错误:用表达式做键名
items <- list(
  paste0(var_name, " Median") = "5.2"  # 语法错误!
)

# ✅ 正确:先创建 list 再用 [[ 赋值
items <- list()
items[[paste0(var_name, " Median")]] <- "5.2"

陷阱 3tryCatch 会吞掉 warning 导致结果丢失

错误信息: 无明确错误,但返回 NULL 或非预期结果

原因: tryCatch(expr, warning = function(w) {...}) 捕获第一个 warning 后中断 expr 执行,返回 warning handler 的返回值。gtsummary、car 等包常发 warning导致主计算被中断。

# ❌ 错误tryCatch 捕获 warning 会中断执行
tbl <- tryCatch({
  tbl_summary(df) %>% add_p()  # 如果 add_p() 发 warningtbl 变成 NULL
}, warning = function(w) {
  invokeRestart("muffleWarning")  # 在 tryCatch 中无效!
})

# ✅ 正确withCallingHandlers 处理 warning不中断执行tryCatch 只捕获 error
tbl <- tryCatch(
  withCallingHandlers(
    { tbl_summary(df) %>% add_p() },
    warning = function(w) {
      warnings_list <<- c(warnings_list, w$message)
      invokeRestart("muffleWarning")
    }
  ),
  error = function(e) { return(NULL) }
)

陷阱 4gtsummary table_body 的 p.value 是 list 列

错误信息: invalid subscript type 'list'

原因: gtsummary 的内部数据结构 tbl$table_body$p.value 是 list 列(每个元素可能是 NULL 或 numeric不能直接用 < 比较。

# ❌ 错误:直接对 list 列做比较
p_rows <- body[body$p.value < 0.05, ]  # 报错!

# ✅ 正确:先 unlist + as.numeric
p_vals <- as.numeric(unlist(body$p.value))
sig_idx <- which(!is.na(p_vals) & p_vals < 0.05)

陷阱 5浮点数比较不能用 ==

错误信息: 无明确错误,但条件判断逻辑错误

# ❌ 错误:直接比较浮点数
if (sd(values) == 0) { ... }  # 可能因精度问题漏判

# ✅ 正确:使用容差比较
if (isTRUE(sd(values) < .Machine$double.eps^0.5)) { ... }

陷阱 6变量可能为 NULL 导致 glue/round 崩溃

错误信息: non-numeric argument to mathematical functionsubscript out of bounds

原因: 某些统计结果字段(如 fstatistic)在边界条件下为 NULL。

# ❌ 错误:直接使用可能为 NULL 的值
log_add(glue("F = {round(f_stat[1], 2)}"))  # f_stat 为 NULL 时崩溃

# ✅ 正确:先检查再使用
if (!is.null(f_stat)) {
  log_add(glue("F = {round(f_stat[1], 2)}"))
} else {
  log_add("F = NA")
}

陷阱 7新增 R 包后 utils/ 修改需要重启容器

现象: make_table_block() 新增了 metadata 参数,但调用时报 unused argument

原因: utils/*.R 在服务启动时一次性加载,不像 tools/*.R 有热重载。修改后必须:

cd r-statistics-service
docker-compose restart

陷阱 8meta 包返回对数尺度效应量v1.4 新增)

现象: Meta 分析返回 HR = -0.52,但期望 HR ≈ 0.59

原因: meta R 包的 metagen()/metabin() 对比值类效应量HR、OR、RR对数尺度上计算。TE.random/TE.fixed 返回的是 log(HR)log(OR),而非原始比值。

# ❌ 错误:直接返回 TE对数尺度
pooled_effect = ma_result$TE.random  # 返回 -0.52(这是 log(HR)

# ✅ 正确:对比值类指标做 exp() 反变换
is_ratio <- sm_label %in% c("HR", "OR", "RR")
display_te <- if (is_ratio) exp(pooled_te) else pooled_te  # 返回 0.59(真实 HR

影响范围:meta_analysis.R不影响其他统计工具。连续型指标MD无需反变换。

6.7 开发环境新增 R 包

当新工具依赖尚未安装的 R 包时,有两种方式:

方式 1临时安装到运行中的容器开发测试用

# 容器以 appuser 运行,无写权限,需用 root
docker exec -u root ssa-r-statistics R -e "install.packages('新包名', repos='https://cloud.r-project.org/', quiet=TRUE)"

注意:容器重启后丢失,仅用于开发验证。

方式 2更新 Dockerfile 并重建镜像(正式方案)

  1. Dockerfileinstall.packages() 中添加新包名
  2. 重建:docker-compose up -d --build

7. 运维指南

7.1 日志查看

# 实时日志
docker logs -f ssa-r-statistics

# 最近 100 行
docker logs --tail 100 ssa-r-statistics

7.2 性能监控

# 容器资源使用
docker stats ssa-r-statistics

7.3 重启服务

# 开发环境
docker-compose restart

# 生产环境 (SAE)
通过 SAE 控制台重启实例

7.4 镜像更新

# 1. 构建新镜像
docker build -t ssa-r-statistics:1.0.2 .

# 2. 推送到镜像仓库
docker tag ssa-r-statistics:1.0.2 registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.2
docker push registry.cn-beijing.aliyuncs.com/aiclinical/ssa-r-statistics:1.0.2

# 3. 更新 SAE 部署

8. 常见问题

Q1: 构建时 httpuv 安装失败

错误: fatal error: zlib.h: No such file or directory

解决: 添加 zlib1g-dev 到系统依赖

Q2: 构建时 nloptr 安装失败

错误: CMAKE NOT FOUND

解决: 添加 cmake 到系统依赖

Q3: /tmp 权限问题

错误: cannot open file '/tmp/Rtmpxxx': No such file or directory

解决: 不要在启动命令中清理 /tmp

Q4: DEV_MODE 热重载不生效

原因: 没有挂载 volumes

解决:

volumes:
  - ./tools:/app/tools

Q5: 容器启动后无法访问

检查:

  1. 端口映射是否正确
  2. 健康检查是否通过
  3. 查看容器日志

Q6: 数据加载失败inline 模式)

错误: 内部错误: 数据加载失败

原因: 数据格式不正确,或数据为空

解决:

  1. 确保 data_source.data 是有效的 JSON 数组
  2. 行格式:[{"col1": val1}, {"col1": val2}]
  3. 检查是否有空数据或全 NA 列

Q7: R 脚本语法错误

错误: unexpected symbollexical error

常见原因:

  1. glue() 字符串中使用 \' 转义(应直接使用 '
  2. 中文注释编码问题
  3. 代码块中的花括号不匹配

解决:

# 错误glue 中的转义
glue("# Cramer\'s V = ...")  # ❌

# 正确:直接使用单引号或避免
glue("# Cramer V = ...")     # ✅

Q8: JSON 序列化失败

错误: No method asJSON S3 class: table

原因: R 的 table 对象无法直接序列化为 JSON

解决:

# 错误
observed = as.matrix(contingency_table)  # ❌ 可能保留 table 属性

# 正确:显式转换为纯数值矩阵
observed = matrix(
  as.numeric(contingency_table),
  nrow = nrow(contingency_table),
  ncol = ncol(contingency_table)
)  # ✅

Q9: 新端点返回 404

原因: 修改 plumber.R 后未重启服务

解决:

# 修改 plumber.R 后必须重启
docker-compose restart

# 如果修改了 docker-compose.yml如添加新 volume
docker-compose up -d --force-recreate

Q10: 变量类型判断错误missing value where TRUE/FALSE needed

原因: 对包含 NA 的数据进行布尔比较

解决:

# 错误
if (var_type == "numeric") { ... }  # var_type 可能是 NA

# 正确
if (identical(var_type, "numeric")) { ... }  # ✅ 处理 NA

Q11: 修改 utils/ 后新参数报 unused argument

原因: utils/*.R(如 block_helpers.R)在服务启动时加载进内存,不支持热重载(与 tools/*.R 不同)。

解决:

docker-compose restart

Q12: Docker 已 build 但包仍不存在(there is no package called 'xxx'

原因: docker-compose.yml 中的 volumes 挂载会覆盖镜像中的文件,但不影响已安装的 R 包。常见场景是更新了 Dockerfile 却只用了 docker-compose up -d 而没有加 --build

解决:

# 确保 rebuild
docker-compose up -d --build

# 或临时装包(开发验证)
docker exec -u root ssa-r-statistics R -e "install.packages('xxx', repos='https://cloud.r-project.org/', quiet=TRUE)"

Q13: 工具返回成功但 report_blocks 为空

原因: 返回结构中没有 report_blocks 字段或 blocks 列表为空。

检查清单:

  1. 确认使用了 utils/block_helpers.R 的辅助函数构建 blocks
  2. 确认 return 中包含 report_blocks = blocks
  3. 确认每个 block 至少包含 type 字段
  4. 用测试脚本验证:node r-statistics-service/tests/run_all_tools_test.js

9. 测试指南

9.1 单工具测试

# 测试 T 检验
curl -s -X POST "http://localhost:8082/api/v1/skills/ST_T_TEST_IND" \
  -H "Content-Type: application/json" \
  -d '{
    "data_source": {
      "type": "inline",
      "data": [
        {"group": "A", "value": 23}, {"group": "A", "value": 25},
        {"group": "B", "value": 30}, {"group": "B", "value": 32}
      ]
    },
    "params": {"group_var": "group", "value_var": "value"}
  }'

9.2 健康检查

curl -s http://localhost:8082/health | jq

9.3 R 工具集中测试脚本13 工具 + JIT

项目提供了 R 统计引擎的全工具测试脚本:

# 仅测试 R 服务层12 工具 + JIT 护栏 + report_blocks 校验)
node r-statistics-service/tests/run_all_tools_test.js

测试覆盖:

  • 13 个统计工具Phase 2A × 7 + Phase Deploy × 5 + ASL × 1
  • JIT 护栏检查ST_T_TEST_IND / ST_ANOVA_ONE / ST_FISHER / ST_LINEAR_REG
  • report_blocks 协议校验类型、必填字段、metadata

9.4 端到端测试脚本(三层联调)

三层联调测试覆盖 R → Python → Node.js

cd docs/03-业务模块/SSA-智能统计分析/05-测试文档
node run_e2e_test.js

测试覆盖:

  • Layer 1: R 服务13 个统计工具 + JIT 护栏)
  • Layer 2: Python DataProfile API
  • Layer 3: Node.js 后端 API登录 → 会话 → 规划 → 执行)

9.5 Meta 分析引擎专项 E2E 测试v1.4 新增)

覆盖 Meta 分析的完整链路Node.js → R Docker → 森林图/漏斗图生成):

cd backend
npx tsx src/modules/asl/meta-analysis/__tests__/meta-e2e-test.ts

测试覆盖8 个测试项36 个断言):

  • R 服务健康检查 + meta_analysis 工具注册验证
  • HR 风险比 Meta 分析5 个研究,随机效应)
  • 二分类 OR Meta 分析4 个研究)
  • 二分类 RR Meta 分析(效应指标切换)
  • 连续型 MD Meta 分析5 个研究)
  • 固定效应模型切换
  • 边界条件:仅 1 个研究(应返回错误)
  • 森林图 + 漏斗图 Base64 PNG 生成验证

最近一次测试结果2026-02-26

Results: 36 passed, 0 failed (1.3s)
HR = 0.5948 [0.5255, 0.6733], I² = 15.4%
OR = 0.4530 [0.3328, 0.6166], p < .001
MD = -1.4923 [-1.9098, -1.0749], I² = 70%

附录:文件结构

r-statistics-service/
├── Dockerfile              # 生产镜像定义(含 gtsummary/gt/broom/scales/gridExtra/meta
├── docker-compose.yml      # 开发环境编排(含 volume 挂载)
├── renv.lock               # R 包版本锁定(备用)
├── .Rprofile               # R 启动配置(备用)
├── plumber.R               # API 入口(含 JIT 护栏端点,自动发现 tools/Agent withCallingHandlers 错误处理)
├── utils/
│   ├── data_loader.R       # 数据加载(支持行格式/列格式)
│   ├── guardrails.R        # 统计护栏 + JIT 检查12 工具全覆盖)
│   ├── error_codes.R       # 错误映射20+ 模式 + format_agent_error 结构化错误)
│   ├── result_formatter.R  # 结果格式化
│   └── block_helpers.R     # Block-based 输出辅助函数Phase E+ 协议)
├── tools/                  # 统计工具13 个)
│   ├── t_test_ind.R        # 独立样本 T 检验
│   ├── t_test_paired.R     # 配对 T 检验
│   ├── mann_whitney.R      # Mann-Whitney U 检验
│   ├── chi_square.R        # 卡方检验
│   ├── correlation.R       # 相关分析
│   ├── logistic_binary.R   # 二元 Logistic 回归
│   ├── descriptive.R       # 描述性统计
│   ├── fisher.R            # 🆕 Fisher 精确检验Phase Deploy
│   ├── anova_one.R         # 🆕 单因素方差分析Phase Deploy
│   ├── wilcoxon.R          # 🆕 Wilcoxon 符号秩检验Phase Deploy
│   ├── linear_reg.R        # 🆕 线性回归Phase Deploy
│   ├── baseline_table.R    # 🆕 基线特征表 — 复合工具Phase Deploy
│   └── meta_analysis.R     # 🆕 Meta 分析引擎 — HR/二分类/连续型ASL 工具 5
├── tests/
│   ├── run_all_tools_test.js  # 🆕 全工具自动化测试12 工具 + JIT + blocks 校验)
│   ├── test_t_test.json       # T 检验测试数据
│   ├── test_fisher.json       # Fisher 测试数据
│   ├── test_anova_one.json    # ANOVA 测试数据
│   ├── test_wilcoxon.json     # Wilcoxon 测试数据
│   ├── test_linear_reg.json   # 线性回归测试数据
│   ├── test_baseline_table.json # 基线表测试数据
│   └── fixtures/
│       └── normal_data.csv    # 测试数据
├── metadata/               # 工具元数据(预留)
└── templates/              # 解释模板(预留)

更新日志

版本 日期 更新内容
v1.6 2026-03-07 Agent 通道错误处理增强:execute-code 端点新增结构化错误响应(error_type/error_line/error_context/console_outputwithCallingHandlers 捕获 warnings+messagesformat_agent_error() 从 20+ 模式匹配中提取错误分类+行号+上下文+修复建议§5.5),便于 CoderAgent 自动修复重试
v1.5 2026-03-02 SSA 双通道架构:新增 POST /api/v1/execute-code 沙箱端点§5.5)供 Agent 通道执行 LLM 生成的 R 代码,含超时 + 隔离环境;架构图新增 Agent 通道入口
v1.4 2026-02-26 ASL Meta 分析引擎:工具 12→13+ST_META_ANALYSISDockerfile 新增 meta 包,新增 §5.7 Meta 分析 API 示例、陷阱 8对数尺度反变换、§9.5 Meta E2E 测试36 断言全通过),架构图更新 ASL 调用方
v1.3 2026-02-22 开发者体验增强:新工具模板补全 report_blocks§6.1)、各工具 params 速查表§6.5、R 语言 7 大陷阱实录§6.6)、新增 R 包操作指南§6.7)、新增 Q11-Q13 常见问题
v1.2 2026-02-22 Phase Deploy 完成:工具 7→12+Fisher/ANOVA/Wilcoxon/线性回归/基线表、Dockerfile 新增 gtsummary 等 5 包、Block-based 输出协议文档化§6.4)、全工具测试脚本
v1.1 2026-02-20 Phase 2A 完成7 个统计工具、JIT 护栏、热重载说明、常见问题补充
v1.0 2026-02-19 初始版本架构设计、部署指南、T 检验工具

文档结束