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サービスの問題かを切り分けられる。
📋 前提条件: まず以下を習得していること
- レッスン5: REST APIの基礎
1. 学べること
- ollama Pythonライブラリのインストールと同期・非同期API
chat()/generate()/list()の使い方- 非同期ストリーミングレスポンスによるリアルタイム出力の実装
- Pythonの型アノテーションとエラー処理
- AliceのSupportBot V1 Pythonラッパー
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) ストリーミング出力の原理
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_duration、eval_count、prompt_eval_countフィールドが含まれる。ストリーミングレスポンスの最後のチャンクにこれらの統計が含まれる。📖 まとめ
ollamaPythonパッケージ——1行でインストール、同期・非同期APIの両方に対応chat()はマルチターン対話に、generate()は単発生成に適している- ストリーミング出力は
stream=True+ チャンクの反復でリアルタイム表示 - 非同期ストリーミングは
async for反復、Webサービスの同時実行に適している - エラー処理はConnectionError、ResponseError、TimeoutErrorを網羅すること
- SupportBot V1はdataclassで会話履歴と推論パラメータをカプセル化
📝 練習問題
- 基本(難易度 ⭐):Python SDKで
chat()とgenerate()をそれぞれ1回ずつ実装し、出力の違いを比較する。 - 中級(難易度 ⭐⭐):スライディングウィンドウ(直近5ターン保持)付きのストリーミングチャット関数を実装し、5ターンを超えると古いメッセージを自動破棄する。
- 上級(難易度 ⭐⭐⭐):エラーリトライ、タイムアウト制御、JSON形式出力付きのSupportBot V1+を構築し、カスタマーサービス会話をファイルに保存し、各呼び出しの推論時間をログ出力する。