# Azure OpenAI Service API 設計演進：從部署導向到統一 v1 介面

這兩年如果你有同時碰 OpenAI API 與 Azure OpenAI Service，你一定會覺得同樣是呼叫 LLM 的 API，明明都是一樣的服務，但是 Azure OpenAI 的 API 設計就是特別難用，要先部署模型、呼叫路徑要帶 deployment name、每個請求還要綁 `?api-version=YYYY-MM-DD` 查詢字串 (Query String)。這套設計不是沒有理由，但對應用開發者來說，確實也帶來不少心智負擔。

![Image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/8062295708ffe2c64e58-17de8c45-a307-4279-baca-4d58577d3450.webp)

到了 2025 年下半年，Azure OpenAI 開始推進統一的 `v1` 介面，逐步把使用體驗往 OpenAI 官方 API 靠攏。這篇文章就來把這段演進史講清楚：**為什麼以前是部署導向？開發者痛在哪裡？v1 到底解了哪些問題，又還留下了哪些現實限制？**

### TL;DR

1.  舊版 Azure OpenAI API 以「部署」為核心，是為了對齊 Azure 的資源治理、配額配置、資料落地與 SLA 管理需求。
2.  這個設計在企業治理上合理，但應用開發者要承受 API 版本頻繁演進、模型名稱與部署名稱差異、跨 OpenAI/Azure 切換成本高等痛點。
3.  新一代 `v1` API 把介面統一到 `.../openai/v1/...`，且 GA 能力不再需要每月追 `api-version`，大幅降低整合複雜度。
4.  但 `v1` 並不是「所有能力一夜完成」，仍有部分功能採 preview 路徑或 preview header，遷移策略仍然要分階段做。

### 為什麼當年一定要「部署導向」？

Azure OpenAI 一開始的目標就不只是「把模型 API 包一層」，而是要把它放進 Azure 的企業治理體系裡。

#### 1) 把模型能力變成可治理的 Azure 資源

Azure OpenAI 的資源模型本來就分成 control plane 與 data plane。你先在 control plane 建立資源、建立 deployment，然後在 data plane 呼叫推論 API。

這代表「模型可用性」是透過 deployment 管理，而不是直接拿模型名稱就呼叫。也因此，舊版 API 路徑自然就長成這樣：

```http
POST https://{resource}.openai.azure.com/openai/deployments/{deployment-id}/chat/completions?api-version=2024-10-21
```

這樣的好處是，治理與推論被明確分層：誰可以部署、部署到哪個區域、配多少能力，都能在 Azure 管理面統一控管。

#### 2) 配額與效能模型本來就是「跟 deployment 綁在一起」

Azure OpenAI 的 quota 是以「每訂用帳戶 / 每區域 / 每模型或部署型別」切分，再把 TPM 配給各 deployment。部署建立時配多少 TPM，就直接映射到該 deployment 的速率上限。

換句話說，deployment 不是純名稱別名，而是**容量與限流的落點**。如果沒有 deployment 這層，企業端就很難做精細化流量與成本治理。

#### 3) 資料處理位置、法遵與 SLA 需求，需要 deployment type 來承載

Azure OpenAI 有 Global、Data Zone、Regional、Provisioned 等 deployment type。你可以按法遵與延遲需求，決定資料處理區域與計費 / 吞吐模式。

這也是為什麼早期設計不是「一支 endpoint 打天下」：在 Azure 的世界裡，**部署配置本身就是產品能力的一部分**。

### 這套設計的代價：開發者到底痛在哪？

企業治理視角很合理，但應用開發者會遇到幾個非常真實的痛點。

#### 痛點 1：每月追 `api-version`，維護成本偏高

過去 Azure OpenAI 的新能力常常伴隨新日期版 API。你要吃新功能，就得更新程式碼、環境變數、甚至 SDK 版本。

這種節奏在 PoC 還能接受，但到多服務、多環境的大型系統，維運壓力會明顯放大。

#### 痛點 2：OpenAI 與 Azure OpenAI 之間切換不夠平滑

