AI

Claude Haiku 5.5入門——小さなAIタスクをFirestoreで原価管理する

Claude Haiku 5.5を題材に、Next.js + Firestoreで問い合わせ分類や要約などの高頻度AIタスクを低コストに回し、実行単価を監査する実践手順を解説します。

2026年10月8日
AnthropicClaude Haiku 5.5Next.jsFirestoreAIコスト管理
Claude Haiku 5.5入門——小さなAIタスクをFirestoreで原価管理する

はじめに

AI機能をプロダクトに入れると、実は「大きな推論」よりも「小さな処理」が先に増えます。

小さな処理ありがちな課題
問い合わせの一次分類1件あたりは軽いが、毎日数千件になる
チケット要約失敗すると担当者の確認時間が増える
GitHub Issueの優先度付け判断根拠とコストが後から追えない
長い会話の圧縮便利だが、積み上がると月額が読みにくい

Anthropicは2026年10月7日に Claude Haiku 5.5 を発表しました。公式発表では、高頻度でコストに敏感なタスク、要約、圧縮、データベースクエリ、分類、ライブカスタマーサポート、ブラウザ利用などに向いた小型モデルとして紹介されています。モデルIDは claude-haiku-5-5 です。

本記事では、架空の社内問い合わせ管理ツール「HelpPort」を題材に、Next.js App Router + Firestore + TypeScriptで「Haiku 5.5用の実行台帳」を作ります。この記事を読み終えると、AI分類を単に呼び出すだけでなく、入力トークン、出力トークン、推定コスト、confidence、人手確認の有無をFirestoreに残せるようになります。

参考にした一次情報は次の通りです。

何が変わったのか

Haiku 5.5の価値は、「全部を高性能モデルへ投げる」設計を見直せる点にあります。Anthropicの発表では、Haiku 5.5はHaiku 4.5より大幅に安く、100,000トークン以下のプロンプトでは入力が100万トークンあたり0.10ドル、出力が100万トークンあたり0.50ドルと案内されています。さらに、Haikuクラスとして初めて調整可能な effort を持つため、軽い分類は低め、少し迷う要約は中程度、という運用に寄せられます。

ただし移行時の注意点もあります。公式の移行ガイドでは、Haiku 5.5に移る場合、モデルIDの差し替えだけでなく、トークン数の再計測、thinking の扱い、temperature / top_p / top_k の削除、assistant prefillの見直し、stop_reason: "refusal" の処理が必要だと説明されています。

HelpPortでは、Haiku 5.5を「安いから何でも任せるモデル」ではなく、「高頻度の小タスクを測りながら任せるモデル」として扱います。

事前準備

サーバー側だけでAPIキーを扱います。ここではSDKの便利メソッド名を推測せず、公式API Referenceで確認できる POST /v1/messages を fetch で呼びます。

yarn add firebase-admin
ANTHROPIC_API_KEY=sk-ant-...

Firestoreには、問い合わせ本体の supportTickets と、AI呼び出しの記録である aiTaskRuns を分けて保存します。本文を長期保存しすぎない方針なら、台帳側には問い合わせID、処理種別、要約、分類結果、トークン使用量だけを残す設計にしてください。

コレクション用途
supportTickets問い合わせ本文、顧客プラン、現在の担当キュー
aiTaskRunsモデル、effort、入力種別、usage、推定コスト、判断結果

ハンズオン1: Haiku 5.5の分類関数を作る

まず、問い合わせを受け取り、構造化JSONで分類結果を返す関数を作ります。ClaudeのStructured outputsは output_config.format にJSON Schemaを渡す仕組みです。後続処理を壊さないよう、分類カテゴリは enum で固定します。

type TriageResult = {
  queue: "billing" | "technical" | "success";
  urgency: "low" | "medium" | "high" | "critical";
  summary: string;
  confidence: number;
  needsHumanReview: boolean;
};

type ClaudeMessageResponse = {
  stop_reason: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "refusal" | string;
  content: Array<{ type: "text"; text: string } | { type: string }>;
  usage?: {
    input_tokens: number;
    output_tokens: number;
  };
};

export async function classifyTicketWithHaiku(input: {
  ticketId: string;
  subject: string;
  message: string;
  customerPlan: "free" | "team" | "enterprise";
}) {
  const apiKey = process.env.ANTHROPIC_API_KEY;
  if (!apiKey) {
    throw new Error("ANTHROPIC_API_KEY is not configured");
  }

  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": apiKey,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-haiku-5-5",
      max_tokens: 1024,
      thinking: { type: "adaptive" },
      output_config: {
        effort: "low",
        format: {
          type: "json_schema",
          schema: {
            type: "object",
            properties: {
              queue: { type: "string", enum: ["billing", "technical", "success"] },
              urgency: { type: "string", enum: ["low", "medium", "high", "critical"] },
              summary: { type: "string" },
              confidence: { type: "number" },
              needsHumanReview: { type: "boolean" },
            },
            required: ["queue", "urgency", "summary", "confidence", "needsHumanReview"],
            additionalProperties: false,
          },
        },
      },
      system:
        "You classify support tickets for a SaaS product. Return concise operational labels only.",
      messages: [
        {
          role: "user",
          content:
            `Ticket ID: ${input.ticketId}\n` +
            `Customer plan: ${input.customerPlan}\n` +
            `Subject: ${input.subject}\n` +
            `Message: ${input.message}`,
        },
      ],
    }),
  });

  if (!response.ok) {
    throw new Error(`Claude API failed: ${response.status}`);
  }

  const payload = (await response.json()) as ClaudeMessageResponse;
  if (payload.stop_reason === "refusal") {
    throw new Error("Claude refused this request");
  }

  const text = payload.content.find((block) => block.type === "text")?.text;
  if (!text) {
    throw new Error("Claude did not return a text block");
  }

  return {
    result: JSON.parse(text) as TriageResult,
    usage: payload.usage,
  };
}

