0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code × OpenRouter Free Models — コスト0円で自動化エージェントを3倍速で回す実践パターン5選

0
Posted at

TL;DR

  • OpenRouter の :free モデルは API キー 1 本で 10+ モデルを無料で呼び出せる
  • Claude Code のサブエージェント (Task ツール) に :free モデルを組み合わせると、思考ループのコストを大幅削減できる
  • 本記事では「呼び出しパターン」「モデル選定指針」「フォールバック設計」「プロンプトキャッシュとの併用」「コスト上限ガード」を具体的なコードつきで解説

背景

Claude Code (旧 claude-dev) は Anthropic の API を直接呼び出す設計のため、複雑なタスクを解かせると Opus / Sonnet のトークン消費が積み上がる。

一方 OpenRouter は 2024 年末から :free サフィックス付きモデルを無料枠として提供しており、2025〜2026 年現在では以下のようなモデルが無料で利用できる。

モデル名 (OpenRouter ID) 提供元 用途感
google/gemini-2.0-flash-exp:free Google 高速・長コンテキスト
meta-llama/llama-4-scout:free Meta コード生成・英語
qwen/qwen3-235b-a22b:free Alibaba 多言語・推論
mistralai/mistral-small-3.2-24b-instruct:free Mistral 軽量・欧州系タスク
deepseek/deepseek-r1:free DeepSeek 数学・ロジック
moonshotai/kimi-k2:free Moonshot 長文・要約

これらを「思考の下請け」として使い、Claude Sonnet には「統合・判断・最終出力」だけを担わせる構成が、コスト最小・品質維持の王道になりつつある。


パターン 1 — タスク分類ルーター: 「安いモデルで十分か」を最初に判定する

最もシンプルなパターン。入力タスクを A: 定型 / B: 推論 / C: 創造 に分類し、A は :free モデルへ流す。

import httpx
import json

OPENROUTER_API = "https://openrouter.ai/api/v1/chat/completions"

ROUTER_MODEL  = "qwen/qwen3-235b-a22b:free"
PREMIUM_MODEL = "anthropic/claude-sonnet-4-5"   # ← 有料、最後の砦

HEADERS = {
    "Authorization": f"Bearer {OPENROUTER_API_KEY}",  # 実環境では os.environ
    "Content-Type": "application/json",
}


def classify_task(user_input: str) -> str:
    """A=routine / B=reasoning / C=creative"""
    prompt = f"""Classify the following task into exactly one label:
- A: routine  (data transform, format, extract, summarize template)
- B: reasoning (multi-step logic, math, code debug)
- C: creative  (novel writing, architecture design, strategy)

Task: {user_input}
Reply with only the single letter."""
    payload = {
        "model": ROUTER_MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": 3,
    }
    res = httpx.post(OPENROUTER_API, headers=HEADERS, json=payload, timeout=20)
    return res.json()["choices"][0]["message"]["content"].strip().upper()


def route_and_call(user_input: str) -> str:
    label = classify_task(user_input)
    model = ROUTER_MODEL if label in ("A", "B") else PREMIUM_MODEL
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": user_input}],
    }
    res = httpx.post(OPENROUTER_API, headers=HEADERS, json=payload, timeout=60)
    choice = res.json()["choices"][0]["message"]["content"]
    return f"[{model}] {choice}"

ポイント: 分類自体も :free モデルに任せる。分類は 3 トークンで完結するので高速・ゼロコスト。


パターン 2 — ファンアウト並列: 3 モデルに同じ問いを投げて多数決

コード生成や SQL 生成など「正解がある問い」では、複数の :free モデルに並列で聞いて 多数決 or ユニーク出力をマージ する手法が有効。

import asyncio
import httpx

FREE_MODELS = [
    "google/gemini-2.0-flash-exp:free",
    "meta-llama/llama-4-scout:free",
    "qwen/qwen3-235b-a22b:free",
]

async def call_free(client: httpx.AsyncClient, model: str, prompt: str) -> str:
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": 512,
    }
    r = await client.post(OPENROUTER_API, headers=HEADERS, json=payload, timeout=30)
    return r.json()["choices"][0]["message"]["content"]


async def fan_out(prompt: str) -> list[str]:
    async with httpx.AsyncClient() as client:
        tasks = [call_free(client, m, prompt) for m in FREE_MODELS]
        return await asyncio.gather(*tasks, return_exceptions=True)