以前很多團隊會同時維護 OpenAI 與 Azure OpenAI 路徑，常見結果是 codebase 內有兩套初始化與呼叫流程。

官方文件也直接點出，舊模式下從 OpenAI 遷移到 Azure OpenAI (或反向) 有 Azure-specific client 與路徑差異帶來的額外負擔。

#### 痛點 3：`model` 參數看起來像模型名，實際上要填 deployment name

這是最多人踩的坑。OpenAI 直接用模型名；Azure OpenAI 則要求你在 API 呼叫時使用 deployment name (即使參數名稱叫 `model`)。

對新手非常不直覺，也很容易變成 404 / Not Found 類型問題。

### 演進轉折：從日期版 API 到統一 `v1`

到了 2025 年下半年，Azure OpenAI 開始推進下一代 `v1` API。官方定位很明確：**降低版本追逐成本、降低跨 OpenAI/Azure 的切換摩擦**。

#### v1 帶來的核心改變

1.  **統一路徑**：改成 `.../openai/v1/...`
    
    例如以下端點網址：
    
    ```
    https://resource-name.cognitiveservices.azure.com/openai/v1/chat/completions
    ```
    
2.  **GA 能力不再強迫每月換 `api-version`**：v1 GA 呼叫可不再依賴日期版版本切換。
    
    早期的端點網址長這樣，這個 `api-version` 參數用來指定 API 版本：
    
    ```
    https://resource-name.cognitiveservices.azure.com/openai/deployments/gpt-4.1/chat/completions?api-version=2024-05-01-preview
    ```
    
3.  **OpenAI client 對齊**：可以直接使用 `OpenAI()` client，`base_url` 指到 Azure 的 `.../openai/v1/`。
    
4.  **認證體驗更一致**：可用 API Key，也可用 Entra ID token provider。
    

典型寫法會像這樣：

```python
from openai import OpenAI

client = OpenAI(
    api_key="AZURE_OPENAI_API_KEY",
    base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
)
```

### API 呼叫範例：前後對照

#### 1) REST Chat Completions (HTTP)

**Before (舊版部署導向 + 日期版 API)**

```bash
# 舊版：路徑綁 deployment，且每次呼叫都要帶 api-version
POST https://{resource}.openai.azure.com/openai/deployments/gpt-4o-prod/chat/completions?api-version=2024-10-21
Content-Type: application/json
api-key: {AZURE_OPENAI_API_KEY}

{
  "messages": [
    { "role": "user", "content": "請用一句話介紹 v1 API。" }
  ]
}
```

**After (v1 統一介面)**

```bash
# v1：改用統一 /openai/v1 路徑，model 欄位填 deployment name
POST https://{resource}.openai.azure.com/openai/v1/chat/completions
Content-Type: application/json
api-key: {AZURE_OPENAI_API_KEY}

{
  "model": "gpt-4o-prod",
  "messages": [
    { "role": "user", "content": "請用一句話介紹 v1 API。" }
  ]
}
```

💡 重點：路徑與版本策略是最大差異；治理上仍然是「先部署、再呼叫」。

#### 2) Python SDK 初始化 (AzureOpenAI → OpenAI)

**Before (舊版)**

```python
import os
from openai import AzureOpenAI

# 舊版：Azure 專屬 client，且通常需要明確指定 api_version
client = AzureOpenAI(
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),
    azure_endpoint="https://YOUR-RESOURCE-NAME.openai.azure.com/",
    api_version="2024-10-21",
)

resp = client.chat.completions.create(
    model="gpt-4o-prod",  # 注意：在 Azure 這裡填的是 deployment name
    messages=[{"role": "user", "content": "哈囉"}],
)
```

**After (v1)**

```python
import os
from openai import OpenAI

# v1：直接用 OpenAI client，改用 base_url 指向 Azure v1 端點
client = OpenAI(
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),
    base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/",
)

resp = client.chat.completions.create(
    model="gpt-4o-prod",  # 在 Azure 仍是 deployment name 的語意
    messages=[{"role": "user", "content": "哈囉"}],
)
```

