Hadley Wickham(R 语言首席科学家、tidyverse 之父,现任 Posit 首席科学家)这两年在 AI 话题上相当高调,观点也很成体系。以下是对其核心观点的梳理。
他提出的问题本身就是态度:"Why code when AI?"
2025 年他在美国统计学会(ASA)统计学习与数据科学分会的网络研讨会上做了一个演讲,标题直白地叫 "Why code when ai?"(既然有 AI,为什么还要写代码?)——这不是反问,而是他把整个 R 社区当前面临的最根本问题摆到了台面上。他的答案是:AI 应当增强(augment)而不是取代(replace) R 用户的工作流,AI 是"思考伙伴",不是用户的替代品。
useR! 2025 主题演讲:直接用 LLM 写了一场演讲来回答这个问题
他这场演讲的标题很挑衅——"I wrote this talk with an LLM"(我用 LLM 写了这场演讲)。整个演讲的大纲、幻灯片、代码示例都与 LLM 共同创作,连摘要都是 Claude 生成后他自己轻改的,他还公开了自己的 prompt。这本身就是一个行为实验:亲身验证 AI 协作的边界在哪里。他的结论大致是:
AI 擅长的:快速原型、生成文档、创意构思(rapid prototyping, documentation generation, creative ideation) 人类不可替代的:统计判断与领域专业能力——他对 AI 的"承诺与局限"都做了坦诚的反思 留给社区的开放问题:AI 将如何重塑 R 的教学?哪些新技能最重要?如何保证工具对所有人都可及?
落到行动上:他在把 R 变成"构建 AI 应用的语言"
这点最有意思——他不只是评论 AI,而是在造工具:
ellmer包:他主导开发的 tidyverse 系列包,让 R 可以直接调用各种 LLM API(OpenAI、Claude、Gemini 等),用于在 R 里构建 AI 助手和 AI 驱动的应用。配套还有面向社区的 2 小时官方 workshop。Posit Assistant 实战演示:他亲自录视频演示用 Posit 的 AI 助手在 RStudio 里分析 TidyTuesday 数据集。 与 Joe Cheng(Shiny 作者)联合演讲:"Making the most of artificial and human intelligence for data science",探讨 LLM 复杂且常常自相矛盾的特性。 他甚至专门复活的 Substack(Tidy Design Principles)就是为了讨论 AI,称 LLM 重新点燃了他的好奇心;还推荐社区跟着 Simon Couch 的文章了解用 LLM 生成 R 代码的最新模型动态。
以下就引出了我们的主角**ellmer包**
ellmer:在 R 中与大语言模型对话
定位与背景
ellmer 是 tidyverse 家族中专门用于从 R 调用大语言模型(LLM)的包,由 Hadley Wickham(维护者)、Joe Cheng(Shiny 作者)、Aaron Jacobs、Garrick Aden-Buie、Barret Schloerke 共同开发,Posit 公司持有版权,MIT 协议开源。当前 CRAN 版本 0.4.2(仍在活跃迭代,API 偶有变动)。
它的核心定位是:把各家 LLM 提供商统一到一个一致的 R 接口背后,并提供流式输出、工具/函数调用、结构化数据提取、异步请求、图像输入等能力。用一句话概括:让统计学家用熟悉的 R 语法构建 AI 应用,而不是为了用 LLM 去学一套新的工程栈。
安装:
install.packages("ellmer")
核心概念:有状态的 Chat 对象
ellmer 的中心抽象是 chat 对象——一个有状态的 R6 对象,自动保留对话上下文,所有方法通过 $ 调用:
library(ellmer)
# 第一个参数是 system prompt(设定模型角色),model 指定具体模型
chat <- chat_openai("Be terse", model = "gpt-4o-mini")
# 最简单的用法:流式输出到控制台,并invisible返回结果
chat$chat("R语言主要受哪些语言影响?")
# 交互式聊天:在控制台或浏览器里持续对话
live_console(chat)
live_browser()
如果是在函数里编程式调用(不想在控制台刷屏),用 echo = "none",$chat() 就安静地返回字符串:
my_function <- function() {
chat <- chat_openai("Be terse", model = "gpt-4o-mini", echo = "none")
chat$chat("What is 6 times 7?")
}
my_function()
#> [1] "6 times 7 is 42."
支持的提供商
官方维护(约 12 家):chat_anthropic()(Claude)、chat_openai()、chat_google_gemini()、chat_aws_bedrock()、chat_azure_openai()、chat_databricks()、chat_deepseek()、chat_ollama()、chat_snowflake()、chat_github()、chat_posit() 等。
社区维护:Cloudflare、Groq、Hugging Face、LM Studio、Mistral、OpenRouter、Perplexity、vLLM 等。
几个官方给出的选型建议:
入门首选 chat_openai()或chat_anthropic()——文档特别提到 Claude Sonnet 写 R 代码尤其好(和我们之前讨论"语料质量"时的社区共识一致);chat_google_gemini()有免费额度(但数据可能被用于训练);chat_ollama()可以本地运行开源模型,免费且数据不出本机——对处理敏感数据(如临床、金融数据)的场景很关键。
认证方面:OpenAI/Anthropic 走 API 密钥(建议存环境变量);云平台(AWS、Azure、Databricks、Snowflake)会自动探测 OAuth/IAM 凭据,包括 Posit Workbench/Connect 托管的凭据——企业部署友好。
两大杀手锏之一:结构化数据提取
这是 ellmer 最有"R 味"的功能。与其让模型吐一段"希望是合法"的 JSON,不如直接给它一个类型规约(type specification),保证输出形状,并自动转换成 R 对象:
chat <- chat_openai()
chat$chat_structured(
"My name is Susan and I'm 13 years old",
type = type_ob ject(
name = type_string(),
age = type_number()
)
)
#> $name "Susan" $age 13
类型系统分三组,可以嵌套组合:
| 组别 | 函数 | 类比 |
|---|---|---|
| 标量 | type_boolean(), type_integer(), type_number(), type_string(), type_enum() |
长度 1 向量 / factor |
| 数组 | type_array() |
向量 / 列表 |
| 对象 | type_ob ject() |
命名列表 |
转换规则非常贴合 R 直觉:标量数组变向量、对象变命名列表、对象数组直接变 tibble。想从一段文本里挖表格,就把结构定义为"对象数组":
type_people <- type_array(
type_ob ject(
name = type_string(),
age = type_integer(),
height = type_number("in m")
)
)
几个实战要点:
每个类型的第一个参数是给 LLM 看的字段描述,写清楚单位、格式(如日期格式)能显著提高抽取质量; 字段默认 required = TRUE,会导致模型编造值(幻觉);设required = FALSE则返回NA——做数据抽取时几乎总该加上;parallel_chat_structured()可以把抽取并行应用到大批 prompt,返回一个 tibble——这实际上就是用 LLM 做非结构化数据的批处理 ETL;支持图像输入:可以从截图里直接抽表格数据( content_image_url()/content_image_file())。
两大杀手锏之二:工具/函数调用(Tool Calling)
让 LLM 能"调用"你的 R 函数。关键机制:模型从不执行代码,只是请求调用——它决定何时调用、传什么参数、如何使用结果,实际执行永远在你的 R 进程里,安全可控。
定义工具三步走:
# 1. 写普通 R 函数(roxygen 注释会被用来生成工具定义)
#' @param tz The time zone to get the current time in.
get_current_time <- function(tz = "UTC") {
format(Sys.time(), tz = tz, usetz = TRUE)
}
# 2. 用 tool() 包装,附上模型需要的元数据
get_current_time <- tool(
get_current_time,
desc ription = "Returns the current time.",
arguments = list(
tz = type_string("Time zone", required = FALSE)
)
)
# 3. 注册并对话
chat <- chat_openai(model = "gpt-4o")
chat$register_tool(get_current_time)
chat$chat("阿姆斯特朗登月是多久以前?")
执行时是一个三轮循环:用户提问 → 模型返回 [tool request]: get_current_time(tz = "UTC") → ellmer 在 R 里执行并把 [tool result] 送回 → 模型给出最终答案。模型可能多轮调用,也可能批量调用(比如一次传入多个城市名)。
两个亮点:
create_tool_def()会用 LLM 帮你生成工具定义代码(需人工审核)——用 AI 写接 AI 的胶水代码;工具的返回值可以是图像/PDF:比如截图工具返回 content_image_file(),模型"看到"截图后再回答。结合 Shiny,工具还能直接设置 reactive 值,触发响应式更新——AI 驱动的 Shiny 应用就是这么搭的。
其他能力
流式输出:响应边生成边打到控制台( echo参数控制);异步 API:非阻塞请求,适合 Shiny 应用和批量任务(配合 coro协程);图像输入:把 logo、图表、截图的 URL 或本地文件直接丢给模型提问。
生态系统:ellmer 只是拼图的第一块
Posit 围绕它建了一整套配套包,这暴露了他们的真实野心:
| 包 | 用途 |
|---|---|
shinychat |
用 Shiny 快速搭聊天机器人 UI |
ragnar |
RAG(检索增强生成)框架 |
vitals |
LLM 输出质量评估 |
mcptools |
接入 MCP(Model Context Protocol) |
chatlas |
Python 等价物(跨语言复用思路) |
我的评价
ellmer 的设计处处透着 tidyverse 的哲学:一致的 API 屏蔽掉 12 家提供商的差异(就像 dbplyr 屏蔽数据库差异)、类型规约对应 R 的数据结构直觉、工具调用把"执行权"留在用户手里。它把"在 R 里用 LLM"从拼 HTTP 请求的苦活,变成了符合 R 习惯的声明式编程。
对你而言,如果想在分析流程里嵌入 LLM(比如批量清洗非结构化文本、从报表截图提取数据、给分析报告加个问答界面),ellmer + chat_structured() + Shiny 是目前 R 生态里最正统的组合。需要注意的两点:一是它版本迭代快,API 可能有变动,写正式代码前查一下当前版本的函数名;二是敏感数据务必走 Ollama 本地模型或企业级部署。


雷达卡




京公网安备 11010802022788号







