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
- Lesson 5: REST API Fundamentals
1. What You Will Learn
/v1/chat/completionsand/v1/embeddingsendpoint mapping- Switching the openai Python library to Ollama
- Compatibility differences and feature limitations
- Migration in practice: converting a ChatGPT application to Ollama backend
- Alice's case study: saving 2,000 USD/month
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 |
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
- Ollama provides /v1/chat/completions and other OpenAI compatible endpoints, covering Chat/Embeddings/Models
- Migration requires changing only base_url and api_key — two lines of code
- Function Calling is partially supported; JSON mode is recommended as an alternative
- Embedding Models differ; migrating RAG requires rebuilding the Vector index
- Hybrid strategy: 80% local + 20% cloud for maximum cost-effectiveness
- Alice saves 2,000 USD/month, completing migration in 5 minutes
📝 Exercises
- Basic (Difficulty ⭐): Connect to Ollama using the openai Python library, complete a chat.completions call, and verify compatibility.
- Intermediate (Difficulty ⭐⭐): Migrate an existing OpenAI script (with streaming output and JSON mode) to Ollama, documenting the changes needed.
- 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.