Overclocking Time's 「20代♀ベンチャー非正規雇用」の限界エンジニアによる技術ブログ!
  • Home
  • Blog
JA EN
☰ 目次
    2026年3月20日
    #生成AI#技術解説

    Ollama APIと履歴 / VRAM管理を自前実装!公式ライブラリの依存を断ち切る!

    Ollama APIと履歴 / VRAM管理を自前実装!公式ライブラリの依存を断ち切る!

    ローカルで手軽にLLMを動かせる「Ollama」。Pythonから呼び出す際には、公式のollama-pythonライブラリを使用するのが一般的です。

    しかし、実際のプロジェクト開発(特にAIエージェントの開発など)においては、あえて公式ライブラリを使わず、requestsモジュールを用いてAPIを直接叩く独自実装を採用することがあります。

    本記事では、 公式ライブラリと独自実装の比較 を行いつつ、依存関係を排除した汎用的なOllamaクライアントのコードを詳しく解説します♪

    サンプルコードについて 本記事で解説するCLIチャットアプリの完全なソースコードは、以下のGitHubリポジトリに公開しています。

    GitHub - suzuai-tech/ollama_py_api_sample
    Contribute to suzuai-tech/ollama_py_api_sample development by creating an account on GitHub.
    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の直接呼び出しを検討してみてください♪

    airi

    airi

    都内 ❘ 27♀ ❘ 非正規 趣味はダンス(お休み中)とゲーム ベンチャーを転々としています ※メールはお仕事と気まぐれのみ

    関連記事

    • 漫画のセリフとキャプションを自動配置するツールをYOLOモデルを使って作ってみた
      漫画のセリフとキャプションを自動配置するツールをYOLOモデルを使って作ってみた 2026/3/22
    • ローカル完結!Qwen3.5を用いた画像データセット用キャプション自動生成ツールの作り方
      ローカル完結!Qwen3.5を用いた画像データセット用キャプション自動生成ツールの作り方 2026/3/22
    • yolo26を使ってデータセット作成を効率化する
      yolo26を使ってデータセット作成を効率化する 2026/3/17
    • PageSpeed Insightsを活用してWebサイトの表示速度を改善してみる
      PageSpeed Insightsを活用してWebサイトの表示速度を改善してみる 2026/3/15
    • 「face fusion」のNSFWフィルター解除を題材にコードの改ざん防止を解説
      「face fusion」のNSFWフィルター解除を題材にコードの改ざん防止を解説 2026/3/12

    最新記事

    • 漫画のセリフとキャプションを自動配置するツールをYOLOモデルを使って作ってみた
      漫画のセリフとキャプションを自動配置するツールをYOLOモデルを使って作ってみた 2026/3/22
    • ローカル完結!Qwen3.5を用いた画像データセット用キャプション自動生成ツールの作り方
      ローカル完結!Qwen3.5を用いた画像データセット用キャプション自動生成ツールの作り方 2026/3/22
    • yolo26を使ってデータセット作成を効率化する
      yolo26を使ってデータセット作成を効率化する 2026/3/17
    • PageSpeed Insightsを活用してWebサイトの表示速度を改善してみる
      PageSpeed Insightsを活用してWebサイトの表示速度を改善してみる 2026/3/15
    • 独自ドメインをCloudflareで購入するのはあり?
      独自ドメインをCloudflareで購入するのはあり? 2026/3/14
    Overclocking Time's

    「20代♀ベンチャー非正規雇用」の限界エンジニアによる技術ブログ!

    Navigation

    Home Blog Privacy Policy

    Connect

    Connect

    © 2026 Overclocking Time's. All rights reserved.