【TypeScript】Claude API入門|@anthropic-ai/sdkでテキスト生成・ストリーミング・ツール呼び出しを実装する方法

【TypeScript】Claude API入門|@anthropic-ai/sdkでテキスト生成・ストリーミング・ツール呼び出しを実装する方法 AI開発

Claude APIをTypeScriptで使うと、型安全なAIアプリケーションを素早く構築できます。AnthropicはTypeScriptファーストな公式SDK @anthropic-ai/sdk を提供しており、リクエスト・レスポンスの全パラメータに型定義が付いているため、エディタ補完とコンパイル時チェックの恩恵を受けながら開発できます。

この記事では @anthropic-ai/sdk を使って、Claude APIをTypeScriptで呼び出す方法をステップごとに解説します。APIキーの取得・環境構築から始まり、テキスト生成・システムプロンプト・マルチターン会話・ストリーミング・ツール呼び出しまで、実務に直結するコード例を豊富に取り上げます。

この記事でわかること

  • @anthropic-ai/sdk のインストールとTypeScript環境構築
  • Messages APIでテキストを生成する基本パターン
  • システムプロンプトでClaude の振る舞いをカスタマイズする方法
  • マルチターン会話(会話履歴の管理)の実装
  • ストリーミングでリアルタイムに出力を受け取る方法
  • ツール呼び出し(Function Calling)で外部関数と連携する方法
  • レート制限・タイムアウトを考慮したエラーハンドリング
  • 実践サンプル:TypeScriptコードレビューボットの実装

動作確認環境:Node.js 20以上、TypeScript 5.x、@anthropic-ai/sdk 最新版。Node.js + TypeScript 環境の構築がまだの方は【TypeScript】Node.js + TypeScript 完全ガイドを先にご覧ください。

スポンサーリンク

APIキーの取得と環境構築

Claude APIを使うには、Anthropic Consoleでアカウントを作成し、APIキーを発行する必要があります。APIキーは sk-ant- で始まる文字列です。コードに直接書き込まず、必ず環境変数で管理します。

プロジェクトのセットアップ

Terminal
mkdir claude-api-sample && cd claude-api-sample
npm init -y
npm install typescript tsx @types/node --save-dev
npm install @anthropic-ai/sdk
npx tsc --init

tsconfig.json の最低限の設定です。targetES2022 以上にすることで、top-level await が使えます。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

環境変数の設定

.env
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx
APIキーの管理
.env ファイルは必ず .gitignore に追加してください。GitHubにAPIキーを公開すると即座に失効させられます。CI/CD環境ではシークレット変数として設定します。
.gitignore
.env
node_modules/
dist/

.env ファイルの読み込みには dotenv パッケージを使うか、Node.js 20.6以降なら node --env-file=.env フラグが使えます。

Terminal(Node.js 20.6以降)
node --env-file=.env -r tsx/esm src/index.ts

基本的なテキスト生成

まず最もシンプルなケースから始めます。anthropic.messages.create() が Claude API のメイン関数です。modelmax_tokensmessages の3つが必須パラメータです。

src/index.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
// ANTHROPIC_API_KEY 環境変数から自動的にAPIキーを読み込む

const response = await client.messages.create({
  model: "claude-opus-4-6",
  max_tokens: 1024,
  messages: [
    { role: "user", content: "TypeScriptの型推論の仕組みを200字で説明してください。" }
  ],
});

// テキストを取り出す
const text = response.content[0];
if (text.type === "text") {
  console.log(text.text);
}

// トークン使用量の確認
console.log(`入力: ${response.usage.input_tokens} / 出力: ${response.usage.output_tokens} トークン`);

レスポンスの型構造

messages.create() が返す Message 型の主なフィールドを確認しておきます。SDKにすべてのフィールドの型定義が含まれているため、エディタ補完が効きます。

レスポンス構造(型定義)
import type { Message, ContentBlock } from "@anthropic-ai/sdk/resources";

// Message 型の主なフィールド
interface Message {
  id: string;           // メッセージID(例: msg_01XxxxxX)
  type: "message";
  role: "assistant";
  model: string;        // 使用したモデル名
  content: ContentBlock[];   // 生成されたコンテンツの配列
  stop_reason: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | null;
  usage: {
    input_tokens: number;   // 入力トークン数(= 課金対象)
    output_tokens: number;  // 出力トークン数(= 課金対象)
  };
}

// ContentBlock は通常 type: "text" のオブジェクト
// ツール呼び出し時は type: "tool_use" になる

