Ollama: OpenAI Compatible API

The OpenAI compatible API is the bridge for migration — change two lines of code, move from cloud to local.

💡 Tip: Ollama's OpenAI compatible API makes migration extremely simple — just change base_url to http://localhost:11434/v1, set api_key to any non-empty string (e.g., "ollama"), and change the Model name to a local Model (e.g., "qwen2.5"). You can then reuse all existing openai library code. This means your existing ChatGPT applications, LangChain projects, and AutoGen workflows can switch to local with zero code changes.

📋 Prerequisites: You need to master the following first

1. What You Will Learn


2. A Real Story from a SaaS Founder

⚠️ Warning: Ollama's OpenAI compatible API is not 100% complete — it doesn't support Function Calling (Tools), Streaming with tool_calls, Assistants API, etc. Before migrating, be sure to check whether your application depends on these features; otherwise, you'll need to switch to a ReAct Agent or call the /api/chat endpoint directly as an alternative.

ℹ️ Info: api_key="ollama" is a placeholder convention for Ollama's compatible endpoint — Ollama doesn't validate API Key content. This means api_key can be any string (e.g., "sk-1234", "dummy"), with identical functionality. Real authentication must be implemented at the reverse proxy layer.

(1) The Pain Point: GPT-4 Monthly Cost of 2,000 USD

Alice's SupportBot uses the GPT-4 API, processing 5 million tokens per month with a bill of 2,000 USD. The company demands cost reduction, but migration requires rewriting a lot of code — all calls are tied to the openai Python library.

(2) The Solution: Switch to Local with Two Lines of Code

The OpenAI compatible API lets Alice change only base_url and api_key, completing migration in 5 minutes:

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. Compatible Endpoint Mapping

💡 Tip: api_key="ollama" is a placeholder convention for the Ollama compatible API — Ollama doesn't validate the API Key. This means anyone who knows your Ollama address can call it. In production, always add real Key authentication via a reverse proxy like Nginx.

(1) API Endpoint Reference

OpenAI Endpoint Ollama Compatible Endpoint Status
/v1/chat/completions ✅ Fully compatible Primary use
/v1/completions ✅ Compatible Legacy completions
/v1/embeddings ✅ Compatible Vector Embedding
/v1/models ✅ Compatible Model listing
/v1/images/generations ❌ Not supported Image generation
/v1/audio/transcriptions ❌ Not supported Audio transcription
100%
flowchart LR
    A[OpenAI SDK] -->|base_url change| 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) Compatibility Differences Detailed

Feature OpenAI Ollama Compatible Difference
Streaming SSE format ✅ SSE compatible Consistent
Function Calling Full support ⚠️ Partial support Some Models support
JSON Mode response_format ✅ format=json Consistent
Embeddings text-embedding-3 ✅ Uses nomic-embed-text Different Model
Vision gpt-4o vision ✅ Uses llava Different Model
Fine-tuning Supported Use Modelfile instead
Rate Limiting RPM/TPM Requires external implementation

▶ Example 1: openai Library Basic Switch

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)

Output:

TEXT
# Execution successful

4. Core Feature Migration

(1) Feature Migration Reference

Feature OpenAI Code Ollama Change Effort
Chat model="gpt-4" model="qwen2.5" Change Model name
Streaming stream=True No change needed Zero changes
Embeddings model="text-embedding-3-small" model="nomic-embed-text" Change Model name
JSON Mode response_format={"type": "json_object"} Same No change needed
System Prompt messages=[{"role":"system"...}] Same Zero changes
Function Calling tools=[...] ⚠️ Partial Model support Needs testing

▶ Example 2: Streaming Output Migration

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()

Output:

TEXT
# Execution successful

▶ Example 3: Embeddings Migration

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

Output:

TEXT
# Execution successful

▶ Example 4: JSON Mode Migration

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))

Output:

TEXT
# Execution successful

5. Migration in Practice and Compatibility Limitations

⚠️ Note: Ollama's OpenAI compatible API is not a 100% complete implementation — Function Calling (Tools) is only partially supported by some Models and is less stable than GPT-4. Assistants API, Fine-tuning API, and Batch API are not supported. Before migrating, be sure to check whether your application depends on these features; otherwise, you'll need to use JSON mode + Prompt to simulate Function Calling, or call the /api/chat endpoint directly as an alternative.

(1) Migration Checklist

Check Item Description Risk
Model name gpt-4 → qwen2.5/llama3.1 Need quality assessment
Function Calling Test if it works properly May partially fail
Maximum context gpt-4: 128K → varies by local Model Note num_ctx
Output token limit max_tokens parameter Needs testing
Rate limiting OpenAI RPM → local no limit Must build yourself
Concurrency OpenAI high concurrency → local limited Needs scaling

(2) Incompatible Feature Alternatives

OpenAI Feature Ollama Alternative Implementation
Fine-tuning Modelfile Detailed in Lesson 8
Function Calling Prompt + JSON parsing Manual implementation
Image Generation llava (understanding only) Generation not supported
Batch API Shell/Python scripts Self-orchestration
Assistants API LangChain Agent Detailed in Lesson 13

▶ Example 5: Function Calling Alternative

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)

Output:

TEXT
Direct response:

6. Comprehensive Example: SupportBot GPT-4 Migration

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()

❓ FAQ

Q What should I fill in for api_key?
A Ollama doesn't validate the API Key — any non-empty string works (e.g., "ollama"). But you must provide one, otherwise the openai library will throw an error.
Q Can Function Calling be used?
A Partially supported by some Models (e.g., qwen2.5, llama3.1), but less stable than GPT-4. We recommend using JSON mode + prompt to simulate Function Calling for more control.
Q What if quality degrades after migration?
A Use a hybrid strategy — simple questions use the local 8B Model, complex questions fall back to GPT-4. An 8B Model covering 80% of scenarios can already significantly reduce costs.
Q Do different Embedding dimensions affect RAG?
A Yes. nomic-embed-text outputs 768 dimensions; OpenAI text-embedding-3-small outputs 1536 dimensions. Migrating a RAG system requires rebuilding the Vector index.
Q How do I use both OpenAI and Ollama simultaneously?
A Instantiate two clients — one pointing to OpenAI, one to Ollama. Route requests to different clients based on complexity or budget.
Q Is there a performance difference between Ollama's /v1 and /api endpoints?
A No. The /v1 endpoint is a compatibility layer over the /api endpoint, calling the same Inference engine underneath. Performance is identical.

📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Connect to Ollama using the openai Python library, complete a chat.completions call, and verify compatibility.
  2. Intermediate (Difficulty ⭐⭐): Migrate an existing OpenAI script (with streaming output and JSON mode) to Ollama, documenting the changes needed.
  3. Advanced (Difficulty ⭐⭐⭐): Implement a dual-backend router that automatically routes to local Ollama or cloud GPT-4 based on question complexity, and tracks cost savings.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