はじめての API 呼び出し:チャット・ストリーミング

AI Navigate Original / 2026/5/16

共有:

要点

  • 3 基本:同期チャット・ストリーミング・マルチターンを実装
  • システムプロンプト追加、OpenAI/Vercel AI SDK も同パターン
  • エラー(429/401)処理、指数バックオフで再試行
  • トークンコスト追跡、複雑パターンは practice 章で

環境構築が済んだら、いよいよ AI に話しかけてみます。やることは驚くほど単純で、「メッセージを送り、返事を受け取る」だけ。本記事では、最初に覚えるべき 3 つの基本形(同期チャット・ストリーミング・マルチターン会話)を、Anthropic(Claude)・OpenAI・Vercel AI SDK の実コードで身につけます。最後にエラー処理・リトライ・コスト把握といった「動かし続けるための土台」まで触れます。

あなたの プログラム リクエスト(質問) AI モデル (API) レスポンス(応答) 回答テキスト

FIG.1 API 呼び出しは「リクエストを送り、レスポンスを受け取る」往復だけ

以下のコードは Node.js(TypeScript)を前提にしています。実行前に API キーを環境変数(例:ANTHROPIC_API_KEY / OPENAI_API_KEY)に設定しておけば、各 SDK は自動でそれを読み込みます。キーをコードに直書きしないのが鉄則です。

01同期チャット ── まず一往復

最初の一歩は「質問を送り、応答を全部受け取ってから表示する」最小パターンです。返事が出そろうのを待つので同期(一括)と呼びます。短い処理やバッチ向きで、コードがいちばん読みやすい形です。

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

// 環境変数 ANTHROPIC_API_KEY を自動で読む
const client = new Anthropic();

const res = await client.messages.create({
  model: "claude-sonnet-4-6",   // 用途に応じてモデルを選ぶ(後述)
  max_tokens: 1024,             // 応答の最大トークン数(必須)
  messages: [
    { role: "user", content: "TypeScript と JavaScript の違いを簡潔に教えて" },
  ],
});

console.log(res.content[0].text);   // 応答テキスト
console.log("usage:", res.usage);   // 入出力トークン数(コスト把握に使う)

ポイントは 3 つです。max_tokens は Anthropic では必須(応答の上限。料金の上振れ防止にもなる)。応答本文は res.content[0].text に入り、res.usage に使ったトークン数が返ります。後述のコスト把握はこの usage が起点です。

モデルはどう選ぶ?

同じ API でも、呼び出すモデル名で賢さ・速さ・料金が変わります。2026 年 6 月時点の Claude は主に次の 3 つです(モデルは更新が速いので、最新の正式名は必ず公式ドキュメントで確認してください)。

Haiku(軽量・高速)

claude-haiku-4-5。分類・要約・大量処理など、速度とコストを優先する用途に。

Sonnet(バランス)

claude-sonnet-4-6。日常的なチャットや開発の主力。迷ったらここから。

Opus(最高性能)

claude-opus-4-8。難しい推論・設計判断など、品質を最優先する場面で。

学習や試作の段階では Sonnet か Haiku で十分なことが多く、コストも抑えられます。「とりあえず一番賢いモデル」を全部に使うと、簡単な処理でも料金がかさみます。難所だけ Opus に上げる、という使い分けが現実的です。

02ストリーミング ── 1 文字ずつ届ける

同期チャットは「全部できてから一気に表示」なので、長い回答だと待ち時間が体感で長く感じます。ストリーミングは、生成された文字を少しずつ(チャンクごとに)受け取って、その場で画面に流していく方式。ChatGPT のように文字がタタタッと出てくる、あの挙動です。体感速度が大きく上がります。

同期(一括) 待つ… 全部到着 ストリーミング(逐次) 届くそばから表示

FIG.2 同期は「待って一括」、ストリーミングは「届くそばから逐次表示」

使い方は stream: true を付けて、返ってきたイベントを for await で 1 件ずつ読むだけです。テキストの増分は content_block_delta イベントに入ってきます。

const stream = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  messages: [{ role: "user", content: "東京の見どころを 5 つ" }],
  stream: true,
});

for await (const event of stream) {
  // テキストの増分だけを拾って、その場で出力する
  if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
    process.stdout.write(event.delta.text);
  }
}

イベントには本文以外(開始・終了の合図、ツール使用の通知など)も流れてきます。だから event.typeテキスト増分だけを選り分けるのがコツ。なお Anthropic SDK には client.messages.stream(...) という補助ヘルパーもあり、こちらは .on("text", ...) でテキストだけを簡単に受け取れます。どちらでも構いませんが、まずは上の for await 形で「何が流れてくるか」を体感すると理解が速いです。

03マルチターン会話 ── 文脈を持たせる