利用可能なモデル一覧

モデルID 特徴 入力価格 出力価格 コンテキスト
claude-opus-4-6 最高性能・複雑な推論・コーディング $5/MTok $25/MTok 1Mトークン
claude-sonnet-4-6 速度と性能のバランス(実務の第一選択) $3/MTok $15/MTok 1Mトークン
claude-haiku-4-5-20251001 最速・軽量タスク向け $1/MTok $5/MTok 200kトークン
モデル選択の目安
開発・プロトタイプ段階では claude-haiku-4-5-20251001 でコストを抑えて試し、品質確認後に claude-sonnet-4-6claude-opus-4-6 に切り替えるのが効率的です。モデルIDは定数で管理しておくと切り替えが容易です。

システムプロンプトでClaudeの振る舞いを定義する

システムプロンプトは、Claudeが会話全体を通じて従う指示を定義します。「あなたはTypeScript専門家です」「回答は日本語で300字以内にしてください」のように、AIの役割・制約・出力形式を指定します。system パラメータは messages とは別に渡します。

src/system-prompt.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  // system はメッセージ配列とは別に指定する
  system: `あなたはTypeScriptの専門家です。
以下のルールを厳守してください:
- 回答は必ず日本語で行う
- コード例は常にTypeScriptで記述する
- 型定義を省略しない
- 初心者にもわかるよう丁寧に説明する`,
  messages: [
    { role: "user", content: "型アサーションとは何ですか?" }
  ],
});

const text = response.content[0];
if (text.type === "text") {
  console.log(text.text);
}
システムプロンプトのベストプラクティス

  • 役割を明示する:「あなたは〜です」と宣言する
  • 出力形式を指定する:長さ・言語・構造(JSON、箇条書きなど)
  • 制約を書く:やってほしくないこと(「コードコメントは省略しない」など)
  • 具体的に書く:曖昧な指示より具体的な条件の方が効果的

マルチターン会話(会話履歴の管理)

Claude APIはステートレスです。会話の文脈を保持するには、これまでのやり取りをすべて messages 配列に含めて送信します。「user → assistant → user → assistant …」と交互に積み上げていくのが基本パターンです。

src/multi-turn.ts
import Anthropic from "@anthropic-ai/sdk";
import type { MessageParam } from "@anthropic-ai/sdk/resources";

const client = new Anthropic();

// 会話履歴を配列で管理
const history: MessageParam[] = [];

async function chat(userMessage: string): Promise<string> {
  // ユーザーメッセージを履歴に追加
  history.push({ role: "user", content: userMessage });

  const response = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    system: "あなたはTypeScriptの親切な講師です。",
    messages: history,  // 会話履歴を毎回すべて渡す
  });

  // Claudeの返答を取り出す
  const assistantMessage = response.content[0];
  if (assistantMessage.type !== "text") {
    throw new Error("Unexpected response type");
  }

  // アシスタントの返答も履歴に追加
  history.push({ role: "assistant", content: assistantMessage.text });

  return assistantMessage.text;
}

// 会話例
console.log(await chat("TypeScriptのジェネリクスを教えてください。"));
console.log(await chat("先ほどの説明で出てきた<T>はどういう意味ですか?"));  // 文脈を参照できる
console.log(await chat("実務でよく使うパターンを教えてください。"));
会話履歴とトークン消費
会話が長くなるほどhistoryが大きくなり、毎回のリクエストで全履歴分のトークンが課金されます。実務では古いメッセージをトリミングするか、プロンプトキャッシング(cache_control)を活用してコストを抑えることを検討してください。

ストリーミングでリアルタイム出力を受け取る

デフォルトでは生成完了まで待ってから結果を返しますが、ストリーミングを使うとトークンが生成されるたびにリアルタイムで受け取れます。チャットUIや長い文章生成など、ユーザーを待たせたくないシーンで効果的です。

src/streaming.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// stream() は Promise ではないため await 不要
const stream = client.messages.stream({
  model: "claude-sonnet-4-6",
  max_tokens: 2048,
  messages: [
    { role: "user", content: "TypeScriptのasync/awaitのベストプラクティスを解説してください。" }
  ],
});

// テキストが届くたびに処理する(コールバック方式)
stream.on("text", (text) => {
  process.stdout.write(text);  // 改行なしで出力(逐次表示)
});

// ストリーム完了後の最終メッセージを取得
const finalMessage = await stream.finalMessage();
console.log(`

--- 完了 ---`);
console.log(`入力: ${finalMessage.usage.input_tokens} / 出力: ${finalMessage.usage.output_tokens} トークン`);

