# 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)
```