SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

Claude API 教程:在 Ubuntu VPS 上构建日志诊断工具

学习获取 Claude API key,并在 Ubuntu 24.04 上安全存储。用约 60 行 Python 实现支持流式输出、类型化异常、systemd 和成本限制的日志解释器。

构建内容

在全新的 Ubuntu 24.04 VPS 上构建一个命令行工具。您可以将错误消息或一段日志通过管道传入,工具会返回一份简明的英文诊断:journalctl -u nginx -n 50 | explain。整个工具大约只有 60 行 Python 代码,但涵盖了真实 Claude API 应用所需的全部基础知识:正确存储密钥、使用 virtualenv、处理 SDK 的响应结构、使用流式传输、处理类型化异常链,以及配置 systemd unit 让工具无需人工操作即可运行。

我有意选择了这个项目。大多数“第一个 API 应用”教程都会让您构建一个以后再也不会打开的聊天机器人。日志解释器从第一天起就能在服务器上发挥作用,还会迫使您掌握初学者最容易出错的两点:正确读取响应对象,以及控制开支。API 按 token 计费,费用上限只取决于您设置的限制。因此,成本控制是本项目的设计输入,而不是事后补救;当您进一步学习在同一台 VPS 的 tmux 中 运行 Claude Code 时,也必须遵循同样的纪律。

从 Console 获取 API 密钥

API 访问权限在 Anthropic Console(platform.claude.com)中管理。注册后,进入 Settings → API Keys 创建密钥(文档会直接链接到 platform.claude.com/settings/keys)。Console 会要求您为密钥命名、选择过期时间,并将其关联到您本人或服务账户。Anthropic 建议生产服务以及任何需要共享的场景使用服务账户密钥。密钥只显示一次,以 sk-ant- 开头,之后无法再次找回。请立即复制;否则只能删除并重新生成。

关于费用:截至 October 2026,API 没有持续提供的免费层。Anthropic 的定价文档说明,新用户会获得少量免费额度用于测试;具体金额以注册时 Console 显示的金额为准。额度用完后,您必须为账户充值,请求才能成功。这与 claude.ai 订阅相互独立,API 密钥也不会提供聊天应用的访问权限。Pro 计划不包含 API 额度,但 Max 和 Team 计划包含:截至 October 2026,Max 5x 每月提供 $100,Max 20x 每月提供 $200(Team 每个 Standard 席位提供 $20,每个 Premium 席位提供 $100,上限为 $500)。您需要在 claude.ai 的账单设置中关联 Console 组织来领取额度。未使用的额度会在每个计费周期结束时过期,且不能用于 Claude Code;详情请参阅Max 和 Team 每月 API 额度的工作方式。如果您正在权衡订阅和 API,这种取舍属于另一个主题:您实际需要哪个 Claude 计划。

将密钥限制到单个项目或服务器。当密钥泄露时,随着时间推移,这种情况迟早会发生,您应能撤销该密钥,而不会影响您拥有的其他资源。

不要将密钥放入 .bashrc

常见做法是在 export ANTHROPIC_API_KEY=sk-ant-... 中设置 ~/.bashrc。不要这样做。这里有三个独立的问题:

  • 每个进程都会继承它。 登录 shell 中导出的环境变量会传递给你启动的所有程序,包括 Web 应用、会将环境变量写入错误报告的崩溃报告程序,以及有人忘记禁用的 phpinfo() 页面。密钥的暴露面会变成“该用户运行过的所有程序”。
  • 输入内容会写入 ~/.bash_history。 手动执行一次 export 后,密钥就会永久保存在明文文件中,并同步到主目录的每个备份。
  • systemd 需要它时却找不到。 服务不会读取你的 .bashrc,因此当你将脚本提升为 systemd unit 时,这种做法会直接失效,通常表现为清晨 6 点出现一个难以解释的 401 错误。

服务器上的正确做法是创建一个权限为 600 的专用环境文件,只由需要该密钥的进程加载:

