這兩年在做AI應用時,很多團隊最先遇到的問題,已經不是「如何呼叫某個模型」,而是「如何管理越來越多的模型入口」。
剛開始做PoC時,事情通常很單純。
選一個模型平台、申請一組API Key、安裝SDK,幾行程式碼就能把第一版功能跑起來。
但只要專案往前走,情況很快就會變複雜。
例如:
• 文件生成或程式碼輔助想測試Claude
• 通用聊天功能想保留GPT系模型
• 長文本整理或多模態任務又想比較Gemini
• 中文任務可能還會納入DeepSeek或Qwen
• 後續甚至還要加入影像、語音或影片能力
這時候,開發團隊真正面對的問題,往往不再是「某個模型能不能呼叫」,而是:
• 不同平台的驗證方式各自獨立
• SDK與回傳格式存在差異
• 模型切換成本逐漸升高
• 業務邏輯容易和模型供應商耦合
• 後續要做A/B測試、故障切換、成本控管時,維護難度明顯上升
因此,對實際要維運的AI應用來說,與其讓業務程式直接綁定單一模型,更合理的做法通常是先建立一層統一的模型呼叫層。
這篇文章想談的,就是如何透過OpenAI相容API,把GPT、Claude、Gemini、DeepSeek這類模型整合到同一套呼叫邏輯中,讓後續的切換、比較與擴充更容易進行。
文中會以Crazyrouter作為示例,原因很簡單:它提供OpenAI相容介面,適合用來說明這種多模型抽象層的做法。重點不在特定平台本身,而在於這類架構思路對工程實作的幫助。
───
為什麼要把模型呼叫抽象成一層?
最常見的起手式通常像這樣:
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://crazyrouter.com/v1"
)
resp = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "請用Python寫一個快速排序"}
]
)
print(resp.choices[0].message.content)
這段程式碼沒有問題,對驗證功能也很有效率。
但如果直接把這種寫法一路帶進正式專案,通常很快就會遇到幾個典型問題。
第一,模型名稱會散落在不同模組與業務邏輯裡。
第二,錯誤處理、重試策略與逾時控制會難以統一。
第三,當團隊要比較不同模型,或在上游異常時切換備援模型,修改成本會快速放大。
第四,如果未來要把模型層做成可配置、可路由、可觀測的基礎設施,前面那些直接寫死的程式很容易變成技術債。
換句話說,Demo可以直接呼叫模型;但要做成可演進的產品,最好還是先把模型呼叫整理成一層獨立能力。
───
OpenAI相容API的價值在哪裡?
目前不少開發團隊已經以OpenAI SDK作為基礎。
這時候,若新的模型入口也能維持OpenAI相容格式,會有幾個很明顯的好處。
首先,原有程式碼資產可以直接沿用,不需要為每個模型平台重新學一套介面。
其次,多數框架、工具鏈與中介套件本來就已圍繞OpenAI生態發展,接入成本相對低。
再者,團隊在切換模型時,可以把改動控制在base_url與model層級,而不是連呼叫邏輯都要重寫。
從工程角度來看,這代表模型供應商的變動,較不容易直接衝擊業務程式本身。
───
最小可用版本:先建立統一呼叫函式
先從一個最簡單的版本開始。
如果只是要快速驗證多模型切換,寫一個統一函式就已經比把呼叫邏輯散落各處好很多。
from openai import OpenAI
client = OpenAI(
api_key="YOUR_CRAZYROUTER_KEY",
base_url="https://crazyrouter.com/v1"
)
def chat(model: str, prompt: str) -> str:
response = client.chat.completions.create(
model=model,
messages=[
{"role": "user", "content": prompt}
]
)
return response.choices[0].message.content
if __name__ == "__main__":
prompt = "請用Python寫一個快速排序"
print(chat("gpt-4o", prompt))
print(chat("claude-sonnet-4-20250514", prompt))
print(chat("gemini-2.5-pro", prompt))
print(chat("deepseek-chat", prompt))
from openai import OpenAI
client = OpenAI(
api_key="YOUR_CRAZYROUTER_KEY",
base_url="https://crazyrouter.com/v1"
)
def chat(model: str, prompt: str) -> str:
response = client.chat.completions.create(
model=model,
messages=[
{"role": "user", "content": prompt}
]
)
return response.choices[0].message.content
if __name__ == "__main__":
prompt = "請用Python寫一個快速排序"
print(chat("gpt-4o", prompt))
print(chat("claude-sonnet-4-20250514", prompt))
print(chat("gemini-2.5-pro", prompt))
print(chat("deepseek-chat", prompt))
from openai import OpenAI
client = OpenAI(
api_key="YOUR_CRAZYROUTER_KEY",
base_url="https://crazyrouter.com/v1"
)
def chat(model: str, prompt: str) -> str:
response = client.chat.completions.create(
model=model,
messages=[
{"role": "user", "content": prompt}
]
)
return response.choices[0].message.content
if __name__ == "__main__":
prompt = "請用Python寫一個快速排序"
print(chat("gpt-4o", prompt))
print(chat("claude-sonnet-4-20250514", prompt))
print(chat("gemini-2.5-pro", prompt))
print(chat("deepseek-chat", prompt))
這樣做最直接的好處是,模型切換只剩下一個參數的差異。
對早期PoC、模型比較、內容品質測試來說,已經能省下不少重複工作。
不過,若要進一步走向正式專案,還需要把重試、例外處理與統一設定補齊。
───
封裝成可維護的多模型Client
下一步,可以把它整理成一個獨立的Client類別。
這樣一來,之後不論要加入紀錄、監控、快取、路由或權限控制,都比較容易擴充。
from typing import Optional
from openai import OpenAI
class MultiModelClient:
def __init__(
self,
api_key: str,
base_url: str = "https://crazyrouter.com/v1",
max_retries: int = 2,
retry_delay: float = 1.5,
):
self.client = OpenAI(
api_key=api_key,
base_url=base_url
)
self.max_retries = max_retries
self.retry_delay = retry_delay
def chat(
self,
model: str,
prompt: str,
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 1024,
) -> str:
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
last_error = None
for attempt in range(1, self.max_retries + 2):
try:
response = self.client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
)
return response.choices[0].message.content
except Exception as e:
last_error = e
print(f"[WARN] model={model}, attempt={attempt}, error={e}")
if attempt <= self.max_retries:
time.sleep(self.retry_delay)
raise RuntimeError(f"Request failed after retries: {last_error}")
呼叫方式如下:
llm = MultiModelClient(api_key="YOUR_CRAZYROUTER_KEY")
result = llm.chat(
model="gpt-4o",
prompt="請以後端工程師的角度解釋Bloom Filter",
system_prompt="你是一位資深軟體工程師",
temperature=0.2
)
print(result)
這個版本的意義不只是「把程式寫漂亮」而已。它真正的價值在於:模型呼叫策略終於有了一個集中管理的位置。未來無論要補上日誌、指標、限流、觀測或模型計價邏輯,都不需要回頭重改業務邏輯。
───
不要把模型名稱寫死在業務程式裡
多模型專案很容易踩到一個坑:
模型名一開始寫起來很方便,結果很快就到處都是gpt-4o、claude-sonnet-4-20250514這類字串。
短期看起來無傷大雅,長期卻會讓調整變得很痛苦。比較合理的做法,是先把模型對應到某種「能力角色」。
"fast_chat": "gpt-4o-mini",
"best_writing": "claude-sonnet-4-20250514",
"best_reasoning": "gemini-2.5-pro",
"cn_task": "deepseek-chat",
}
業務程式呼叫時,不直接指定底層模型,而是指定用途。
model=MODEL_CONFIG["best_reasoning"],
prompt="請分析這份系統設計的可能瓶頸"
)
這種設計有幾個好處。
首先,模型替換不需要改動業務流程。
其次,之後若要做A/B測試,可以很容易從設定層切換。
再來,若未來要將模型配置移到環境變數、資料庫或配置中心,也會順很多。
這看起來只是小細節,但在真正維護中的專案裡,差異非常大。
───
補上Fallback機制
在多模型架構裡,常見需求之一是:
主模型失敗時,自動切換到下一個備援模型。
例如某些高品質任務平常優先走Claude,若遇到異常,再回退到GPT;如果還是失敗,再切到成本較低或可用性較高的模型。
下面是一個簡化版本的實作:
from typing import Optional, List
from openai import OpenAI
class MultiModelClient:
def __init__(
self,
api_key: str,
base_url: str = "https://crazyrouter.com/v1",
max_retries: int = 2,
retry_delay: float = 1.5,
):
self.client = OpenAI(api_key=api_key, base_url=base_url)
self.max_retries = max_retries
self.retry_delay = retry_delay
def _single_chat(
self,
model: str,
prompt: str,
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 1024,
) -> str:
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
last_error = None
for attempt in range(1, self.max_retries + 2):
try:
response = self.client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
)
return response.choices[0].message.content
except Exception as e:
last_error = e
print(f"[WARN] model={model}, attempt={attempt}, error={e}")
if attempt <= self.max_retries:
time.sleep(self.retry_delay)
raise RuntimeError(f"{model} failed: {last_error}")
def chat_with_fallback(
self,
models: List[str],
prompt: str,
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 1024,
) -> str:
last_error = None
for model in models:
try:
print(f"[INFO] trying model={model}")
return self._single_chat(
model=model,
prompt=prompt,
system_prompt=system_prompt,
temperature=temperature,
max_tokens=max_tokens,
)
except Exception as e:
print(f"[WARN] fallback model failed: {model}, error={e}")
last_error = e
raise RuntimeError(f"All fallback models failed: {last_error}")
使用方式:
llm = MultiModelClient(api_key="YOUR_CRAZYROUTER_KEY")
result = llm.chat_with_fallback(
models=[
"claude-sonnet-4-20250514",
"gpt-4o",
"deepseek-chat"
],
prompt="請寫一個Redis分散式鎖的Python範例",
system_prompt="你是一位資深後端工程師",
temperature=0.2
)
print(result)
這種做法的價值在於,模型切換不再需要由上層業務邏輯去處理。
對正式服務而言,這會讓故障應對更集中,也更容易標準化。
───
用簡單Benchmark做A/B測試
多模型環境裡,討論「哪個模型比較好」通常很容易流於主觀。
更務實的做法,是先建立一套最基本的比較腳本,至少能看回應時間、成功率與輸出風格。
from openai import OpenAI
client = OpenAI(
api_key="YOUR_CRAZYROUTER_KEY",
base_url="https://crazyrouter.com/v1"
)
MODELS = [
"gpt-4o",
"claude-sonnet-4-20250514",
"gemini-2.5-pro",
"deepseek-chat"
]
PROMPT = "請實作一個支援LRU淘汰策略的Python快取類別,並說明時間複雜度。"
def run_once(model: str, prompt: str):
start = time.time()
try:
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=0.2,
max_tokens=1200
)
elapsed = time.time() - start
text = response.choices[0].message.content
return {
"model": model,
"success": True,
"latency_sec": round(elapsed, 2),
"preview": text[:200]
}
except Exception as e:
elapsed = time.time() - start
return {
"model": model,
"success": False,
"latency_sec": round(elapsed, 2),
"error": str(e)
}
if __name__ == "__main__":
results = []
for model in MODELS:
results.append(run_once(model, PROMPT))
for item in results:
print("=" * 80)
print(item)
這當然不算正式壓測,但對團隊前期選型已經很有幫助。
至少可以快速看到:
• 哪些模型速度較穩定
• 哪些模型較常失敗
• 相同Prompt下各模型的輸出差異
• 哪些模型更適合程式碼、推理或摘要等任務
與其憑感覺選模型,不如先把比較流程簡單自動化。
───
讓任務與模型選擇分離
再往前一步,很多團隊會發現:
不是所有任務都應該交給同一個模型處理。
例如:
• 簡單對話可以走低延遲模型
• 程式碼生成可以優先交給Claude
• 複雜推理可交給Gemini
• 成本敏感的大量批次任務則可考慮較低成本模型,因此,可以把模型選擇做成簡單路由策略。
"simple_chat": "gpt-4o-mini",
"code_generation": "claude-sonnet-4-20250514",
"reasoning": "gemini-2.5-pro",
"chinese_task": "deepseek-chat",
}
再加上一層查詢函式:
return TASK_ROUTER.get(task_type, "gpt-4o")
使用時:
model = route_model(task_type)
result = llm.chat(
model=model,
prompt="請實作一個thread-safe的Python Singleton"
)
print(result)
做到這一步時,你的系統就不再只是「接了一堆模型API」,而是逐漸形成一個可管理的模型調度層。
這也是多模型架構真正有工程價值的地方。
───
為什麼這種做法比直接對接多家官方API更適合前期專案?
直接串OpenAI、Anthropic、Gemini各自原生API,理論上當然可行。
但在產品早期,通常會帶來幾個額外負擔:
• 每家SDK與驗證方式不同
• 回應結構與錯誤處理難以統一
• A/B測試與Fallback邏輯要自己做多份適配
• 後期要重整成統一架構時,成本不低
相較之下,若先透過OpenAI相容API建立一層通用呼叫邏輯,至少可以把專案初期的複雜度控制住。
等到產品成熟、需求更清楚之後,再決定是否對某些高頻場景做更深度的原生整合,會是相對務實的策略。
以Crazyrouter這類平台來看,它的價值不只是「可呼叫很多模型」,而是讓團隊比較容易先把多模型能力收斂到同一個呼叫平面上。對於需要快速迭代、持續比較模型、又不希望過早把業務邏輯鎖死在單一供應商上的團隊來說,這種方式確實有其工程上的實用性。
───
結語
AI應用進入多模型時代之後,真正值得提早處理的,往往不是模型數量,而是呼叫層的設計。
如果一開始就把模型呼叫抽象化,讓模型名稱配置化,補上基本的重試、Fallback與比較能力,後續無論要換模型、擴功能或做成本優化,都會省下不少力氣。
很多時候,團隊需要的不是更多「能用的模型」,而是一個足夠乾淨、可維護、可切換的模型基礎層。
如果你目前已經在用OpenAI SDK開發,同時也在思考如何整合多個模型入口,那麼像Crazyrouter這類提供OpenAI相容介面的方案,確實值得拿來做架構驗證。
https://crazyrouter.com (https://crazyrouter.com/)