JA ▾
API キーを取得

PythonでのOllama APIとホスト版の代替案

PythonでのOllama APIは約10行で実装でき、同じコード構成はホストエンドポイントでも動作します。本記事では両方を紹介し、どちらを選ぶべきかの基準を説明します。

更新日:

主要ポイント

  • Ollamaはポート11434でリッスンし、ネイティブAPIとOpenAI風の/v1エンドポイントを提供します。
  • ollamaパッケージ、通常のrequests、OpenAI SDKのすべてが動作します。OpenAI SDKが最も移植性が高いです。
  • ホストAPIへの移行は、ベースURL、キー、モデル名の変更を意味します。
  • ローカルはオフライン作業と固定ハードウェアに最適です。GPUがない場合はホストの方が簡単です。
光るケーブルでクラウドサーバーに接続されたローカルコンピュータ。ローカルとホストのLLM APIを表します

簡易版

ollama api pythonを検索する場合、通常は次の3つのいずれかを望んでいます:自分のマシンで動作するモデルを呼び出す、トークンをストリーミングする、または同じスクリプトを別の環境で動作させる。Ollamaはこれらすべてを提供します。デフォルトではhttp://localhost:11434でローカルサーバーを起動し、ollama Pythonパッケージ、通常のHTTP、またはその/v1パスを指すOpenAIクライアントを使って通信します。

ただし、モデルはあなたのハードウェア上で実行する必要があります。適切なGPUを搭載していないラップトップでは大きなモデルで遅くなり、それがないサーバーでは適していません。その場合、ホスト型OpenAI互換APIの方が簡単な方法です。リクエスト形式が同じため、切り替えるには設定を3行変更するだけです。以下に、貼り付け可能なコード付きで両方のパスをカバーします。

ollamaパッケージによるローカルモデルの呼び出し

Ollamaをインストールし、モデルをダウンロードして、サーバーが実行中であることを確認してください。次に、Pythonクライアントをインストールします:

pip install ollama
ollama pull llama3.2

基本的なチャット呼び出しは以下のようになります。モデル名はあなたがpullしたもので構いません。llama3.2は例示に過ぎません。

import ollama

response = ollama.chat(
    model="llama3.2",
    messages=[{"role": "user", "content": "Explain DNS in two sentences."}],
)
print(response["message"]["content"])

ストリーミング

stream=Trueを渡すと、1つの応答の代わりにチャンクのイテレータが得られます。各チャンクにはmessage.contentに答えの一部が含まれます。

import ollama

stream = ollama.chat(
    model="llama3.2",
    messages=[{"role": "user", "content": "Write a haiku about servers."}],
    stream=True,
)
for chunk in stream:
    print(chunk["message"]["content"], end="", flush=True)
print()

クライアントを別のマシンに指す

Ollamaが別のホストで実行されている場合は、明示的なアドレスでクライアントを作成します。例:ollama.Client(host="http://192.168.1.20:11434")。デフォルトではサーバーはローカルマシンでのみリッスンするため、リモートアクセスにはサーバー側での明示的な設定が必要です。ネイティブAPIには独自のキーチェックがないため、プロキシと認証を前に配置せずに公開インターネットに公開しないでください。

requestsによるプレーンHTTP

パッケージは必須ではありません。ネイティブチャットエンドポイントはPOST /api/chatです。ストリーミングはデフォルトで有効であり、ストリームはSSEではなく改行区切りのJSONです。単一のオブジェクトを返す場合は"stream": falseを設定します。

import requests

r = requests.post(
    "http://localhost:11434/api/chat",
    json={
        "model": "llama3.2",
        "messages": [{"role": "user", "content": "Name three HTTP status codes."}],
        "stream": False,
    },
    timeout=120,
)
r.raise_for_status()
print(r.json()["message"]["content"])

余裕のあるタイムアウトを設定してください。pull後の最初のリクエストではモデルをメモリに読み込む必要があり、その後のリクエストよりもはるかに時間がかかる場合があります。

ストリームを手動で読み取るには、さらに数行追加するだけです。requests.post(..., stream=True)を呼び出し、r.iter_lines()をループし、空の行をスキップし、各行でjson.loadsを実行します。各オブジェクトは返信の小さな断片を含み、最後のものは"done": trueを持ちます。これは、別のパッケージをインストールしたくない最小限のコンテナでは便利であり、ライブラリがあなたのために何をしているかを明確にします。ホストAPIはSSEの行として異なる方法でストリーミングし、data:で始まるため、移植性のあるコードでOpenAI SDKにストリームを解析させるのがよい理由の一つです。

