Chapter 02

把对话写进 API

Messages API 看着只有几个字段,但每一个都有坑。这一章把 system prompt、多轮对话规则、temperature/top_p、stop_sequences、token counting 说透——看完能避免 80% 的低级错误。

对话的最小形式

{
  "model": "claude-sonnet-4-6",
  "max_tokens": 1024,
  "system": "你是严谨的技术编辑。",
  "messages": [
    { "role": "user", "content": "帮我润色这段:..." }
  ]
}

System Prompt 的正确位置

Claude 的 system prompt 是 顶层独立字段,不是 messages 数组里的一项。这和 OpenAI 的习惯不同。

const msg = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  // ✅ 正确:system 是顶层
  system: "你是资深的 iOS 工程师。回答简洁、准确、带代码示例。",
  messages: [
    { role: "user", content: "SwiftData 怎么做多线程?" },
  ],
});

// ❌ 错误:把 system 塞进 messages
const bad = {
  messages: [
    { role: "system", content: "..." },   // 400 Error
    { role: "user", content: "..." },
  ]
};

Structured System Prompt

system 不只是一段字符串,也可以是 结构化数组——为 Prompt Caching 打点用(第 6 章):

system: [
  {
    type: "text",
    text: "你是金融数据分析师。必须用 markdown 表格呈现。",
  },
  {
    type: "text",
    text: readFileSync("company_knowledge.md", "utf-8"),   // 长上下文
    cache_control: { type: "ephemeral" },   // 缓存这一大块
  },
],

多轮对话

把历史对话堆到 messages 数组里,role 必须 user / assistant 交替:

{
  "messages": [
    { "role": "user",      "content": "帮我起个域名。" },
    { "role": "assistant", "content": "你的项目做什么?" },
    { "role": "user",      "content": "个人博客。" },
    { "role": "assistant", "content": "三个候选:..." },
    { "role": "user",      "content": "第二个不错,再给三个类似风格。" }
  ]
}
硬规则
· 必须以 user 开头
· user / assistant 必须交替,不能 user→user 或 assistant→assistant
· 不允许 role=system(要用顶层 system 字段)
违反 → 400 错误,Claude 不接受就是不接受

续写 assistant 消息

一个巧妙用法:messages 最后一条是 assistant,Claude 会从那里接着写(适合"让 Claude 按固定开头输出"):

{
  "messages": [
    { "role": "user",      "content": "用 JSON 返回人名和年龄。" },
    { "role": "assistant", "content": "{" }
  ]
}

Claude 会从 { 开始续写,几乎保证是合法 JSON。配合 stop_sequences 可以精确截断。(工具调用更推荐用 Tool Use,第 4 章)

content 不只是 string

单条消息的 content 可以是字符串,也可以是 block 数组——存在多模态/工具/缓存需求时必须用数组:

{
  role: "user",
  content: [
    { type: "text", text: "这张图是什么?" },
    { type: "image", source: { type: "base64", media_type: "image/png", data: "..." } },
  ],
}

Block 类型包括:text / image / document(PDF) / tool_use / tool_result / thinking。后续各章讲。

temperature 和 top_p

temperature(0–1)
0 → 最确定,相同输入几乎同样输出;1 → 最随机
top_p(0–1)
核采样:只从累积概率 top_p 的候选里抽;低 → 更聚焦

推荐:调一个就够,别同时调两个。典型设置:

max_tokens 是硬上限

不是"目标长度",而是"生成多了就截断"。被截断时 stop_reason = "max_tokens",这是 bug 信号,要么调大,要么让 Claude 在 prompt 里更短。

各模型上限不同,Sonnet 4.x 可达 64K output tokens,Opus 可更高;写 system prompt 的时候也要算进 context window(input + output 总和)。

stop_sequences 停止词

const msg = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 500,
  stop_sequences: ["###", "END_ANSWER"],
  messages: [{ role: "user", content: "只到 ### 为止" }],
});

// 命中时 stop_reason = "stop_sequence",stop_sequence 字段告诉你命中了哪个

经典用途:定界符裹住 JSON(<JSON> 开始、</JSON> 停止),解析更稳。

Token Counting:发请求前先算

长文档场景,想提前知道一个 prompt 要花多少钱/会不会超上下文——SDK 有 count_tokens:

const count = await client.messages.countTokens({
  model: "claude-sonnet-4-6",
  system: "...",
  messages: [{ role: "user", content: veryLongDoc }],
});

console.log(count.input_tokens);   // 比如 84732

这不消耗任何模型推理费用,只是计算——生产 RAG 流程里常用来做"截断策略的参考线"。

上下文窗口是多少

Claude 4.x 系列普遍支持 200K input tokens。超长时有两种处理:

  1. 提前截断:文档检索后只放相关段 + 对话最近 N 轮
  2. 摘要滚动:当对话越过某阈值,让 Claude 总结前半段,再把总结塞回 messages

关于请求的幂等性

同一个 prompt 两次调用,即使 temperature=0,也不保证字节相同——模型浮点运算、采样实现都有细微抖动。要做回放测试,用快照比对 + LLM as judge,别指望字符串 equality。

元数据 metadata

const msg = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 500,
  metadata: {
    user_id: "user_abc123",    // 用于 Anthropic 侧滥用检测
  },
  messages: [...],
});

metadata.user_id 非必填,但生产环境推荐带上——Anthropic 可以基于此做异常检测和问题排查(这字段不计入 prompt、不影响模型输出)。

错误处理模板

import Anthropic from "@anthropic-ai/sdk";

try {
  const msg = await client.messages.create({...});
} catch (err) {
  if (err instanceof Anthropic.APIConnectionError) {
    // 网络问题,重试
  } else if (err instanceof Anthropic.RateLimitError) {
    // 429,指数退避
  } else if (err instanceof Anthropic.APIStatusError) {
    console.error(err.status, err.error);
    throw err;
  }
}

请求超时

const client = new Anthropic({
  timeout: 120 * 1000,        // 2 分钟,长输出情况够用
  maxRetries: 3,              // SDK 默认 2
});

常见错误 Top 5

① 忘填 max_tokens
Anthropic 和 OpenAI 不同,必填。忘了就 400
② role 不交替
连续两个 user / 缺 assistant 回应 → 400。合并成一条 user 消息
③ 拼错 model id
claude-sonnet-4.6(错,用点) vs claude-sonnet-4-6(对,用短横)
④ 空 content
不能传空字符串 / 空数组,会被拒
⑤ 把 system 塞 messages
role=system 不存在,顶层字段才对

最佳实践

写 Claude Prompt 的 3 个心法
用 XML 标签分段:<context><question><example> —— Claude 天然对 XML 有良好理解
说明输出格式:"以 JSON 返回,包含字段 a、b、c" 比"返回 JSON" 可靠 10 倍
先定位角色再发问:system 里讲清楚"你是谁",比在 user 里反复强调有效

本章小结