DeepSeek API Python SDK 超時與重試最佳實踐

在開發基於 DeepSeek API 的應用程式時,面對現實世界的網路不穩定性、服務暫時性故障、或突發的流量高峰,確保 API 呼叫的穩定性與可靠性至關重要。超時 (Timeout) 與重試 (Retry) 機制正是應對這些挑戰的兩項核心策略。適當的超時設定可以避免程式因等待回應過久而無響應,而智能的重試邏輯則能在瞬時錯誤發生時自動恢復操作,極大提升用戶體驗與系統韌性。

了解 API 超時與重試的重要性

DeepSeek API 透過其高效能的模型提供強大的語言能力,但任何基於網路的服務都無法完全避免偶發性問題。以下情況突顯了超時與重試的重要性:

  • 網路延遲或中斷: 客戶端與 DeepSeek 伺服器之間的網路連線可能因多種原因出現暫時性延遲或斷線。
  • 伺服器過載: DeepSeek 服務在極高負載下,可能會短暫拒絕連線或延遲回應。
  • 速率限制 (Rate Limiting): 當應用程式發送請求過快,觸發 DeepSeek API 的速率限制時,通常會收到特定的錯誤碼(例如 HTTP 429 Too Many Requests)。
  • 臨時性錯誤: 伺服器端可能由於內部維護、資料庫問題或其他瞬時錯誤而返回錯誤回應。

如果缺乏超時與重試機制,應用程式可能會長時間掛起、崩潰,或因錯過重要回應而導致功能失效。

DeepSeek Python SDK 的超時配置

DeepSeek Python SDK 允許您輕鬆配置 API 請求的超時時間。這可以確保您的應用程式不會無限期地等待響應,從而提高其響應能力和穩定性。

1. 初始化客戶端時設定全域超時

您可以在初始化 Deepseek 客戶端時設定一個全域的超時時間。此設定會應用於該客戶端發出的所有請求。超時值以秒為單位。

import os
from deepseek import Deepseek

# 確保你的 DEEPSEEK_API_KEY 環境變量已設定
# 如果未設定,請手動替換為你的 API 密鑰
api_key = os.environ.get("DEEPSEEK_API_KEY", "YOUR_DEEPSEEK_API_KEY")

# 初始化客戶端,設定全域超時為 60 秒
# 如果在 60 秒內未收到回應,將拋出 TimeoutError
try:
    client = Deepseek(api_key=api_key, timeout=60.0)
    print("DeepSeek 客戶端已初始化,全域超時設定為 60 秒。")
except Exception as e:
    print(f"初始化客戶端時發生錯誤: {e}")

# 範例:發送一個簡單的聊天請求
try:
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "user", "content": "香港的官方語言是什麼?"}
        ]
    )
    print("成功收到 DeepSeek 回應:")
    print(response.choices[0].message.content)
except Exception as e:
    print(f"API 請求失敗: {e}")

2. 針對個別請求設定超時

有時,您可能需要為特定的 API 請求設定不同的超時時間,例如某些請求需要處理大量數據,而另一些則需要快速響應。您可以在 create 方法中覆蓋全域超時設定:

import os
from deepseek import Deepseek

api_key = os.environ.get("DEEPSEEK_API_KEY", "YOUR_DEEPSEEK_API_KEY")
client = Deepseek(api_key=api_key, timeout=60.0) # 全域超時 60 秒

# 範例 1:使用全域超時 (60 秒)
try:
    response_default = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "user", "content": "描述一下香港的天際線。"}
        ]
    )
    print("\n使用全域超時的請求成功。")
except Exception as e:
    print(f"\n使用全域超時的請求失敗: {e}")

# 範例 2:為特定請求設定超時為 30 秒
try:
    response_custom = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "user", "content": "列出三個香港的著名地標。"}
        ],
        timeout=30.0 # 覆蓋全域設定,此請求超時為 30 秒
    )
    print("\n使用自定義超時的請求成功。")
except Exception as e:
    print(f"\n使用自定義超時的請求失敗: {e}")

選擇合適的超時值需要權衡。超時時間過短可能導致正常請求也因網路波動而失敗;超時時間過長則可能讓應用程式在等待無效回應上浪費資源。建議從一個合理的預設值(例如 30-60 秒)開始,並根據實際應用場景和 DeepSeek API 的響應速度進行調整。

DeepSeek API 流量處理架構概念圖

