中級 15 分API

APIを介してOllamaをPythonアプリケーションに統合する REST

Ollamaはポート11434で2つのHTTP APIを公開しています。ネイティブAPI(/api/generate、/api/chat)と、OpenAI互換API(/v1/chat/completions)です。後者は、PythonからOllamaのAPIを利用するための最適な方法です。コードはGPT-4を使う場合とまったく同じSDKを使用しながら、お使いのマシン上で動作します。このガイドでは、ストリーミング、構造化JSON、function callingという具体的なパターンを、プロジェクトにそのまま貼り付けられるFastAPIとFlaskの例とともに紹介します。

著者 Mohamed Meguedmi·更新 2026-08-27·Windows・macOS・Linuxでテスト済み

#REST APIを使う理由

CLI の ollama run est はテストに便利ですが、アプリから呼び出すには適していません。一方、REST API はそのために設計されています。HTTP リクエスト、JSON 入出力、Server-Sent Events を通じたストリーミング。すべてのインターフェース(Open WebUI、Cline、LangChain)が内部でこれを使用しています。

OpenAI との互換性
エンドポイント /v1/chat/completions は、api.openai.com/v1/chat/completions とまったく同じリクエストペイロードを受け付けます。URLとキーを変更すれば、既存のコードが動作します。
作り直す必要なし
Pythonで提供されるOpenAIの公式SDK(または任意のHTTPクライアント)は、Ollama に直接接続します。特定のクライアントを学ぶ必要はありません。
ランタイムの分離
PythonアプリとOllamaは、それぞれ別のコンテナ内で動作します。vLLMやLM Studioに移行する際は、base_urlを変更するだけで済みます。
複数クライアントの同時接続
複数のPythonスクリプト、Jupyterノートブック、Open WebUIから、同じOllamaインスタンスにアクセスできます。デーモンがキューを自動的に管理します。
i
ネイティブAPIとOpenAI互換APIの比較
Ollamaは両方のAPIを維持しています。ネイティブAPI(/api/chat)では固有のパラメータ(num_ctx、num_predict、mirostat)を利用できますが、移植性は低くなります。OpenAI互換APIはニーズの95%をカバーし、ほかのどのプロバイダーでも利用できます。基本的にはこちらを選んでください。

#前提条件

ローカルコパイロットキット

このガイドでモデルの導入まで、キットでエディタ内でコードを書くコパイロットの導入まで進められます。

  • 永久に利用できるオンラインスペース
  • PDF + ファイル
  • 30日間返金対応
Ollamaがインストール済みで起動していること
デーモンは http://localhost:11434 で待ち受けている必要があります。curl http://localhost:11434 で確認してください。「Ollama is running」と表示されるはずです。
Python 3.10+
最近のSDK(openai 1.x)には少なくともPython 3.8が必要ですが、新しい型アノテーションを使うには3.10以降が必要です。
チャット対応可能なモデル
ollama pull qwen3.5:9b ou gemma4:12b. Pour le function calling, choisissez un modèle qui le supporte : Qwen 3.5, Granite 4.2, Mistral Small 24B, Devstral.
十分なVRAM
9B Q4モデル(例:Qwen 3.5 9B)には約6〜7 GBのVRAMが必要で、12Bモデル(Gemma 4 12B)には約8 GBが必要です。GPUがなくても動作しますが、速度は5〜10トークン/秒です。

#1. Ollama 側の 2 つの API

Pythonを書く前に、まずターミナルからエンドポイントを確認して、実際に何が起きているかを把握しましょう。curlを使用することで、デーモンに直接接続し、抽象化なしで通信できます。

ネイティブAPI — /api/chat
curl http://localhost:11434/api/chat -d '{
  "model": "qwen3.5:9b",
  "messages": [{"role": "user", "content": "Bonjour"}],
  "stream": false
}'
OpenAI API — /v1/chat/completions
curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.5:9b",
    "messages": [{"role": "user", "content": "Bonjour"}]
  }'

後者はOpenAIのペイロードと厳密に同一のものを返します。choices[0].message.content、id、model、usageフィールドです。これがドロップイン対応を可能にしています。

→
APIキーは無視されますが、必須です
openai SDK では api_key パラメータが必須です。Ollama はその値を検証しないため、"ollama" または空でない任意の文字列を指定してください。いつもの習慣で実際の OpenAI API キーを設定しても、そのキーはお使いのマシン内に留まりますが、混同を避けるため、特定の意味を持たない文字列を使うことをおすすめします。