💡 重點：初始化流程更接近 OpenAI，但 `model` 參數在 Azure 端仍需對應既有 deployment 規劃。

#### 3) C# (Legacy vs v1)

**Before (Legacy)**

```csharp
using System.Text;
using System.Text.Json;

var resource = Environment.GetEnvironmentVariable("AZURE_OPENAI_RESOURCE");
var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");

using var http = new HttpClient();
http.DefaultRequestHeaders.Add("api-key", apiKey);

var legacyUrl = $"https://{resource}.openai.azure.com/openai/deployments/gpt-4o-prod/chat/completions?api-version=2024-10-21";
var legacyBody = JsonSerializer.Serialize(new
{
    messages = new[]
    {
        new { role = "user", content = "請用一句話介紹 v1 API。" }
    }
});

var legacyResp = await http.PostAsync(
    legacyUrl,
    new StringContent(legacyBody, Encoding.UTF8, "application/json"));
```

**After (v1)**

```csharp
using System.Text;
using System.Text.Json;

var resource = Environment.GetEnvironmentVariable("AZURE_OPENAI_RESOURCE");
var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");

using var http = new HttpClient();
http.DefaultRequestHeaders.Add("api-key", apiKey);

var v1Url = $"https://{resource}.openai.azure.com/openai/v1/chat/completions";
var v1Body = JsonSerializer.Serialize(new
{
    model = "gpt-4o-prod",
    messages = new[]
    {
        new { role = "user", content = "請用一句話介紹 v1 API。" }
    }
});

var v1Resp = await http.PostAsync(
    v1Url,
    new StringContent(v1Body, Encoding.UTF8, "application/json"));
```

💡 重點：C# 呼叫邏輯幾乎不變，主要是把 URL 換成 `.../openai/v1/...` 並在 payload 補上 `model`。這樣可以把舊有 deployment-path 的耦合降到最低。

#### 4) TypeScript (Legacy vs v1)

**Before (Legacy)**

```typescript
const resource = process.env.AZURE_OPENAI_RESOURCE!;
const apiKey = process.env.AZURE_OPENAI_API_KEY!;

const legacyUrl =
  `https://${resource}.openai.azure.com/openai/deployments/gpt-4o-prod/chat/completions?api-version=2024-10-21`;

const legacyResp = await fetch(legacyUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "api-key": apiKey,
  },
  body: JSON.stringify({
    messages: [{ role: "user", content: "請用一句話介紹 v1 API。" }],
  }),
});
```

**After (v1)**

```typescript
const resource = process.env.AZURE_OPENAI_RESOURCE!;
const apiKey = process.env.AZURE_OPENAI_API_KEY!;

const v1Url =
  `https://${resource}.openai.azure.com/openai/v1/chat/completions`;

