欢迎光临
我们一直在努力

将Bibliometrix接入国产AI deepseekv4(个人笔记)

引文

本文仅为个人学习笔记,内容都是我在 AI 辅助下做的摸索实践,非专业权威教程,有不对的地方恳请各位大佬在评论区指正,轻喷~

在使用 Bibliometrix 开展文献计量分析的学习过程中,我发现该工具虽内置 AI 功能,但仅原生支持对接 Google Gemini 服务。恰逢 DeepSeek V4 模型发布,为实现以更低成本调用 AI 能力辅助文献计量工作,我摸索并实现了 Bibliometrix 对国产大模型服务的接入适配。本文以硅基流动平台提供的deepseek-ai/DeepSeek-V4-Flash、Qwen/Qwen3.6-27B两款模型为例,为方便后续扩展对接其他模型,全程采用 OneAPI 进行接口中

目录

引文

目录

Oneapi 安装和配置

下载Oneapi

Oneapi的安装配置

Bibliometrix包的修改

获取包路径

修改Bibliometrix包

成果


Oneapi 安装和配置

下载Oneapi

OneAPI 是一款托管于 GitHub 的开源项目,可实现大模型接口的统一管理、协议转换与多模型分发,能够便捷地将非 OpenAI 协议的模型接口转换为标准 OpenAI 格式,完美适配本次适配需求。

oneapi的guihub链接

找到Releases 点开 然后找到exe文件进行下载

Oneapi的安装配置

  • 双击运行下载完成的 OneAPI 安装包,按页面指引完成安装并进入 OneAPI 管理后台。
  • 使用系统默认账号密码登录后台:
    • 账号:root
    • 密码:123456

进入One API

登录后,依次点击左侧菜单栏「渠道」-「添加渠道」,按以下规则完成核心配置:

  • 自定义渠道名称(便于区分不同模型,如 “硅基流动 – DeepSeek V4”);
  • 「模型」栏填写从硅基流动平台复制的对应模型全称(如deepseek-ai/DeepSeek-V4-Flash、Qwen/Qwen3.6-27B);
  • 「代理」地址栏填写硅基流动官方 API 接口地址:https://api.siliconflow.cn;其余配置项保持默认即可。配置完成后点击「测试」按钮,若提示测试成功,即代表渠道配置生效;若测试失败,请核对模型名称、API 地址、硅基流动平台的 API 密钥是否正确,以及账号是否有对应模型的调用权限。
  •  硅基流动api地址:https://api.siliconflow.cn

访问令牌创建渠道配置完成后,依次点击左侧菜单栏「令牌」-「添加令牌」,按需创建访问令牌(本地测试建议创建不限时、不限金额的令牌)。令牌创建成功后,请复制并妥善保存令牌内容,后续源码配置将使用该令牌。

Bibliometrix包的修改

获取包路径

打开 RStudio,在控制台输入以下代码,即可获取 Bibliometrix 包的本地安装绝对路径:

修改Bibliometrix包

  • 根据上述代码输出的路径,打开 Bibliometrix 包的安装目录,依次进入biblioshiny子文件夹,找到并打开biblioAI.R文件(该文件为 Bibliometrix 内置 AI 功能的核心源码文件)。
  • 在文件中找到原生的gemini_ai函数,将其完整替换为以下适配后的代码:

