1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者向け】サーバ屋が生成AIのAPIを初めて触る ③つまずき解決編 〜実際に遭遇したエラー6連発と解決法FAQ〜

1
Last updated at Posted at 2026-08-04

はじめに

シリーズ3回目です。①でセットアップ、②で全機能を体験してきましたが、順調に見えて裏では結構つまずいていました。今回はその全部を「症状 → 原因 → 解決」のFAQ形式で公開します。

各回のタイトル 内容
①セットアップ編 無償プラン契約〜アカウントトークン(APIキー)発行〜最初の1リクエスト
②全機能ためす編 チャット生成/ベクトル埋め込み/音声の文字起こし/音声の読み上げ
③つまずき解決編(本記事) 実際に遭遇したエラーと解決法FAQ
④まとめ編 30分で作るミニアプリ+無償枠の賢い使い方(予定)

エラーメッセージで検索してたどり着いた方は、下の早見表から該当箇所へ飛んでください。

症状の早見表

# 症状 発生場面
Q1 SSL certificate OpenSSL verify result: unable to get local issuer certificate Windows環境 + PHPでcurlのAPI呼び出し
Q2 php.iniを編集したのに反映されない/エラーが何も表示されない Windows環境構築
Q3 Invalid date.timezone value 'JST' という起動時Warning PHPインストール時のタイムゾーン設定
Q4 HTTP 400 — This model is not available. チャットAPIの呼び出し
Q5 curlコマンドでは動くのに、PHPからだと401になる コマンドプロンプト利用者のみ
Q6 ストリーミングが無言で正常終了する(エラーなし・出力なし) 思考型モデルのストリーミング

前半(Q1〜Q3)は環境構築系、後半(Q4〜Q6)はAPI呼び出し系です。


Q1. SSL certificate OpenSSL verify result: unable to get local issuer certificate

症状

Windows環境でPHPのcurlからAPIを呼び出すと、下記エラーが発生し通信に失敗する。

> php chat_basic.php
通信エラー: SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)

原因

Windows版PHPは「信頼できる証明書のリスト(CA証明書)」の参照先が未設定のため。

LinuxではOSが持つCA証明書ストアをPHPが自動で参照しますが、Windows版PHPは何も設定されていません。そのためHTTPS接続先(さくらのAPIサーバ)の証明書を検証できず、安全のため接続を拒否します。さくら側の障害ではなく、手元のPHPの設定不足です。

解決

  1. curl公式のcacert.pem をダウンロード
  2. php.iniと同じフォルダ(場所は php --ini で確認)に置く
  3. php.iniに1行追加:
curl.cainfo = "C:\Users\<ユーザー名>\AppData\Local\Programs\PHP\current\cacert.pem"

やってはいけない解決法

検索すると次のコードがよく出てきますが、使わないでください

// ダメな例: 証明書の検証を放棄する
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

エラーは消えますが、「通信相手が本物か確認しない」設定です。偽サーバに誘導されてもエラーにならず、APIキーごと送信してしまいます。エラーを消すことと問題を解決することは別物、という典型例です。


Q2. php.iniを編集したのに反映されない / エラーが何も表示されない

症状

  • php.iniの extension=curl を有効にしたはずなのに Call to undefined function curl_init() になる
PS C:\sakura-ai-engine> php chat_basic.php
PHP Fatal error:  Uncaught Error: Call to undefined function curl_init() in C:\sakura-ai-engine\chat_basic.php:16
Stack trace:
#0 {main}
  thrown in C:\sakura-ai-engine\chat_basic.php on line 16

Fatal error: Uncaught Error: Call to undefined function curl_init() in C:\sakura-ai-engine\chat_basic.php:16
Stack trace:
#0 {main}
  thrown in C:\sakura-ai-engine\chat_basic.php on line 16
  • コードにミスがあるはずなのに、実行しても何も表示されず終わる

原因

原因として、次の2パターンが考えられるので、それぞれチェックして対応が必要となります。

原因A: 編集したphp.iniが、実際に読み込まれているphp.iniと違う。 PHPが複数入っている環境や、インストーラが作る深い階層のパスでは、まったく別のファイルを編集していることがあります。

原因B: エラーは起きているが、エラー表示がOffになっている。 ②全機能ためす編で使ったPHP公式インストールスクリプトが作るphp.iniは本番環境向けの設定(php.ini-production)がベースのため、display_errors = Offになっています。PHPコードのエラーが発生していても何も表示されません。

解決

まず「本当に読み込まれているphp.ini」を特定します。

php --ini
PS C:\> php --ini
Configuration File (php.ini) Path:
Loaded Configuration File:         C:\Users\hoge\AppData\Local\Programs\PHP\current\php.ini
Scan for additional .ini files in: (none)
Additional .ini files parsed:      (none)

