Ollama: OpenAI互換API

OpenAI互換APIは移行の架け橋——2行のコードを変更するだけで、クラウドからローカルへ。

💡 ヒント: OllamaのOpenAI互換APIで移行は極めてシンプル——base_urlhttp://localhost:11434/v1に変更し、api_keyに任意の空でない文字列(例:"ollama")を設定し、モデル名をローカルモデル(例:"qwen2.5")に変更するだけ。既存のopenaiライブラリコードをすべて再利用できる。つまり、既存のChatGPTアプリケーション、LangChainプロジェクト、AutoGenワークフローがコード変更ゼロでローカルに切り替え可能。

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

1. 学べること


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

⚠️ 警告: OllamaのOpenAI互換APIは100%完全な実装ではない——Function Calling(Tools)、ストリーミングtool_calls、Assistants APIなどは非対応。移行前に、アプリケーションがこれらの機能に依存していないか必ず確認すること;依存している場合は、ReActエージェントに切り替えるか、/api/chatエンドポイントを直接呼び出す代替手段が必要。

ℹ️ 情報: api_key="ollama"はOllama互換エンドポイントのプレースホルダー規約——OllamaはAPI Keyの内容を検証しない。つまりapi_keyは任意の文字列(例:"sk-1234""dummy")で機能は同一。本物の認証はリバースプロキシ層で実装する必要がある。

(1) ペインポイント:GPT-4の月額2,000ドル

AliceのSupportBotはGPT-4 APIを使用し、月に500万トークンを処理して請求額は2,000ドル。会社からコスト削減を求められているが、移行には大量のコード書き換えが必要——すべての呼び出しがopenai Pythonライブラリに依存している。

(2) ソリューション:2行のコードでローカルに切り替え

OpenAI互換APIにより、Aliceはbase_urlapi_keyを変更するだけで、5分で移行完了:

PYTHON
from openai import OpenAI

# Before: OpenAI cloud
# client = OpenAI(api_key="sk-xxx")

# After: Ollama local (only 2 lines changed!)
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

3. 互換エンドポイントマッピング

💡 ヒント: api_key="ollama"はOllama互換APIのプレースホルダー規約——OllamaはAPI Keyを検証しない。つまりOllamaのアドレスを知っていれば誰でも呼び出せる。本番環境では、Nginxなどのリバースプロキシで本当のKey認証を必ず追加すること。

(1) APIエンドポイント一覧

OpenAIエンドポイント Ollama互換エンドポイント 状態
/v1/chat/completions ✅ 完全互換 主要用途
/v1/completions ✅ 互換 従来の補完
/v1/embeddings ✅ 互換 ベクトル埋め込み
/v1/models ✅ 互換 モデル一覧
/v1/images/generations ❌ 非対応 画像生成
/v1/audio/transcriptions ❌ 非対応 音声書き起こし
100%
flowchart LR
    A[OpenAI SDK] -->|base_url変更| B[Ollama /v1/...]
    B --> C[/v1/chat/completions]
    B --> D[/v1/completions]
    B --> E[/v1/embeddings]
    B --> F[/v1/models]
    B --> G[/v1/images ❌]

(2) 互換性の差異詳細

機能 OpenAI Ollama互換 差異
ストリーミング SSE形式 ✅ SSE互換 一致
Function Calling 完全対応 ⚠️ 部分対応 一部モデルが対応
JSONモード response_format ✅ format=json 一致
Embeddings text-embedding-3 ✅ nomic-embed-textを使用 モデルが異なる
Vision gpt-4o vision ✅ llavaを使用 モデルが異なる
ファインチューニング 対応 Modelfileで代替
レート制限 RPM/TPM 外部実装が必要

(3) ▶ サンプル:openaiライブラリ基本切替

PYTHON
from openai import OpenAI

# Point to local Ollama server
client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # Any non-empty string works
)

# Chat completion (identical to OpenAI API usage)
response = client.chat.completions.create(
    model="qwen2.5",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain RAG in 2 sentences"}
    ],
    temperature=0.3
)

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

出力:

TEXT
# Execution successful

4. コア機能移行

(1) 機能移行一覧