def majority_vote(answers: list[str]) -> str:
    # 簡易: 最も文字数が多い (= 最も詳細な) ものを返す
    valid = [a for a in answers if isinstance(a, str)]
    return max(valid, key=len)
results = asyncio.run(fan_out("Write a SQL query to find top 5 users by order count"))
best = majority_vote(results)
print(best)

注意: :free モデルはレートリミットがある (多くは 10 RPM 程度)。
asyncio.gather の並列度は 3〜5 程度に留め、asyncio.Semaphore で絞るのが安全。


パターン 3 — フォールバックチェーン: エラー時に自動で上位モデルへ昇格

:free モデルはサーバー過負荷で 429 / 503 を返すことがある。エクスポネンシャルバックオフ+モデル昇格のチェーンを組む。

import time
import httpx

MODEL_CHAIN = [
    "deepseek/deepseek-r1:free",
    "qwen/qwen3-235b-a22b:free",
    "anthropic/claude-sonnet-4-5",        # 最終手段・有料
]

def call_with_fallback(prompt: str, max_retries_per_model: int = 2) -> str:
    for model in MODEL_CHAIN:
        wait = 1
        for attempt in range(max_retries_per_model):
            try:
                payload = {
                    "model": model,
                    "messages": [{"role": "user", "content": prompt}],
                }
                r = httpx.post(
                    OPENROUTER_API, headers=HEADERS, json=payload, timeout=45
                )
                if r.status_code == 200:
                    print(f"[OK] model={model} attempt={attempt+1}")
                    return r.json()["choices"][0]["message"]["content"]
                if r.status_code in (429, 503):
                    print(f"[WAIT] {model} status={r.status_code} sleep={wait}s")
                    time.sleep(wait)
                    wait *= 2
                else:
                    # 4xx 系はリトライ不要
                    break
            except httpx.TimeoutException:
                print(f"[TIMEOUT] {model}")
                time.sleep(wait)
                wait *= 2
    raise RuntimeError("All models exhausted")

設計思想: 無料モデルが死んでも有料モデルで確実にジョブを完遂する。
本番では有料モデルへの到達頻度をメトリクスで監視して :free モデルの安定性を評価する。


パターン 4 — Claude Code の Task ツールと組み合わせるプロンプト設計

Claude Code の .claude/commands/ にカスタムコマンドを置くと、Claude が自身でサブエージェントを起動できる。

<!-- .claude/commands/free-draft.md -->
# Draft with Free Model

## Usage
/free-draft <topic>

## Instructions
You are a coordinator. Your job is:
1. Call the OpenRouter API directly via `Bash` tool using curl, targeting model `qwen/qwen3-235b-a22b:free`
2. Pass the user's topic as the prompt
3. Return the raw response as a draft
4. DO NOT use any paid Anthropic model for the draft step
5. After receiving the draft, summarize it in 3 bullet points yourself

This minimizes token usage on the premium model (you) to only the summarization step.

Bash ツールから curl で直接 OpenRouter を叩く例:

# Claude Code の Bash ツール内で実行されるイメージ
curl -s https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen/qwen3-235b-a22b:free",
    "messages": [{"role":"user","content":"Draft an article about Rust async runtimes"}],
    "max_tokens": 1024
  }' | jq -r '.choices[0].message.content'

ポイント: Claude Sonnet が「ドラフトのサマリー」だけを担当するため、実際のトークン消費は Claude 側で数百トークン程度に収まる。


パターン 5 — コスト上限ガード: トークンカウンタで Sonnet への昇格を抑制

無意識に Sonnet を何度も呼んでしまう問題を防ぐため、セッション内のトークン消費量を累積カウントし、上限に達したら :free モデルへ強制ルーティングする。

from dataclasses import dataclass, field