ここで表示されたphp.iniファイルを編集し、エラーを表示するよう次のように設定します。

display_errors = On

なぜ本番環境はOffなのか: エラーメッセージにはファイルパスやコードの断片が含まれ、攻撃者へのヒントになるからです。「本番はOff、手元はOn」という使い分け自体がPHP運用の基本です。


Q3. PHP Warning: Invalid date.timezone value 'JST'

症状

PHPを実行するたび、下記のようなPHP Warningが表示される。

PHP Warning:  PHP Startup: Invalid date.timezone value 'JST', using 'UTC' instead in Unknown on line 0

原因

②全機能ためす編で使ったPHP公式インストールスクリプトの選択肢にて、日本時間のつもりで JST と入力し設定したため。PHPのタイムゾーン設定はタイムゾーンデータベースの名前(地域/都市の形式)しか受け付けず、JST のような略称は無効となる。無効な値は無視されてUTC扱いになるため、時刻が日本時間と9時間ずれてしまいます。

解決

php.iniで正式名を設定します。

date.timezone = "Asia/Tokyo"

②全機能ためす編のインストールスクリプトの対話形式でタイムゾーンを聞かれた際、Asia/Tokyo と入力します。ちなみにタイムゾーン略称がダメな理由は、同じ略称が世界で重複するから(例: CSTだと、アメリカ中部標準時・中国標準時など)。「都市名まで書けば一意になる」という設計になっています。


Q4. HTTP 400 — This model is not available.

症状

PHPを実行すると、下記のようなAPIエラーが表示される。

PS C:\sakura-ai-engine> php chat_basic.php
APIエラー (HTTP 400): {"error":{"message":"This model is not available."}}

原因

モデル名の指定ミス。モデル名は gpt-oss-120b のような短いものから llm-jp-3.1-8x13b-instruct4 のような長いものまであり、大文字小文字・ハイフンの位置まで完全一致が必要です。手入力するのではなく、正式名をコピーして使うのが確実です。

また、以前使えていたモデルが「提供終了」になることもあるため、定期的にチェックするようにしましょう。

image.png

解決

使えるモデルの正式名は、API自身に聞くのが確実です。

curl -s https://api.ai.sakura.ad.jp/v1/models \
  -H "Authorization: Bearer <アカウントトークン>"

この一覧からコピペしてください。①セットアップ編で「最初の1リクエストはモデル一覧の取得がおすすめ」と書いたのは、これが理由です。


Q5. curlコマンドでは動くのに、PHPからだと401になる(コマンドプロンプトの怪)

個人的に今回一番面白かったつまずきです。同じ環境変数を使っているのに、curlコマンドは成功し、PHPは認証エラーになるという現象です。

症状

①セットアップ編の手順で、コマンドプロンプトの環境変数にアカウントトークンを設定します。

set SAKURA_API_KEY="<アカウントトークン>"

この状態で、curlコマンドは動きます

curl -s https://api.ai.sakura.ad.jp/v1/models -H "Authorization: Bearer %SAKURA_API_KEY%"
→ モデル一覧が正常に返る

ところが、PHPから同じ環境変数を読むと401になります。

chat_test.php:

<?php
$apiKey = getenv('SAKURA_API_KEY');

$payload = [
    'model' => 'gpt-oss-120b',
    'messages' => [
        ['role' => 'system', 'content' => 'あなたは簡潔に答えるアシスタントです。'],
        ['role' => 'user',   'content' => 'HTTPとHTTPSの違いを2文で教えてください。'],
    ],
    'max_tokens' => 300,
];

$ch = curl_init('https://api.ai.sakura.ad.jp/v1/chat/completions');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);