實現自定義重試邏輯

DeepSeek Python SDK 本身沒有內置開箱即用的高級重試邏輯。為了實現更智能、更健壯的重試策略,我們可以使用第三方函式庫,其中 tenacity 是一個非常流行且功能強大的選擇。

1. 安裝 tenacity

首先,確保您的環境中安裝了 tenacity

pip install tenacity

2. 使用 tenacity 實現基本重試

tenacity 提供了 @retry 裝飾器,可以輕鬆地應用於任何函式。

import os
import logging
from deepseek import Deepseek
from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type, wait_random_exponential
from deepseek import APIConnectionError, APIStatusError, APITimeoutError

# 配置日誌
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

api_key = os.environ.get("DEEPSEEK_API_KEY", "YOUR_DEEPSEEK_API_KEY")
client = Deepseek(api_key=api_key, timeout=60.0)

@retry(
    stop=stop_after_attempt(3), # 最多重試 3 次 (總共嘗試 4 次)
    wait=wait_fixed(2),         # 每次重試之間等待 2 秒
    retry=(
        retry_if_exception_type(APIConnectionError) | # 網路連線錯誤
        retry_if_exception_type(APITimeoutError) |    # 超時錯誤
        retry_if_exception_type(APIStatusError)       # API 返回錯誤狀態碼
    ),
    reraise=True # 如果所有重試都失敗,則重新拋出最後一個異常
)
def get_deepseek_completion_with_retry(prompt: str, model: str = "deepseek-chat"):
    """
    發送 DeepSeek 聊天請求,並處理重試邏輯。
    """
    logging.info(f"嘗試發送 DeepSeek 請求,提示詞: '{prompt}'...")
    try:
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "user", "content": prompt}
            ]
        )
        logging.info("DeepSeek 請求成功。")
        return response.choices[0].message.content
    except APIStatusError as e:
        # 特別處理 APIStatusError,例如 429 Too Many Requests
        if e.status_code == 429:
            logging.warning(f"觸發速率限制 (HTTP 429)。將在重試。")
        else:
            logging.error(f"DeepSeek API 返回錯誤狀態碼 {e.status_code}: {e.response}")
        raise # 重新拋出異常以便 tenacity 捕獲並重試
    except (APIConnectionError, APITimeoutError) as e:
        logging.warning(f"DeepSeek 連線或超時錯誤: {e}。將在重試。")
        raise # 重新拋出異常以便 tenacity 捕獲並重試
    except Exception as e:
        logging.error(f"未知錯誤發生: {e}")
        raise # 對於其他未知錯誤,也嘗試重試或直接拋出

# 測試重試功能
if __name__ == "__main__":
    test_prompt = "請提供一個關於香港飲食文化的簡短描述。"
    try:
        result = get_deepseek_completion_with_retry(test_prompt)
        print("\n最終成功獲取 DeepSeek 回應:")
        print(result)
    except Exception as e:
        print(f"\n所有重試嘗試均失敗: {e}")

    # 可以模擬失敗來測試重試。例如,暫時設置一個無效的 API_KEY
    # 或在本地網路層面製造延遲。

3. tenacity 的關鍵配置選項

  • stop:定義何時停止重試。
    • stop_after_attempt(n): 在嘗試 n 次後停止 (包括第一次嘗試)。
    • stop_after_delay(s): 在 s 秒後停止。
  • wait:定義重試之間的等待策略。
    • wait_fixed(s): 每次重試都等待固定的 s 秒。
    • wait_random_exponential(min=1, max=60, multiplier=1): 實現指數退避,每次等待時間呈指數級增長,並加入隨機抖動,以避免所有客戶端同時重試造成「驚群效應」。minmax 設定等待時間的上下限。
    • wait_incrementing(start=0, increment=1, max=None): 每次增加固定時間。
  • retry:定義觸發重試的條件。
    • retry_if_exception_type(ExceptionType): 僅當拋出指定類型的異常時才重試。
    • retry_if_result(predicate): 當函式返回特定結果時重試(例如,函式返回 None)。
  • before_sleep:在每次重試等待前執行的函式,通常用於日誌記錄。
    • before_sleep=tenacity.before_sleep_log(logging.root, logging.INFO)
  • reraise: 如果所有重試都失敗,是否重新拋出最後一個異常。預設為 False,會返回最後一次失敗的結果,但通常我們希望它拋出異常。