sudo mkdir -p /opt/explain
sudo install -m 600 -o root -g root /dev/null /etc/claude-explain.env
printf 'ANTHROPIC_API_KEY=sk-ant-YOUR-KEY-HERE\n' | sudo tee /etc/claude-explain.env >/dev/null

如果希望避免密钥写入编辑器的交换文件,可以通过 printf 使用 tee,而不是编辑器;无论采用哪种方式,都应使用 ls -l /etc/claude-explain.env 验证它读取的是 -rw-------,且文件归 root 所有。交互式 shell 通过包装脚本(见下文)在每次调用时获取密钥;systemd 则通过 EnvironmentFile= 获取密钥。root 会在降权前读取该文件,因此服务用户无需拥有读取权限。密钥不会出现在代码、git、ps 输出或 shell 历史记录中。

在 venv 中安装 SDK

Ubuntu 24.04 随附的 Python 3.12 启用了 PEP 668 强制机制,因此直接对系统解释器执行 pip install anthropic 会失败,并显示 error: externally-managed-environment。这是操作系统的预期行为,请使用 virtualenv:

sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropic

服务器上无需执行激活操作:直接调用 /opt/explain/venv/bin/python 始终会使用 venv 中的软件包。

首次调用,并正确读取响应

import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from the environment

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1000,
    messages=[{"role": "user", "content": "Explain what a systemd unit file is in three sentences."}],
)

for block in response.content:
    if block.type == "text":
        print(block.text)

这12行代码中有两点最能体现该 API 的使用模型。第一,anthropic.Anthropic() 不带参数时会从环境变量中读取密钥,不要将密钥作为字符串字面量直接传入。第二,response.content 是一个内容块列表,而不是字符串。直接打印它会得到初学者最常见的输出:

[TextBlock(citations=None, text='A systemd unit file is...', type='text')]

这不是错误,而是对象的 repr。响应可以包含多种内容块类型(文本、工具调用、思考),因此应遍历列表,并在访问 .text 前先检查 block.type == "text"。对于 Opus 5.5,这一点从第一次调用开始就很重要:思考始终启用,因此除非模型在简单请求中跳过思考,否则列表开头会是一个 thinking 块,其文本默认为空,而 response.content[0].text 并不是你的答案。第一天就接入这段循环,可以避免一整类“打印出来是乱码”的问题。

使用准确的模型 ID claude-opus-5-5。从 4.6 代开始,模型 ID 不再包含日期;无日期 ID 就是固定的快照。因此,不要凭经验或旧博客文章的说法,为当前模型追加日期后缀;这样会返回 404,下面会介绍这一点。较旧的模型则相反:Haiku 4.5 的规范 ID 是带日期的 claude-haiku-4-5-20251001。

实际工具:explain

下面是完整程序:从 stdin 读取输入,流式输出诊断结果,并处理错误:

#!/usr/bin/env python3
"""explain: pipe an error or log excerpt in, get a diagnosis out."""
import sys
import anthropic

MODEL = "claude-opus-5-5"

def main() -> int:
    text = sys.stdin.read().strip()
    if not text:
        print("usage: journalctl -u nginx -n 50 | explain", file=sys.stderr)
        return 1

    client = anthropic.Anthropic()
    try:
        with client.messages.stream(
            model=MODEL,
            max_tokens=4096,
            system=(
                "You are a senior Linux sysadmin. The user pipes you server "
                "logs or error output. Name the most likely cause outright, "
                "then give the commands to confirm and fix it. Be terse."
            ),
            messages=[{"role": "user", "content": text}],
        ) as stream:
            for chunk in stream.text_stream:
                print(chunk, end="", flush=True)
        print()
    except anthropic.RateLimitError as e:
        retry_after = e.response.headers.get("retry-after", "60")
        print(f"rate limited; retry in {retry_after}s", file=sys.stderr)
        return 2
    except anthropic.APIStatusError as e:
        print(f"API error {e.status_code}: {e.message}", file=sys.stderr)
        return 2
    except anthropic.APIConnectionError:
        print("network error reaching the API", file=sys.stderr)
        return 2
    return 0

if __name__ == "__main__":
    sys.exit(main())