for await を使った非同期イテレーション方式

コールバックではなく for await ループで処理することもできます。TypeScriptの非同期処理に慣れている方にはこちらの方が直感的かもしれません。

src/streaming-iterator.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// for await 方式でも await 不要
const stream = client.messages.stream({
  model: "claude-sonnet-4-6",
  max_tokens: 2048,
  messages: [
    { role: "user", content: "TypeScriptのasync/awaitを解説してください。" }
  ],
});

for await (const chunk of stream) {
  // content_block_delta イベントにテキストが入っている
  if (
    chunk.type === "content_block_delta" &&
    chunk.delta.type === "text_delta"
  ) {
    process.stdout.write(chunk.delta.text);
  }
}

console.log("
完了");

ツール呼び出し(Function Calling)

ツール呼び出しは、Claudeが「この処理はツールで実行すべき」と判断したときに、指定した関数の呼び出し指示を返す機能です。外部API取得・DB検索・計算処理など、Claude単体ではできないことをTypeScriptコードと連携させられます。

src/tool-use.ts
import Anthropic from "@anthropic-ai/sdk";
import type { Tool, MessageParam } from "@anthropic-ai/sdk/resources";

const client = new Anthropic();

// ── ツールの定義 ──────────────────────────────
const tools: Tool[] = [
  {
    name: "get_current_time",
    description: "現在の日時を取得します",
    input_schema: {
      type: "object",
      properties: {},
      required: [],
    },
  },
  {
    name: "calculate",
    description: "四則演算を実行します",
    input_schema: {
      type: "object",
      properties: {
        expression: {
          type: "string",
          description: "計算式(例: '10 + 20 * 3')",
        },
      },
      required: ["expression"],
    },
  },
];

// ── 実際のツール実装 ──────────────────────────
function executeTool(name: string, input: Record<string, unknown>): string {
  switch (name) {
    case "get_current_time":
      return new Date().toLocaleString("ja-JP");
    case "calculate": {
      // ⚠️ 実務では eval() / Function() は使わず、
      // mathjs や expr-eval などの数式パーサーライブラリを使うこと
      const expr = input.expression as string;
      if (!/^[\d\s+\-*/.()]+$/.test(expr)) {
        return "エラー: 使用できない文字が含まれています";
      }
      // eslint-disable-next-line no-new-func
      return String(new Function(`"use strict"; return (${expr})`)());
    }
    default:
      return "未実装のツールです";
  }
}

// ── ツール呼び出しループ ──────────────────────
async function runWithTools(userMessage: string): Promise<string> {
  const messages: MessageParam[] = [
    { role: "user", content: userMessage }
  ];

  // Claude がツール使用を止めるまでループ
  while (true) {
    const response = await client.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 1024,
      tools,
      messages,
    });

    // ツール使用なし → テキスト応答を返す
    if (response.stop_reason === "end_turn") {
      const text = response.content.find((b) => b.type === "text");
      return text?.type === "text" ? text.text : "";
    }

    // ツール呼び出しがある場合
    if (response.stop_reason === "tool_use") {
      // アシスタントの返答(ツール呼び出し指示)を履歴に追加
      messages.push({ role: "assistant", content: response.content });

      // ツールを実行してresultを返す
      const toolResults = response.content
        .filter((b) => b.type === "tool_use")
        .map((b) => {
          if (b.type !== "tool_use") return null;
          const result = executeTool(b.name, b.input as Record<string, unknown>);
          return {
            type: "tool_result" as const,
            tool_use_id: b.id,
            content: result,
          };
        })
        .filter(Boolean);

      messages.push({ role: "user", content: toolResults as MessageParam["content"] });
    }
  }
}

// 使用例
console.log(await runWithTools("今の時刻を教えて、さらに 123 × 456 を計算してください。"));
ツール呼び出しの処理フロー

  1. ユーザーメッセージを送信
  2. Claudeが「このタスクにはツールが必要」と判断 → stop_reason: "tool_use" で返答
  3. アプリ側でツールを実行し、結果を tool_result として返す
  4. Claudeが結果を受け取り、最終的な回答を生成
  5. この流れを stop_reason === "end_turn" になるまで繰り返す

エラーハンドリング

APIを本番で使うには、エラーハンドリングが不可欠です。SDKは Anthropic.APIError というクラスでエラーを包んで投げます。status コードによって対応を変えるのがポイントです。