ここで temperature や top_p を指定していない点が重要です。Haiku 5.5の移行ガイドでは、これらのサンプリングパラメータを削除するよう案内されています。出力の安定性は、温度調整ではなく、スキーマ、プロンプト、effort、アプリ側の検証で担保します。

ハンズオン2: Firestoreに原価台帳を残す

次に、Route Handlerから分類を実行し、トークン使用量と推定コストを aiTaskRuns に保存します。ここでは100,000トークン以下の一般的な問い合わせを想定し、公式発表のHaiku 5.5価格を使って概算します。

// src/app/api/tickets/[ticketId]/triage/route.ts
import { FieldValue } from "firebase-admin/firestore";
import { NextRequest, NextResponse } from "next/server";
import { adminDb } from "@/lib/firebase/admin";
import { classifyTicketWithHaiku } from "@/lib/ai/classifyTicketWithHaiku";

type Params = {
  params: Promise<{ ticketId: string }>;
};

function estimateHaiku55Usd(usage?: { input_tokens: number; output_tokens: number }) {
  if (!usage) return null;

  const inputUsd = (usage.input_tokens / 1_000_000) * 0.10;
  const outputUsd = (usage.output_tokens / 1_000_000) * 0.50;

  return Number((inputUsd + outputUsd).toFixed(8));
}

export async function POST(request: NextRequest, { params }: Params) {
  const { ticketId } = await params;
  const body = (await request.json()) as {
    subject?: string;
    message?: string;
    customerPlan?: "free" | "team" | "enterprise";
  };

  if (!body.subject || !body.message || body.message.length > 8000) {
    return NextResponse.json({ error: "invalid ticket" }, { status: 400 });
  }

  const runRef = adminDb.collection("aiTaskRuns").doc();

  await runRef.set({
    ticketId,
    task: "support_triage",
    provider: "anthropic",
    model: "claude-haiku-5-5",
    effort: "low",
    status: "running",
    createdAt: FieldValue.serverTimestamp(),
    updatedAt: FieldValue.serverTimestamp(),
  });

  try {
    const { result, usage } = await classifyTicketWithHaiku({
      ticketId,
      subject: body.subject,
      message: body.message,
      customerPlan: body.customerPlan ?? "free",
    });

    const needsHumanReview =
      result.needsHumanReview ||
      result.confidence < 0.72 ||
      result.urgency === "critical";

    await runRef.update({
      status: "succeeded",
      result,
      needsHumanReview,
      usage: usage ?? null,
      estimatedUsd: estimateHaiku55Usd(usage),
      updatedAt: FieldValue.serverTimestamp(),
    });

    await adminDb.collection("supportTickets").doc(ticketId).set(
      {
        triage: {
          queue: result.queue,
          urgency: result.urgency,
          summary: result.summary,
          aiTaskRunId: runRef.id,
          needsHumanReview,
        },
        updatedAt: FieldValue.serverTimestamp(),
      },
      { merge: true },
    );

    return NextResponse.json({ result, needsHumanReview });
  } catch (error) {
    await runRef.update({
      status: "failed",
      error: error instanceof Error ? error.message : "unknown error",
      updatedAt: FieldValue.serverTimestamp(),
    });

    return NextResponse.json({ error: "triage failed" }, { status: 500 });
  }
}

実務では、estimatedUsd だけでなく、ユーザー、顧客プラン、GitHub Issue番号、画面名も一緒に残すと便利です。1件ごとの単価が小さくても、月間10万件の分類になると意思決定に効いてきます。

ハンズオン3: GitHub Actionsで日次レポートを作る

最後に、Firestoreの台帳を日次で集計し、予算超過の兆候をGitHub Issueへ通知できる形にします。ここでは処理の考え方だけを示します。

監査したい値見る理由
task ごとの合計コストどのAI機能が増えているか分かる
confidence の分布自動化しすぎていないか確認できる
needsHumanReview の比率AIの品質低下や入力変化を検知できる
stop_reason / エラーrefusalやmax_tokensを早期に見つけられる

Haiku 5.5のような小型モデルは、1回の呼び出し単価だけを見ると「気にしなくてよさそう」に見えます。しかし、プロダクトに組み込むなら、数ではなく線で見た方が安全です。日次の推移、機能別の偏り、特定顧客での急増を追えるようにしておくと、モデル変更やプロンプト変更の判断がかなり楽になります。

運用で気をつけたいこと

Haiku 5.5への移行では、まず既存プロンプトのトークン数を数え直してください。移行ガイドでは、Haiku 5.5は新しいトークナイザーを使うため、同じテキストでもHaiku 4.5より多くのトークンとして数えられる場合があると説明されています。max_tokens と予算見積もりは、過去の数値をそのまま使わない方が安全です。

また、effort は「高いほど常に正義」ではありません。HelpPortなら、問い合わせ分類は low、要約は medium、法務やセキュリティに関わる判断はHaikuに閉じず人手確認または上位モデルへ回す、という分担にします。

まとめ

Claude Haiku 5.5は、AI機能を「たまに使う高級な推論」から「毎日大量に走る業務処理」へ広げるときに扱いやすいモデルです。

特にNext.js + Firestoreの構成では、API呼び出しをRoute Handlerに閉じ込め、aiTaskRuns にusageと推定コストを保存するだけで、AI機能の原価が見えるようになります。小さな分類、要約、圧縮から始め、confidenceと人手確認率を見ながら適用範囲を広げていくのがおすすめです。