API は基本的に記憶を持ちません。1 回の呼び出しは独立していて、前回何を話したかをモデルは覚えていません。会話を続けたいときは、過去のやり取りを自分で messages 配列に積み上げて毎回送るのが基本です。これがマルチターン会話の正体です。

user:見どころを 1 つ assistant:浅草寺が… user:雨でも楽しめる所は? これまで全部を「履歴」として渡す AI モデル 文脈に沿った 次の応答

FIG.3 モデルは履歴を覚えない。会話の続きは「過去の発言を全部渡す」ことで成立する

const history = [
  { role: "user", content: "東京の見どころを 1 つ" },
  { role: "assistant", content: "浅草寺がおすすめです。雷門が…" },
  { role: "user", content: "もう 1 つ、雨でも楽しめる場所を" },
];

const res = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  messages: history,   // user / assistant を交互に積む
});

roleuser(あなた)と assistant(モデルの過去の応答)を交互に並べます。実際のアプリでは、モデルから返ってきた応答を historyassistant として push し、次のユーザー発言を user として足す、という流れを繰り返します。

会話を覚えているように見えるのは、毎回 過去ログをまるごと送り直しているから。

注意点として、履歴が長くなるほど毎回のトークン量(=コストと遅延)が増えます。長い会話では古い発言を要約して圧縮する、といった工夫が後々必要になります。

04システムプロンプト ── 役割と方針を与える

「丁寧な日本語で」「200 字以内で」のような振る舞いの指示は、会話本文に混ぜるのではなく system パラメータに渡すのが定石です。これをシステムプロンプトと呼び、全ターンに通底する「役割・トーン・制約」を設定します。

const res = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  system: "あなたは丁寧な日本語の旅行ガイドです。回答は 200 字以内で。",
  messages: [{ role: "user", content: "京都の見どころは?" }],
});

システムプロンプトは「キャラ設定+ルール表」のようなもの。出力の言語・形式・禁止事項などをここで一度宣言しておくと、毎回のユーザー発言に書き足さずに済み、応答が安定します。

05OpenAI(GPT)の場合 ── 形は似ている

プロバイダが変わっても発想はほぼ同じです。OpenAI では client.chat.completions.create を使い、システムプロンプトも messages 配列の中に role: "system" として入れるのが Anthropic との主な違いです(Anthropic は system を独立パラメータにする)。2026 年 6 月時点の主力は gpt-5.5 ですが、こちらもモデル名は公式で確認してください。

import OpenAI from "openai";
const client = new OpenAI();   // 環境変数 OPENAI_API_KEY を自動で読む

const res = await client.chat.completions.create({
  model: "gpt-5.5",
  messages: [
    { role: "system", content: "丁寧な日本語で。" },   // ← messages 内に入れる
    { role: "user", content: "京都の見どころは?" },
  ],
});

console.log(res.choices[0].message.content);

ストリーミング

同じく stream: true を付け、増分は chunk.choices[0]?.delta?.content から取り出します。Anthropic とはレスポンスの形(取り出すプロパティ名)が違う点に注意してください。

