2025年3月12日 星期三

OpenAI API 學習筆記 : 例外處理

透過 OpenAI API 向模型提出請求時可能會因網路異常或伺服器等問題出現錯誤, 導致應用程式發生例外而吐出一堆錯誤資訊, 這會讓使用者一頭霧水, 解決之道是用 try except 語法將查詢請求納入 Python 例外處理機制中. 

本系列測試文章索引參考 : 


OpenAI 在 openai 套件 v1.0.0 版之後對例外處理做了大幅變更, 在 openai 命名空間下定義了許多例外類別, 它們均衍生自 OpenAIError 這個基礎類別的子類別 APIError :  

Exception
      |__ OpenAIError 
                   |__ APIError 
                               |__ 其他例外子類別

關於例外類別參考 OpenAI 官網 :


例如, 當要求的最大 tokens 數超過模型上限時就會觸發 InvalidRequestError 例外 (又稱為 BadRequestError), 每個模型都有不同的最大 tokens 數上限, 例如 gpt-3.5-turbo 最高是 4096 個; 而 gpt-4o-mini 則是 128k 個, 模型回應預設會受此限制, 但若請求時用 max_tokens 參數所指定的數量超過模型回應上限就會拋出例外. 

例如下面的 ask_gpt() 函式刻意設定 max_tokens 值為 4097 :  

>>> from openai import OpenAI   
>>> def ask_gpt(prompt, api_key, model='gpt-4o-mini'):
    client=OpenAI(api_key=api_key)
    chat_completion=client.chat.completions.create(
        messages=[{"role": "user", "content": prompt}],
        model=model,
        max_tokens=4097 
        )
    return chat_completion.choices[0].message.content

因 ask_gpt() 預設模型為 gpt-4o-mini, 此模型的回應上限是 128k, 指定的 4097 並未超出上限, 故呼叫 ask_gpt() 可順利取得回應 : 

>>> api_key='OpenAI API key'    
>>> ask_gpt('你是誰?', api_key)   
'我是一个由OpenAI开发的人工智能助手,旨在回答问题、提供信息以及帮助解决各种问题。有什么我可以帮助你的吗?'

但是若呼叫 ask_gpt() 時傳入 model 參數指定模型為 gpt-3.5-turbo, 這樣就會超出 4096 上限而拋出例外, 應用程式會吐出一堆錯誤訊息後終止執行 : 

>>> ask_gpt('你是誰?', api_key, 'gpt-3.5-turbo')    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in ask_gpt
  File "C:\Users\tony1\AppData\Local\Programs\Thonny\lib\site-packages\openai\_utils\_utils.py", line 275, in wrapper
    return func(*args, **kwargs)
  File "C:\Users\tony1\AppData\Local\Programs\Thonny\lib\site-packages\openai\resources\chat\completions.py", line 829, in create
    return self._post(
  File "C:\Users\tony1\AppData\Local\Programs\Thonny\lib\site-packages\openai\_base_client.py", line 1280, in post
    return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
  File "C:\Users\tony1\AppData\Local\Programs\Thonny\lib\site-packages\openai\_base_client.py", line 957, in request
    return self._request(
  File "C:\Users\tony1\AppData\Local\Programs\Thonny\lib\site-packages\openai\_base_client.py", line 1061, in _request
    raise self._make_status_error_from_response(err.response) from None
openai.BadRequestError: Error code: 400 - {'error': {'message': 'max_tokens is too large: 4097. This model supports at most 4096 completion tokens, whereas you provided 4097.', 'type': 'invalid_request_error', 'param': 'max_tokens', 'code': None}}

可見程式拋出了一個 BadRequestError 例外, 必須用 try except 語法來攔截此例外, 避免讓使用者看到程式被異常終止的原始資訊, 只要直接捕捉 APIError 例外即可, 毋須去捕捉特定的例外子類別 (例如上面的 BadRequestError), 上面的 ask_gpt() 修改為 :

def ask_gpt(prompt, api_key, model='gpt-4o-mini'):
    client=OpenAI(api_key=api_key)
    try: 
        chat_completion=client.chat.completions.create(
            messages=[{"role": "user", "content": prompt}],
            model=model,
            max_tokens=4097
            )
        return chat_completion.choices[0].message.content
    except APIError as e:
        print(e.message)

此例當捕捉到 APIError 例外時印出錯誤訊息, 結果如下 :

>>> ask_gpt('你是誰?', api_key, 'gpt-3.5-turbo')    
Error code: 400 - {'error': {'message': 'max_tokens is too large: 4097. This model supports at most 4096 completion tokens, whereas you provided 4097.', 'type': 'invalid_request_error', 'param': 'max_tokens', 'code': None}}

可見經過 try except 捕捉例外後, 程式拋出例外時就不會異常終止了. 

另一個例子是前一篇的 response_format 參數測試, 此參數若傳入 {'type': 'json_object'} 字典要求 API 生成一個 JSON 格式的回應, 這時 prompt 中必須攜帶 JSON 字眼, 否則會出現例外, 這就需要用 try except 攔截, 例如 :

>>> prompt='合歡山北峰經緯度是多少?'    
>>> def ask_gpt(prompt, api_key, model='gpt-4o-mini'):
    client=OpenAI(api_key=api_key)
    try: 
        chat_completion=client.chat.completions.create(
            messages=[{"role": "user", "content": prompt}],
            model=model,
            response_format={'type': 'json_object'}
            )
        return chat_completion.choices[0].message.content
    except APIError as e:
        print(e.message)
  
此函式中有攜帶 response_format={'type': 'json_object'} 參數, 因此若 prompt 中沒有提到 json 字眼就會拋出例外, 但會被 try except 捕捉, 不會讓程式吐出一堆讓使用者傻眼的錯誤資訊 : 

>>> ask_gpt(prompt, api_key, 'gpt-3.5-turbo')    
Error code: 400 - {'error': {'message': "'messages' must contain the word 'json' in some form, to use 'response_format' of type 'json_object'.", 'type': 'invalid_request_error', 'param': 'messages', 'code': None}}

如果 prompt 中包含 json 字眼就不會拋出例外 : 

>>> prompt='合歡山北峰經緯度是多少? (請用繁體中文 JSON 格式)'    
>>> reply=ask_gpt(prompt, api_key, 'gpt-3.5-turbo')    
>>> print(reply)   
{
    "山峯": "合歡山北峰",
    "緯度": "24.135566",
    "經度": "121.276159"
}

總之, 網路查詢是一種 IO 作業, 存在不確定性, 都要用 try except 攔截可能之例外. 

沒有留言 :