#2. OpenAI SDKの接続先をOllamaに設定

PythonからOllamaのAPIを利用する基本パターンは、5行で書けます。OpenAI SDKをインストールし、ローカルのbase_urlを指定してインスタンスを作成し、通常どおりchat.completions.createを呼び出します。

インストール
pip install openai
client.py — 基本的な呼び出し
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",  # ignoré, mais requis par le SDK
)

reponse = client.chat.completions.create(
    model="qwen3.5:9b",
    messages=[
        {"role": "system", "content": "Tu réponds en français, de façon concise."},
        {"role": "user", "content": "Explique en une phrase ce qu'est un LLM."},
    ],
    temperature=0.3,
)

print(reponse.choices[0].message.content)

スクリプトを実行してください。Ollama が正常に起動し、モデルがダウンロードされた場合、文が生成されます。ConnectionRefusedErrorが表示された場合は、ollama psでデーモンが正常に動作しているかを確認してください。

model
ollama list で表示される正確な名前(qwen3.5:9b, gemma4:12b, mistral-small など)
messages
会話の各ターンのリストです。対応するロールは system、user、assistant、tool です。
temperature
決定論的な出力には0、創造的な出力には0.7を設定します。データ抽出では0または0.1にしてください。
max_tokens
回答の上限。省略可能です。Ollamaはnum_predictに妥当なデフォルト値を適用します。

#3. SSEでトークンごとにストリーミング出力

チャットボットや長文生成で良好なユーザー体験を提供するには、生成が終わるまで待つのではなく、トークンを順次表示したいところです。OllamaはServer-Sent Eventsによるストリーミングに対応しており、OpenAI SDKを使えば、Pythonの簡単なループで実装できます。

streaming.py
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

flux = client.chat.completions.create(
    model="qwen3.5:9b",
    messages=[{"role": "user", "content": "Raconte une courte histoire de robot."}],
    stream=True,
)

for chunk in flux:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
print()

各チャンクにはデルタ(追加されたテキストの部分)が含まれます。最後のチャンクではdelta.contentがNoneになり、finish_reasonが設定されます。これは終了のサインです。

!
flush=True を忘れないでください
flush=Trueを指定しないと、Pythonはstdoutを行単位でバッファリングするため、ターミナルではストリーミング表示になりません。一方、HTTP APIではウェブサーバー(uvicorn、gunicorn)がバッファをフラッシュするので、自分でその処理を行う必要はありません。

#4. JSONモードによる構造化出力

応答をパースしたい場合(抽出、分類、ペイロード生成)、プロンプトで「JSONを返して」と頼むだけでは不十分です。モデルはJSONの前後に余分なテキストを加えることがよくあります。JSONモードは、デコーダーが有効なJSONだけを生成するように強制します。

json_mode.py
from openai import OpenAI
import json

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

reponse = client.chat.completions.create(
    model="qwen3.5:9b",
    messages=[
        {"role": "system", "content": (
            "Tu extrais des informations structurées. "
            "Réponds uniquement avec un objet JSON contenant les clés : "
            "nom (string), age (int), ville (string)."
        )},
        {"role": "user", "content": "Marie a 34 ans, elle habite à Lyon."},
    ],
    response_format={"type": "json_object"},
    temperature=0,
)

donnees = json.loads(reponse.choices[0].message.content)
print(donnees)
# {'nom': 'Marie', 'age': 34, 'ville': 'Lyon'}

response_format={"type": "json_object"} はJSONモードを有効にします。Ollama側では、サンプラーに制約が適用され、不正なJSONにつながるトークンはすべて拒否されます。「JSONで答えて」とプロンプトで指示して、あとは祈るよりも確実です。

→
プロンプトに "JSON" を記載してください
OpenAIと同様に、JSONモードでは、会話(systemまたはuser)に「JSON」という単語への言及が少なくとも1つ必要です。これを欠くと、一部のモデルは空のオブジェクトを生成します。systemプロンプト内で期待されるスキーマを記述してください。これが内容の指針となり、JSONモードは構文の保証のみを行います。

#5. 関数呼び出し(ツール使用)

function calling(関数呼び出し)により、モデルは直接回答する代わりにPython関数を呼び出したいと示すことができます。すべてのモデルが対応しているわけではありません。ollama.com/libraryで、対応機能に「tools」と表示されているか確認してください。Qwen 3.5、Granite 4.2、Mistral Small 24B、Devstralは標準で対応しています。

tools.py
from openai import OpenAI
import json

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