if ($response === false) {
    exit('通信エラー: ' . curl_error($ch) . "\n");
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

$data = json_decode($response, true);
if ($status !== 200) {
    exit("APIエラー (HTTP {$status}): " . $response . "\n");
}
echo $data['choices'][0]['message']['content'] . "\n";

実行結果:

c:\sakura-ai-engine>php chat_test.php
APIエラー (HTTP 401): {"error":{"message":"Invalid token"}}

原因

原因は set で使った引用符です。コマンドプロンプトの set は、bashやPowerShellと違って引用符も値の一部として保存します。確認してみましょう。

echo [%SAKURA_API_KEY%]

実行結果:

c:\sakura-ai-engine>echo [%SAKURA_API_KEY%]
["3 ... F"]   ← 引用符ごと入っている!(実際はアカウントトークン全文が表示されます。ここでは伏せています)

では、なぜcurlは動いたのか? 展開後のコマンドラインは次の形になります。

-H "Authorization: Bearer "<アカウントトークン>""

これをcurl.exeが引数として解釈するとき、「"Authorization: Bearer "(引用区間その1)+<トークン>+""(空の引用区間その2)」と連結処理され、引用符が偶然すべて相殺されて、正しいヘッダが送信されていたのです。curl -v を付けると、送信ヘッダに引用符が無いことが確認できます。

一方PHPの getenv() は環境変数の値をそのまま(引用符ごと)受け取るため、Bearer "<トークン>" という壊れたヘッダが送信されて401になります。

解決

コマンドプロンプトでは引用符なしで設定するのが正解です。

set SAKURA_API_KEY=<アカウントトークン>

ただ、②全機能ためす編でAPIキーを環境変数から設定ファイル(config.php)方式に切り替えたのは、まさにこの種のOS依存の罠から逃れるためでした。ファイル読み込みなら全環境で同じ挙動になります。


Q6. chatストリーミングが無言で正常終了する(エラーなし・出力なし)

②全機能ためす編で遭遇した、この連載最大のつまずきです。

症状

PS C:\sakura-ai-engine> php chat_stream.php

PS C:\sakura-ai-engine>

エラーなし。出力なし。終了コードも正常。手がかりなし

原因

2段構えの罠でした。

その1: 思考型モデルは、本文の前に「思考」を流してくる。 gpt-oss-120bは回答前に思考(reasoning)を行うモデルで、ストリーミングでは思考トークンが delta.content ではなく別フィールド(delta.reasoning)で流れてきます。content だけを拾う単純な実装では、思考中ずっと無言になります。

その2: 思考もmax_tokensを消費する。 max_tokens: 300 程度だと、思考だけで枠を使い切り、finish_reason: length で終了。本文は1文字も生成されません。「思考の分までトークンを確保する」必要があります。

実は①セットアップ編にヒントがありました。最初のcurl実行で返ってきたJSONに、見慣れない reasoning フィールドがあったのです。あのときは「そういうものか」とスルーしましたが、ここでつまづきの原因として理解できました。

解決

3点セットで対策します。②全機能ためす編に完成版コードを掲載済みです。

  1. 思考の進行を可視化する: delta.reasoning を検知したらドット等で表示
  2. max_tokensを多めに確保する: 思考型モデルなら2000程度
  3. 本文ゼロで終わったら理由を表示する: finish_reason をチェックし、length なら「トークン切れ」と表示

対策後の動作:

※ドットの数だけ思考チャンクが流れています(この例で1,000個超)。20字の質問に対する思考量として眺めてみてください

PS C:\sakura-ai-engine> php chat_stream.php
(思考中..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................)
生成AIの歴史は、1990年代後半の統計的言語モデルに始まる。2006年に深層学習が注目され、2014年のGAN登場で画像生成が革命的に変化。2018年のGPTが自然言語生成を大幅に向上させ、以降、ChatGPTやStable Diffusionなど多様なモデルが実用化され、日常や産業に浸透している。教育や医療でも応用が広がり、倫理議論が活発化している。今後は制御と創造性の調和が重要になる時代だ。

コラム: モデルgpt-oss-120bが「私はChatGPTです」と名乗る件

①セットアップ編の実行結果をよく見ると、gpt-oss-120bが自己紹介で「ChatGPT」と名乗っていました。これはエラーではありません。gpt-ossはOpenAIが公開したオープンウェイトモデルで、学習の由来から自分をChatGPTと認識することがあります。AIの自己申告は正確とは限らないので、モデルの正体はAPIレスポンスの model フィールドで確認するのが確実です。


まとめ

6つのつまずきを並べると、共通する教訓が3つ見えてきます。

  • エラーを消すことと、問題を解決することは別物(Q1の検証オフ、Q5の「たまたま動く」)
  • 「何も表示されない」には必ず理由がある(Q2のdisplay_errors、Q6の思考トークン)
  • 困ったら、思い込みではなく事実を確認するコマンドを叩く(php --iniecho [%VAR%]curl -v/v1/models)

3つ目は生成AIに限らず、サーバ運用のトラブルシューティングと完全に同じ対応です。生成AIのAPIといっても、結局は「HTTPSで利用できる外部サービス」。普段の障害調査のやり方がそのまま通用できます。

なお、この記事のために再現実験で消費したリクエストは9回。累計でもまだ枠の1%程度です。
image.png

次回予告: ④まとめ編

最終回は、ここまでの学びを全部使って30分で作れるミニアプリを1本仕上げます。あわせて、無償枠(チャット3,000/埋め込み10,000/音声各50)の賢い配分についても連載のまとめとして書く予定です。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?