はじめに
Ollamaには公式の ollama Pythonライブラリがあるが、「pipを入れたくない」「単発のスクリプトに依存を増やしたくない」場面では、標準ライブラリの urllib だけで十分にチャットできる。この記事では、標準ライブラリのみでストリーミング(生成されながら順次表示)するチャットCLIを書いて動かす。
環境
- Windows 11 / Python 3.11(標準ライブラリのみ。追加インストールなし)
- Ollama 0.40.4(
http://127.0.0.1:11434) - モデル:
qwen2.5:0.5b
やったこと
スクリプト
chat_stream.py:
import json
import sys
import urllib.request
def stream_chat(model: str, prompt: str) -> None:
req = urllib.request.Request(
"http://127.0.0.1:11434/api/chat",
data=json.dumps({
"model": model,
"messages": [{"role": "user", "content": prompt}],
"stream": True,
}).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as res:
for line in res:
chunk = json.loads(line)
piece = chunk.get("message", {}).get("content", "")
print(piece, end="", flush=True)
if chunk.get("done"):
break
print()
if __name__ == "__main__":
stream_chat(
sys.argv[1] if len(sys.argv) > 1 else "qwen2.5:0.5b",
sys.argv[2] if len(sys.argv) > 2 else "自己紹介を1文で。",
)
実行
python chat_stream.py qwen2.5:0.5b "Count from 1 to 5, separated by spaces, nothing else."
実行結果:
1 2 3 4 5
文字が順に出力されていく(パイプにリダイレクトした場合は一括表示になるが、ターミナル上では生成の進み具合が見える)。
仕組み
/api/chat に stream: true(既定)でリクエストすると、応答はNDJSON(1行1JSON)で届く。各行は次のような構造だ。
{"model":"...","message":{"role":"assistant","content":"1"},"done":false}
{"model":"...","message":{"role":"assistant","content":" "},"done":false}
...
{"model":"...","message":{"role":"assistant","content":""},"done":true, ...}
そのため、for line in res: で1行ずつ読み、message.content を取り出して print(..., end="", flush=True) するだけでストリーミング表示になる。最後は done: true のチャンクが来るので、それを合図にループを抜ける。統計情報(トークン数など)もこの最終チャンクに含まれる。
ハマった点
-
初回はモデルのロードで待たされる。
urllibの既定ではタイムアウトが無制限なので問題になりにくいが、独自にタイムアウトを付けている場合は長めに設定すること(実測では0.5Bモデルの初回リクエストに約30秒かかった)。 -
resをイテレートすると行単位で読める。urllibのレスポンスはバイナリ行を返すので、通常のJSONパース前にUTF-8の扱いでつまずかないよう、json.loadsはbytesのまま渡してよい(Python 3.6以降はUTF-8を自動判定する)。 -
ollamaライブラリは不要だが、あってもよい。会話履歴の管理やトークン統計の整形まで自前でやるのが手間なら、公式ライブラリの利用も検討する。この記事の目的は「依存ゼロで動かすこと」だ。
まとめ
-
urllib+jsonだけでOllamaのストリーミングチャットが実装できる。 - ポイントは
/api/chatのstream: trueが返すNDJSONを1行ずつ処理すること。 -
done: trueを終了条件にすると、統計情報も取り逃さない。
関連する本
ストリーミングの続きとして、毎朝のダイジェスト自動作成までを一冊にまとめました。第1章・第2章は無料で読めます。