# 1. Une fonction Python réelle
def meteo(ville: str) -> dict:
    # En vrai, vous appelleriez Open-Meteo ou autre
    return {"ville": ville, "temperature_c": 18, "conditions": "nuageux"}

# 2. Sa description au format OpenAI
outils = [{
    "type": "function",
    "function": {
        "name": "meteo",
        "description": "Donne la météo actuelle d'une ville française.",
        "parameters": {
            "type": "object",
            "properties": {
                "ville": {"type": "string", "description": "Nom de la ville"},
            },
            "required": ["ville"],
        },
    },
}]

messages = [{"role": "user", "content": "Quel temps fait-il à Bordeaux ?"}]

# 3. Premier appel : le modèle décide d'appeler la fonction
reponse = client.chat.completions.create(
    model="qwen3.5:9b",
    messages=messages,
    tools=outils,
)

appel = reponse.choices[0].message.tool_calls[0]
args = json.loads(appel.function.arguments)
resultat = meteo(**args)

# 4. Second appel : on renvoie le résultat au modèle pour la réponse finale
messages.append(reponse.choices[0].message)
messages.append({
    "role": "tool",
    "tool_call_id": appel.id,
    "content": json.dumps(resultat),
})

finale = client.chat.completions.create(model="qwen3.5:9b", messages=messages)
print(finale.choices[0].message.content)

このループは2回のやり取りで構成されます。1回目ではtool_callsが返されます(モデルが「ville=Bordeauxでmeteoを呼び出して」と指示します)。関数を実行してその結果を渡すと、2回目では自然な言葉での回答が返されます。本番環境では、tool_callsが空でない限りループを繰り返します。

!
すべてのモデルが同等というわけではありません
ツールへの対応が不十分なモデル(古いLlama 2、Mistral 7B v0.1)では、形式の不正な呼び出しや、モデルがでっち上げた引数が生成されます。その場合は、(1) モデルがツールを正式にサポートしていることを確認し、(2) 温度を0に下げ、(3) パラメータのスキーマを簡素化してください。

#6. Ollama を FastAPI で公開

典型的なケース:あなたのフロントエンドがPythonのバックエンドを呼び出し、そのバックエンドが Ollama を呼び出します。FastAPI は非同期を適切に管理し、StreamingResponse によってストリーミングがブラウザに到達します。

依存関係
pip install fastapi uvicorn openai
main.py
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from openai import OpenAI

app = FastAPI()
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

class Question(BaseModel):
    message: str
    model: str = "qwen3.5:9b"

@app.post("/chat")
def chat(q: Question):
    reponse = client.chat.completions.create(
        model=q.model,
        messages=[{"role": "user", "content": q.message}],
    )
    return {"reponse": reponse.choices[0].message.content}

@app.post("/chat/stream")
def chat_stream(q: Question):
    def generateur():
        flux = client.chat.completions.create(
            model=q.model,
            messages=[{"role": "user", "content": q.message}],
            stream=True,
        )
        for chunk in flux:
            delta = chunk.choices[0].delta.content
            if delta:
                yield delta
    return StreamingResponse(generateur(), media_type="text/plain")
サーバーを起動
uvicorn main:app --reload --port 8000
CLIでテスト
curl -N -X POST http://localhost:8000/chat/stream \
  -H "Content-Type: application/json" \
  -d '{"message": "Écris un haïku sur Paris."}'

curl の -N (--no-buffer) オプションは、クライアント側のバッファリングを無効にし、リアルタイムでストリーミングを確認できます。フロントエンドの JS では、fetch の応答から ReadableStream を読み取ります。これは OpenAI API と同じです。

#7. 履歴付きFlaskチャットボット

完全なチャットボットを実現するには、ターン間のメッセージ履歴を保持する必要があります。以下は、メモリに会話履歴を保存する最小限のFlaskバージョンです(本番環境では実際のセッションまたはデータベースに置き換える必要があります)。

依存関係
pip install flask openai
app.py
from flask import Flask, request, jsonify, Response
from openai import OpenAI
from collections import defaultdict

app = Flask(__name__)
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

# Historiques par session — en prod : Redis, Postgres, etc.
historiques: dict[str, list] = defaultdict(lambda: [
    {"role": "system", "content": "Tu es un assistant en français, concis et utile."},
])

@app.post("/chat/<session_id>")
def chat(session_id: str):
    message = request.json["message"]
    historique = historiques[session_id]
    historique.append({"role": "user", "content": message})

    reponse = client.chat.completions.create(
        model="qwen3.5:9b",
        messages=historique,
    )
    contenu = reponse.choices[0].message.content
    historique.append({"role": "assistant", "content": contenu})
    return jsonify({"reponse": contenu})