高級重試策略與考量

1. 指數退避與抖動 (Exponential Backoff with Jitter)

這是推薦的重試策略,特別是對於可能會觸發速率限制的 API。指數退避意味著每次失敗後,等待的時間會越來越長,給伺服器足夠的時間恢復。加入抖動 (jitter) 則是在等待時間中引入隨機性,防止大量客戶端在同一時間點重試,造成新的流量高峰。tenacitywait_random_exponential 函式完美地支持這一點。

@retry(
    stop=stop_after_attempt(5), # 最多重試 5 次
    wait=wait_random_exponential(min=1, max=20), # 指數退避,等待 1 到 20 秒,帶有抖動
    retry=(
        retry_if_exception_type(APIConnectionError) |
        retry_if_exception_type(APITimeoutError) |
        retry_if_exception_type(APIStatusError)
    ),
    before_sleep=tenacity.before_sleep_log(logging.getLogger(__name__), logging.INFO),
    reraise=True
)
def get_deepseek_completion_exponential_backoff(prompt: str, model: str = "deepseek-chat"):
    """
    發送 DeepSeek 聊天請求,使用指數退避和抖動進行重試。
    """
    logging.info(f"嘗試發送 DeepSeek 請求 (指數退避),提示詞: '{prompt}'...")
    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "user", "content": prompt}
        ]
    )
    logging.info("DeepSeek 請求成功。")
    return response.choices[0].message.content

2. 熔斷機制 (Circuit Breaker)

當某個服務持續失敗時,熔斷器模式可以暫時停止向該服務發送請求,避免不斷重試導致資源浪費和對下游服務的進一步衝擊。tenacity 本身不直接提供熔斷器,但可以與如 pybreakercircuitbreaker 等函式庫結合使用。在 DeepSeek API 的場景中,這意味著如果 API 在短時間內大量返回錯誤,可以暫時「熔斷」所有 DeepSeek 請求,等待一段時間後再嘗試「半開」,以驗證服務是否恢復。

3. 冪等性 (Idempotency)

當您實施重試時,考慮 API 請求的冪等性至關重要。冪等操作是指執行一次或多次會產生相同結果的操作。例如,讀取資料通常是冪等的,但創建新資源(如果沒有去重機制)則不是。對於 DeepSeek API 的聊天補全請求,通常是冪等的讀取操作,即使多次發送相同的提示,理論上也應返回相似的結果。但對於涉及修改狀態的 API,重試前需確保操作的冪等性,或實現自定義去重邏輯。

4. 錯誤處理與日誌記錄

無論是超時還是重試,完善的錯誤處理和日誌記錄都是不可或缺的。在 try-except 區塊中捕獲特定的 DeepSeek 異常 (APIConnectionError, APITimeoutError, APIStatusError),並記錄詳細的錯誤信息。這有助於診斷問題,了解重試是否成功,以及是否需要調整重試策略。

Python 程式碼調試與錯誤處理流程

整合 DeepSeek SDK 與最佳實踐

將超時配置與 tenacity 的重試邏輯結合,可以構建一個非常強健的 DeepSeek API 互動層。

import os
import logging
from deepseek import Deepseek, APIConnectionError, APIStatusError, APITimeoutError
from tenacity import retry, stop_after_attempt, wait_random_exponential, retry_if_exception_type, before_sleep_log

# 配置日誌,以便在重試時看到詳細信息
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 初始化 DeepSeek 客戶端,設定一個全域超時
api_key = os.environ.get("DEEPSEEK_API_KEY", "YOUR_DEEPSEEK_API_KEY")
deepseek_client = Deepseek(api_key=api_key, timeout=90.0) # 全域超時 90 秒