ステータスコード エラーの種類 対応方法
401 認証エラー(APIキー不正) APIキーを確認・再発行
403 アクセス拒否(権限不足) Anthropic Consoleで権限確認
429 レート制限超過 retry-afterヘッダーの時間待機後にリトライ
500〜529 Anthropicサーバーエラー 指数バックオフでリトライ
timeout タイムアウト タイムアウト値を調整・リトライ
src/error-handling.ts
import Anthropic from "@anthropic-ai/sdk";

// maxRetries と timeout をSDK初期化時に設定できる
const client = new Anthropic({
  maxRetries: 3,     // 自動リトライ回数(デフォルト2)
  timeout: 30_000,   // 30秒でタイムアウト
});

async function safeChat(message: string): Promise<string | null> {
  try {
    const response = await client.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 1024,
      messages: [{ role: "user", content: message }],
    });

    const text = response.content[0];
    return text.type === "text" ? text.text : null;

  } catch (error) {
    if (error instanceof Anthropic.APIError) {
      switch (error.status) {
        case 401:
          console.error("APIキーが無効です。環境変数を確認してください。");
          break;
        case 429: {
          // レート制限: retry-after ヘッダーの時間を待つ
          const retryAfter = error.headers?.["retry-after"];
          const waitSec = retryAfter ? parseInt(retryAfter, 10) : 60;
          console.warn(`レート制限。${waitSec}秒後にリトライしてください。`);
          break;
        }
        case 529:
          console.warn("Anthropicサーバーが過負荷状態です。しばらく待ってください。");
          break;
        default:
          console.error(`APIエラー (${error.status}): ${error.message}`);
      }
      return null;
    }

    // SDK以外の予期しないエラー
    throw error;
  }
}

const result = await safeChat("こんにちは");
if (result) {
  console.log(result);
}

より複雑なエラーハンドリングパターンについては【TypeScript】エラーハンドリング完全ガイドで詳しく解説しています。Result型パターンやcatch節のunknown型の扱い方も参考にしてください。

実践サンプル:TypeScriptコードレビューボット

ここまでの内容を組み合わせて、TypeScriptコードを入力するとレビューを返してくれるCLIツールを作ります。システムプロンプト・マルチターン・エラーハンドリングを組み合わせた実務に近い例です。

src/code-review-bot.ts
import Anthropic from "@anthropic-ai/sdk";
import * as fs from "fs/promises";
import * as path from "path";
import type { MessageParam } from "@anthropic-ai/sdk/resources";

const client = new Anthropic({ maxRetries: 2 });

const SYSTEM_PROMPT = `あなたはTypeScriptの上級エンジニアです。
送られてきたコードをレビューし、以下の観点でフィードバックを日本語で返してください:

1. **型安全性**:型定義が適切か、anyが不必要に使われていないか
2. **パフォーマンス**:不要な再レンダリングや計算がないか
3. **可読性**:命名・構造がわかりやすいか
4. **バグリスク**:null参照・エッジケースの見落としはないか
5. **改善案**:具体的なコード例とともに提案する

深刻な問題は⚠️、軽微な提案は?でマークしてください。`;

// ファイルを読み込んでレビューする
async function reviewFile(filePath: string): Promise<void> {
  const code = await fs.readFile(filePath, "utf-8");
  const fileName = path.basename(filePath);

  console.log(`? ${fileName} をレビューしています...
`);

  const messages: MessageParam[] = [
    {
      role: "user",
      content: `以下の TypeScript コードをレビューしてください。

ファイル名: ${fileName}

\`\`\`typescript
${code}
\`\`\``,
    },
  ];

  // ストリーミングで出力
  const stream = client.messages.stream({
    model: "claude-opus-4-6",  // コードレビューは高精度モデルを使用
    max_tokens: 2048,
    system: SYSTEM_PROMPT,
    messages,
  });

  stream.on("text", (text) => process.stdout.write(text));

  const finalMsg = await stream.finalMessage();
  console.log(`

--- レビュー完了(${finalMsg.usage.output_tokens} トークン使用)---`);

  // フォローアップ質問
  const reviewText = finalMsg.content[0];
  if (reviewText.type === "text") {
    messages.push({ role: "assistant", content: reviewText.text });
  }
  messages.push({
    role: "user",
    content: "最も優先度が高い修正点を1つだけ教えてください。",
  });

  const followUp = await client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 512,
    system: SYSTEM_PROMPT,
    messages,
  });

  const followUpText = followUp.content[0];
  if (followUpText.type === "text") {
    console.log(`
? 最優先の修正点:
${followUpText.text}`);
  }
}

