Commit f261dcec authored by wuzekai's avatar wuzekai

更新了大模型调用方式

parent b3e914bc
......@@ -3,11 +3,12 @@
S3method(dtab,explore)
S3method(store,explore)
S3method(summary,explore)
export(ai_generate)
export(ai_get_data_call)
export(ai_run_code)
export(build_r_prompt)
export(chat_completion)
export(build_chart_prompt)
export(build_metrics_prompt)
export(chart_completion)
export(chart_generate)
export(chart_get_data_call)
export(chart_run_code)
export(cv)
export(does_vary)
export(empty_level)
......@@ -16,6 +17,10 @@ export(flip)
export(ln)
export(me)
export(meprop)
export(metrics_completion)
export(metrics_generate)
export(metrics_get_data_call)
export(metrics_run_code)
export(modal)
export(n_missing)
export(n_obs)
......
# === 配置 ===
MODELSCOPE_OPENAI_URL <- "https://api-inference.modelscope.cn/v1"
MODELSCOPE_API_KEY <- Sys.getenv("MODELSCOPE_API_KEY", "ms-5b9f3668-ea8e-4a2c-8cd3-a1a9ba04810b")
MODEL_ID <- "deepseek-ai/DeepSeek-V3.1"
OLLAMA_URL <- "http://180.169.131.147:8139/api/generate"
MODEL_ID <- "qwen3-coder:30b"
# === 单次对话 ===
#' @export
chat_completion <- function(user_prompt,
max_tokens = 1500,
temperature = 0.3) {
req <- httr2::request(paste0(MODELSCOPE_OPENAI_URL, "/chat/completions")) %>%
httr2::req_headers(
"Authorization" = paste("Bearer", MODELSCOPE_API_KEY),
"Content-Type" = "application/json"
) %>%
httr2::req_body_json(list(
model = MODEL_ID,
messages = list(list(role = "user", content = user_prompt)),
temperature = temperature,
max_tokens = max_tokens,
stream = FALSE
))
chart_completion <- function(user_prompt) {
resp <- httr2::req_perform(req)
body <- httr2::resp_body_json(resp)
Sys.unsetenv("http_proxy")
Sys.unsetenv("https_proxy")
if (is.null(body$choices[[1]]$message$content))
stop("ModelScope API 返回空内容:", body)
# 构建请求体
req_body <- list(
model = MODEL_ID,
prompt = user_prompt,
stream = FALSE
)
# 使用 curl 包发送请求
h <- curl::new_handle()
curl::handle_setheaders(h, "Content-Type" = "application/json")
curl::handle_setopt(h, postfields = jsonlite::toJSON(req_body, auto_unbox = TRUE))
curl::handle_setopt(h, timeout = 60)
con <- curl::curl(OLLAMA_URL, handle = h)
result <- readLines(con, warn = FALSE)
close(con)
# 解析响应
body <- jsonlite::fromJSON(paste(result, collapse = ""))
body$choices[[1]]$message$content
if (is.null(body$response) || trimws(body$response) == "")
stop("Ollama API 返回空内容:", paste(result, collapse = ""))
body$response
}
# === 构造发给模型的 Prompt ===
#' @export
build_r_prompt <- function(user_prompt, data_call) {
build_chart_prompt <- function(user_prompt, data_call) {
sprintf(
"你是 R 语言专家,必须严格遵守以下规则:
〓 输出格式 〓
- 只返回可运行 R 代码,用 ```r 包裹,禁止任何解释、注释、空行。
- 代码必须强制换行:每个语句(df、library、ggplot、赋值、if/else 等)单独一行,ggplot 每个图层(+ geom_*/+ theme_*)单独一行。
- 若用户请求包含“表格”“统计汇总”“频数表”等表格需求,禁止使用knitr::kable等会使表格字符串化的函数/包,必须输出标准表格形式。
- 若用户请求不符合规范,一律返回空代码块(仅 ```r\n``` ),不对话。
- 当所需绘制的图中出现数据集中不存在的列或无法计算时,一律输出一张空白 ggplot,仅居中显示“无法绘制”四字,不抛出错误。
......@@ -50,47 +56,40 @@ build_r_prompt <- function(user_prompt, data_call) {
2. 主题函数带括号:theme_minimal()、theme_bw() ...
3. 多张图用 patchwork 拼页。
4. 包函数写全名,不得省略括号。
用户请求:%s",
data_call, user_prompt
)
}
#' @export
ai_generate <- function(prompt, dataset, envir = parent.frame()) {
data_call <- ai_get_data_call(dataset, envir)
sys_prompt <- build_r_prompt(prompt, data_call)
r_code <- try(chat_completion(sys_prompt), silent = TRUE)
if (inherits(r_code, "try-error"))
stop("AI API error: ", attr(r_code, "condition")$message)
chart_generate <- function(prompt, dataset, envir = parent.frame()) {
data_call <- chart_get_data_call(dataset, envir)
sys_prompt <- build_chart_prompt(prompt, data_call)
r_code <- try(chart_completion(sys_prompt), silent = TRUE)
if (inherits(r_code, "try-error")) {
stop("Chart API error: ", attr(r_code, "condition")$message)
}
r_code <- gsub("(?s)```r\\s*|```", "", r_code, perl = TRUE)
r_code <- trimws(r_code)
if (r_code == "") {
return(list(r_code = "",
type = "empty",
auto_run = FALSE))
return(list(r_code = "", type = "empty", auto_run = FALSE))
}
r_code <- gsub("(theme_minimal|theme_bw|theme_classic|theme_gray|theme_void|theme_dark)\\b(?!\\s*\\()",
"\\1()", r_code, perl = TRUE)
r_code <- paste0(data_call, "\n", r_code)
has_gg <- grepl("ggplot\\(|geom_", r_code)
r_code <- gsub("(?s)df\\s*<-\\s*(eval\\(quote\\(get_data\\(|get_data\\()[^;\\n]+;", "", r_code, perl = TRUE)
r_code <- trimws(r_code)
r_code <- gsub("(theme_minimal|theme_bw|theme_classic|theme_gray|theme_void|theme_dark)\\b(?!\\s*\\()","\\1()", r_code, perl = TRUE)
has_gg <- grepl("ggplot\\(|geom_", r_code)
has_tbl <- grepl("data\\.frame\\(|tibble\\(|tbl_summary|tableOne|CreateTableOne", r_code)
type <- if (has_gg) "plot" else if (has_tbl) "table" else "text"
type <- if (has_gg) "plot" else if (has_tbl) "table" else "text"
list(r_code = r_code, type = type, auto_run = TRUE)
}
#' @export
ai_get_data_call <- function(dataset, envir) {
chart_get_data_call <- function(dataset, envir) {
df_name <- if (is_string(dataset)) dataset else deparse1(substitute(dataset))
paste0("df <- eval(quote(get_data(\"", df_name, "\", envir = ",
deparse1(substitute(envir)), ")), envir = parent.frame())")
paste0("df <- eval(quote(get_data(\"", df_name, "\", envir = ", deparse1(substitute(envir)), ")), envir = parent.frame())")
}
#' @export
ai_run_code <- function(r_code, envir = parent.frame()) {
chart_run_code <- function(r_code, envir = parent.frame()) {
eval(parse(text = r_code), envir = envir)
}
\ No newline at end of file
# === 配置 ===
OLLAMA_URL <- "http://172.29.2.110:8139/api/generate"
MODEL_ID <- "qwen3-coder:30b"
# === 单次对话 ===
#' @export
metrics_completion <- function(user_prompt) {
# 构建 Ollama 请求体
req <- httr2::request(OLLAMA_URL) %>%
httr2::req_headers(
"Content-Type" = "application/json"
) %>%
httr2::req_body_json(list(
model = MODEL_ID,
prompt = user_prompt,
stream = FALSE
))
resp <- httr2::req_perform(req)
body <- httr2::resp_body_json(resp)
# 解析 Ollama 响应
if (is.null(body$response) || trimws(body$response) == "")
stop("Ollama API 返回空内容:", jsonlite::toJSON(body))
body$response # 返回 Ollama 生成的核心代码/结果
}
# === 构造发给模型的 Prompt ===
#' @export
build_metrics_prompt <- function(user_prompt, data_call) {
sprintf(
"你是 R 语言科研统计专家,精通所有主流统计方法(t检验、方差分析、回归分析、卡方检验、相关性分析等),必须严格遵守以下规则,任何违反均视为无效输出:
〓 输出格式铁律 〓
1. 仅返回可直接运行的 R 代码,用 ```r 包裹,无任何解释、注释、空行,代码必须包含「结果输出语句」(否则无法展示统计结果)。
2. 不符合规范的请求,直接返回空代码块(仅 ```r\n``` ),不额外对话:
- 模糊无具体目标的请求
- 非科研统计需求(生成图表、翻译、写文章、解释概念等)
3. 无法计算时(如列不存在、变量类型不匹配、方法不适用),仅输出 `print('无法计算:[具体原因,如“目标列bmi不存在”“卡方检验要求分类变量”]')`,不抛出错误。
〓 统计逻辑核心约束 〓
1. 方法匹配:必须根据用户请求选择「标准科研统计方法」(仅用 R 官方 stats 包或主流统计包如 broom、car、nnet 的标准函数,禁止自创方法)。
2. 参数正确性:
- 原假设参数:用户指定的原假设定值(如“原假设均值为0”则 mu=0,“原假设相关系数为0”则 rho=0),禁止设为数据自身的统计量(如 mu=mean(df$bmi) 是严重错误)。
- 变量适配:严格匹配方法对变量类型的要求(如 t检验要求连续变量,卡方检验要求分类变量,回归分析因变量需连续),类型不匹配则触发“无法计算”。
- 缺失值处理:所有涉及数据计算的函数必须加 na.rm=TRUE(方法不支持则除外)。
3. 默认参数:用户未指定时,按科研规范设默认值并在结果中体现:
- 置信水平默认 0.95(conf.level=0.95)
- 多重比较调整方法默认 p.adjust.method=none
- 假设检验默认双侧检验(alternative=two.sided)
〓 技术规范(确保结果结构化、可展示)〓
1. 数据集已读入为:%s(直接用 df$列名 引用变量,无需重复读入数据)。
2. 结果输出要求:
- 优先用 broom::tidy() 或 broom::glance() 将统计结果转为结构化数据框(包含统计量、p值、置信区间、自由度、显著性标记等核心指标),再用 print() 输出。
- 若 broom 包不支持该方法(如部分复杂模型),直接 print(统计函数结果),确保保留显著性标记(***、**、*)。
3. 函数规范:统计函数必须写全名+完整括号(如 t.test()、lm()、chisq.test()),禁止省略括号或简写。
4. 变量校验:自动校验变量存在性和类型(如分组变量必须是因子/字符型,连续变量不能用于卡方检验),校验失败则输出“无法计算”。
用户请求:%s",
data_call, user_prompt
)
}
#' @export
metrics_generate <- function(prompt, dataset, envir = parent.frame()) {
data_call <- metrics_get_data_call(dataset, envir)
sys_prompt <- build_metrics_prompt(prompt, data_call)
r_code <- try(metrics_completion(sys_prompt), silent = TRUE)
if (inherits(r_code, "try-error"))
stop("Metrics API error: ", attr(r_code, "condition")$message)
r_code <- gsub("(?s)```r\\s*|```", "", r_code, perl = TRUE)
r_code <- trimws(r_code)
if (r_code == "") {
return(list(r_code = "",
type = "empty",
auto_run = FALSE))
}
r_code <- paste0(data_call, "\n", r_code)
has_gg <- grepl("ggplot\\(|geom_", r_code)
has_tbl <- grepl("data\\.frame\\(|tibble\\(|tbl_summary|tableOne|CreateTableOne|t.test|lm|cor.test|anova", r_code)
type <- if (has_gg) "plot" else if (has_tbl) "table" else "text"
list(r_code = r_code, type = type, auto_run = TRUE)
}
#' @export
metrics_get_data_call <- function(dataset, envir) {
df_name <- if (is_string(dataset)) dataset else deparse1(substitute(dataset))
paste0("df <- eval(quote(get_data(\"", df_name, "\", envir = ",
deparse1(substitute(envir)), ")), envir = parent.frame())")
}
#' @export
metrics_run_code <- function(r_code, envir = parent.frame()) {
eval(parse(text = r_code), envir = envir)
}
help_quickgen <- c(
"一键生成描述性统计" = "quickgen_basic.md",
"大模型对话引导助手" = "quickgen_chat.md",
"大模型生成描述性统计" = "quickgen_ai.md"
"大模型生成描述性统计(图表)" = "quickgen_chart.md",
"大模型生成描述性统计(指标)" = "quickgen_metrics.md"
)
output$help_quickgen <- reactive(append_help("help_quickgen", file.path(getOption("radiant.path.quickgen"), "app/tools/help"), Rmd = TRUE))
......
......@@ -2,7 +2,7 @@
r_url_list <- getOption("radiant.url.list")
r_url_list[["Generate descriptive statistics with one click"]] <- "quickgen/basic/"
r_url_list[["AI chat guidance"]] <- "quickgen/chat/"
r_url_list[["LLM generates descriptive statistics"]] <- "quickgen/ai/"
r_url_list[["AI generates descriptive statistics(chart)"]] <- "quickgen/chart"
options(radiant.url.list = r_url_list)
rm(r_url_list)
......@@ -15,9 +15,12 @@ options(
tags$head(
tags$script(src = "www_quickgen/js/run_return.js")
),
"----", i18n$t("Quick Statistics"),
tabPanel(i18n$t("Generate descriptive statistics with one click"), uiOutput("quickgen_basic")),
"----", i18n$t("AI assistance"),
tabPanel(i18n$t("AI chat guidance"), uiOutput("quickgen_chat")),
tabPanel(i18n$t("AI generates descriptive statistics"), uiOutput("quickgen_ai"))
tabPanel(i18n$t("AI generates descriptive statistics(chart)"), uiOutput("quickgen_chart")),
tabPanel(i18n$t("AI generates descriptive statistics(metrics)"), uiOutput("quickgen_metrics")),
)
)
)
\ No newline at end of file
......@@ -1145,7 +1145,7 @@ observeEvent(input$qgb_invert_selection, {
output$quickgen_basic <- renderUI({
stat_tab_panel(
menu = i18n$t("Oneclick generation > Generate descriptive statistics"),
menu = i18n$t("Oneclick generation > Quick Statistics"),
tool = i18n$t("Generate descriptive statistics with one click"),
tool_ui = "ui_quickgen_basic",
output_panels = tagList(
......
......@@ -6,7 +6,7 @@ output$quickgen_chat <- renderUI({
tagList(
useShinyjs(),
stat_tab_panel(
menu = i18n$t("One-click generation > AI chat guidance"),
menu = i18n$t("Oneclick generation > AI assistance"),
tool = i18n$t("AI chat guidance"),
tool_ui = "chat_main_ui",
output_panels = tabPanel(
......
This diff is collapsed.
> 大模型生成描述性统计
## 使用方法
以下是 `大模型生成描述性统计`的使用方法。
1.`数据对象`必须要有,且必须和数据集中的名字一致(大小写也一致)
2.`图像类型`必须要有,比如分布图、散点图等。可以要求模型返回多张图表,需向模型明确。
3.`生成代码`如果有误,或者想要修改,可以点击`编辑`按钮对R代码进行修改,保存后点击`运行`按钮即可。
## 示例
**1. 散点图**
请用diamonds画一个散点图,X轴是carat,Y轴是price,用color来区分颜色,并加上theme_bw()。
**2. 箱线图**
请用diamonds画箱线图,把price按cut分组,看看不同切工的价格分布。
**3. 直方图**
请用diamonds画carat的直方图,分面按clarity排布,bin宽度取0.1。
**4. 柱状图**
请用diamonds统计各color等级的数量,画一个柱状图,颜色按实际颜色填充。
**5. 密度图**
请用diamonds画出0.5~2克拉范围内,不同color的carat密度曲线,要求半透明重叠。
**6. 分组均值表**
请用diamonds按cut分组,计算每组price与carat的平均值、标准差,输出成表格。
**7. 价格对数-克拉线性拟合图**
请用diamonds画log(price)和carat的散点图,并加上回归直线,颜色按clarity区分,用theme_minimal()。
\ No newline at end of file
> 大模型生成描述性统计(图表)
## 使用方法
以下是 `大模型生成描述性统计`的使用方法。
1.`数据对象`必须要有,且必须和数据集中的名字一致(大小写也一致)
2.`图像类型`必须要有,比如分布图、散点图等。可以要求模型返回多张图表,需向模型明确。
3.`生成代码`如果有误,或者想要修改,可以点击`编辑`按钮对R代码进行修改,保存后点击`运行`按钮即可。
## 示例
**1. 散点图**
请用该数据集画一个散点图,X 轴是 bmi(身体质量指数),Y 轴是 charges(年度医疗费用),用 smoker(吸烟状态)来区分颜色。
**2. 箱线图**
请用该数据集画箱线图,把 charges 按 smoker 分组,看看吸烟 / 不吸烟人群的医疗费用分布。
**3. 直方图**
请用该数据集画 age(年龄)的直方图,分面按 sex(性别)排布,bin 宽度取 2(年龄区间)。
**4. 柱状图**
请用该数据集统计各 region(居住区域)的样本数量,画一个柱状图,颜色按 region 实际类别填充。
**5. 密度图**
请用该数据集画出 bmi 在 18~35 范围内,不同 sex 的 bmi 密度曲线,要求半透明重叠展示。
**6. 分组均值表**
请用该数据集按 sex 分组,计算每组 age、bmi、charges 的平均值、标准差,输出成结构化表格。
**7. 费用 - 年龄线性拟合图**
请用该数据集画 charges 和 age 的散点图,并加上回归直线,颜色按 smoker 区分。
**8. 交叉频数表**
请用该数据集统计 sex 与 smoker 的交叉频数(即 “男性吸烟 / 不吸烟人数、女性吸烟 / 不吸烟人数”),输出二维汇总表格。
**9. 儿童数量分布柱状图**
请用该数据集统计 children(受抚养者数量)的不同取值对应的样本数,画一个柱状图展示各数量的分布。
**10. 区域 - 费用箱线图**
请用该数据集画箱线图,把 charges 按 region 分组,对比不同区域的医疗费用差异,并用不同颜色标注。
\ No newline at end of file
> 大模型对话引导助手
### 1. 界面概述
本工具是 **“一键生成” 模块下的 R 语言科研辅助助手 **,专为数据集的科研分析设计,可自动结合数据集字段信息,为你提供字段解读、分析方法建议、统计结果解读等科研支持。
### 2. 使用步骤
1. **确认数据集**:进入工具后,,字段信息会自动加载至智能体;
2. 发起对话
- 方式 1:点击预设问题按钮,快速发起提问;
- 方式 2:在底部输入框中自定义科研问题,点击右侧发送按钮提交;
3. **获取回复**:AI 会结合传入的字段信息,生成科研场景下的专业回复;
4. **重置对话**:若需切换分析主题,点击 “开启新对话” 清空历史即可。
### 3. 常见操作示例
#### 示例 1:字段解读(预设问题 1)
- 操作:点击 “这个数据集中的字段是什么意思?有什么科研分析用途?” 按钮
- 效果:AI 会解释`insurance``age`(年龄)、`smoker`(吸烟状态)、`charges`(医疗费用)等字段的含义,并说明各字段的科研分析场景(如`smoker`可用于分析吸烟对医疗费用的影响)。
#### 示例 2:分析方法建议(预设问题 2)
- 操作:点击 “基于当前数据集的字段信息,我该选择什么科研分析方法?” 按钮
- 效果:AI 会根据`insurance`的字段类型(如分类变量`sex`、数值变量`charges`),推荐适配的统计方法(如箱线图分析分组费用差异、线性回归分析年龄对费用的影响)。
#### 示例 3:统计结果解读(预设问题 3)
- 操作:点击 “我得到的科研结果(比如模型参数、统计指标)该怎么解读?” 按钮
- 效果:若你提供具体结果(如 “`smoker``charges`的回归系数是 20000”),AI 会解读该指标的科研意义(如 “吸烟人群的年度医疗费用平均比非吸烟人群高 20000 单位”)。
#### 示例 4:自定义科研问题
- 输入:“帮我说明用`insurance`数据集中`bmi``charges`做散点图的科研意义是什么?”
- 效果:AI 会结合两个字段的含义,解释散点图可用于观察身体质量指数与医疗费用的相关性,为后续回归分析提供可视化依据。
### 4. 注意事项
1. 字段信息会**自动传递给 AI**,无需手动输入数据集字段内容;
2. “开启新对话” 会清空当前历史,若需保留对话记录,请勿点击该按钮。
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment