(作業メモ 20260920) PHP で file_get_contents から curl への変更

20 9月

API サーバーとのやり取りにおいて、file_get_contents のところが問題になっているように思えて、curl を試してみました。その記録です。リクエストに長い時間がかかる場合の話です。通信は2段階あって、その最初が PHP から PHP です。続けて PHP から Python を起動して ollama API と通信します。返事をもらうまでにいまのところ2、3分必要です。それがうまくいかない。

最初の PHP から PHP への通信を検討してみました。もとは下記のように file_get_contents で書いていました。最初は timeout を書いていなかったのですが、あとで追加しています。timeout を書き加えるまでは、ちょうど1分で切れていました。PHP のデフォルトの時間のようです(www-data が実行)。

$postdata = http_build_query(array('base64data' => $tmpBase64, 'TAnsAndEtc' => $tmpBase64a));

$headers = array(
    'Content-Type: application/x-www-form-urlencoded',
    'Content-Length: '.strlen($postdata)
    );

$context = stream_context_create([	
	'http' => ['method'  => 'POST',
		'header'=>implode( "\r\n", $headers),
		'ignore_errors' => true,
		'content' => $postdata,
		'timeout' => 300
	]	
]);

$res_base64 = file_get_contents($server . '/phpSimpleQuestion/allAnswerTest.php', false, $context);

timeout を追加して多少改善されたと思ったのですが、それでもかなりの確率で失敗します。そこで curl を使った下記のような記述に改めました。これは TIMEOUT を指定しないと、無期限に待つことになります。

$param = array('base64data' => $tmpBase64, 'TAnsAndEtc' => $tmpBase64a);

$url = $server . '/phpSimpleQuestion/allAnswerTest.php';

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($param));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 300);

$res_base64 = curl_exec($ch);

curl_close($ch);


$res = base64_decode($res_base64);

上記も確実というわけではありませんが、まあだいぶ良くなりました。

(自分への注意書き:RequestToRemoteServer.php の中の記述。採点差配サーバーのファイルなので、あとで忘れずに修正をかける必要がある。)

ついでなので、この先の通信のことを追記します。下記の Python コードを 実行して ollama API と通信します。こちらも問題なのかもしれません。時々 ollama から返事が無い。いまのところ import ollama を避けて、requests でやっています。www-data で実行するのですが、そうすると import ollama をするためには、特別な環境を用意する(activate する)必要があると思って避けています。下記では画像を読み込んで一緒に送っています。

import requests
fullpath = foldername + '/sample.png'

with open(fullpath, "rb") as f:
  img_base64 = base64.b64encode(f.read()).decode("utf-8")

url = "http://192.168.100.xxx:11434/api/chat"
payload = {
    "model": "gemma4:12b",
    "messages": [{
        "role": "user",
        "content": "この画像は物理学の問題です。解答するためのヒントを教えてください。",
        "images": [img_base64],
    }],
    "stream": False,
}

response = requests.post(url, json=payload, timeout=300)
print(response.json()["message"]["content"])

現状それほど安定していないので、import ollama が利用できる特別な環境を準備して、ollama Python ライブラリーを試したほうが良いのかもしれません。その時はまた追記します。

20260922

繰り返しではありますが ollama Python ライブラリーを使うことを避けていた理由は、www-data でコードを実行するからです。www-data はグローバルな環境(こんな言い方で良いのかな?)でプログラムを動かします。グローバルな環境では pip install ollama とか実行できなくて、ollama ライブラリー が使用可能な環境をグローバルとは別に用意する必要があります。しかし用意したとしても www-data で pyhton を実行するときに、特別な環境で実行する方法が分かりません。それでとりあえず www-data からアクセス可能なところにライブラリーを配置することで対応してみました。グローバルとは別に用意した環境で ollama ライブラリーをインストールして、そのファイルのコピーを www-data がアクセス可能な場所に配置しただけです(sys.path.insert のところ)。

import sys, os
import base64

sys.path.insert(0, '/どこか/lib/python3.12/site-packages/') # ollama ライブラリーの場所

from ollama import Client, ResponseError, RequestError

def generateTextWithOllamaClient(content: str, imageFilePath, hostURL, modelName) -> str | None:

    client = Client(host=hostURL, timeout=180)

    try:
        response = client.chat(
            model=modelName,
            messages=[{
                'role': 'user',
                'content': content,
                'images': [imageFilePath]
            }]
        )
        
        # 応答から生成されたテキストを取得
        generated_text = response['message']['content'].replace("'", "\\'")
        if generated_text:
            return generated_text.strip()
        else:
            print("エラー: レスポンスに 'message' キーが含まれていません。")
            print("レスポンス内容:", generated_text)
            return None        
        
    except RequestError as e:
        # 接続エラーなど、リクエスト自体の問題
        print(f"エラー: Ollamaサーバー への接続またはリクエスト中にエラーが発生しました。")
        print(f"詳細: {e}")
        print("Ollamaサーバーが起動しているか、ネットワーク設定(ファイアウォール等)を確認してください。")
        return None

    except ResponseError as e:
        # Ollamaサーバーからのエラー応答 (例: モデルが見つからない)
        print(f"エラー: Ollamaサーバーからエラー応答がありました。")
        print(f"ステータスコード: {e.status_code}")
        print(f"エラー内容: {e.error}")
        return None

    except Exception as e:
        # その他の予期せぬエラー
        print(f"予期せぬエラーが発生しました: {e}")
        return None

hostURL = 'http://192.168.100.206:11434'
modelName = 'gemma4:12b'
aContent = '画像は物理の練習問題です。問いを解くためのヒントを教えてください。正解は教えないでください。'
imageFile = './cutycapt.png'

generated_text = generateTextWithOllamaClient(aContent, imageFile, hostURL, modelName)

print(generated_text)

いまのところ、こちらのほうが失敗が少ないような気がします。でもまだ失敗することは普通にあるので、もっと経験を積む必要がありそうです。