Ollama: OpenAI兼容API
OpenAI 兼容 API 是迁移的桥梁——改两行代码,从云端回到本地。
💡 提示:Ollama的OpenAI兼容API让迁移变得极其简单——只需将
base_url 改为 http://localhost:11434/v1,api_key 设为任意非空字符串(如"ollama"),模型名改为本地模型(如"qwen2.5"),即可复用现有openai库的所有代码。这意味着你原有的ChatGPT应用、LangChain项目、AutoGen工作流都可以零代码改动地切换到本地。
📋 前置知识:需要先掌握以下内容
- 第5课:REST API入门
1. 你将学到
- 与 端点映射
- openai Python 库切换到 Ollama
- 兼容性差异与功能限制
- 迁移实战:将 ChatGPT 应用改为 Ollama 后端
- Alice 的月省 2,000 USD 迁移案例
2. 一个 SaaS 创业者的真实故事
⚠️ 警告: Ollama 的 OpenAI 兼容 API 并非 100% 完整——不支持 Function Calling(Tools)、Streaming with tool_calls、Assistants API 等。迁移前务必检查应用是否依赖这些功能,否则需改用 ReAct Agent 或直接调用
/api/chat 端点替代。
ℹ️ 信息:
api_key="ollama" 是 Ollama 兼容端点的占位写法,Ollama 不校验 API Key 内容。这意味着 api_key 可以是任意字符串(如 "sk-1234"、"dummy"),功能完全相同。真正的认证需在反向代理层实现。
(1) 痛点:GPT-4 月费 2,000 USD
Alice 的 SupportBot 使用 GPT-4 API,每月处理 5 million tokens,账单高达 2,000 USD。公司要求降本,但迁移需要重写大量代码——所有调用都绑定了 openai Python 库。
(2) 解法:两行代码切换本地
OpenAI 兼容 API 让 Alice 只改 和 ,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 兼容端点 | 状态 |
|---|---|---|
| ✅ 完全兼容 | 主要使用 | |
| ✅ 兼容 | 旧版补全 | |
| ✅ 兼容 | 向量嵌入 | |
| ✅ 兼容 | 模型列表 | |
| ❌ 不支持 | 图像生成 | |
| ❌ 不支持 | 语音转写 |
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) 兼容性差异详解
| 功能 | OpenAI | Ollama 兼容 | 差异说明 |
|---|---|---|---|
| Streaming | SSE 格式 | ✅ SSE 兼容 | 一致 |
| Function Calling | 完整支持 | ⚠️ 部分支持 | 部分模型支持 |
| JSON Mode | response_format | ✅ format=json | 一致 |
| Embeddings | text-embedding-3 | ✅ 用 nomic-embed-text | 模型不同 |
| Vision | gpt-4o vision | ✅ 用 llava | 模型不同 |
| Fine-tuning | 支持 | ❌ | 用 Modelfile 替代 |
| Rate Limiting | RPM/TPM | ❌ | 需外部实现 |
▶ 示例 1: 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
📖 仅展示
# 执行成功
4. 核心功能迁移
(1) 各功能迁移对照
| 功能 | OpenAI 代码 | Ollama 修改 | 工作量 |
|---|---|---|---|
| Chat | 改模型名 | ||
| Streaming | 无需修改 | 零改动 | |
| Embeddings | 改模型名 | ||
| JSON Mode | 无需修改 | ||
| System Prompt | 无需修改 | 零改动 | |
| Function Calling | ⚠️ 部分模型支持 | 需测试 |
▶ 示例 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
📖 仅展示
# 执行成功
▶ 示例 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
📖 仅展示
# 执行成功
▶ 示例 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
📖 仅展示
# 执行成功
5. 迁移实战与兼容性限制
⚠️ 注意:Ollama的OpenAI兼容API并非100%完整实现——Function Calling(Tools)仅部分模型支持且稳定性不如GPT-4,Assistants API、Fine-tuning API、Batch API等均不支持。迁移前务必检查你的应用是否依赖这些功能,否则需要改用JSON模式+Prompt模拟Function Calling,或直接调用
/api/chat 端点替代。
(1) 迁移检查清单
| 检查项 | 说明 | 风险 |
|---|---|---|
| 模型名称 | gpt-4 → qwen2.5/llama3.1 | 需评估质量 |
| Function Calling | 测试是否正常 | 可能部分失败 |
| 最大上下文 | gpt-4: 128K → 本地模型各异 | 注意 num_ctx |
| 输出 token 限制 | max_tokens 参数 | 需测试 |
| 速率限制 | OpenAI RPM → 本地无限制 | 需自建 |
| 并发能力 | OpenAI 高并发 → 本地有限 | 需扩展 |
(2) 不兼容功能替代方案
| OpenAI 功能 | Ollama 替代 | 实现方式 |
|---|---|---|
| Fine-tuning | Modelfile | Lesson 8 详解 |
| Function Calling | Prompt + JSON 解析 | 手动实现 |
| Image Generation | llava (仅理解) | 不支持生成 |
| Batch API | Shell/Python 脚本 | 自行编排 |
| Assistants API | LangChain Agent | Lesson 13 详解 |
▶ 示例 5: 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 模式 + prompt 模拟 Function Calling,更可控。
Q 迁移后质量下降怎么办?
A 用混合策略——简单问题用本地 8B 模型,复杂问题回退 GPT-4。8B 模型覆盖 80% 场景即可大幅降本。
Q embeddings 维度不同会影响 RAG 吗?
A 会。nomic-embed-text 输出 768 维,OpenAI text-embedding-3-small 输出 1536 维。迁移 RAG 系统时需重建向量索引。
Q 如何同时使用 OpenAI 和 Ollama?
A 实例化两个 client,一个指向 OpenAI,一个指向 Ollama。根据请求复杂度或成本预算路由到不同 client。
Q Ollama 的 /v1 端点性能和 /api 端点有区别吗?
A 没有。/v1 端点是 /api 端点的兼容层,底层调用同一推理引擎。性能完全一致。
📖 小节
- Ollama 提供 /v1/chat/completions 等 OpenAI 兼容端点,覆盖 Chat/Embeddings/Models
- 迁移只需改 base_url 和 api_key 两行代码
- Function Calling 部分支持,建议用 JSON 模式替代
- Embeddings 模型不同,迁移 RAG 需重建向量索引
- 混合策略:80% 本地 + 20% 云端,最大化性价比
- Alice 月省 2,000 USD,5 分钟完成迁移
📝 作业
- 基础题(难度⭐):用 openai Python 库连接 Ollama,完成一次 chat.completions 调用,验证兼容性。
- 进阶题(难度⭐⭐):将一个现有的 OpenAI 脚本(含流式输出和 JSON 模式)迁移到 Ollama,记录需要修改的地方。
- 挑战题(难度⭐⭐⭐):实现一个双后端路由器——自动根据问题复杂度决定走本地 Ollama 还是云端 GPT-4,并统计成本节约。