gemini_ai <- function(
image = NULL,
docs = NULL,
prompt = "Explain these images",
model = "2.5-flash",
image_type = "png",
retry_503 = 5,
api_key = NULL,
outputSize = "medium"
) {
mime_doc_types <- list(
pdf = "application/pdf",
txt = "text/plain",
html = "text/html",
csv = "text/csv",
rtf = "text/rtf"
)

switch(
outputSize,
"small" = {
generation_config <- list(
temperature = 1,
maxOutputTokens = 8192,
topP = 0.95,
topK = 40,
seed = 1234
)
},
"medium" = {
generation_config <- list(
temperature = 1,
maxOutputTokens = 16384, #8192,
topP = 0.95,
topK = 40,
seed = 1234
)
},
"large" = {
generation_config <- list(
temperature = 1,
maxOutputTokens = 32768, #8192,
topP = 0.95,
topK = 40,
seed = 1234
)
},
"huge" = {
generation_config <- list(
temperature = 1,
maxOutputTokens = 131072, #8192,
topP = 0.95,
topK = 40,
seed = 1234
)
}
)

# # Default config
# generation_config <- list(
# temperature = 1,
# maxOutputTokens = 16384,#8192,
# topP = 0.95,
# topK = 40,
# seed = 1234
# )

# Build URL
model_query <- paste0("gemini-", model, ":generateContent")
url <- paste0(
"https://generativelanguage.googleapis.com/v1beta/models/",
model_query
)
if (is.null(api_key)) {
api_key <- Sys.getenv("GEMINI_API_KEY")
}

# Base structure of parts
parts <- list(list(text = prompt))

# Handle images if provided
if (!is.null(image)) {
if (!is.vector(image)) {
image <- as.vector(image)
}
mime_type <- paste0("image/", image_type)

for (img_path in image) {
if (!file.exists(img_path)) {
return(paste0("❌ Error: Image file does not exist: ", img_path))
}

image_data <- tryCatch(
base64enc::base64encode(img_path),
error = function(e) {
return(NULL)
}
)

if (is.null(image_data)) {
return(paste0("❌ Failed to encode image: ", img_path))
}

parts <- append(
parts,
list(
list(
inline_data = list(
mime_type = mime_type,
data = image_data
)
)
)
)
}
}

# Handle documents if provided
if (!is.null(docs)) {
if (!is.vector(docs)) {
docs <- as.vector(docs)
}
for (doc_path in docs) {
if (!file.exists(doc_path)) {
return(paste0("❌ Error: Document file does not exist: ", doc_path))
}

doc_data <- tryCatch(
base64enc::base64encode(doc_path),
error = function(e) {
return(NULL)
}
)

if (is.null(doc_data)) {
return(paste0("❌ Failed to encode document: ", doc_path))
}

doc_type <- tools::file_ext(doc_path) |> tolower()

if (doc_type %in% names(mime_doc_types)) {
mime_type <- mime_doc_types[[doc_type]]
} else {
mime_type <- "application/pdf" # Default to PDF if unknown type
}

parts <- append(
parts,
list(
list(
inline_data = list(
mime_type = "application/pdf",
data = doc_data
)
)
)
)
}
}

# Assemble request body
request_body <- list(
contents = list(
parts = parts
),
generationConfig = generation_config
)

# Retry loop
for (attempt in seq_len(retry_503)) {
# Build and send request
req <- request(url) |>
req_url_query(key = api_key) |>
req_headers("Content-Type" = "application/json") |>
req_body_json(request_body) |>
req_timeout(120)

resp <- tryCatch(
req_perform(req),
error = function(e) {
return(list(
status_code = stringr::str_extract(e$message, "(?<=HTTP )\\\\d+") |>
as.numeric(),
error = TRUE,
message = paste("❌ Request failed with error:", e$message)
))
}
)

# # Handle connection-level error
# if (is.list(resp) && isTRUE(resp$error)) {
# return(resp$message)
# }

# Retry on HTTP 503 or 429
if (resp$status_code %in% c(429, 503)) {
if (attempt < retry_503) {
message(paste0(
"⚠️ HTTP 503 (Service Unavailable) – retrying in 2 seconds (attempt ",
attempt,
"/",
retry_503,
")…"
))
Sys.sleep(min(2^attempt, 16))
next
} else {
return(
paste0(
"❌ HTTP 503: Service Unavailable.\\n",
"The Google Gemini servers are currently overloaded or under maintenance.\\n",
"All retry attempts failed (",
retry_503,
"). Please try again in a few minutes. Alternatively, consider using a different AI model with lower latency."
)
)
}
}

# HTTP errors
# 400 api key not valid
if (resp$status_code == 400) {
msg <- tryCatch(
{
parsed <- jsonlite::fromJSON(httr2::resp_body_string(resp))
parsed$error$message
},
error = function(e) {
"Please check your API key. It seems to be not valid!"
}
)
return(paste0("❌ HTTP ", resp$status_code, ": ", msg))
}
# Other HTTP errors
if (resp$status_code != 200) {
msg <- tryCatch(
{
parsed <- jsonlite::fromJSON(httr2::resp_body_string(resp))
parsed$error$message
},
error = function(e) {
"Service unavailable or unexpected error. Please check your API key and usage limit."
}
)

return(paste0("❌ HTTP ", resp$status_code, ": ", msg))
}

# Successful response
candidates <- httr2::resp_body_json(resp)$candidates
outputs <- unlist(lapply(candidates, \\(c) c$content$parts))
return(outputs[1])
}
}