将其保存为 /opt/explain/explain.py,然后添加一个用于交互式使用的包装脚本来加载密钥:

sudo tee /usr/local/bin/explain >/dev/null <<'EOF'
#!/bin/sh
set -a; . /etc/claude-explain.env; set +a
exec /opt/explain/venv/bin/python /opt/explain/explain.py "$@"
EOF
sudo chmod 755 /usr/local/bin/explain

(包装脚本需要通过 sudo 运行;或者,让环境文件归属于管理员用户所属的组。请明确选择其中一种方式,不要为了省事将文件权限放宽到 644。)

为什么使用流式输出。 client.messages.stream 会在令牌到达时立即打印,而不是等待完整生成结果后才输出。对于较长输出,这也可以避免 HTTP 超时;出于同样的原因,SDK 在非流式调用中会直接拒绝过大的 max_tokens 值。text_stream 只返回答案文本。使用默认的思考显示设置和 Opus 5.5 时,思考文本不会被流式输出,因此结果保持整洁。如果之后需要完整的对象,请在 with 块中调用 stream.get_final_message()。

为什么按此顺序处理异常。 SDK 会抛出类型明确的异常,顺序应从最具体的类型开始:RateLimitError 表示 429 响应,并包含 retry-after 标头,用于说明需要等待多长时间;APIStatusError 覆盖其他非 2xx 响应(检查 e.status_code >= 500 以判断是否存在服务器端问题);APIConnectionError 表示请求完全没有收到响应。在编写重试循环之前还要注意:SDK 已经会自动重试 429 和 5xx 错误,默认使用指数退避重试 2 次(客户端上的 max_retries)。执行到 except 时,重试已经用尽。因此,CLI 的正确做法是报告错误并退出,而不是休眠后继续重复请求。

成本控制

这一点值得单独成节,因为除非您自行配置,API 没有内置的月度上限,而且这里的每个错误都会在不易察觉的情况下不断累积。

max_tokens 是每次调用的费用上限。 输出 token 的价格更高;在 Opus 5.5 上,输出价格是输入价格的 5 倍,而 max_tokens 会硬性限制模型最多生成的 token 数量。即使提示词失控,输出费用也不会超过您允许的上限。在 5.5 模型上,该上限也涵盖思考过程:思考 token 会计入 max_tokens,并按输出计费,尽管思考文本不会返回给您。应根据任务设置上限:4,096 足以让模型在给出简短日志诊断前完成必要的思考;分类任务所需的上限要低得多。如果响应因 stop_reason: "max_tokens" 提前结束,说明上限设置过低。应有意识地提高上限,或降低 effort 设置以减少思考,而不是直接使用过大的默认值。

发送前先统计 token。 输入也会产生费用,而且日志通常很大。API 提供免费的 token 统计端点(该端点有独立于消息创建的速率限制):

count = client.messages.count_tokens(
    model="claude-opus-5-5",
    messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)

使用它可以防止误将 2 GB 日志通过工具发送,并传入您实际要调用的模型:Claude 4.7 及更高版本使用更新的 tokenizer;对于相同文本,它生成的 token 通常比早期 Claude 模型多约 30%。不要为此使用 tiktoken,它是 OpenAI 的 tokenizer,不是 Claude 的,因此统计结果不会与实际计费一致。

按任务选择模型,不要固定使用某个模型。 截至 October 2026,Opus 5.5(claude-opus-5-5)的价格为每百万输入 token $4、每百万输出 token $20;Haiku 5.5(claude-haiku-5-5)在不超过 100,000 token 的提示下价格为 $0.10/$0.50,超过该数量后为 $0.50/$2.50,且上下文窗口为 1M;Sonnet 5.5(claude-sonnet-5-5)介于两者之间,价格为 $2/$10。具体来说,包含 2,000 token 的日志摘录并生成 500 token 的答案,在 Opus 上的费用约为 $0.018,在 Haiku 上约为 $0.00045,另加模型执行思考所产生的费用,该费用按输出计费。在评估输出质量时,可以先使用 Opus,然后对相同提示词尝试 Haiku;对于高并发量的简单转换任务,Haiku 的效果通常难以区分,而每个 token 的价格只有 Opus 的四十分之一。在将这些数字硬编码到预算中之前,请在定价页面确认当前价格。