機能 OpenAIコード Ollama変更 工数
Chat model="gpt-4" model="qwen2.5" モデル名変更
ストリーミング stream=True 変更不要 ゼロ変更
Embeddings model="text-embedding-3-small" model="nomic-embed-text" モデル名変更
JSONモード response_format={"type": "json_object"} 同じ 変更不要
システムプロンプト messages=[{"role":"system"...}] 同じ ゼロ変更
Function Calling tools=[...] ⚠️ 一部モデルのみ対応 テスト必要

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

PYTHON
from openai import OpenAI

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

# Streaming works identically
stream = client.chat.completions.create(
    model="qwen2.5",
    messages=[{"role": "user", "content": "Write a short poem about AI"}],
    stream=True,
    temperature=0.7
)

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

出力:

TEXT
# Execution successful

(3) ▶ サンプル:Embeddings移行

PYTHON
from openai import OpenAI

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

# Embeddings: just change the model name
response = client.embeddings.create(
    model="nomic-embed-text",  # Was: text-embedding-3-small
    input="What is the return policy for electronics?"
)

print(f"Embedding dimension: {len(response.data[0].embedding)}")
# 768 dimensions for nomic-embed-text

出力:

TEXT
# Execution successful

(4) ▶ サンプル:JSONモード移行

PYTHON
from openai import OpenAI
import json

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

# JSON mode: identical API
response = client.chat.completions.create(
    model="qwen2.5",
    messages=[
        {"role": "system", "content": "You are a product catalog API."},
        {"role": "user", "content": "List 3 laptops under $500"}
    ],
    response_format={"type": "json_object"},
    temperature=0.3
)

data = json.loads(response.choices[0].message.content)
print(json.dumps(data, indent=2))

出力:

TEXT
# Execution successful

5. 実践移行と互換性制限

⚠️ : OllamaのOpenAI互換APIは100%完全な実装ではない——Function Calling(Tools)は一部モデルのみ部分対応で、GPT-4より安定性が低い。Assistants API、ファインチューニングAPI、Batch APIは非対応。移行前にアプリケーションがこれらの機能に依存していないか必ず確認;依存している場合は、JSONモード+プロンプトでFunction Callingをシミュレートするか、/api/chatエンドポイントを直接呼び出す代替手段が必要。

(1) 移行チェックリスト

チェック項目 説明 リスク
モデル名 gpt-4 → qwen2.5/llama3.1 品質評価が必要
Function Calling 正常動作するかテスト 部分的に失敗する可能性
最大コンテキスト gpt-4: 128K → ローカルモデルによる num_ctxに注意
出力トークン制限 max_tokensパラメータ テスト必要
レート制限 OpenAI RPM → ローカルは制限なし 自前で構築必要
同時実行 OpenAI高同時 → ローカルは限定 スケーリング必要

(2) 非互換機能の代替手段

OpenAI機能 Ollama代替 実装方法
ファインチューニング Modelfile レッスン8で詳細解説
Function Calling プロンプト+JSONパース 手動実装
画像生成 llava(理解のみ) 生成は非対応
Batch API Shell/Pythonスクリプト 自前オーケストレーション
Assistants API LangChainエージェント レッスン13で詳細解説

(3) ▶ サンプル:Function Calling代替実装

PYTHON
from openai import OpenAI
import json

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

# Instead of Function Calling, use JSON mode + prompt
tools = {
    "get_order_status": {"order_id": "string"},
    "process_refund": {"order_id": "string", "amount": "number"},
    "search_products": {"query": "string", "category": "string"}
}

response = client.chat.completions.create(
    model="qwen2.5",
    messages=[
        {"role": "system", "content": f"""You are a customer service bot.
Available tools: {json.dumps(tools)}
If you need to call a tool, respond with JSON: {{"tool": "name", "args": {{...}}}}
Otherwise, respond normally."""},
        {"role": "user", "content": "Where is my order #12345?"}
    ],
    response_format={"type": "json_object"},
    temperature=0.2
)

result = json.loads(response.choices[0].message.content)
if "tool" in result:
    print(f"Call tool: {result['tool']} with args: {result['args']}")
    # Call: get_order_status with {"order_id": "12345"}
else:
    print("Direct response:", result)

出力:

TEXT
Direct response:

6. 総合サンプル:SupportBot GPT-4移行

PYTHON
# ============================================
# Comprehensive: SupportBot migration
# From GPT-4 to local Ollama with OpenAI SDK
# ============================================

