Ollama: Python SDK統合

Python SDKはローカルAIへの扉を開く鍵——3行のコードで、スクリプトからアプリケーションへシームレスに移行。

💡 ヒント: Python SDK(ollamaライブラリ)は本質的にOllama REST APIのラッパー——chat()メソッドは/api/chatを、generate()/api/generateを、list()/api/tagsをラップする。この関係を理解するとトラブルシューティングに役立つ:SDKがエラーを出したとき、curlでAPIを直接呼び出し、SDKの問題かOllamaサービスの問題かを切り分けられる。

📋 前提条件: まず以下を習得していること

1. 学べること


2. SaaS創業者のリアルな事例

⚠️ 警告: Python SDKが直接Ollama APIを呼び出す際、Ollamaには組み込み認証がない。Ollamaを0.0.0.0にバインドしている場合、SDK接続情報(例:http://your-server:11434)が第三者に悪用される可能性がある。本番環境では必ず認証レイヤーを追加すること。

(1) ペインポイント:Shellスクリプトでは限界がある

AliceはcurlでSupportBotプロトタイプを構築したが、Shellスクリプトでは会話状態の管理、エラー処理、Webサービスとの統合が困難。本番グレードのアプリケーションを構築するには、適切なプログラミング言語が必要。

(2) ソリューション:Python SDK 3行で統合

PYTHON
import ollama

response = ollama.chat(model='qwen2.5', messages=[
    {'role': 'user', 'content': 'Hello'}
])
print(response['message']['content'])

3. インストールとAPI概要

ℹ️ 情報: ollama Python SDKはデフォルトでhttp://localhost:11434に接続し、追加設定は不要。Ollamaが別のアドレスで動作している場合、OLLAMA_HOST環境変数で設定するか、コード内でClient(host='http://...')を指定して上書きできる。

💡 ヒント: Python SDKではgenerate()よりもchat()メソッドを推奨——chatはマルチターン対話(messages配列)に対応し、generateは単発のみ。単発の質問でも、chatのロール区別(system/user/assistant)により出力品質が向上する。

(1) インストールと接続確認

BASH
# Install the ollama Python package
pip install ollama

# Verify connection to Ollama server
python3 -c "import ollama; print(ollama.list())"

(2) 同期APIと非同期APIの比較

⚠️ : 非同期API(AsyncClient)を使用する際、すべての呼び出しにawaitプレフィックスが必要。例:await client.chat(...)awaitを忘れるとコルーチンオブジェクトが返され、実際の結果は得られない——エラーにはならないが正しい出力も得られない。FastAPIなどの非同期フレームワークでは非同期APIを必ず使用すること;そうしないとイベントループがブロックされ、同時実行性能が低下する。

項目 同期API 非同期API
モジュール ollama ollama(AsyncClient)
呼び出し方式 ollama.chat() await client.chat()
ブロック 現在のスレッドをブロック 非ブロック、同時実行可能
ユースケース スクリプト、シンプルツール Webサービス、同時処理
ストリーミング対応 for chunk in stream async for chunk in stream

(3) ▶ サンプル:同期と非同期の基本呼び出し

PYTHON
import ollama
import asyncio

# Synchronous call
def sync_chat():
    response = ollama.chat(
        model='qwen2.5',
        messages=[{'role': 'user', 'content': 'Hello!'}]
    )
    print(response['message']['content'])

# Asynchronous call
async def async_chat():
    client = ollama.AsyncClient()
    response = await client.chat(
        model='qwen2.5',
        messages=[{'role': 'user', 'content': 'Hello!'}]
    )
    print(response['message']['content'])

sync_chat()
asyncio.run(async_chat())

出力:

TEXT
# Function defined successfully

4. コアメソッド詳細解説

(1) chat()メソッド

パラメータ 説明
model str モデル名
messages list[dict] メッセージリスト。各要素にrole/contentを含む
stream bool ストリーム出力の有無
format str 出力形式:json
options dict 推論パラメータ(temperatureなど)
keep_alive str モデルのメモリ保持時間

(2) generate()メソッド

パラメータ 説明
model str モデル名
prompt str プロンプトテキスト
system str システムプロンプト
stream bool ストリーム出力の有無
options dict 推論パラメータ

(3) ▶ サンプル:chatとgenerateの比較

PYTHON
import ollama

# chat(): multi-turn with message history
response = ollama.chat(
    model='qwen2.5',
    messages=[
        {'role': 'system', 'content': 'You are a SQL expert.'},
        {'role': 'user', 'content': 'Write a query for top 5 customers'}
    ],
    stream=False,
    options={'temperature': 0.3}
)
print('chat:', response['message']['content'])

# generate(): single-shot text generation
response = ollama.generate(
    model='qwen2.5',
    prompt='Write a haiku about debugging',
    system='You are a poet.',
    stream=False
)
print('generate:', response['response'])

出力:

TEXT
chat:
generate:

5. ストリーミングレスポンスの実装

(1) ストリーミング出力の原理

100%
sequenceDiagram
    participant P as Pythonアプリ
    participant O as Ollamaサーバー
    P->>O: chat(stream=True)
    loop 各トークンチャンク
        O-->>P: chunk {"content": "単語"}
        P->>P: print(単語, end="")
    end
    O-->>P: chunk {"done": true}

(2) ▶ サンプル:同期ストリーミング出力

PYTHON
import ollama

# Stream chat response in real-time
stream = ollama.chat(
    model='qwen2.5',
    messages=[{'role': 'user', 'content': 'Explain RAG in 3 sentences'}],
    stream=True
)

for chunk in stream:
    content = chunk['message']['content']
    print(content, end='', flush=True)

print()  # newline at end

出力:

TEXT
# Execution successful

(3) ▶ サンプル:非同期ストリーミング出力

PYTHON
import ollama
import asyncio

async def stream_chat():
    client = ollama.AsyncClient()
    stream = await client.chat(
        model='qwen2.5',
        messages=[{'role': 'user', 'content': 'Tell me about Ollama'}],
        stream=True
    )
    async for chunk in stream:
        content = chunk['message']['content']
        print(content, end='', flush=True)
    print()

asyncio.run(stream_chat())

出力:

TEXT
# Function defined successfully

6. エラー処理と型アノテーション

(1) 一般的なエラータイプ

エラー 発生条件 対処方法
ConnectionError Ollamaサービスが未起動 サービス起動またはリトライ
ResponseError モデル未検出/無効パラメータ モデル名とパラメータを確認
TimeoutError 推論タイムアウト num_ctxを減らすかタイムアウトを増やす
JSONDecodeError format=json出力の異常 JSONバリデーションとリトライを追加

(2) ▶ サンプル:堅牢なエラー処理

PYTHON
import ollama
import json
from typing import Optional

def safe_chat(
    model: str,
    messages: list[dict],
    temperature: float = 0.3,
    max_retries: int = 3
) -> Optional[str]:
    """Chat with error handling and retries."""
    for attempt in range(max_retries):
        try:
            response = ollama.chat(
                model=model,
                messages=messages,
                stream=False,
                options={'temperature': temperature}
            )
            return response['message']['content']

        except ConnectionError:
            print(f"Connection failed (attempt {attempt + 1})")
            if attempt == max_retries - 1:
                return None

        except ollama.ResponseError as e:
            print(f"API error: {e.error}")
            return None

        except Exception as e:
            print(f"Unexpected error: {e}")
            if attempt == max_retries - 1:
                return None

    return None

# Usage
result = safe_chat('qwen2.5', [
    {'role': 'user', 'content': 'What is your return policy?'}
])
if result:
    print(result)
else:
    print("Failed to get response")

出力:

TEXT
Failed to get response

7. 総合サンプル:SupportBot V1 Pythonラッパー

PYTHON
# ============================================
# Comprehensive: SupportBot V1
# Python wrapper for e-commerce customer service
# ============================================

import ollama
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class SupportBot:
    model: str = "qwen2.5"
    temperature: float = 0.4
    max_history: int = 10
    system_prompt: str = (
        "You are SupportBot, an e-commerce customer service agent. "
        "Be polite, concise, and helpful. "
        "If unsure, say 'Let me connect you with a human agent.'"
    )
    messages: list[dict] = field(default_factory=list)

    def __post_init__(self):
        self.messages = [
            {"role": "system", "content": self.system_prompt}
        ]

    def chat(self, user_input: str) -> str:
        self.messages.append({"role": "user", "content": user_input})
        try:
            response = ollama.chat(
                model=self.model,
                messages=self.messages[-self.max_history:],
                stream=False,
                options={"temperature": self.temperature}
            )
            assistant_msg = response["message"]["content"]
            self.messages.append({"role": "assistant", "content": assistant_msg})
            return assistant_msg
        except Exception as e:
            self.messages.pop()  # Remove failed user message
            return f"Error: {str(e)}"

    def stream_chat(self, user_input: str):
        self.messages.append({"role": "user", "content": user_input})
        full_response = []
        try:
            stream = ollama.chat(
                model=self.model,
                messages=self.messages[-self.max_history:],
                stream=True,
                options={"temperature": self.temperature}
            )
            for chunk in stream:
                content = chunk["message"]["content"]
                full_response.append(content)
                print(content, end="", flush=True)
            print()
            self.messages.append({"role": "assistant", "content": "".join(full_response)})
        except Exception as e:
            print(f"\nError: {e}")

    def reset(self):
        self.messages = [{"role": "system", "content": self.system_prompt}]

# Usage
if __name__ == "__main__":
    bot = SupportBot(model="qwen2.5", temperature=0.4)

    print("=== SupportBot V1 ===")
    print(bot.chat("Where is my order #12345?"))
    print()
    print(bot.chat("It has been 7 days since I ordered."))
    print()
    print(bot.chat("Can I get a refund instead?"))
💻 出力:

TEXT
=== SupportBot V1 ===
I'd be happy to check on your order #12345. Based on our records, your order is currently in transit and expected to arrive within 2-3 business days. You can track it at our website.

I understand your concern. If you'd prefer a refund instead of waiting, I can initiate that for you. Our refund policy covers orders that haven't been delivered within the estimated timeframe.

Yes, I can process a full refund for order #12345. The refund will be credited to your original payment method within 3-5 business days. Would you like me to proceed?

❓ よくある質問

Q pip install ollamaが失敗する場合は?
A Python >= 3.8とpipの最新版を確認:pip install --upgrade pip。ネットワーク問題の場合はミラーを使用:pip install ollama -i https://pypi.tuna.tsinghua.edu.cn/simple
Q 同期APIと非同期APIはどう選ぶ?
A スクリプトやシンプルツールなら同期(よりシンプル)。Webサービス(FastAPI/Django)なら非同期でイベントループのブロックを防ぐ。単一ユーザーシナリオでは差はほとんどない。
Q ストリーミング出力がJupyter Notebookで表示されない場合は?
A Jupyterはflush=Trueのサポートが限定的。IPython.display.clear_outputでループ更新するか、非ストリーミングモードに切り替える。
Q Ollamaサーバーのアドレスを指定するには?
A デフォルトはlocalhost:11434。OLLAMA_HOST環境変数で変更するか、コード内でClient(host='http://...')をインスタンス化する。
Q messagesリストが長くなりすぎるとどうなる?
A モデルのコンテキストウィンドウ(num_ctx)を超える内容は切り捨てられる。スライディングウィンドウを実装し、直近Nターンのみ保持するか、以前の会話を要約することを推奨。
Q 推論速度などの統計情報を取得するには?
A 非ストリーミングレスポンスにはtotal_durationeval_countprompt_eval_countフィールドが含まれる。ストリーミングレスポンスの最後のチャンクにこれらの統計が含まれる。

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐):Python SDKでchat()generate()をそれぞれ1回ずつ実装し、出力の違いを比較する。
  2. 中級(難易度 ⭐⭐):スライディングウィンドウ(直近5ターン保持)付きのストリーミングチャット関数を実装し、5ターンを超えると古いメッセージを自動破棄する。
  3. 上級(難易度 ⭐⭐⭐):エラーリトライ、タイムアウト制御、JSON形式出力付きのSupportBot V1+を構築し、カスタマーサービス会話をファイルに保存し、各呼び出しの推論時間をログ出力する。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%