const stream = await client.chat.completions.create({
  model: "gpt-5.5",
  messages: [{ role: "user", content: "東京の見どころを 5 つ" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || "");
}
Anthropic(Claude)OpenAI(GPT)
client.messages.createclient.chat.completions.create
system は独立パラメータsystem は messages 内に role:"system" で入れる
max_tokens は必須max_tokens は任意(省略可)
本文:res.content[0].text本文:res.choices[0].message.content
増分:content_block_delta増分:delta.content

06Vercel AI SDK ── プロバイダを共通化する

「Claude も GPT も、もっと統一的に呼びたい」というときに便利なのが Vercel AI SDK です。プロバイダごとの細かな違いを吸収し、generateText(一括)/ streamText(逐次)という共通の関数で書けます。モデル部分を差し替えるだけでプロバイダを切り替えられるのが利点です。

import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

const result = streamText({
  model: anthropic("claude-sonnet-4-6"),
  prompt: "東京の見どころを 5 つ",
});

// テキスト増分は textStream として共通の形で受け取れる
for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

モデルを openai("gpt-5.5") に変えれば、同じ streamText のままプロバイダを切り替えられます。Next.js などで チャット UI を作るときに特に相性がよく、サーバー側で streamText を呼んでブラウザへ逐次配信する、という構成が定番です。一方、プロバイダ固有の機能を細かく制御したいときは、純正 SDK(前述の @anthropic-ai/sdk など)を直接使う方が見通しが良い場合もあります。

Production Basics

動かし続けるための 3 点セット

サンプルが動いたら、実運用に向けてエラー処理・リトライ・コスト把握を足します。これらが無いと、ネットワークの一時不調やレート制限で簡単に止まり、料金も見えなくなります。順に最小形を示します。

指数バックオフ(待ち時間を倍々にして再試行) 試行1 失敗 1秒 試行2 失敗 2秒 試行3 失敗 4秒 試行4 成功 回数上限で あきらめる

FIG.4 一時的な失敗は、待ち時間を倍にしながら数回だけ再試行する

07エラーハンドリング ── 失敗を見分ける

API はネットワークやレート制限で必ず時々失敗します。重要なのは、何が起きたかで対応を変えること。Anthropic SDK はエラーの種類ごとにクラスを用意しているので、種類で分岐できます。

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

try {
  const res = await client.messages.create({ /* … */ });
} catch (e) {
  if (e instanceof Anthropic.RateLimitError) {
    // 429: 混んでいる → 少し待って再試行(次節のリトライへ)
    await new Promise((r) => setTimeout(r, 1000));
  } else if (e instanceof Anthropic.AuthenticationError) {
    // 401: キーが無効/未設定 → 環境変数を確認(待っても直らない)
    console.error("API key invalid");
  } else if (e instanceof Anthropic.APIError) {
    // その他の API エラー。e.status で HTTP ステータスも見られる
    console.error("API error:", e.status);
  } else {
    throw e;   // 想定外は握りつぶさず投げ直す
  }
}

分け方の勘所は「待てば直るか」です。429(レート制限)や一時的なサーバーエラーは時間を置けば回復するのでリトライ向き。一方 401(認証エラー)はキーの問題なので、何度試しても直りません ── ここはリトライせず設定を直します。想定外の例外を黙って握りつぶさないこと(最後の throw e)も大切です。

08リトライ戦略 ── 指数バックオフ

「待てば直る」系の失敗には、指数バックオフで再試行します。待ち時間を 1 秒 → 2 秒 → 4 秒…と倍々に伸ばしながら、回数の上限を決めて諦める方式です(FIG.4)。一定間隔で叩き続けると混雑を悪化させるため、間隔を空けるのがコツです。

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (e) {
      if (i === maxRetries - 1) throw e;        // 最後の試行で失敗 → あきらめる
      const wait = Math.pow(2, i) * 1000;       // 1秒 → 2秒 → 4秒(指数バックオフ)
      await new Promise((r) => setTimeout(r, wait));
    }
  }
  throw new Error("unreachable");
}

// 使い方:失敗しうる呼び出しを包む
const res = await withRetry(() =>
  client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    messages: [{ role: "user", content: "こんにちは" }],
  })
);

実務では、本当に再試行すべきエラー(429・一時的な 5xx)だけを対象にし、401 のような恒久的エラーは即座に投げ直すのが理想です。各 SDK にも自動リトライ機能が組み込まれていることが多いので、まずは標準機能を確認し、足りなければ上のような自前ラッパーを足す、という順序がおすすめです。

09コスト把握 ── usage を必ず見る

API は使ったトークン量に応じて課金されます。料金はモデルごとに、しかも入力トークンと出力トークンで単価が異なり、改定も頻繁です。だからこそ、開発初期から usage をログに出して「自分のリクエストがどれくらいのトークンを使っているか」を見える化しておくと安心です。

const res = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  messages: [{ role: "user", content: "こんにちは" }],
});

console.log({
  inputTokens: res.usage.input_tokens,
  outputTokens: res.usage.output_tokens,
});
// 概算金額 = 入力トークン × 入力単価 + 出力トークン × 出力単価
// 単価はモデル・時期で変わるため、必ず公式の料金ページで最新を確認する

具体的な単価をコードに直書きすると古くなりがちなので、料金は公式の最新ページで確認するのが安全です。コストを抑える定番は、(1) 用途に合った軽いモデルを選ぶ、(2) max_tokens で出力上限を絞る、(3) マルチターンで履歴を渡しすぎない、の 3 つ。さらに同じ前置きを何度も送るなら、各社が提供するプロンプトキャッシュでコストを下げられる場合もあります。

10まとめと次のステップ

ここまでで、AI API の基本の往復(同期・ストリーミング・マルチターン)と、動かし続ける土台(エラー処理・リトライ・コスト把握)が一通りそろいました。プロバイダが違っても考え方は共通で、変わるのは関数名とレスポンスの形くらい、という感覚がつかめれば十分です。

01

まず一往復を動かす

Sonnet か Haiku で同期チャットを実行し、usage をログに出して感覚をつかむ。

02

ストリーミングと会話に拡張

stream:truemessages 積み上げで、対話アプリの骨格を作る。

03

土台を足す

エラー分岐・指数バックオフ・コストログを入れて「止まらない・見える」状態に。

04

応用へ進む

Tool Use(道具を使わせる)・構造化出力・RAG(社内知識の参照)は practice 章へ。ローカルで動かす Ollama を試すなら次の記事へ。

次の記事では、API ではなく自分の PC でローカル LLM を動かす(Ollama)方法を扱います。クラウド API とローカル実行の違いを押さえると、用途に応じた使い分けができるようになります。