from openai import OpenAI
import json
from dataclasses import dataclass
from typing import Optional

@dataclass
class SupportBotMigrator:
    """SupportBot with easy OpenAI/Ollama switching."""

    # Change these 2 lines to switch between cloud and local
    base_url: str = "http://localhost:11434/v1"
    api_key: str = "ollama"
    model: str = "qwen2.5"
    temperature: float = 0.4

    def __post_init__(self):
        self.client = OpenAI(
            base_url=self.base_url,
            api_key=self.api_key
        )
        self.system_prompt = (
            "You are SupportBot, an e-commerce customer service agent. "
            "Be polite, concise, and helpful. "
            "For order queries, ask for order number. "
            "If unsure, say 'Let me connect you with a human agent.'"
        )
        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 = self.client.chat.completions.create(
                model=self.model,
                messages=self.messages[-10:],  # sliding window
                temperature=self.temperature
            )
            reply = response.choices[0].message.content
            self.messages.append({"role": "assistant", "content": reply})
            return reply
        except Exception as e:
            self.messages.pop()
            return f"Error: {e}"

    def classify_intent(self, user_input: str) -> dict:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": """Classify the customer intent.
Return JSON: {"intent": "order_status|refund|product_query|shipping|other", "confidence": 0.0-1.0}"""},
                {"role": "user", "content": user_input}
            ],
            response_format={"type": "json_object"},
            temperature=0.1
        )
        return json.loads(response.choices[0].message.content)

    def generate_embedding(self, text: str) -> list[float]:
        response = self.client.embeddings.create(
            model="nomic-embed-text",
            input=text
        )
        return response.data[0].embedding

# Usage - compare cloud vs local
if __name__ == "__main__":
    # Local Ollama (current config)
    bot = SupportBotMigrator()

    # Switch to OpenAI cloud (uncomment to use)
    # bot = SupportBotMigrator(
    #     base_url="https://api.openai.com/v1",
    #     api_key="sk-your-key",
    #     model="gpt-4"
    # )

    queries = [
        "Where is my order #88765?",
        "I want a refund for damaged goods",
        "Does this laptop have HDMI port?"
    ]

    for q in queries:
        intent = bot.classify_intent(q)
        reply = bot.chat(q)
        print(f"Q: {q}")
        print(f"Intent: {intent}")
        print(f"A: {reply[:100]}...")
        print()

❓ よくある質問

Q api_keyには何を入力する?
A OllamaはAPI Keyを検証しない——任意の空でない文字列が機能する(例:"ollama")。ただし、指定しないとopenaiライブラリがエラーを出すので必ず入力すること。
Q Function Callingは使える?
A 一部モデル(qwen2.5、llama3.1など)で部分対応だが、GPT-4より安定性が低い。JSONモード+プロンプトでシミュレートする方が制御しやすいことを推奨。
Q 移行後に品質が低下したら?
A ハイブリッド戦略を使用——単純な質問はローカル8Bモデル、複雑な質問はGPT-4にフォールバック。8Bモデルで80%のシナリオをカバーすればコスト削減は十分。
Q 異なる埋め込み次元数はRAGに影響する?
A はい。nomic-embed-textは768次元、OpenAI text-embedding-3-smallは1536次元を出力。RAGシステムの移行にはベクトルインデックスの再構築が必要。
Q OpenAIとOllamaを同時に使うには?
A 2つのクライアントをインスタンス化——1つはOpenAI、1つはOllamaを指す。複雑さや予算に応じてリクエストを異なるクライアントにルーティング。
Q Ollamaの/v1と/apiエンドポイントに性能差はある?
A ない。/v1エンドポイントは/apiエンドポイントの互換レイヤーで、同じ推論エンジンを呼び出す。性能は同一。

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐):openai PythonライブラリでOllamaに接続し、chat.completions呼び出しを完了して互換性を検証する。
  2. 中級(難易度 ⭐⭐):既存のOpenAIスクリプト(ストリーミング出力とJSONモード付き)をOllamaに移行し、必要な変更点を文書化する。
  3. 上級(難易度 ⭐⭐⭐):質問の複雑さに基づいてローカルOllamaまたはクラウドGPT-4に自動ルーティングするデュアルバックエンドルーターを実装し、コスト削減を追跡する。
Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%