// コマンドライン引数でファイルパスを受け取る
const [, , targetFile] = process.argv;
if (!targetFile) {
  console.error("使い方: npx tsx src/code-review-bot.ts <ファイルパス>");
  process.exit(1);
}

await reviewFile(targetFile);
実行方法
npx tsx src/code-review-bot.ts ./src/some-component.ts

TypeScript型定義を活用した安全な実装

@anthropic-ai/sdkresources サブパスから型をエクスポートしています。これらを活用することで、APIの使い方を型レベルで守れる安全なラッパー関数が作れます。

src/typed-client.ts
import Anthropic from "@anthropic-ai/sdk";
import type {
  MessageParam,
  MessageCreateParamsNonStreaming,
  Message,
  TextBlock,
} from "@anthropic-ai/sdk/resources";

// ── 型安全な設定型 ──────────────────────────────
type ClaudeModel =
  | "claude-opus-4-6"
  | "claude-sonnet-4-6"
  | "claude-haiku-4-5-20251001";

interface ChatOptions {
  model?: ClaudeModel;
  maxTokens?: number;
  system?: string;
  temperature?: number;  // 0〜1(デフォルト1)
}

// ── 型安全なラッパー関数 ───────────────────────
const client = new Anthropic();

async function chat(
  messages: MessageParam[],
  options: ChatOptions = {}
): Promise<string> {
  const params: MessageCreateParamsNonStreaming = {
    model: options.model ?? "claude-sonnet-4-6",
    max_tokens: options.maxTokens ?? 1024,
    messages,
    ...(options.system && { system: options.system }),
    ...(options.temperature !== undefined && { temperature: options.temperature }),
  };

  const response: Message = await client.messages.create(params);

  // TextBlock のみを抽出して結合
  return response.content
    .filter((block): block is TextBlock => block.type === "text")
    .map((block) => block.text)
    .join("");
}

// ── 使用例 ────────────────────────────────────
const answer = await chat(
  [{ role: "user", content: "TypeScriptのReadonly型を説明してください。" }],
  {
    model: "claude-sonnet-4-6",
    system: "簡潔に日本語で答えてください。",
    maxTokens: 512,
  }
);

console.log(answer);

まとめ

この記事では、TypeScriptから Claude API を使う方法を基礎から実践まで解説しました。

機能 使用するAPI/メソッド 主なユースケース
テキスト生成 messages.create() 質問回答・文章生成・要約
システムプロンプト system パラメータ 役割・制約・出力形式の固定
マルチターン会話 messages 配列に履歴を積む チャットボット・対話型ツール
ストリーミング messages.stream() チャットUI・長文生成
ツール呼び出し tools + ループ処理 外部API連携・計算・DB検索
エラーハンドリング Anthropic.APIError 本番サービス・安定稼働

次のステップとして、TypeScriptの非同期処理を深く理解することで、ストリーミングや並列リクエスト処理をより堅牢に実装できます。また、Node.js + TypeScript 環境構築を整備すれば、Claude APIを使ったCLIツールやバックエンドサービスの開発基盤ができあがります。

よくある質問

QAPIキーはどこで取得できますか?

AAnthropic Console(console.anthropic.com)にアクセスし、アカウントを作成してAPIキーを発行します。有料プランへの登録が必要です。

Q無料で使えますか?

A新規アカウントには無料クレジットが付与されますが、継続利用は有料です。料金はモデルとトークン数によって異なります。claude-haiku が最も安価なので、開発・テスト段階ではこちらを使うことをお勧めします。

Qブラウザ(フロントエンド)から直接使えますか?

ASDKはブラウザにも対応していますが、APIキーがクライアントに露出するため本番環境では使わないでください。バックエンドのAPIエンドポイント経由で呼び出すのが正しい構成です。

QOpenAI APIとの違いは何ですか?

AClaude APIは messages.create()、OpenAI APIは chat.completions.create() と似た構造ですが、ツール定義の形式・システムプロンプトの渡し方・レスポンスの型構造が異なります。両者を比較した記事は【Python】OpenAI APIを使ってチャットボットを作る方法も参考にしてください。

QストリーミングはExpress.jsやHonoでも使えますか?

Aはい。SSE(Server-Sent Events)またはReadableStreamでラップすることで利用できます。Vercel AI SDKを使うとNext.jsやHonoとの統合が簡単です。