OpenAI APIをTypeScriptで使うと、型安全なAIアプリケーションを効率よく構築できます。公式SDKの openai パッケージはTypeScriptファーストに設計されており、すべてのリクエスト・レスポンスに細かい型定義が付いています。コンパイル時にミスを検出できるため、JavaScriptよりも安心して実装を進められます。
この記事では openai SDK を使って、OpenAI APIをTypeScriptで呼び出す方法をゼロから解説します。環境構築・Chat Completions の基本から始まり、ストリーミング・Function Calling・画像認識(Vision)・構造化出力(Structured Outputs + Zod)まで、実務で使えるパターンを豊富なコード例とともに紹介します。
openaiSDKのインストールとTypeScript環境の構築方法- Chat Completions APIでテキストを生成する基本パターン
- システムプロンプトとマルチターン会話(会話履歴)の管理方法
- ストリーミングでリアルタイムに出力を表示する実装
- Function Calling(ツール呼び出し)で外部処理と連携する方法
- 画像を入力してテキストで回答させるVision(gpt-4o)の使い方
- Zodと組み合わせた型安全な構造化出力の実装
- レート制限・タイムアウトに対応したエラーハンドリングパターン
動作確認環境:Node.js 20以上、TypeScript 5.x、openai SDK v6系。Node.js + TypeScriptのセットアップがまだの方は【TypeScript】Node.js + TypeScript 完全ガイドを先にご参照ください。なお、Anthropic社のClaude APIをTypeScriptで使いたい方は【TypeScript】Claude API入門もあわせてご覧ください。
環境構築とAPIキーの設定
まずプロジェクトを作成し、OpenAI SDKをインストールします。APIキーはOpenAIのダッシュボード(platform.openai.com)で発行できます。sk- で始まる文字列です。
プロジェクトセットアップ
mkdir openai-ts-sample && cd openai-ts-sample npm init -y npm install typescript tsx @types/node --save-dev npm install openai npx tsc --init
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
APIキーの設定
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
APIキーをソースコードに直接書き込むと、GitHubへの誤プッシュでキーが漏洩します。
.env ファイルは必ず .gitignore に追加してください。本番環境ではサーバーの環境変数またはシークレット管理サービスを使います。.env node_modules/ dist/
Node.js 20.6以降は node --env-file=.env フラグで .env を直接読み込めます。それ以前のバージョンは dotenv パッケージを使ってください。
node --env-file=.env -r tsx/esm src/index.ts
Chat Completions APIの基本
client.chat.completions.create() が OpenAI API のメイン関数です。model・messages が必須パラメータで、max_tokens・temperature などは任意指定です。
import OpenAI from "openai";
const client = new OpenAI();
// OPENAI_API_KEY 環境変数から自動でAPIキーを読み込む
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "user", content: "TypeScriptの型推論の仕組みを200字で説明してください。" }
],
});
// 生成されたテキストを取り出す
const text = response.choices[0].message.content;
console.log(text);
// トークン使用量の確認
console.log(`入力: ${response.usage?.prompt_tokens} / 出力: ${response.usage?.completion_tokens} トークン`);
レスポンスの型構造
chat.completions.create() が返す ChatCompletion 型の主なフィールドです。SDKに全フィールドの型定義が含まれているため、エディタ補完が効きます。
import type { ChatCompletion } from "openai/resources";
// ChatCompletion の主なフィールド
interface ChatCompletionShape {
id: string; // レスポンスID
model: string; // 使用したモデル
choices: Array<{
index: number;
message: {
role: "assistant";
content: string | null; // テキスト応答
tool_calls?: ToolCall[]; // Function Calling 使用時
};
finish_reason:
| "stop" // 正常終了
| "length" // max_tokens に到達
| "tool_calls" // Function Calling 要求
| "content_filter"; // コンテンツフィルター
}>;
usage?: {
prompt_tokens: number; // 入力トークン
completion_tokens: number; // 出力トークン
total_tokens: number; // 合計(= 課金対象)
};
}
主要モデル一覧
| モデルID | 特徴 | コンテキスト | 主な用途 |
|---|---|---|---|
gpt-4o |
高精度・マルチモーダル対応 | 128kトークン | テキスト・画像・複雑なタスク |
gpt-4o-mini |
低コスト・高速 | 128kトークン | 軽量タスク・大量処理 |
o1 |
深い推論に特化 | 200kトークン | 数学・コーディング・複雑な推論 |
o3-mini |
o1より高速・低コスト推論 | 200kトークン | STEM推論・コード生成 |
OpenAIは頻繁に新モデルをリリースします。記事執筆時点で
gpt-4o が最もバランスの良い選択ですが、最新情報は platform.openai.com/docs/models を必ず確認してください。システムプロンプトとマルチターン会話
messages 配列に role: "system" を追加することで、AIの役割・制約・出力形式をあらかじめ指定できます。また、会話履歴を配列に蓄積することでマルチターンの対話を実現できます。
import OpenAI from "openai";
import type { ChatCompletionMessageParam } from "openai/resources";
const client = new OpenAI();
// 会話履歴を配列で管理
const history: ChatCompletionMessageParam[] = [
{
role: "system",
content: `あなたはTypeScriptの専門家です。
以下のルールを守ってください:
- 回答は必ず日本語
- コード例はTypeScriptで記述し、型定義を省略しない
- 初心者にもわかりやすく説明する`
},
];
async function chat(userMessage: string): Promise<string> {
// ユーザーメッセージを履歴に追加
history.push({ role: "user", content: userMessage });
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: history, // 会話履歴を毎回すべて渡す
temperature: 0.7,
});
const assistantText = response.choices[0].message.content ?? "";
// アシスタントの返答も履歴に追加(次の質問で文脈が使える)
history.push({ role: "assistant", content: assistantText });
return assistantText;
}
// 会話例
console.log(await chat("ジェネリクスを教えてください。"));
console.log(await chat("さっき出てきた<T>はどういう意味ですか?")); // 文脈を参照
console.log(await chat("実務でよく使うパターンを3つ挙げてください。"));
会話が長くなるほど
history が大きくなり、毎リクエストで全履歴分のトークンが課金されます。長期の会話では古いメッセージを削除するか、要約に置き換える設計が必要です。gpt-4o の最大コンテキストは128kトークンですが、長くなるほどコストも増えます。ストリーミングでリアルタイム出力を受け取る
デフォルトでは生成が完全に完了してから結果が返ります。stream: true を指定すると、生成されるたびにチャンク単位でデータを受け取れます。チャットUIや長い文章生成でユーザーの体感待ち時間を大幅に短縮できます。
import OpenAI from "openai";
const client = new OpenAI();
// stream: true でストリーミングを有効化
const stream = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "user", content: "TypeScriptのasync/awaitのベストプラクティスを解説してください。" }
],
stream: true,
});
// for await でチャンクを逐次処理
for await (const chunk of stream) {
// 各チャンクのテキスト差分を取得
const delta = chunk.choices[0]?.delta?.content;
if (delta) {
process.stdout.write(delta); // 改行なしで逐次表示
}
// finish_reason が "stop" になったら完了
if (chunk.choices[0]?.finish_reason === "stop") {
console.log("
--- 生成完了 ---");
}
}
ストリーミング + 使用トークン数の取得
ストリーミング中はデフォルトでトークン数が返ってきません。stream_options: { include_usage: true } を指定すると、最終チャンクにトークン数が含まれます。
import OpenAI from "openai";
const client = new OpenAI();
const stream = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "短い詩を書いてください。" }],
stream: true,
stream_options: { include_usage: true }, // openai SDK v6以降で利用可能
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
// 最終チャンクに usage が含まれる
if (chunk.usage) {
console.log(`
入力: ${chunk.usage.prompt_tokens} / 出力: ${chunk.usage.completion_tokens} トークン`);
}
}
Function Calling(ツール呼び出し)
Function Calling は、モデルが「この処理は外部関数で実行すべき」と判断したとき、関数名と引数の呼び出し指示を返す機能です。外部API取得・DB検索・計算処理など、モデル単体では実現できない処理をTypeScriptコードと組み合わせられます。
import OpenAI from "openai";
import type {
ChatCompletionTool,
ChatCompletionMessageParam,
ChatCompletionToolMessageParam,
} from "openai/resources";
const client = new OpenAI();
// ── ツールの定義 ──────────────────────────────────
const tools: ChatCompletionTool[] = [
{
type: "function",
function: {
name: "get_weather",
description: "指定した都市の現在の天気を取得します",
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "都市名(例: 東京、大阪、札幌)",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "温度の単位",
},
},
required: ["city"],
},
},
},
{
type: "function",
function: {
name: "search_articles",
description: "ブログ記事をキーワードで検索します",
parameters: {
type: "object",
properties: {
keyword: {
type: "string",
description: "検索キーワード",
},
limit: {
type: "number",
description: "取得件数(デフォルト: 5)",
},
},
required: ["keyword"],
},
},
},
];
// ── ツールの実装(実際の処理) ───────────────────────
function executeFunction(name: string, args: Record<string, unknown>): string {
switch (name) {
case "get_weather": {
// 実際はここで天気APIを呼び出す
const city = args.city as string;
return JSON.stringify({ city, temp: 22, condition: "晴れ", humidity: 60 });
}
case "search_articles": {
// 実際はここでDBやElasticsearchを検索する
const keyword = args.keyword as string;
return JSON.stringify([
{ id: 1, title: `${keyword}の基礎`, url: "/article/1" },
{ id: 2, title: `${keyword}実践ガイド`, url: "/article/2" },
]);
}
default:
return JSON.stringify({ error: "未実装のツールです" });
}
}
// ── Function Calling ループ ───────────────────────
async function runWithTools(userMessage: string): Promise<string> {
const messages: ChatCompletionMessageParam[] = [
{ role: "user", content: userMessage },
];
// モデルが "stop" を返すまでループ
while (true) {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages,
tools,
tool_choice: "auto", // "auto": 必要なときだけ呼び出す
});
const choice = response.choices[0];
// ツール呼び出しなし → テキスト応答を返す
if (choice.finish_reason === "stop") {
return choice.message.content ?? "";
}
// ツール呼び出し要求
if (choice.finish_reason === "tool_calls") {
// アシスタントのメッセージ(ツール呼び出し指示)を履歴に追加
messages.push(choice.message);
// 各ツールを実行して結果を返す
const toolResults: ChatCompletionToolMessageParam[] = (
choice.message.tool_calls ?? []
).map((toolCall) => {
const args = JSON.parse(toolCall.function.arguments) as Record<string, unknown>;
const result = executeFunction(toolCall.function.name, args);
console.log(`[ツール実行] ${toolCall.function.name}(${toolCall.function.arguments})`);
console.log(`[結果] ${result}`);
return {
role: "tool" as const,
tool_call_id: toolCall.id,
content: result,
};
});
messages.push(...toolResults);
}
}
}
// 使用例
const answer = await runWithTools(
"東京と大阪の天気を調べて、TypeScriptの記事も検索してください。"
);
console.log("
最終回答:", answer);
- ユーザーメッセージ + ツール定義をAPIに送信
- モデルが「ツールが必要」と判断 →
finish_reason: "tool_calls"で返答 - アプリ側でツールを実行し、結果を
role: "tool"として追加 - モデルが結果を受け取り最終回答を生成 →
finish_reason: "stop" - このループを
stopになるまで繰り返す(複数ツールの連鎖も可)
Vision:画像を入力してテキストで回答させる
gpt-4o はテキストだけでなく画像も入力できます(マルチモーダル)。content を配列にして image_url タイプを含めるだけで使えます。スクリーンショットの解析・図の説明・OCR的な用途に便利です。
URLで画像を渡す
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "user",
content: [
{ type: "text", text: "この画像に写っているコードの問題点を指摘してください。" },
{
type: "image_url",
image_url: {
url: "https://example.com/code-screenshot.png",
detail: "high", // "low" | "high" | "auto"(default)
},
},
],
},
],
});
console.log(response.choices[0].message.content);
ローカルファイルをBase64で渡す
import OpenAI from "openai";
import * as fs from "fs";
import * as path from "path";
const client = new OpenAI();
// サポートする画像フォーマット: JPEG, PNG, GIF, WebP
function imageToBase64(filePath: string): { base64: string; mimeType: string } {
const ext = path.extname(filePath).toLowerCase();
const mimeMap: Record<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".webp": "image/webp",
};
const mimeType = mimeMap[ext] ?? "image/jpeg";
const base64 = fs.readFileSync(filePath).toString("base64");
return { base64, mimeType };
}
const { base64, mimeType } = imageToBase64("./screenshot.png");
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "user",
content: [
{ type: "text", text: "このスクリーンショットのエラーを日本語で説明してください。" },
{
type: "image_url",
image_url: {
url: `data:${mimeType};base64,${base64}`,
},
},
],
},
],
max_tokens: 1024,
});
console.log(response.choices[0].message.content);
detail パラメータ |
トークン消費 | 向いているケース |
|---|---|---|
"low" |
少ない(約85トークン) | 画像全体の大まかな説明・サムネイル判断 |
"high" |
多い(画像サイズ依存) | 文字・細部の読み取り・コードスクリーンショット |
"auto"(デフォルト) |
自動調整 | とりあえずこれでOK |
構造化出力(Structured Outputs)でJSONを確実に取得する
「JSONで返してください」とプロンプトに書いても、モデルが別の形式で返すことがあります。Structured Outputs を使うと、指定したJSONスキーマに100%準拠した応答が得られます。Zodと組み合わせると型安全な実装が可能です。
JSON mode(基本)
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "system",
content: "ユーザーの入力を解析し、必ずJSONで返してください。",
},
{
role: "user",
content: "山田太郎、30歳、エンジニアの情報をJSONにしてください。",
},
],
response_format: { type: "json_object" }, // JSON modeを有効化
});
// まだ any 型なので手動でパースが必要
const data = JSON.parse(response.choices[0].message.content ?? "{}");
console.log(data);
Structured Outputs + Zod(推奨)
より強力な方法は response_format に json_schema を指定する方法です。Zodスキーマをそのまま使える zodResponseFormat ヘルパーが SDK に付属しており、レスポンスの型安全性を完全に保証できます。
npm install zod
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
const client = new OpenAI();
// ── 出力スキーマを Zod で定義 ──────────────────────
const ArticleSchema = z.object({
title: z.string().describe("記事タイトル"),
summary: z.string().describe("100字程度の要約"),
tags: z.array(z.string()).describe("関連タグ(3〜5件)"),
difficulty: z
.enum(["beginner", "intermediate", "advanced"])
.describe("難易度"),
estimatedReadingMinutes: z
.number()
.describe("想定読了時間(分)"),
});
// Zod スキーマから TypeScript 型を生成
type Article = z.infer<typeof ArticleSchema>;
// ── 構造化出力で記事メタデータを生成 ───────────────
const response = await client.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "user",
content:
"TypeScriptのジェネリクスについての記事メタデータを生成してください。",
},
],
// zodResponseFormat でスキーマを指定(strict: true が自動適用される)
response_format: zodResponseFormat(ArticleSchema, "article"),
});
// parsed は Article 型(型安全)
const article: Article = response.choices[0].message.parsed!;
console.log("タイトル:", article.title);
console.log("要約:", article.summary);
console.log("タグ:", article.tags.join(", "));
console.log("難易度:", article.difficulty);
console.log("読了時間:", article.estimatedReadingMinutes, "分");
- JSON mode:有効なJSONを返すことだけを保証。スキーマ準拠は不保証
- Structured Outputs:定義したスキーマに100%準拠。スキーマ違反なし
- Zodを使うと
zodResponseFormat()で簡単にスキーマ定義できる - フィールドの有無・型・enumの値まで型レベルで保証される
エラーハンドリング
OpenAI SDKは OpenAI.APIError 系のクラスでエラーを分類して投げます。ステータスコードに応じて適切に対処することで、安定したサービスを構築できます。
| エラークラス | ステータス | 主な原因と対処法 |
|---|---|---|
AuthenticationError |
401 | APIキーが不正。環境変数を確認 |
PermissionDeniedError |
403 | アクセス権限なし。プランを確認 |
NotFoundError |
404 | モデル名のtypoが多い。モデルIDを確認 |
RateLimitError |
429 | レート制限超過。retry-afterの時間待機してリトライ |
InternalServerError |
500〜 | OpenAIサーバーエラー。指数バックオフでリトライ |
APIConnectionError |
— | ネットワーク障害。接続を確認してリトライ |
import OpenAI from "openai";
// maxRetries と timeout を SDK 初期化時に一括設定できる
const client = new OpenAI({
maxRetries: 3, // 自動リトライ回数(デフォルト2)
timeout: 30_000, // 30秒でタイムアウト
});
async function safeCompletion(message: string): Promise<string | null> {
try {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: message }],
});
return response.choices[0].message.content;
} catch (error) {
// OpenAI SDK のエラーは APIError のサブクラスに分類される
if (error instanceof OpenAI.AuthenticationError) {
console.error("APIキーが無効です。OPENAI_API_KEY を確認してください。");
throw error; // 再試行しても意味がないので再スロー
}
if (error instanceof OpenAI.RateLimitError) {
// retry-after ヘッダーがあれば参照、なければ60秒待つ
const retryAfter = error.headers?.["retry-after"];
const wait = retryAfter ? parseInt(String(retryAfter), 10) * 1000 : 60_000;
console.warn(`レート制限です。${wait / 1000}秒後に手動リトライしてください。`);
return null;
}
if (error instanceof OpenAI.InternalServerError) {
console.warn(`OpenAIサーバーエラー (${error.status})。しばらく待ってください。`);
return null;
}
if (error instanceof OpenAI.APIConnectionError) {
console.error("ネットワーク接続エラー。インターネット接続を確認してください。");
return null;
}
// その他の APIError
if (error instanceof OpenAI.APIError) {
console.error(`APIエラー (${error.status}): ${error.message}`);
return null;
}
// SDK外の予期しないエラー
throw error;
}
}
const result = await safeCompletion("こんにちは");
if (result) console.log(result);
より高度なエラーハンドリングパターン(Result型・exhaustive check)は【TypeScript】エラーハンドリング完全ガイドで詳しく解説しています。
実践サンプル:ブログ記事の自動メタデータ生成ツール
ここまでの内容を組み合わせた実用的なサンプルです。記事の本文を入力すると、SEOタイトル・メタディスクリプション・タグ・カテゴリを一括生成します。Structured Outputs で型安全に、ストリーミングで進捗を表示しながら処理します。
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
const client = new OpenAI();
// ── 出力スキーマ ─────────────────────────────────
const BlogMetaSchema = z.object({
seoTitle: z
.string()
.describe("SEO最適化済みタイトル(32文字以内、キーワードを先頭に)"),
metaDescription: z
.string()
.describe("メタディスクリプション(80〜120字、CTA含む)"),
slug: z
.string()
.describe("URLスラッグ(英小文字・ハイフン区切り)"),
tags: z
.array(z.string())
.describe("関連タグ(5件以内)"),
category: z
.enum(["typescript", "javascript", "css", "python", "sql", "bat", "ai"])
.describe("最も適したカテゴリ"),
difficulty: z
.enum(["beginner", "intermediate", "advanced"])
.describe("対象読者の技術レベル"),
internalLinkSuggestion: z
.string()
.describe("関連する記事として内部リンクを貼るとよいキーワード"),
});
type BlogMeta = z.infer<typeof BlogMetaSchema>;
// ── メタデータ生成関数 ────────────────────────────
async function generateBlogMeta(articleContent: string): Promise<BlogMeta> {
console.log("メタデータを生成中...
");
const response = await client.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "system",
content: `あなたはSEO専門のコンテンツストラテジストです。
与えられた技術ブログ記事を分析し、SEO効果の高いメタデータを日本語で生成してください。
タイトルは検索意図に合わせ、メタディスクリプションはクリック率を高める文章にしてください。`,
},
{
role: "user",
content: `以下の記事のメタデータを生成してください:
${articleContent}`,
},
],
response_format: zodResponseFormat(BlogMetaSchema, "blog_meta"),
});
const meta = response.choices[0].message.parsed;
if (!meta) throw new Error("メタデータの生成に失敗しました");
return meta;
}
// ── 実行例 ───────────────────────────────────────
const sampleArticle = `
TypeScriptのジェネリクスは、型を引数として受け取る機能です。
<T>という記法を使い、さまざまな型に対応した汎用的な関数やクラスを作れます。
例えば、配列の最初の要素を返すfirst<T>(arr: T[]): T関数は
どんな型の配列にも使えます。実務では、APIレスポンスの型定義や
状態管理のストアなどでよく活用されます。
`;
try {
const meta = await generateBlogMeta(sampleArticle);
console.log("=== 生成されたメタデータ ===");
console.log("SEOタイトル:", meta.seoTitle);
console.log("メタディスクリプション:", meta.metaDescription);
console.log("スラッグ:", meta.slug);
console.log("タグ:", meta.tags.join(", "));
console.log("カテゴリ:", meta.category);
console.log("難易度:", meta.difficulty);
console.log("内部リンク候補:", meta.internalLinkSuggestion);
} catch (error) {
if (error instanceof OpenAI.APIError) {
console.error(`APIエラー: ${error.message}`);
} else {
throw error;
}
}
まとめ
この記事ではTypeScriptからOpenAI APIを使う方法を基礎から実践まで解説しました。
| 機能 | API/パラメータ | 主なユースケース |
|---|---|---|
| テキスト生成 | chat.completions.create() |
Q&A・文章生成・要約 |
| システムプロンプト | role: "system" |
役割・制約・出力形式の固定 |
| マルチターン会話 | messages配列に履歴を蓄積 |
チャットボット・対話ツール |
| ストリーミング | stream: true |
リアルタイム表示・長文生成 |
| Function Calling | tools + ループ |
外部API・DB・計算処理との連携 |
| Vision(画像入力) | image_urlコンテンツタイプ |
スクリーンショット解析・OCR |
| 構造化出力 | zodResponseFormat() |
型安全なJSONレスポンス |
| エラーハンドリング | OpenAI.RateLimitError等 |
レート制限・認証エラー対応 |
次のステップとして、TypeScriptの非同期処理を深く理解することで、並列リクエスト(Promise.all)やバッチ処理をより効率的に実装できます。また、Claude APIをTypeScriptで使う方法も合わせて学ぶと、モデルの特性に応じて使い分けられるようになります。
よくある質問
QAPIキーはどこで取得できますか?
Aplatform.openai.com/api-keys にアクセスし、OpenAIアカウントを作成後にAPIキーを発行できます。有料プランへの登録(クレジットカードの登録)が必要です。
Qgpt-4o と gpt-4o-mini はどちらを使えばよいですか?
A開発・テスト段階やコスト重視のケースでは gpt-4o-mini、精度が重要な本番タスクや複雑な推論には gpt-4o を選びましょう。gpt-4o-mini は gpt-4o の約1/10のコストで利用できます。
Qブラウザ(フロントエンド)から直接呼び出せますか?
A技術的には可能ですが、APIキーがクライアントに露出するため本番環境では絶対に避けてください。バックエンドAPIを経由してサーバー側から呼び出すのが正しい設計です。Next.js + Route Handlersや Express.js などを中継層として使います。
QClaude APIとOpenAI APIの使い分け方は?
A長文・複雑なコード生成・安全性重視のタスクには Claude、画像認識(Vision)・Function Callingの充実度・既存ツールとの連携しやすさでは OpenAI が強いとされています。実務では両方を試してタスクごとに使い分けるのが一般的です。Claude APIの使い方もあわせてご参照ください。
Qストリーミング中にエラーが発生した場合はどう処理しますか?
Afor await ループを try/catch で囲むことでエラーをキャッチできます。ストリームが途中で切れた場合は、それまでに受け取ったテキストを保存しておき、ユーザーに「中断されました」と表示しながら部分的な結果を返すのがUX上の定石です。
Qリクエストの並列処理はできますか?
APromise.all() を使えば複数リクエストを同時に送れますが、レート制限(RPM・TPM)に注意が必要です。OpenAIのTier1プランではデフォルト500 RPM程度です。大量処理が必要な場合は Batch API の利用を検討してください(コスト50%削減)。