@dataclass
class CostGuard:
    """
    Tracks estimated token usage.
    Prices (approx, USD per 1M tokens):
      claude-sonnet-4-5 input $3 / output $15
      :free models        $0 / $0
    """
    budget_usd: float = 0.10       # セッション予算 10 セント
    _input_tokens: int  = field(default=0, init=False)
    _output_tokens: int = field(default=0, init=False)

    INPUT_PRICE  = 3.0 / 1_000_000   # $3 per 1M
    OUTPUT_PRICE = 15.0 / 1_000_000  # $15 per 1M

    def record(self, input_tokens: int, output_tokens: int) -> None:
        self._input_tokens  += input_tokens
        self._output_tokens += output_tokens

    @property
    def spent_usd(self) -> float:
        return (self._input_tokens  * self.INPUT_PRICE
              + self._output_tokens * self.OUTPUT_PRICE)

    @property
    def over_budget(self) -> bool:
        return self.spent_usd >= self.budget_usd

    def select_model(self, preferred_premium: str, free_fallback: str) -> str:
        if self.over_budget:
            print(f"[GUARD] Budget exhausted (${self.spent_usd:.4f}). Using free model.")
            return free_fallback
        return preferred_premium


guard = CostGuard(budget_usd=0.05)

def smart_call(prompt: str) -> str:
    model = guard.select_model(
        preferred_premium="anthropic/claude-sonnet-4-5",
        free_fallback="qwen/qwen3-235b-a22b:free",
    )
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
    }
    r = httpx.post(OPENROUTER_API, headers=HEADERS, json=payload, timeout=60)
    data = r.json()
    usage = data.get("usage", {})
    guard.record(
        input_tokens=usage.get("prompt_tokens", 0),
        output_tokens=usage.get("completion_tokens", 0),
    )
    return data["choices"][0]["message"]["content"]

補足: OpenRouter のレスポンスには usage フィールドが含まれる。モデルによって精度が異なるが、予算ガードとして十分機能する。


5 パターンのまとめ表

# パターン名 コスト削減効果 向いているユースケース
1 タスク分類ルーター ★★★★☆ 汎用 CLI ツール・チャットボット
2 ファンアウト並列 ★★★☆☆ SQL 生成・コード生成
3 フォールバックチェーン ★★☆☆☆ バッチ処理・夜間ジョブ
4 Claude Code Task 連携 ★★★★★ 記事執筆・コードレビュー自動化
5 コスト上限ガード ★★★★☆ 長時間セッション・マルチターン

よくあるハマりどころ

:free モデルの RPM 制限

ほとんどの :free モデルは 10 RPM / 200 RPD 程度の制限がある。ループ内でそのまま叩くと即 429 になるので、最低でも time.sleep(6) を挟む。

import time

for item in large_batch:
    result = call_with_fallback(item)
    time.sleep(6)  # 10 RPM → 6秒間隔

thinking モード (extended thinking) は :free 非対応

DeepSeek R1 や Qwen3 の「thinking」モードは OpenRouter :free 枠では無効または制限される場合がある。
推論問題でコストを出したくない場合は deepseek/deepseek-r1:free の通常モードを使い、出力に <think>...</think> タグが含まれていたらパース時に除去する。

import re

def strip_thinking(text: str) -> str:
    return re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL).strip()

OpenRouter モデルの廃止・名称変更

:free モデルのリストは頻繁に変わる。定期的に https://openrouter.ai/api/v1/models を叩いてモデル一覧を取得し、:free サフィックスを持つものだけをフィルタするコードを保守しておくと安全。

def fetch_free_models() -> list[str]:
    r = httpx.get("https://openrouter.ai/api/v1/models", timeout=10)
    models = r.json().get("data", [])
    return [m["id"] for m in models if m["id"].endswith(":free")]

まとめ

  • OpenRouter :free モデルはプロダクションでも十分使える (ただし信頼性は有料に劣る)
  • 分類→並列→フォールバックの 3 層設計が最もコスト・品質のバランスが良い
  • Claude Code の Task ツールやカスタムコマンドと組み合わせると、Sonnet の消費をサマリーステップのみに絞れる
  • コスト上限ガードを必ず入れる。ループバグで予算が溶けた時点でフリーモデルへ強制フォールバックする設計は保険として有効

自動化エージェントの「コスト vs. 品質」トレードオフを設計する際の参考になれば幸いです。


参考リンク


✍️ 本記事の著者: 合同会社ジモラボ

ジモラボは、八王子を拠点に AI を活用した SaaS を多数開発しています。本記事の技術検証もそうした開発過程の副産物です。

興味を持っていただけたら、ぜひ各 SNS のフォローもお願いします!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?