const v1Resp = await fetch(v1Url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "api-key": apiKey,
  },
  body: JSON.stringify({
    model: "gpt-4o-prod",
    messages: [{ role: "user", content: "請用一句話介紹 v1 API。" }],
  }),
});
```

💡 重點：TypeScript 端遷移也幾乎是「換路徑 + 補 `model`」，其餘 middleware、重試與 logging 大多可沿用。若你有前後端共用 SDK，這種改法也更容易統一抽象層。

### 差異比較表 (舊版 vs v1)

| 比較面向 | 舊版 (部署導向 + 日期版 API) | v1 (統一介面) | 實務影響 |
| --- | --- | --- | --- |
| Endpoint 路徑 | `/openai/deployments/{deployment}/...` | `/openai/v1/...` | 呼叫 URL 更一致，跨 OpenAI/Azure 抽換成本下降 |
| 版本策略 | 幾乎每波新能力都要指定 / 更新 `api-version=YYYY-MM-DD` | GA 能力走 `v1` 穩定介面 (部分功能仍另走 preview) | 降低版本追逐與多環境同步更新壓力 |
| Client 初始化 | 常見 `AzureOpenAI(azure_endpoint, api_version, ...)` | 可用 `OpenAI(base_url=.../openai/v1/)` | SDK 初始化與共用元件更容易標準化 |
| Model/Deployment 命名語意 | `model` 參數實際上是 deployment name，且常與路徑 deployment 雙重綁定 | `model` 參數在 Azure 仍對應 deployment name，但路徑語意更一致 | 可讀性提升，但命名規範仍要管好避免誤用 |
| Preview 功能啟用方式 | 多半透過特定日期版 API 或獨立 preview 路徑 | 可能透過 preview header 或 preview path (依功能而定) | 需把 GA/Preview 呼叫明確隔離，避免互相汙染 |
| 維護成本 | 版本、端點、SDK 參數差異多，跨平台整合成本高 | 介面收斂、抽象層更單純 | 長期維運與遷移成本可明顯下降 |

### 那是不是從此天下太平？其實還有幾個現實

#### 1) `v1` 是演進，不是一次性重寫

官方也明講：`v1` 初始 GA 階段只涵蓋部分 inference / authoring 能力，其餘能力持續補齊。

所以你會看到有些功能走 GA 路徑、有些功能仍在 preview，甚至需要 preview header 或 preview path。

#### 2) 部署概念沒有消失，只是體感變好

即便在 `v1`，Azure OpenAI 仍要求你先有 deployment，且 `model` 參數在 Azure 側仍是 deployment name。

也就是說，**治理模型還在，但 API 體驗變得更接近 OpenAI**。這是一種工程折衷，不是完全捨棄 Azure 的管理哲學。

#### 3) 舊版 API 仍會存在一段時間

現場實務通常是「新服務優先 v1，舊服務分批遷移」。只要你還有既有相依 (例如特定 preview 能力或舊 SDK)，短期內雙軌共存是常態。

### 我會怎麼建議你遷移？

如果你現在手上有既有系統，我會建議這樣走：

1.  **先把 deployment naming 規範化** (避免 `model` / `deployment` 混淆)。
2.  **把 endpoint 與認證抽象成可切換配置** (OpenAI / Azure 共用一層 adapter)。
3.  **新功能一律優先走 `v1`**，舊功能再逐步搬遷。
4.  **對 preview 功能做顯式隔離** (header/path/feature flag 不要跟 GA 混在一起)。
5.  **建立回歸測試基線** (token 用量、延遲、錯誤碼、輸出格式一致性) 再切流量。

### 總結

回頭看這段演進，其實很有意思：

-   **舊版部署導向 API** 解決的是企業治理、配額與法遵問題；
-   **新一代 v1 API** 解決的是開發者體驗與跨平台整合效率問題。

這不是誰對誰錯，而是產品成熟度的自然路徑：先把「可管、可控、可落地」做好，再把「好用、好遷移、好維護」拉上來。

如果你問我一句話結論：**Azure OpenAI 沒有放棄 deployment-centric 的治理核心，但在 v1 這一波，終於把開發者體驗補到比較像現代 API 平台該有的樣子了。**

### 相關連結

-   [Azure OpenAI API lifecycle (v1)](https://learn.microsoft.com/azure/ai-foundry/openai/api-version-lifecycle)
-   [Azure OpenAI REST API reference (legacy dated API)](https://learn.microsoft.com/azure/ai-foundry/openai/reference)
-   [Azure OpenAI v1 REST API reference (GA)](https://learn.microsoft.com/azure/ai-foundry/openai/latest)
-   [Azure OpenAI v1 REST API reference (preview)](https://learn.microsoft.com/azure/ai-foundry/openai/reference-preview-latest)
-   [How to switch between OpenAI and Azure OpenAI endpoints](https://learn.microsoft.com/azure/developer/ai/how-to/switching-endpoints)
-   [Create resource and deploy model](https://learn.microsoft.com/azure/ai-foundry/openai/how-to/create-resource)
-   [Deployment types for Azure OpenAI / Foundry Models](https://learn.microsoft.com/azure/ai-foundry/openai/how-to/deployment-types)
-   [Manage quota](https://learn.microsoft.com/azure/ai-foundry/openai/how-to/quota)
-   [Quotas and limits](https://learn.microsoft.com/azure/ai-foundry/openai/quotas-limits)