OllamaでのOpenAI SDKの使用

Ollamaは/v1の下にOpenAIスタイルのエンドポイントも公開しています。つまり、公式のopenaiパッケージが動作し、カスタムベースURLを設定できるソフトウェアのほとんどが動作します。キーはSDKによって必須ですが、ローカルのOllamaでは無視されるため、任意の文字列で構いません。

from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
resp = client.chat.completions.create(
    model="llama3.2",
    messages=[{"role": "user", "content": "Give me a one-line summary of TCP."}],
)
print(resp.choices[0].message.content)

これは最も有用な形式です。コードをサーバーから切り離すことができるためです。同じ関数は、今日はローカルモデルと、明日は別のものと通信できます。これが次のセクションです。

ホストAPIでの同じコード

ここで紹介するホストオプションは、単一のモデルuncensoredを持つOpenAI互換APIで、独立したプロバイダによって実行され、法的な成人利用に対してコンテンツ拒否なしで回答するようにチューニングされています。テキストのみです。ベースURLはhttps://api.ollamaapi.cc/v1で、APIキーページから取得したBearerキーで認証します。

ターゲットを環境変数から読み取るバージョンを以下に示します。デフォルト値としてホスト値を使用します。ローカルサーバーに戻すには、2つの変数をエクスポートして、それ以外の変更を加えないでください。

import os
from openai import OpenAI

client = OpenAI(
    base_url=os.getenv("LLM_BASE_URL", "https://api.ollamaapi.cc/v1"),
    api_key=os.getenv("LLM_API_KEY", "YOUR_API_KEY"),
)
MODEL = os.getenv("LLM_MODEL", "uncensored")

resp = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "You are a concise assistant."},
        {"role": "user", "content": "List four uses of a message queue."},
    ],
    temperature=0.7,
)
print(resp.choices[0].message.content)

ローカル実行には、LLM_BASE_URL=http://localhost:11434/v1、LLM_API_KEY=ollama、LLM_MODEL=llama3.2を設定します。クイックスタートにはcurlとNodeでの同じセットアップがあります。

ホスト側でのストリーミング、JSONモード、ツール

ホストエンドポイントはSSEによるストリーミング、JSONモード、ツール呼び出し、および通常のサンプリングパラメータ(temperature、top_p、stop、seedおよびペナルティ)をサポートします。SDKでのストリーミングは、任意のOpenAI風サーバーに対して記述するのと同じループです:

stream = client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "Tell a two-paragraph story about a lighthouse."}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

構造化出力にはresponse_format={"type": "json_object"}を設定し、プロンプトでJSONを望むことを伝えます。モデルが予期しないものを返す可能性があるため、とにかく常にtryブロック内で解析してください。

ローカル利用とは異なり、コードに影響する重要な点として制限があります。フルキーでは8つの同時リクエストと1分あたり300リクエストが可能で、ストリーミングリクエストはストリームが終了するまでスロットを保持します。スレッドで並列実行する場合、プールを8に制限し、HTTP 429応答時にRetry-Afterヘッダーが存在する場合はそれを使用してリトライします。

会話を継続する

ローカルおよびホストのチャットエンドポイントのどちらも、呼び出し間で何も記憶しません。サーバーはあなたが送信したメッセージのみを認識するため、チャットボットはリストであり、それを追加して再送信します。このパターンは両方のターゲットで同一であり、OpenAI風クライアントをデフォルトとして維持するのがよい理由の1つです。

history = [{"role": "system", "content": "You are a helpful assistant."}]

def ask(text):
    history.append({"role": "user", "content": text})
    resp = client.chat.completions.create(model=MODEL, messages=history)
    answer = resp.choices[0].message.content
    history.append({"role": "assistant", "content": answer})
    return answer

print(ask("Pick a name for a hiking app."))
print(ask("Give me a tagline for it."))

