ローカルで手軽にLLMを動かせる「Ollama」。Pythonから呼び出す際には、公式のollama-pythonライブラリを使用するのが一般的です。
しかし、実際のプロジェクト開発(特にAIエージェントの開発など)においては、あえて公式ライブラリを使わず、requestsモジュールを用いてAPIを直接叩く独自実装を採用することがあります。
本記事では、 公式ライブラリと独自実装の比較 を行いつつ、依存関係を排除した汎用的なOllamaクライアントのコードを詳しく解説します♪
サンプルコードについて 本記事で解説するCLIチャットアプリの完全なソースコードは、以下のGitHubリポジトリに公開しています。
1. 公式ライブラリ vs 独自実装 (requests)
OllamaをPythonで動かす際、公式ライブラリを使うべきか、自分でAPIを叩くべきか。そのメリット・デメリット、および「できること・できないこと」をまとめました。
比較表
| 項目 | 公式ライブラリ (ollama-python) |
独自実装 (requests) |
|---|---|---|
| 導入の手軽さ | ◎ pip install ollama で即実行可能 |
△ APIの仕様理解と実装コードが必要 |
| 依存関係 | △ サードパーティライブラリが増える | ◎ requestsのみ。環境への競合リスクが最小 |
| プロンプトの透明化 | △ 内部フォーマットが隠蔽される事も | ◎ 実際にLLMに送られる最終テキストが明確に見える |
| パラメータの微調整 | ◯ ラッパーの引数として指定可能 | ◎ JSONペイロード内の options (temperatureやtop_p等) に対して明示的かつ透過的に設定が可能 |
| ストリーミング制御 | ◯ 基本的なチャンク取得は可能 | ◎ UI統合や特殊文字のフックなど通信レイヤーで自在に拡張可能 |
| VRAM(メモリ)管理 | △ 最新の隠しパラメーター等への対応がライブラリ更新待ちになる事も | ◎ keep_alive=0 等の仕様を即座にJSONペイロードへ追加可能 |
どんな時に独自実装を選ぶべきか?
- AIエージェントを自作する時: 「LLMに最終的に渡される文字列」や「温度(temperature)などの詳細パラメータ」を完全にコントロール・デバッグしたい場合。
- メモリ・リソース管理がシビアな現場: 後述する「モデルの一覧取得」や「GPU(VRAM)からの即時アンロード」といった細かい制御を頻発して行う場合。
2. 実装の解説:モデル一覧取得とVRAM解放(アンロード)
APIの直接操作を行う最も大きなメリットは、 GPU内にあるモデルの状態を確認 したり、不要なモデルをVRAMから追い出して メモリを解放できる 点ですね。
def list_models() -> List[str]:
"""登録されているすべてのOllamaモデルを取得"""
resp = requests.get(f"{OLLAMA_BASE_URL}/api/tags")
return [m["name"] for m in resp.json().get("models", [])]
def list_loaded_models() -> List[Dict]:
"""現在VRAM(GPUメモリ)にロードされているモデル一覧を取得"""
resp = requests.get(f"{OLLAMA_BASE_URL}/api/ps")
return resp.json().get("models", [])
def unload_model(model_name: str) -> bool:
"""指定モデルに対して keep_alive=0 でリクエストし即時アンロード(VRAM解放)する"""
payload = {"model": model_name, "keep_alive": 0}
requests.post(f"{OLLAMA_BASE_URL}/api/generate", json=payload)
return True
AIを組み込んだサーバーなどを運用していると、複数のLLMプロセスによってすぐにVRAMが枯渇します。「チャットセッションの終了時(プロセス終了時)に unload_model() を呼び出し、即座にVRAMを解放する」仕組みをもたせることは、安定的かつセキュアなリソース管理において非常に大切です。
3. 実装の解説:セッションと会話履歴の管理(SessionManager)
LLMとの会話において最大の障壁となるのが コンテキストウィンドウ(トークン数の上限)の超過 です。
これに対処するため、一時メモリ上で会話を管理し「古い履歴から自動でトリミングする仕組み」を持つ SessionManager クラスを実装します。
3.1 セッションの初期化と作成
class SessionManager:
def __init__(self, max_history: int = 10):
# session_id をキーに、会話履歴を辞書で管理
self.sessions: Dict[str, Dict] = {}
# 最新何件の会話を保持するか
self.max_history = max_history
def create(self, session_id: Optional[str] = None) -> str:
# UUIDでセッションを発行
sid = session_id or str(uuid.uuid4())
self.sessions[sid] = {"messages": []}
return sid
3.2 過去の履歴の明示的なクリア(clear)
会話が別トピックに移った際などに備え、システムプロンプトだけを残して会話をリセットする clear 機能も実装します。
def clear(self, session_id: str):
"""セッションの会話履歴をクリア(システムプロンプトは維持)"""
msgs = self.sessions.get(session_id, {}).get("messages", [])
system_msgs = [m for m in msgs if m.get("role") == "system"]
self.sessions[session_id]["messages"] = system_msgs
3.3 古い履歴の自動トリミング (_trim_history)
セッション管理で最も重要なロジックです。制限を超えた古い発言は自動で切り落とし、常にコンテキスト上限内に収まるようにします。システムプロンプトは制限から除外します。
def _trim_history(self, session_id: str) -> None:
"""会話履歴を制限(systemは除外)。トークン上限エラーを防ぐ。"""
msgs = self.sessions.get(session_id, {}).get("messages", [])
if not msgs: return
system_msgs = [m for m in msgs if m.get("role") == "system"]
conversation_msgs = [m for m in msgs if m.get("role") in ("user", "assistant")]
# 履歴上限を超えたら古い会話から削除
if len(conversation_msgs) > self.max_history:
conversation_msgs = conversation_msgs[-self.max_history:]
self.sessions[session_id]["messages"] = system_msgs + conversation_msgs
4. 実装の解説:API通信とストリーミング (stream_chat)
Ollama内蔵の /api/generate エンドポイントに対し、HTTPリクエストを投げます。リクエスト時に stream=True に設定し、戻り値を受け取る際も requests の機能である iter_lines() を使うことで、AIが考えている途中の文字を逐次表示できます。
def stream_chat(session_mgr: SessionManager, session_id: str, model: str) -> str:
url = "http://127.0.0.1:11434/api/generate"
headers = {"Content-Type": "application/json"}
prompt = session_mgr.build_prompt(session_id)
# 生成パラメータ (温度やtop_p等) もこのペイロード内で自由に制御可能です
payload = {
"model": model,
"prompt": prompt,
"stream": True, # ストリーミングの有効化
"options": {
"temperature": 0.7, # 0.0〜1.0 (数字が大きいほどランダムで創造的な回答になる)
"top_p": 0.9 # トークン選択の閾値
}
}
assistant_chunks: List[str] = []
try:
# stream=True でレスポンスを開く
with requests.post(url, headers=headers, data=json.dumps(payload), stream=True) as resp:
resp.raise_for_status()
# iter_lines() で行ごとのチャンクを処理
for line in resp.iter_lines():
if line:
data = json.loads(line.decode("utf-8"))
if "response" in data:
chunk = data["response"]
assistant_chunks.append(chunk)
# 標準出力に逐次表示 (ストリーミング効果)
sys.stdout.write(chunk)
sys.stdout.flush()
if data.get("done", False):
break
print()
except requests.exceptions.RequestException as e:
print(f"\n[エラー]通信失敗: {e}")
return ""
assistant_text = "".join(assistant_chunks).strip()
if assistant_text:
session_mgr.add_assistant(session_id, assistant_text)
return assistant_text
この実装においては、単に stream=True とするだけでなく、JSONペイロードの options に temperature (温度) や top_p などの詳細な生成パラメータも直接指定しています。ライブラリのラッパー越しではなく、APIリファレンスにある構造をそのまま記述できるため、どのパラメータが適用されているかが非常に明瞭になります。
さらに独自の拡張を行いたい場合にも活きます。たとえば「GUIのプログレスバーを更新する」「特定の制御文字が来たら関数処理を回す」といった特殊要件があっても、sys.stdout.write(chunk) の付近にコードを追加するだけで自由に対応できます。
5. 実際の動作(出力例)
GitHubにアップしているCLIスクリプトを実行した場合、ターミナル上で以下のように対話できます。
モデルの一覧表示や、チャット中のセッションクリア(/clear)、メモリ解放(/unload)など、独自実装ならではの機能を体験できます。
qwen3.5:9bを試しに呼んでみています♡
=== Ollama Custom CLI Chat ===
【登録されているモデル一覧】
- qwen3.5:9b
- translategemma:12b
- gpt-oss:20b
- gemma3:12b
使用するモデル名を入力してください: qwen3.5:9b
モデル 'qwen3.5:9b' とのチャットを開始します。
-------------------------------------------------
【使用可能なコマンド】
/clear : 現在のセッション履歴をすべてクリア
/models : VRAMにロードされている稼働中のモデルを確認
/unload : 現在の会話モデルを手動でVRAMから即座に解放
/exit : 終了
-------------------------------------------------
You: Pythonのrequestsモジュールについてどう思いますか?
AI: はい、ご主人様!
私、requests モジュール、とても素敵だと思いますよ。
使い勝手が良く、API とのやり取りをスムーズに行えるから、とても便利なツールです。
ご主人様の開発のお仕事を、より快適に進められるように、私がサポートさせていただきますね。
何かコードの組み立てで困っていることがありましたら、いつでも私におっしゃってくださいませ。
一緒に素敵なプログラムを作ってみましょうね。
--------------------------------------------------
You: /models
>>> 現在VRAMにロードされているモデル:
- qwen3.5:9b (VRAM使用量: 8248.8 MB)
You: /unload
>>> モデル 'qwen3.5:9b' を VRAM から解放しています...
>>> 解放に成功しました。(次回チャット時に再度ロードされます)
You: /models
>>> 現在ロードされているモデルはありません。
You: /exit
スクリプト終了: モデルをVRAMから解放(アンロード)しています...
お疲れ様でした。
おわりに
公式ライブラリは手軽で便利ですが、requestsレベルでの独自実装に踏み切ることで、プロンプトの構築ロジックが手元に残り、ストリーミング制御からVRAMの管理まで、より「実運用に強い」堅牢なエージェント・アプリケーションを作ることができます。
依存パッケージを極力減らし、何が起こっているか透明性を高めたい場合は、ぜひAPIの直接呼び出しを検討してみてください♪