# Chapter3. API 參數解析與錯誤處理 :+1: 完整程式碼在 https://github.com/iamalex33329/chatgpt-develop-guide-zhtw ## 其他章節 [Chapter1. OpenAI API 入門](https://hackmd.io/@U3f2IzHERbymAst2-lDdjA/S1cNMYi6T) [Chapter2. 使用 Python 呼叫 API](https://hackmd.io/@U3f2IzHERbymAst2-lDdjA/HyZBg5ia6) [Chapter4. 打造自己的 ChatGPT](https://hackmd.io/@112356044/Hk81U96Tp) [Chapter5. 突破時空限制 - 整合搜尋功能](https://hackmd.io/@112356044/HkbVM-ApT) [Chapter6. 讓 AI 幫 AI - 自動串接流程](https://hackmd.io/@112356044/r1Ke-GR6T) [Chapter7. 網頁版聊天程式與文字生圖 Image API](https://hackmd.io/@112356044/Hyf-AvgAT) [Chapter8. 設計 LINE AI 聊天機器人](https://hackmd.io/@112356044/r1d6HsgAa) [Chapter9. 自媒體業者必看!使用 AI 自動生成高品質字幕](https://hackmd.io/@112356044/rJ2T37V0T) [Chapter10. 把 AI 帶到 Discord](https://hackmd.io/@112356044/Sy_L-B40T) [Chapter11. AI 客製化投資理財應用實戰](https://hackmd.io/@112356044/HkUE0rER6) [Chapter12. 用 LangChain 實作新書宣傳自動小編](https://hackmd.io/@112356044/SybvbdN0p) ## 目錄結構 [TOC] ## 控制生成訊息與 token 數量 先前的例子都是只讓 API 回覆一則訊息,但其實也可以同時產生多個候選訊息,再從中挑選合適回覆。另外也可以控制回覆訊息的 token 數量上限,會是設定**詞彙黑名單**,讓其不會生成不希望出現的詞彙。 ### 指定生成的訊息數量 - n `n` 參數可以讓模型針對輸入,產生指定數量的候選訊息,只是對同一輸入產生不同可能性,而不是連貫的多則回覆。 > n 預設為 1 ``` python= import openai import apikey openai.api_key = apikey.OPENAI_API_KEY reply = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ {'role': 'user', 'content': 'Tell me a joke'} ], n=2 ) for choice in reply['choices']: print(choice['index'], choice['message']['content']) ``` ### 設定詞彙黑名單 - stop 如果希望模型生成回應時不要出現特定的詞彙,可以使用 `stop` 來指定詞彙黑名單,最多四個。當生成語句時,遇到黑名單的詞彙時,會立即終止生成內容,並傳回當前生成結果。 > stop 預設為空串列 ``` python= reply = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ {'role': 'user', 'content': 'Tell me a joke'} ], stop=['OK', 'No', 'Why'] ) ``` ### 設定回覆語句的 tokens 數量上限 - max_tokens 希望模型生成的語句 token 太多時自動截斷,可以使用 `max_tokens` 來設定回覆訊息的 token 限制。 ``` python= reply = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ {'role': 'user', 'content': 'Tell me a joke'} ], max_tokens=10 ) ``` 這邊需要注意,設定 max_tokens 是限制返回的 token 數量,而**不是在限制數量內產生完整回覆**。可以看到 `finish_reason` 為 `length`。 gpt-3.5-turbo 模型最多只能處理 4097 個 tokens,這裡指的是包含所有字串的加總,而非單純使用者的語句! 而 `max_tokens` 預設是**無限大**,指的是扣除系統本身限制數量所能產生的最大 tokens 數量,例如請 openai 執行「重複1000次說我愛你」,最後會因為 tokens 已經到達最高使用量,而停止生成。而 `finish_reason` 也是因為未能在 tokens 長度內回覆,而返回 `length`。 ## 控制回覆內容的變化性 模型在生成回覆的內容時,是根據前面的內容所能夠接在下一個 token 的分數 **logit**,可以想像成分數或是機率,越高代表越適合、越不會出錯。 但若每次的回覆都是差不多的內容,就顯得有點生硬,因此可以透過一些對應參數來調整挑選 token 的方式。 ### 讓回覆更具彈性 - temperature `temperature` 可以設定的範圍為:0~2,`temperature` 越高,生成的內容越有豐富性以及彈性。 ``` python= import openai import apikey openai.api_key = apikey.OPENAI_API_KEY reply = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ {'role': 'user', 'content': 'How to define \'Love\'? In 20 words.'} ], temperature=T, n=3 ) for choice in reply['choices']: print(choice['index'], choice['message']['content']) ``` *temperature = 1.5*,可以明顯看出來內容比較多元 ``` = 0 Unconditional affection and loyalty towards another; strong emotional attachment, empathy, compassion, and deep care for someone's well-being. 1 Love is an intense feeling of affection and connection towards someone, inspiring actions of care, sacrifice, and support. 2 Love is a powerful feeling that brings warmth, joy, affection, and deep connection. It involves trust, support, honesty, Commitment, and sacrifice. ``` *temperature = 0.3*,前兩個回覆甚至一模一樣 ``` = 0 Love is a deep affection and connection towards someone or something, bringing joy, support, and understanding in a relationship. 1 Love is a deep affection and connection towards someone or something, bringing joy, support, and understanding in a relationship. 2 Love is a deep affection and connection towards someone or something, characterized by care, compassion, and selflessness. ``` ### 控制詞彙的豐富度 - top_p 另一種控制選擇 token 的方式是 `top_p`,可以設定的範圍為:0~1,代表依照分數高低排序,前面多少百分比的 token 有機會被選用。 例如: - top_p = 0.3,代表前 30% 的 token 會被選中 - top_p = 0,代表只有最高分數的 token 會被選到 > 建議 temperature 跟 top_p 擇一調整,否則很難辨別效果 ### 控制詞彙的重複性 - presence_penalty 與 frequency_penalty API 還提供兩個參數來降低已經選用過的 token 重複的機會,這兩個參數可設定範圍為:-2~2。 > 預設皆為 0 | 參數名稱 | 用途 | | -------------------- | -------- | | presence_penalty | 出現過的 token 就直接將分數扣掉這個參數值 | | frequency_penalty | 出現過的 token 將其分數依據出現頻率,按比例扣掉這個參數值 | 計算公式如下: > 實際分數 = 原始分數 - 出現頻率 * frequency_penalty - (出現與否 1 or 0) * presence_penalty 因此若 presence_penalty 為正的,代表出現過後,分數會越低;若為負值,則會提高 token 出現的分數。 ### 調整特定 token 的分數 - logit_bias ChatCompletion 有一個 `logit_bias` 參數,可以直接將特定的 token 調整分數,可設定範圍為:-100~100。 - 100 表示強制選用 - -100 表示強制排除不選用 可以先查詢該 token id 後,使用下列參數方式,即可設定: ``` python= import openai import apikey openai.api_key = apikey.OPENAI_API_KEY # '你好' -> [57668, 53901] reply = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ {'role': 'user', 'content': 'How to define \'Love\'? In 20 words.'} ], logit_bias={ 57668: 100, 53901: -100 } ) for choice in reply['choices']: print(choice['index'], choice['message']['content']) ``` > 大概設定 10 以上的數值,就接近強迫被選擇的狀態了。 ## 串流輸出 先前的範例都是等到所有訊息完成,才能返回看到結果,在等待期間是完全沒有回饋,並不符合使用者體驗的設計。因此 API 也提供類似 ChatGPT 實時輸出結果的方式。 ### 可循序傳回結果的生成器(generator)- stream 使用 API 時,若把 `stream` 設定成 True,API 會回傳一個 **Python 生成器物件**,他會循序生成一個個 OpenAIObject 物件,傳回生成內容。 > `stream` 預設為 False ``` python= import openai import apikey openai.api_key = apikey.OPENAI_API_KEY replies = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ {'role': 'user', 'content': 'How to define \'Love\'? In 20 words.'} ], stream=True ) for reply in replies: print(reply) ``` 回傳結果如下 ``` json= { "id": "chatcmpl-91dSnpBqpJdWn6JJc8U6rzHhcWiwo", "object": "chat.completion.chunk", "created": 1710177053, "model": "gpt-3.5-turbo-0125", "system_fingerprint": "fp_4f0b692a78", "choices": [ { "index": 0, "delta": { "role": "assistant", "content": "" }, "logprobs": null, "finish_reason": null } ] } /* ... ... ... */ { "id": "chatcmpl-91dSnpBqpJdWn6JJc8U6rzHhcWiwo", "object": "chat.completion.chunk", "created": 1710177053, "model": "gpt-3.5-turbo-0125", "system_fingerprint": "fp_4f0b692a78", "choices": [ { "index": 0, "delta": {}, "logprobs": null, "finish_reason": "stop" } ] } ``` 可以觀察到: 1. 第一個物件會回傳 role 以及 content 為空,而後面的物件都沒有 role 只有 content 2. 傳回的物件是:`{object": "chat.completion.chunk}` 3. 前面幾個回覆的 `finish_reason` 都是 `null`,而最後一個結果才是 `stop` 若要依序將 content 印出來,直接指定 delta 裡的資料即可 ``` python= for reply in replies: if 'content' in reply['choices'][0]['delta']: print(reply['choices'][0]['delta']['content'], end='') ``` ## 錯誤處理與使用限制 有時 API 發生錯誤時會引發一些例外,如果沒有適當處理,會造成程式意外中斷 ### 使用例外機制處理錯誤 所有 openai 套件的例外都是 openai.OpenAIError 類別所衍伸的,以下是比較重要的幾種: 1. Timeout 2. APIConnectError 3. InvalidRequestError 4. AuthenticationError 5. RateLimitError 6. ServiceUnavailableError 7. APIError > 詳細的錯誤說明可直接參考[官方文件](https://platform.openai.com/docs/guides/error-codes/python-library-error-types) 若只是要單純捕捉例外,可以直接捕捉 `openai.OpenAIError` 類別 ``` python= import openai import apikey openai.api_key = apikey.OPENAI_API_KEY try: reply = openai.ChatCompletion.create( model='gpt-3.5-turbo', messages=[ {'role': 'user', 'content': 'How to define \'Love\'? In 20 words.'} ] ) for choice in reply['choices']: print(choice['index'], choice['message']['content']) except openai.OpenAIError as err: print('Error Type: ', err.error.type) print('Error msg: ', err.error.message) ```