在【Day - 19】時,我們探討了GPT-3.5模型的Function Calling,發現它難以在回覆對話的同時進行文法檢查。另外,我也認為若在和AI的對話中參雜解析和即時指出語法錯誤,可能會打斷原本對話的流暢性。因此,我選擇將文法檢查功能獨立出來,以提示的方式形式呈現,避免影響到對話的進行。在今天的Function Calling實戰第四天中,我們將繼續探索不同的Function Calling實作。Let's Go!
首先,在chatgpt.model.ts
檔案中,新增兩個文法檢查的Model,其中,GrammerCheckDataModel
用於定義Function Calling的輸出格式,而GrammerDataModel
則用於狀態管理:
export interface GrammerCheckDataModel {
hasGrammerMistake: boolean;
}
export interface GrammerDataModel extends GrammerCheckDataModel {
mistakeSentence: string;
}
在openai.service.ts
檔案中,建立一個getGrammerCheckRequestData()
的方法,該方法為文法檢查的Function Calling結構。其中,parameters
的JSON Schema是根據上方定義的GrammerCheckDataModel
來配置的:
private getGrammerCheckRequestData(contentData: string): ChatRequestModel {
return {
model: 'gpt-4',
messages: [
{
role: 'system',
content: '請檢查是否有文法或使用上的任何錯誤。'
},
{
role: 'user',
content: contentData
}
],
temperature: 0.7,
top_p: 1,
functions: [
{
name: 'grammerChecker',
description: '',
parameters: {
type: 'object',
properties: {
hasGrammerMistake: {
type: 'boolean',
description: ''
}
},
required: [
'hasGrammerMistake'
]
}
}
],
function_call: {
name: 'grammerChecker'
}
}
}
接下來在相同的檔案中,建立了一個checkGrammerHasMistake()
的方法。這個方法的結構與chatAPI相似。利用map
Operator,將文法的錯誤和相關內容一併回傳:
.
.
.
public chatAPI(contentData: string) {
.
.
.
}
public checkGrammerHasMistake(contentData: string) {
return this.http.post<ChatResponseModel>('https://api.openai.com/v1/chat/completions', this.getGrammerCheckRequestData(contentData), { headers: this.headers }).pipe(
map(chatAPIResult => {
//取得function_call.arguments的字串並轉成物件
const grammerCheckData = JSON.parse(chatAPIResult.choices[0].message.function_call?.arguments!) as GrammerCheckDataModel;
return {
hasGrammerMistake: grammerCheckData.hasGrammerMistake,
mistakeSentence: contentData
} as GrammerDataModel;
}),
);
}
與【Day - 21】的做法相同,在status.service.ts
檔案中,建立一個grammerStatusSubject$
來追踪和管理文法檢查的狀態。此外,新增了一個setGrammerStatus()
方法,讓外部能夠通過這個方法來修改狀態:
.
.
.
//語氣狀態
private styleStatusSubject$ = new BehaviorSubject<AIStyle>('friendly');
//文法檢查狀態
private grammerStatusSubject$ = new BehaviorSubject<GrammerDataModel>({
hasGrammerMistake: false,
mistakeSentence: ''
});
.
.
.
get grammerStatus$(): Observable<GrammerDataModel> {
return this.grammerStatusSubject$.asObservable();
}
.
.
.
public setGrammerStatus(grammerData: GrammerDataModel) {
this.grammerStatusSubject$.next({
hasGrammerMistake: grammerData.hasGrammerMistake,
mistakeSentence: grammerData.mistakeSentence
});
}
public resetGrammerStatus() {
this.grammerStatusSubject$.next({
hasGrammerMistake: false,
mistakeSentence: ''
});
}
在OnGetRecordingBase64Text()
方法中,我組合了chatAPI()
和checkGrammerHasMistake()
兩個功能,利用forkJoin
Operator確保它們同時執行。當它們都完成時,我們可以從每個功能中獲得最後的結果。然後,將GrammerDataModel
加入到textToSpeech()
方法中:
OnGetRecordingBase64Text(recordingBase64Data: RecordingData) {
const requestData: AudioConvertRequestModel = {
aacBase64Data: recordingBase64Data.value.recordDataBase64
};
//重設文法狀態
this.statusService.resetGrammerStatus();
//啟動讀取
this.statusService.startLoading();
//Audio Convert API
this.http.post<AudioConvertResponseModel>('你的Web APP URL/AudioConvert/aac2m4a', requestData).pipe(
//Whisper API
switchMap(audioAPIResult => this.openaiService.whisperAPI(audioAPIResult.m4aBase64Data)),
//Chat API
switchMap(whisperAPIResult => forkJoin([this.openaiService.chatAPI(whisperAPIResult.text), this.openaiService.checkGrammerHasMistake(whisperAPIResult.text)])),
//Speech Service API
switchMap(chatAndGrammerResult => this.textToSpeech(chatAndGrammerResult[0], chatAndGrammerResult[1])),
finalize(() => {
//停止讀取
this.statusService.stopLoading();
})
).subscribe(result => {
//當前GPT回覆的語氣狀態
this.statusService.setStyleStatus(result.gptStyle);
//當前文法的對錯狀態
this.statusService.setGrammerStatus({
hasGrammerMistake: result.hasGrammerMistake,
mistakeSentence: result.mistakeSentence
});
//播放音訊
this.statusService.playAudio(result.audioFile);
});
}
private textToSpeech(conversationData: ConversationDataModel, grammerData: GrammerDataModel) {
return this.speechService.textToSpeech(conversationData).pipe(
map(audioFileResult => ({
audioFile: audioFileResult,
gptStyle: conversationData.gptResponseTextStyle,
hasGrammerMistake: grammerData.hasGrammerMistake,
mistakeSentence: grammerData.mistakeSentence
}))
);
}
最後,我們來觀察GrammerDataModel
物件的返回值。在測試過程中,我故意說了一句錯誤文法的英文句子:「How do you are?」。而Function Calling也確實成功的捕捉到了這個文法錯誤。
今天是Function Calling實戰的尾聲了,在研究和實作的過程中不僅讓人覺得它充滿趣味和創造力,更是一個非常強大和實用的工具。透過這個功能,我們能夠更精確的操控GPT模型的輸出,定製特定的答案格式,甚至結合其他技術或服務,實現前所未有的應用場景。這不僅為開發者提供了更大的彈性,也為使用者帶來了更為個性化和高質量的互動體驗。隨著技術的進步和模型的演進,期待未來能有更多不同的Function Calling實踐哦!
Github專案程式碼:Ionic結合ChatGPT - Day22