4.1 LLM 编程基础(ellmer) · Programming with LLMs
4.1 LLM 编程基础(ellmer) · Programming with LLMs
本章目标
完成本章后,你能够:
- initialize
ellmer::chat_*()会话并解释 provider/model 参数 - control 用 system prompt 设定角色、语气与输出格式
- inspect 会话对象的消息列表(roles:system / user / assistant)
- estimate 一轮对话的 token 消耗并建立成本直觉
- extract 用
$chat()+ 结构化提取拿回可直接入库的数据(R 数据框)
前置自测
- 会安装包、能读懂一段 dplyr 链(→ 不熟?先回 tidyverse 入门要求)
- 有任一 LLM provider 的 API key(OpenAI / Anthropic / Posit AI Pass 均可)
本章与工作坊统一使用 chat_posit();切换供应商时可换成 chat_anthropic() 或 chat_openai(),并配置相应凭据。没有凭据时先阅读代码,暂不发送请求。
命名提示:旧资料中的 set_system() / extract_data(),本书统一写作 工作坊使用的 set_system_prompt() / chat_structured()。
1. 会话解剖:一次调用里到底发生了什么
聊天界面看起来像「你一句我一句」,API 层面真相是:每次请求都把 完整历史(system → user → assistant → …)整体重发。
flowchart LR
A[你的 R 代码] -->|chat$chat msg| B[HTTP 请求<br/>system+历史+新消息]
B --> C[LLM]
C -->|assistant 消息| D[ellmer 把新消息<br/>append 回会话对象]
library(ellmer)
chat <- chat_posit()
chat$chat("用一句话解释什么是函数式编程")模型没有记忆,是 ellmer 替你重发历史。推论: ① 会话越长,每次请求越贵;② 换个新 chat 对象,「上次说过的话」就全忘了。
2. 检查会话对象:学会”开盖”
ellmer 的会话对象是可检查的——这是编程视角与聊天视角的分水岭。
chat <- chat_posit()
chat$set_system_prompt("你是一名严谨的统计学编辑,回答不超过两句话。")
chat$chat("p 值 0.06 该写成 significant 吗?")
chat # 打印会话,检查提示词与对话轨迹打印会话可检查提示词及 user/assistant 对话轨迹。
新建两个会话,system 分别为 ①「用尽可能少的词回答」②「用三段话详细回答」,问同一个问题: 「ellmer 里创建 Anthropic 会话的函数是什么?」 对比回复的内容与风格差异,并打印两个会话核对各自的 system prompt。 (改编自 llms 工作坊 02_conversation 练习)
3. System prompt 设计三要素
| 要素 | 例子 | 不写的后果 |
|---|---|---|
| 角色 | 「你是临床报告审阅员」 | 回答泛化成百科腔 |
| 约束 | 「不超过两句」「只用中文」 | 长篇大论、语言漂移 |
| 格式 | 「以 CSV 三列输出」 | 需要二次解析 |
system prompt 不是咒语堆砌,是写给一个聪明但不了解你上下文的新同事的入职说明。 超过十条约束时,说明你该用 4.4 章的 skills 而不是继续往 system 里塞。
4. Token 与成本直觉
- token ≈ 0.75 个英文单词 ≈ 0.5 个汉字;输入按全文重发计费
- 成本 = (输入 token × 单价) + (输出 token × 单价),输出通常贵 3–5 倍
照抄本章 §2 的会话代码并跑通:建 chat → set_system_prompt() 设定「统计编辑」人设 → 问一个问题 → 打印 chat 检查对话轨迹。把提示词与回复的对应关系写在注释里。
写 cost_estimate(n_turns, words_per_turn, in_price, out_price) 估算一段 n 轮对话的总输入 token(注意:历史重发!第 k 轮输入 ≈ 前面所有轮之和), 并画出 10/20/50 轮的成本曲线。回答:会话到多少轮时你该考虑”摘要压缩历史”?
5. 第一个结构化输出:让模型吐数据框
自然语言回复对下游代码没用——我们要可直接入库的数据。
chat <- chat_posit()
chat$set_system_prompt("你是数据提取助手。")
recipes <- chat$chat_structured(
"给出三种适合高血压患者的家常菜:名称、钠含量估计mg、一句理由",
type = type_array(type_object(
name = type_string(),
sodium_mg = type_number(),
reason = type_string()
))
)
dplyr::as_tibble(recipes)chat_structured() + type_*() 描述符 = 合同式输出:字段、类型在签名里写死。
结构化输出的底层是 provider 的 tool/JSON 模式;4.2 章会展开类型系统全表与 evals(如何系统性评测提示词质量)。
LLM 的数字是生成的不是查询的。上例的钠含量必须再经真实数据库核对—— 医疗场景铁律:模型负责”格式”,事实负责”内容”,来源要可追溯。
6. AI 线自查:本章的禁用环节
全程禁用 AI 助手(这是本章唯一硬性禁用点):仅凭本章内容 + ?ellmer 文档, 写 translate_labels(labels, target_lang)——用 LLM 把变量标签向量翻译成目标语言, 要求结构化输出、批量处理、失败重试一次。 写完后再开放 AI,让它评审你的重试逻辑——记录它发现了什么。
Capstone · 压轴项目
任务:「研究助理 0.1」——给你领域的 5 篇论文摘要(自备或用班级数据集), 做一个 ellmer 脚本:对每篇摘要提取(研究问题/方法/样本量/主要结论/局限), 输出一个规范 tibble,并渲染成 Quarto 报告附成本统计(总 token / 总费用估算)。
| 维度 | 达到 | 良好 | 卓越 |
|---|---|---|---|
| 提取质量 | 五字段齐且可读 | 类型签名严格、失败可重试 | 抽查 20% 字段与原文核对并报告准确率 |
| 工程性 | 脚本能跑完 | 函数化+错误处理 | 批量并发(预告 4.2 的 parallel) |
| 成本意识 | 报告总 token | 分轮拆解成本 | 提出并实测一种降本方案(如压缩历史) |
| 证据边界 | 结果标注”模型生成” | 区分生成内容与核对内容 | 对局限做敏感性讨论 |
SOURCES · 来源映射
| 讲义节 | 素材 | 性质 |
|---|---|---|
| §1–§3 结构与练习 | posit::conf(2026) llms outline.md Morning 1 + _exercises/01_hello-llm, 02_conversation, 06_word-games(Garrick Aden-Buie, Sara Altman · CC-BY-SA 4.0) |
改编 |
| §5 结构化输出 | llms _exercises/10_structured-output |
改编 |
| 高血压菜谱示例、成本函数练习、研究助理 capstone、rubric | 本项目 | 原创 |
本章以 CC-BY-SA 4.0 发布。