替换为

gemini_ai <- function(
image = NULL,
docs = NULL,
prompt = "Explain these images",
model = "2.5-flash",
image_type = "png",
retry_503 = 5,
api_key = NULL,
outputSize = "medium"
) {
library(httr2)
library(base64enc)

# 1. 核心路由逻辑:根据 UI 的选择,动态切换 LLM 或 VLM
if (grepl("flash", tolower(model))) {
actual_model <- "deepseek-ai/DeepSeek-V4-Flash" # 或填写 DeepSeek-V3
use_vision <- FALSE
} else {
actual_model <- "Qwen/Qwen3.6-27B"
use_vision <- TRUE
}

# 把下面这串换成你在 OneAPI 生成的那个真实的、长长的 Token
api_key <- "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# 3. 动态调整最大输出 Token
max_val <- switch(outputSize, "small" = 2000, "medium" = 4000, "large" = 8000, "huge" = 16000, 8000)

# 4. 构建标准的 OpenAI 消息体
content_list <- list(list(type = "text", text = prompt))

# 5. 视觉处理:只有当模型是 VLM 且有图时,才打包图片数据
if (use_vision && !is.null(image)) {
for (img_path in as.vector(image)) {
if (file.exists(img_path)) {
img_b64 <- base64encode(img_path)
content_list <- append(
content_list,
list(list(
type = "image_url",
image_url = list(url = paste0("data:image/", image_type, ";base64,", img_b64))
))
)
}
}
}

request_body <- list(
model = actual_model,
messages = list(list(role = "user", content = content_list)),
max_tokens = max_val,
temperature = 0.7
)

# 6. 指向你的本地 OneAPI 接口
url <- "http://localhost:3000/v1/chat/completions"

# 7. 发送请求并解析返回结果
for (attempt in seq_len(retry_503)) {
req <- request(url) |>
req_headers("Authorization" = paste("Bearer", api_key), "Content-Type" = "application/json") |>
req_body_json(request_body) |>
req_timeout(180)

resp <- tryCatch(req_perform(req), error = function(e) list(error = TRUE, message = e$message))

if (is.list(resp) && isTRUE(resp$error)) {
if (attempt < retry_503) { Sys.sleep(2); next } else { return(paste("❌ OneAPI 连接失败:", resp$message)) }
}

if (resp$status_code == 200) {
resp_parsed <- httr2::resp_body_json(resp)
if (!is.null(resp_parsed$choices)) {
return(resp_parsed$choices[[1]]$message$content)
}
} else {
msg <- tryCatch({jsonlite::fromJSON(httr2::resp_body_string(resp))$error$message}, error = function(e) "未知 API 错误")
return(paste0("❌ HTTP ", resp$status_code, ": ", msg))
}
}
}

完成源码替换后,保存biblioAI.R文件,关闭并重启 RStudio,确保修改后的源码完整生效。

  • 在 biblioshiny 界面的 AI 设置模块中,即可按需调用对应模型:
    • 模型名称选择带 flash 后缀的选项,将调用 DeepSeek-V4-Flash 文本大模型;
    • 模型名称选择带 pro 后缀的选项,将调用 Qwen3.6-27B 多模态大模型。
  • 补充说明:Qwen3.6-27B 支持多模态视觉能力,可直接识别 Bibliometrix 生成的分析图表,该能力为当前 DeepSeek-V4-Flash 模型不具备的优势;使用 Qwen 模型前,请确保已在 OneAPI 中完成对应模型的渠道添加与配置。
  • 补充说明2:如果你需要调用其他模型 记得在代码里面改掉现在的名字

成果

赞(0)
未经允许不得转载:171主机测评 » 将Bibliometrix接入国产AI deepseekv4(个人笔记)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址