訓練紀錄
,則嘗試從訊息中提取相關數據,例如組數
、次數
、重量
,和感受度
等等,並依照我們要求的格式進行回應,然後將結果記錄在MongoDB中。飲食或食物
,則會分析訊息中的食物種類和營養成分,並依據我們指定的格式回應。Structured Output
。雖然因為時程關係來不及在這個專案使用,但還是介紹一下這個能讓GPT以結構化格式回傳結果,更有利於開發的功能。使用API對GPT下Prompt
"為目的,逐個說明要做的步驟,以及名詞解釋:
n
: 指定GPT可回應的choices數量這邊先說明一下Token和計費的關連。
bearer 你的API Key
,才能成功呼叫API。請注意
: OpenAI目前只有針對API的使用收費,網頁版除了一些進階功能以外,都是免費的。收費方式是使用字段總數,請不要被一些長得超像OpenAI官網的網站用年費、月費等方式給騙了
,如同我們在LINE Bot 基本觀念: 官方帳號的建立流程 / 事件簡介時使用的Channel Access Token一樣,我們可以透過環境變數或外部檔案來指API Key。請注意,API Key的值不要外流,更不要Push到公開的Git上,否則等同讓大家用你的魔法小卡使用API。
除了API Key以外,在呼叫API時,還需要指定Model Id和Endpoint(optional, 有些Library會自己判斷End point)。
在Spring中,我們可以使用@Value
這個Annotation來取得application.properties
中環境變數的值。例如以下我們在OpenAiApiService.class中設定的三個Annotation,可以分別把application.properties中的openai.api.key
、openai.model.id
、openai.api.endpoint
這三個環境變數的值設定給String變數:
Class OpenAiApiService
@Value("${openai.api.key}")
String token;
@Value("${openai.model.id}")
String deploymentOrModelId;
@Value("${openai.api.endpoint}")
String endpoint;
其中:
gpt-4o
,可以在Model的說明頁面內找到。/v1/chat/completions
,指的是/vx
開頭的URL,可以在官網API Reference找到請注意,@Value
只能宣告在Class級別,他是不能宣告在Function內的Local variable上面的。
Message
表示對GPT下的Prompt。system
和user
。這邊大致介紹一下System的運作原理,此使用方式也與我們專案的需求有關:system
這個角色對GPT做的提示,目的是提供一些前情提要(例如對話的上文)來指導模型如何回應。但使用者在網頁版ChatGPT無法直接查看或修改這個system message。const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{
"role": "system",
"content": [
{
"type": "text",
"text": `
You are a helpful assistant that answers programming questions
in the style of a southern belle from the southeast United States.
`
}
]
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "Are semicolons optional in JavaScript?"
}
]
}
]
});
Message
組成給GPT的Prompt:
前提
: 以system為角色的提示,說明了不同情況下應該整理的JSON格式指令
: 以user為角色的指令: 就是User輸入的訊息User
輸入前提
+指令,也就是只有一個User Message
,得到的結果似乎沒有什麼差別。實際上,區分了System
和User
在使用上的差異,可能需要再釐清 final ChatMessage systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), preface);
final ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), userInputPrompt);
訓練記錄
、飲食
,其他
時,應該整理的JSON格式。Please answer according to the type of the User message.
If the message is some kind of training log, you are a Strength and Conditioning Coach. Please try to infer the information of action(should be translated in English), action type, weight(in kilograms, just write the number), repetition, set, percentage of repetition maximum, duration, feeling, date(write today if not mentioned), and advice for the next training (write to “advice” property for each action, should be translated in "繁體中文" if user input is in Chinese.), then respond with a JSON schema.
For example:
User: "I just finished today's training program, deadlift for 3 sets of 5 reps, at about 75% of my 1RM. After that, I went jogging for 1 hour, which made me very tired."
Coach:
{
"messageContent":
[{
"about":"TrainingRecords",
"action":"Deadlift",
"actionType": "Weight training",
"weight": 110,
"repetition": 5,
"set": 3,
"percentagOfRepetitionMaximum": "75",
"duration": null,
"feeling": null,
"advice": null,
"date":"27/05/2023"
},
{
"about":"TrainingRecords",
"action":"Jogging",
"actionType": "cardio",
"weight": null,
"repetition": null,
"set": null,
"percentagOfRepetitionMaximum": null,
"duration": "1 hour",
"feeling": "very tired",
"advice": null,
"date":"27/05/2023"
}]
}
If the message is about food and diet, then you are a Nutritionist. Please analyze the protein and fat
In grams, and calories in kcal then respond with JSON schema.
For example:
User:”Steak”
Nutritionist:
{
"messageContent":
[{
"about":"Diet",
"calories":250,
"protein": 35,
"fat": 10
}
]
}
If the user message is either one of the above types, just respond as ChatGPT at chat.openai.com.
write the response in JSON schema like:
{
"messageContent":
[{
"about":"ChatGPT",
"response":
}]
}
The advice should be translated in Traditional Chinese if user input is in Chinese, otherwise, all the properties in the JSON should be in English.
Don’t respond to anything beside the json which contains your answer.
systemMessage
分成多個比較短的String,或許比較好維護。choices
這個Array。choice[0]
)是模型認為最合適或信心最高的回應,而後面的(例如choice[1]
)則是其他個替代選項,可能略微不那麼確定,但仍然是合理的回應。n
這個參數,決定API最多可以回幾個choice。ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
.builder()
.model("gpt-3.5-turbo") // 指定model id
.messages(messages) // 指定我們組合好的prompt
.n(1) // 我們只需要最相關的choices[0]
.maxTokens(500) // 最多回應500個token
type
為json_schema
json_schema
指定schema內容const response = await openai.chat.completions.create({
model: "gpt-4o-2024-08-06",
messages: [
{ role: "system", content: "You are a helpful math tutor. Guide the user through the solution step by step." },
{ role: "user", content: "how can I solve 8x + 7 = -23" }
],
response_format: {
type: "json_schema",
json_schema: {
name: "math_response",
schema: {
type: "object",
properties: {
steps: {
type: "array",
items: {
type: "object",
properties: {
explanation: { type: "string" },
output: { type: "string" }
},
required: ["explanation", "output"],
additionalProperties: false
}
},
final_answer: { type: "string" }
},
required: ["steps", "final_answer"],
additionalProperties: false
},
strict: true
}
system
和user
兩種角色,對應設定前提
和指令
的方式建立Message,來向GPT下達Prompt,並利用choices參數取得回應。max_tokens
和n
來設定回應的選項數量及token上限。至此,我們已經完成從源頭的LINE Platform、AWS Lambda / EC2、Kafka Message Queue、MongoDB一路串到OpenAI的過程,並逐步解析他們的概念及實作細節。
非常感謝螢幕前的你看到這邊。
下一篇是本系列最後一篇文章,將重點說明我們的Bot Server如何整合OpenAI Service與Kafka Consumer,並分享測試與部署過程,及未來的改進空間。