可延迟的任务使用批处理。 Batches API 会异步处理请求,价格为标准价格的 50%,大多数批次会在 1 小时内完成。夜间摘要、历史数据回填、批量分类,以及所有不需要人工等待的任务,都适合使用批处理。

对重复上下文使用提示缓存。 如果每次调用都会重新发送相同的大型系统提示或运行手册,请将其标记为可缓存:

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1000,
    system=[{
        "type": "text",
        "text": RUNBOOK_TEXT,  # the same 30K tokens on every call
        "cache_control": {"type": "ephemeral"},
    }],
    messages=[{"role": "user", "content": question}],
)
print(response.usage.cache_read_input_tokens)  # non-zero from the second call on

在 5 分钟 TTL 内,缓存写入费用为输入价格的 1.25 倍;Opus 5.5 和 Sonnet 5.5 的缓存读取费用为输入价格的 0.05 倍(大多数其他模型为 0.1 倍),因此在该时间窗口内进行第二次调用时,缓存通常已经抵消了首次写入的费用。需要注意两点。可缓存前缀必须达到各模型的最低 token 数量;Opus 5.5 的最低值为 512 token,因此过短的系统提示会直接无法缓存,且不会明确提示。如果相同调用中的 cache_read_input_tokens 始终为 0,说明前缀在每次请求中都发生了变化,最常见的原因是包含时间戳。

记住哪些内容会计入输入。 系统提示、工具定义,以及多轮对话中每一轮重新发送的完整历史记录,都会按输入 token 计费。始终保留完整历史记录且从不裁剪的聊天循环,会使费用按平方级增长。在构建任何对话式应用之前,应先了解完整的计费方式:Claude token 使用量和实际计费方式。

在 systemd 下运行

环境文件管理规范带来的好处是:可以使用一个计时器每天早晨汇总前一天的错误。

# /etc/systemd/system/log-digest.service
[Unit]
Description=Daily error-log digest via the Claude API

[Service]
Type=oneshot
User=explain
Group=systemd-journal
EnvironmentFile=/etc/claude-explain.env
ExecStart=/bin/sh -c 'journalctl -p err --since yesterday | /opt/explain/venv/bin/python /opt/explain/explain.py >> /var/log/log-digest.txt'
# /etc/systemd/system/log-digest.timer
[Unit]
Description=Run the log digest every morning

[Timer]
OnCalendar=06:15
Persistent=true

[Install]
WantedBy=timers.target
sudo useradd -r -s /usr/sbin/nologin explain
sudo touch /var/log/log-digest.txt && sudo chown explain /var/log/log-digest.txt
sudo systemctl daemon-reload
sudo systemctl enable --now log-digest.timer
sudo systemctl start log-digest.service   # test it once, right now

请注意 EnvironmentFile= 的作用:systemd 会在切换到非特权 explain 用户之前,先读取由 root 拥有且权限为 600 的文件,因此进程可以获取该变量,而该用户无法读取密钥文件。systemd-journal 组授予日志访问权限。使用手动 systemctl start 进行测试,并读取 journalctl -u log-digest.service,不要等到 06:15 才发现拼写错误。当这种模式超出 shell 管道的处理能力后,同样的密钥环境文件方案可以直接用于同一台主机上的由 Claude 驱动的 n8n 工作流。

故障模式及其对应的错误信息

密钥有效但返回 401。 异常信息如下:

anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}

如果密钥在 shell 中有效,但服务返回 401,说明服务根本没有收到该密钥。请记住,systemd 不会读取 .bashrc;检查 EnvironmentFile= 是否指向正确的路径。其他原因包括:将引号粘贴到了环境文件中(ANTHROPIC_API_KEY="sk-ant-...";systemd 会去除引号,但如果引用方式不正确,shell 包装脚本的 . file 会将引号保留在值中)、末尾存在空格、上周在 Console 中撤销了密钥,或者密钥已达到创建时设置的过期时间。