実用的な注意点2つ。まず、ローカルモデルのコンテキストウィンドウ長はあなたが設定するものであり、メモリが保持する必要があります。履歴がそれを超えると、設定に応じて古いターンが切り捨てられるか、呼び出しが失敗します。ホストモデルは完了を含む100,000トークンのウィンドウを受け入れます。これはほとんどのチャットに必要な量をはるかに超えていますが、長い履歴では各ターンで入力トークンが依然としてコストがかかります。第二に、履歴が長くなると、それらすべてを再送信するのではなく、最も古いターンを1つの短いメッセージに要約してください。これにより、どちらのバックエンドでもレイテンシとコストが安定します。

丁寧なリトライ

ホストAPIでは、一時的な過負荷時にHTTP 429とRetry-After: 5ヘッダーが返されます。小さなラッパーはこれを処理します:

import time
from openai import RateLimitError

def chat_with_retry(messages, tries=4):
    for attempt in range(tries):
        try:
            return client.chat.completions.create(model=MODEL, messages=messages)
        except RateLimitError:
            time.sleep(5 * (attempt + 1))
    raise RuntimeError("still rate limited")

ローカルのOllamaはレート制限を行いませんが、モデルがビジー状態のときはリクエストをキューに入れます。そのため、同じラッパーをローカルでも使用しても問題ありません。

ローカルかホストか:判断基準

どちらの選択も一般的により優れているわけではありません。以下の項目をチェックリストとして使用してください。

  • ローカルのOllamaを選択する:オフラインで作業する必要がある場合、データがマシンから決して離れてはならない場合、高性能なGPUをすでに所有している場合、または複数の異なるオープンモデルをすばやく試したい場合。
  • ホストAPIを選択する:GPUがない場合、小型のVPSやCIで実行する場合、複数のユーザーやサービスが1つのバックエンドを共有する場合、またはメモリを自分でサイズ設定せずに長いコンテキストウィンドウが必要な場合。ホストAPIは、プロンプトと完了を合わせて最大100,000トークンを受け入れます。
  • 両方を混合:開発中はローカルで反復し、環境変数を通じて本番環境をホストURLにポイントします。

ホスト利用はトークンごとに課金され、入力トークン100万あたり$0.15、出力トークン100万あたり$0.50で、前払いクレジットから引かれます。価格ページにはチャージ額が記載されており、新しいアカウントはワンクリックで$0.50のトライアルを受け取ることができ、カードは不要です。プロンプトはトレーニングに使用されません。

遭遇するエラー

症状考えられる原因修正方法
ポート11434で接続拒否Ollamaサーバーが実行中ではないアプリを起動するか、ollama serveを実行
モデルが見つからないモデルがpullされていないか、名前にタイプミスがありますollama listを実行し、使用するモデル名をollama pullで取得してください
最初の呼び出しでタイムアウトモデルがまだ読み込み中タイムアウトを延長するか、ウォームアップリクエストを送信してください
ホスト API での認証エラーBearer キーの不足または誤りLLM_API_KEYとベースURLを確認する
同時実行数制限でHTTP 429(コードconcurrency)進行中のリクエストが8件以上(トライアルキーでは2)ワーカー数を減らしてください
コードrate_limitedのHTTP 4291分あたりのリクエスト数制限を超えたバックオフして再試行してください。

ホスト側での失敗または拒否されたリクエストは課金されません。そのため、失敗した試行に対してはリトライロジックの費用もかかりません。

質問と回答

Ollama API のデフォルトアドレスは何ですか?

ローカルのOllamaサーバーはhttp://localhost:11434でリッスンしています。ネイティブエンドポイントは/api配下(例:/api/chat)にあり、OpenAI風エンドポイントは/v1配下にあります。

OllamaでOpenAIのPython SDKを使用できますか?

はい。base_url を http://localhost:11434/v1 に設定し、API キーとして任意の文字列を渡してください。SDK はその後、標準的なチャット完了リクエストをローカルモデルに送信します。

Ollama からホスト API に切り替えるにはどうすればよいですか?

ベース URL、キー、モデル名を変更してください。コードが環境変数からこれらを読み込んでいる場合、コードの変更は不要です。

ホスト API は、私の側で GPU なしでも動作しますか?

はい。モデルはプロバイダー側で実行されるため、スクリプトにはインターネット接続と Bearer キーのみが必要です。前払いクレジットからトークン単位で課金されます。

キーはフォームを1つ埋めるだけで入手できます

アカウントを作成し、キーをコピーし、ベースURLを変更します。これでセットアップは完了です。

API キーを取得