@app.post("/chat/<session_id>/stream")
def chat_stream(session_id: str):
    message = request.json["message"]
    historique = historiques[session_id]
    historique.append({"role": "user", "content": message})

    def generateur():
        morceaux = []
        flux = client.chat.completions.create(
            model="qwen3.5:9b",
            messages=historique,
            stream=True,
        )
        for chunk in flux:
            delta = chunk.choices[0].delta.content
            if delta:
                morceaux.append(delta)
                yield delta
        historique.append({"role": "assistant", "content": "".join(morceaux)})

    return Response(generateur(), mimetype="text/plain")

@app.delete("/chat/<session_id>")
def reset(session_id: str):
    historiques.pop(session_id, None)
    return "", 204

if __name__ == "__main__":
    app.run(port=5000, debug=True)
i
コンテキストの制限
履歴が長くなるほど、呼び出しのたびに消費するトークン数が増えます。Qwen 3.5では、Ollama側のデフォルトのコンテキストウィンドウは2048トークンです。これを超えると、古いメッセージが通知なく切り捨てられます。ネイティブAPIを使うか、Modelfileで設定を上書きして(num_ctx 8192または32768)、コンテキストウィンドウを広げてください。

#本番環境向け

Ollamaをネットワーク上に公開
デフォルトでは、デーモンは127.0.0.1でのみ接続を待ち受けます。他のマシンからのアクセスを許可するには、OLLAMA_HOST=0.0.0.0を指定して起動し、その前段に認証付きのリバースプロキシを設置してください。そうしないと、ネットワーク上の誰でもモデルを呼び出せてしまいます。
並行処理とリクエストキュー
Ollamaはモデルごとにリクエストを順次処理します。複数のユーザーからのリクエストを並列に処理するには、複数のインスタンスを起動するか、動的バッチ処理に標準対応しているvLLMに切り替えてください。
クライアント側のタイムアウト
未ロードのモデルへのリクエストには、VRAMへのロードのため10〜30秒かかることがあります。OpenAIクライアントのタイムアウトは、ライブラリ側のデフォルトの10分のままにせず、OpenAI(..., timeout=120) のように設定してください。ただし、リバースプロキシ側のタイムアウトは短く設定されていることがよくあります。
モデルをロードした状態に保つ
デフォルトでは、Ollamaは5分間使用されなかったモデルをメモリから解放します。APIを使う場合は、ネイティブAPIの/api/chatでkeep_alive="30m"を指定するか、定期的にpingを送って、最初のユーザーリクエスト時にモデルが未ロードの状態になるのを防いでください。
観測可能性
model、prompt_tokens、completion_tokens(reponse.usage に含まれます)を必ずログに記録してください。これらは推論の指標であり、速度が落ちているモデルや、長さが膨れ上がったプロンプトを見つけるのに役立ちます。
→
OpenAI APIから移行
すでにapi.openai.comと通信するコードがある場合、Ollamaへの切り替えは2行で完了します: base_url="https://api.openai.com/v1"をbase_url="http://localhost:11434/v1"に変更し、モデル名を調整してください。ストリーミング、JSONモード、ツールなど、残りのすべては同じように動作します。これがOpenAI互換エンドポイントの大きな利点です。

#さらに詳しく

基本的な要素は揃いました。ユースケースに応じて、さらに進めるための3つの方向性があります。

自律的に判断するエージェントを構築する
LangChainを用いたPythonベースのローカルAIエージェントガイドでは、関数呼び出しをエージェントのループ全体にまで拡張し、複数のツールの管理および多段階の推論を実現します。
自分のドキュメントを対象にRAGを導入する
アプリが内部コーパス(PDF、メモ、コード)に基づいて回答できるようにするには、ベクトルデータベースを接続してください。ローカルRAGの入門ガイドでは、その基礎を解説しています。
モデルの動作をカスタマイズ
呼び出しのたびにシステムプロンプトを繰り返す代わりに、Modelfileを使ってモデルのバリエーションを作成してください。Ollama Modelfileによるカスタマイズガイドでは、フランス語アシスタントやコーディングモードの設定を固定し、再利用できるモデル名で呼び出せるようにする方法を紹介しています。
このガイドは役に立ちましたか?

ご意見、誤りのご指摘、補足はありますか?ぜひお知らせください。皆さんにとってより良いガイドにするために役立ちます。