模型名称拼写错误导致 404。 最常见的情况是,为没有日期版本的模型 ID 添加日期后缀:

anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-5-5-20260922'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}

当前版本的 ID 必须完全按文档中的形式填写:claude-opus-5-5、claude-sonnet-5-5、claude-haiku-5-5、claude-fable-5-1。4.6 版本之前的模型是例外:claude-haiku-4-5 是带日期 ID claude-haiku-4-5-20251001 的便捷别名,两种形式都可以使用。请从模型文档中复制 ID,不要凭记忆填写,也不要使用旧教程中的 ID。

429 rate_limit_error。 错误类型字符串为 rate_limit_error,响应中包含 retry-after 标头,其中给出了需要等待的秒数。在你看到异常之前,SDK 已使用退避策略自动重试了两次。因此,持续出现 429 表示你的持续请求速率确实超过了当前层级的限制。请将任务分批处理或分散执行,不要缩短重试间隔。

输出的是对象,而不是文本。 输出类似于 [TextBlock(citations=None, text='...', type='text')]。你打印了 response.content,而不是遍历各个 block,并从 block.type == "text" 的 block 中读取 .text。上面的每个 SDK 示例都已正确处理这种情况;请直接复制循环代码。

error: externally-managed-environment。 你在 Ubuntu 24.04 的系统 Python 中运行了 pip install。请使用 venv。在需要长期维护的服务器上,绝不要使用 --break-system-packages。

回答被截断。 response.stop_reason == "max_tokens" 表示模型在生成过程中达到了输出上限。这是预期行为;请有意识地提高上限。还要注意,在 5.5 models 中,思考过程也会消耗同一个上限。

第一个应用运行正常后,可以继续阅读使用 Claude 构建 AI agent,将相同的 API 调用转换为可使用工具的 agent。

FAQ

尝试 Claude API 需要多少费用?

对于这类工具,实际费用很低。截至 October 2026,Opus 5.5 的价格为每百万输入 tokens $4、每百万输出 tokens $20。因此,一次典型的日志诊断通常输入几千个 tokens、输出几百个 tokens,在计算 thinking tokens(按输出计费)之前,费用约为两美分;使用 Haiku 5.5($0.10/$0.50)时,费用远低于十分之一美分。每天生成摘要,一个月的费用低于一杯咖啡。风险不在于单次调用的价格,而在于无界循环和无界 max_tokens,因此本指南会显式设置这两项。

Claude API 是否提供免费层?

截至 October 2026,没有持续提供的免费层。Anthropic 的定价文档说明,新用户会获得少量免费额度用于测试 API,这是一次性试用额度,具体金额会在注册时显示在 Console 中;之后需要为账户充值。Max 和 Team 订阅者是例外:其套餐包含每月 API 额度,前文已介绍。如果您的目标是让每次请求都没有边际成本,而不是追求前沿模型质量,另一种方案是使用 Ollama 自行托管开放权重模型,用 RAM 替代 tokens 付费。

如何在服务器上保护 API key?

不要将它写入代码,不要提交到 git,不要从 .bashrc 导出,也不要直接输入到会保留历史记录的 shell 中。应将它存放在由 root 所有且权限为 600 的文件中,并按进程加载:交互式使用时通过包装脚本加载,systemd 使用 EnvironmentFile=。每台服务器或每个项目使用一个独立的 key,这样撤销泄露的 key 时只需处理单个范围,而不会影响其他环境。如果 key 曾出现在粘贴网站或 git 提交中,应立即在 Console 中撤销;删除提交并不能消除已经泄露的 key。

应该从哪个 Claude 模型开始?

在评估输出是否足以用于后续构建时,从 claude-opus-5-5 开始。这样可以用完整质量评估方案,而且在业余使用量下,成本差异只有几美分。提示词确定后,使用真实输入在 claude-haiku-5-5 上重新运行;对于摘要、分类和日志初步分析,它通常已经足够好,而每个 token 的价格只有前者的四十分之一。根据测量结果选择 Haiku 或 Sonnet,不要默认选择。