@retry(
    stop=stop_after_attempt(5), # 最多重試 5 次 (總共嘗試 6 次)
    wait=wait_random_exponential(multiplier=1, min=4, max=60), # 指數退避,等待 4 到 60 秒,帶抖動
    retry=(
        retry_if_exception_type(APIConnectionError) |  # 網路連線問題
        retry_if_exception_type(APITimeoutError) |     # 請求超時
        retry_if_exception_type(APIStatusError)        # API 返回錯誤狀態碼
    ),
    before_sleep=before_sleep_log(logger, logging.INFO), # 每次重試前記錄日誌
    reraise=True # 所有重試失敗後,重新拋出最後一個異常
)
def call_deepseek_api_robustly(
    prompt: str,
    model: str = "deepseek-chat",
    temperature: float = 0.7,
    max_tokens: int = 500,
    request_timeout: float = None # 允許單次請求覆蓋客戶端超時
) -> str:
    """
    以健壯的方式呼叫 DeepSeek 聊天 API,包含超時和重試機制。

    Args:
        prompt (str): 使用者輸入的提示詞。
        model (str): 要使用的 DeepSeek 模型名稱。
        temperature (float): 控制生成文本的隨機性。
        max_tokens (int): 生成的最大令牌數。
        request_timeout (float, optional): 此特定請求的超時時間,覆蓋客戶端預設。

    Returns:
        str: DeepSeek 模型生成的文本回應。

    Raises:
        Exception: 如果所有重試嘗試均失敗。
    """
    logger.info(f"正在嘗試呼叫 DeepSeek API,提示詞: '{prompt[:50]}...'")
    try:
        response = deepseek_client.chat.completions.create(
            model=model,
            messages=[
                {"role": "user", "content": prompt}
            ],
            temperature=temperature,
            max_tokens=max_tokens,
            timeout=request_timeout # 使用單次請求的超時,如果提供
        )
        content = response.choices[0].message.content
        logger.info("DeepSeek API 呼叫成功。")
        return content
    except APIStatusError as e:
        logger.warning(f"DeepSeek API 返回錯誤狀態碼 {e.status_code}: {e.response}。將重試。")
        raise
    except (APIConnectionError, APITimeoutError) as e:
        logger.warning(f"DeepSeek 連線或超時錯誤: {e}。將重試。")
        raise
    except Exception as e:
        logger.error(f"DeepSeek API 呼叫發生意外錯誤: {e}。")
        raise # 對於其他錯誤,也讓 tenacity 處理或最終拋出

# --- 實際應用範例 ---
if __name__ == "__main__":
    example_prompt_1 = "香港有哪些著名的海灘?請列舉三個並簡述其特色。"
    example_prompt_2 = "解釋一下Deep Learning在自然語言處理中的應用,用中文簡述。"
    example_prompt_3 = "為我撰寫一份關於在香港創業的優勢和挑戰的報告,限制在200字以內。"

    print("--- 測試範例 1 ---")
    try:
        result_1 = call_deepseek_api_robustly(example_prompt_1, max_tokens=200, request_timeout=45.0)
        print("\n[成功回應 1]:")
        print(result_1)
    except Exception as e:
        print(f"\n[失敗] 無法從 DeepSeek API 獲取回應 (範例 1): {e}")

    print("\n--- 測試範例 2 ---")
    try:
        result_2 = call_deepseek_api_robustly(example_prompt_2, temperature=0.5, max_tokens=300)
        print("\n[成功回應 2]:")
        print(result_2)
    except Exception as e:
        print(f"\n[失敗] 無法從 DeepSeek API 獲取回應 (範例 2): {e}")

    print("\n--- 測試範例 3 ---")
    try:
        # 故意設定較短的超時來模擬超時錯誤,看看重試是否觸發
        # 在實際環境中,您會設定更合理的超時值
        result_3 = call_deepseek_api_robustly(example_prompt_3, max_tokens=200, request_timeout=5.0)
        print("\n[成功回應 3]:")
        print(result_3)
    except Exception as e:
        print(f"\n[失敗] 無法從 DeepSeek API 獲取回應 (範例 3): {e}")

透過將 DeepSeek 客戶端實例與 tenacity@retry 裝飾器結合使用,我們創建了一個高度彈性的 DeepSeek API 呼叫函式。這個函式不僅能處理常見的網路和 API 錯誤,還能通過智能的指數退避策略來避免對 DeepSeek 服務造成額外壓力。

總結

在開發任何依賴外部 API 的應用程式時,實現健全的超時和重試機制是不可或缺的。對於 DeepSeek API 而言,透過 DeepSeek Python SDK 提供的 timeout 參數設定合理的請求超時時間,並結合 tenacity 這樣的第三方函式庫來實現包括指數退避和抖動在內的高級重試策略,可以顯著提升應用程式的穩定性、韌性與用戶體驗。這些最佳實踐不僅能夠應對偶發性錯誤,還能在高負載或網路不佳的環境中保持服務的連續性,確保您的應用程式能夠可靠地與 DeepSeek 模型進行互動。