現在、主流となっている生成AI APIといえば OpenAI、Claude(Anthropic)、Gemini(Google) の3つが代表格です。
これらのAPIを使い分けたり、用途に応じて切り替えられる仕組みがあると、開発や検証が非常に効率化します。
今回は、複数の生成AI APIを一つの関数で切り替えて使える共通ラッパーの作成方法を紹介します。
さらに、対象ページの情報を取得してJSON形式に直してデータを取得する処理まで実装していきます。
各APIの価格比較
生成AIを活用したアプリケーション開発や業務改善が進む中、主要なAI APIとして注目されているのが「OpenAI(ChatGPT)」「Anthropic Claude」「Google Gemini」かと思います。本章では各APIの特徴等の紹介は省略しますが、
コスト面だけで見るとGeminiが圧倒的に安いです。
現状、利用範囲内において各AIの精度にあまり違いを感じられなかった私はコスト面重視でgeminiを利用することが多いです。
以下コストの比較表だけでもおいておきます。(2025年5月時点)
モデル/API | 入力(1Mトークン) | 出力(1Mトークン) |
---|---|---|
OpenAI GPT-3.5 Turbo | $0.50 | $1.50 |
OpenAI GPT-4o | $2.5 * | $10 |
Claude 3.5 Haiku | $0.80 * | $4 |
Claude 3.7 Sonnet | $3 * | $15 |
Gemini 1.5 Flash | $0.075 | $0.30 |
Gemini 2.5 Flash | $0.15 | 思考あり$0.6 思考なし$3.5 |
またgeminiは無料枠であればレート制限ありますが、1日1500回まで利用可能です。
※ OpenAIやClaudeは特定モデルはPrompt cachingの利用で安くなる可能有ります。
共通ラッパーの構成と使い方
以下のようにsrc/ai/
配下にファイルを分けて管理します。
src/ai/
├── index.ts // API選択ラッパー
├── openai.ts // OpenAI用処理
├── claude.ts // Claude用処理
└── gemini.ts // Gemini用処理
index.ts(共通ラッパー)
import { ZodTypeAny } from "zod";
import { fetchParsedClaude } from "./claude";
import { fetchParsedGemini } from "./gemini";
import { fetchParsedOpenAiGPT4o } from "./openai";
// ---- 型定義 ---- //
export type Option = {
max_tokens?: number;
};
type OpenAIArgs = {
ai: "openai";
messages: any[];
schema: ZodTypeAny;
option?: Option;
};
type ClaudeArgs = {
ai: "claude";
messages: any[];
schema: ZodTypeAny;
option?: Option;
};
type GeminiArgs = {
ai: "gemini";
messages: any[];
schema: ZodTypeAny;
option?: Option;
};
type AIArgs = OpenAIArgs | ClaudeArgs | GeminiArgs;
// ---- 関数オーバーロード(オプション) ---- //
export function fetchParsedWithAi(args: OpenAIArgs): Promise<any>;
export function fetchParsedWithAi(args: ClaudeArgs): Promise<any>;
export function fetchParsedWithAi(args: GeminiArgs): Promise<any>;
// ---- 実装 ---- //
export async function fetchParsedWithAi(args: AIArgs): Promise<any> {
if (args.ai === "openai") {
return fetchParsedOpenAiGPT4o(args.messages, args.schema, args.option);
} else if (args.ai === "claude") {
return fetchParsedClaude(args.messages, args.schema, args.option);
} else if (args.ai === "gemini") {
return fetchParsedGemini(args.messages, args.schema, args.option);
} else {
throw new Error("Unknown AI type");
}
}
各AIの実装例
以下各処理のprocess.env
で設定するAPIキーは各AIのサイトからアカウント作成して取得しておきましょう!
Claude (Anthropic Claude 3)
import Anthropic from "@anthropic-ai/sdk";
import { ZodArray, ZodTypeAny } from "zod";
import { Option } from "./";
export const fetchParsedClaude = async (
messages: any[],
schema: ZodTypeAny,
option?: Option
) => {
const anthropic = new Anthropic({
apiKey: process.env.CLAUDE_API_KEY,
});
const completion = await anthropic.messages.create({
model: "claude-3-7-sonnet-20250219", // モデルは適宜修正
max_tokens: 1024,
temperature: 0,
messages: messages,
...option,
});
const block = completion.content.find((c) => c.type === "text") as
| { type: "text"; text: string }
| undefined;
if (!block) {
throw new Error("Claudeの返答にtextタイプのブロックが含まれていません");
}
const raw = block.text.trim();
if (!raw) {
throw new Error("Claudeの返答が空です");
}
// schemaが配列型か判定
const expectsArray = schema instanceof ZodArray;
let firstBraceIndex: number;
let lastBraceIndex: number;
if (expectsArray) {
firstBraceIndex = raw.indexOf("[");
lastBraceIndex = raw.lastIndexOf("]");
} else {
firstBraceIndex = raw.indexOf("{");
lastBraceIndex = raw.lastIndexOf("}");
}
if (firstBraceIndex === -1 || lastBraceIndex === -1) {
throw new Error("期待する形式のJSON部分が見つかりませんでした");
}
const jsonString = raw.slice(firstBraceIndex, lastBraceIndex + 1);
let parsed;
try {
parsed = JSON.parse(jsonString);
} catch (e) {
console.error("JSONパース失敗:\n", jsonString);
throw e;
}
return schema.parse(parsed);
};
OpenAI (GPT-4o)
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z, ZodArray, ZodTypeAny } from "zod";
import { Option } from "./";
export const fetchParsedOpenAiGPT4o = async (
messages: any[],
schema: ZodTypeAny,
option?: Option
) => {
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// formatNameがなければ"default"を使う
const finalFormatName = "data";
const isArraySchema = schema instanceof ZodArray;
// 配列スキーマならラップ
const fixedSchema = isArraySchema
? z.object({ [finalFormatName]: schema })
: schema;
const completion = await openai.beta.chat.completions.parse({
model: "gpt-4o-2024-08-06", // モデルは適宜修正
messages,
response_format: zodResponseFormat(fixedSchema, finalFormatName),
...option,
});
const parsed = completion.choices[0]?.message?.parsed;
if (!parsed) {
throw new Error("OpenAIの返答が空です");
}
// ★ 配列スキーマだったら unwrap して中身だけ返す
return isArraySchema ? parsed[finalFormatName] : parsed;
};
Gemini (Google)
import OpenAI from "openai";
import { ZodArray, ZodTypeAny } from "zod";
import { Option } from "./";
export const fetchParsedGemini = async (
messages: any[],
schema: ZodTypeAny,
option?: Option
) => {
const openai = new OpenAI({
apiKey: process.env.GEMINI_API_KEY,
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
});
const response = await openai.chat.completions.create({
model: "gemini-2.0-flash", // モデルは適宜修正
messages,
temperature: 0,
max_tokens: 1024,
...option,
});
const raw = response.choices[0]?.message?.content?.trim();
if (!raw) {
throw new Error("Geminiの返答が空です");
}
// schemaが配列型か判定
const expectsArray = schema instanceof ZodArray;
let firstBraceIndex: number;
let lastBraceIndex: number;
if (expectsArray) {
firstBraceIndex = raw.indexOf("[");
lastBraceIndex = raw.lastIndexOf("]");
} else {
firstBraceIndex = raw.indexOf("{");
lastBraceIndex = raw.lastIndexOf("}");
}
if (firstBraceIndex === -1 || lastBraceIndex === -1) {
throw new Error("期待する形式のJSON部分が見つかりませんでした");
}
const jsonString = raw.slice(firstBraceIndex, lastBraceIndex + 1);
let parsed;
try {
parsed = JSON.parse(jsonString);
} catch (e) {
console.error("JSONパース失敗:\n", jsonString);
throw e;
}
return schema.parse(parsed);
};
実際の利用例(run.ts)
src/run.ts
を作成し、AI呼び出しの処理を作成してみましょう!
// プロンプトの生成(適宜修正)
const buildPrompt = (text: string) => `
以下のテキストを要約して、以下のJSON形式で返してください:
{
"summary": "<テキストの概要>"
}
<<<
${text}
>>>
`;
// JSONスキーマを定義
const DataSchema = z.object({
summary: z.string(),
});
type Data = z.infer<typeof DataSchema>;
// メインの処理
async function run() {
const url = 'https://xxx.example.com'; // ←読み込みたいwebサイトのURL
const { data } = await axios.get(`https://r.jina.ai/${url}`);
const prompt = buildPrompt(data);
const response: Data = await fetchParsedWithAi({
ai: "openai", // ←使用したいAIに適宜変更
messages: [{ role: "user", content: prompt }],
schema: DataSchema,
});
console.log(JSON.stringify(response, null ,2));
}
補足:r.jina.ai
とは?
https://r.jina.ai/
は、WebページをLLM向けのプレーンテキストに変換してくれる無料の中継APIです。スクレイピング不要で、対象ページのテキストだけを取得できるため非常に便利です。
おわりに
今回紹介したように、複数の生成AIを簡単に切り替えられるラッパー関数を作っておくと、比較検証や用途に応じた最適なAI選択がスムーズに行えます。
TypeScript × Zod × 各AI SDKを活用した構造化出力のパターン、ぜひプロジェクトに取り入れてみてください。