iT邦幫忙

2023 iThome 鐵人賽

DAY 24
0
Mobile Development

Ionic結合ChatGPT - 30天打造AI英語口說導師APP系列 第 24

【Day - 24】Server Sent Event應用 - 結合GPT即時說明文法錯誤

  • 分享至 

  • xImage
  •  

昨天,我們使用Sheet Modal為文法檢查建立了互動式的視窗。今天,我們會加入GPT模型,來對文法錯誤給予詳細的說明。這次的方法與之前的有所不同,我們將利用【Day - 8】提到的Stream串流模式。通過GPT API的Server Sent Event (SSE),我們可以逐步接收GPT模型回傳的資料。這方式不僅加速了資料傳輸,還大幅縮短了回應等待時間,也讓我們能夠實現與ChatGPT相似的即時交互體驗。

安裝fetch-event-source

為了使用Server Sent Event (SSE),我們可以使用瀏覽器內建的EventSource API。不過要注意的是,它只支持urlwithCredentials這兩個參數。這表示我們不能透過EventSource API傳遞header或body的資料,而且它僅適用於Get方法。
https://ithelp.ithome.com.tw/upload/images/20230924/20161663opaYtavjyY.png
https://ithelp.ithome.com.tw/upload/images/20230924/20161663dIclv2XrMd.png
所以,最後決定使用由微軟提供的fetch-event-source套件。該套件不只可以支援自訂的header和傳輸body,還能使用Fetch API的所有功能。
我們可以透過以下的指令安裝:

npm i @microsoft/fetch-event-source

 

Observable搭配fetchEventSource

在安裝完畢後,在OpenAI Service的openai.service.ts中導入該套件。同時,我們還要加入一個AbortController物件,這可以用於取消正在進行的fetch請求。有關AbortController的功能,可以參考MDN文件

import { fetchEventSource } from '@microsoft/fetch-event-source';
.
.
.
//中斷信號
private ctrl = new AbortController();

我們需要準備兩個方法,分別用於建立header和body。特別要注意,當與GPT API進行通信時,header中的Content-Type必須設定為application/json,否則會無法使用。而當取得body方法時,請帶入從【Day - 14】中所建立的ChatMessageModel。最後,切記要將stream的值設定為true

private getFetchHeader() {
  return {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer {你的Token}'
  };
}

private getFetchBody(chatMessage: ChatMessageModel[]) {
  return {
    model: 'gpt-4',
    messages: chatMessage,
    temperature: 0.7,
    top_p: 1,
    stream: true
  };
}

接下來,我們需要建立chatStreamAPI()方法。這個方法會回傳一個新的Observable,且在其中會執行fetchEventSource()方法。在onmessage()中,我們可以接收到每次返回的訊息。如果返回的訊息不是「[DONE]」(這表示GPT API所有的訊息都已經發送完畢),那麼這些訊息會透過觀察者發送出去:

public chatStreamAPI(chatData: ChatMessageModel[]) {
  return new Observable<ChatResponseModel>(observer => {
    fetchEventSource('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: this.getFetchHeader(),
      body: JSON.stringify(this.getFetchBody(chatData)),
      signal: this.ctrl.signal,
      //接收訊息事件
      onmessage(msg) {
        //如果訊息不是[DONE]則將訊息發送給觀察者
        if (msg.data !== '[DONE]') {
          observer.next(JSON.parse(msg.data));
        }
      },
      //結束事件
      onclose() {
        console.log('%c Open AI API Close', 'color: red');
        observer.complete();
      },
      //錯誤處理事件
      onerror(err) {
        observer.error(new Error(err));
      }
    });
  });
}

使用Stream功能時,其回傳的Response結構和非Stream的回應有所不同。在非Stream模式下,結果回傳於choices[0].message.content中;而在Stream模式下,則是於choices[0].delta.content中。
https://ithelp.ithome.com.tw/upload/images/20230924/20161663m27AMaym1F.png
因此,我們需要對【Day - 14】中的ChatChoicesModel進行相應的調整:

export interface ChatChoicesModel {
  index: number;
  message: ChatMessageModel;
  //加入delta
  delta?: {
    content: string
  }  
  finish_reason: string;
}

 

調整GrammerMistake元件

在【Day - 23】建立的GrammerMistake元件中,我們做了以下調整:在grammermistake.component.ts檔案中,注入OpenAI Service和新增了兩個變數,一個用於判斷是否正在執行,另一個則用於儲存GPT模型的回應:

//是否正在執行API
isGettingExplane = false;
//解釋文本
mistakExplaneText = '';

constructor(private statusService: StatusService,
  private openAIService: OpenAIService) {
}

然後,我們加入了一個getMistakeChatMessageData()的方法,該方法會返回ChatMessageModel。在這個方法中,我們使用的提示,可以讓GPT模型提供文法錯誤的詳細解釋:

private getMistakeChatMessageData(contentData: string): ChatMessageModel[] {
  return [
    {
      role: 'system',
      content: '你是英文文法老師,請用繁體中文詳細解釋為什麼這句英文有錯誤。'
    },
    {
      role: 'user',
      content: contentData
    }
  ];
}

利用【Day - 23】中介紹的「ionModalDidPresent」,這個事件會在Sheet Modal完全開啟後立即執行,我們將在開啟後,直接訂閱chatStreamAPI()以取得詳細解釋:

onIonModalDidPresent(event: Event) {
  if (this.mistakExplaneText === '') {
    this.isGettingExplane = true;
    this.statusService.grammerStatus$.pipe(
      take(1),
      switchMap(grammerStatusResult => this.openAIService.chatStreamAPI(this.getMistakeChatMessageData(grammerStatusResult.mistakeSentence))),
      finalize(() => {
        this.isGettingExplane = false;
      })
    ).subscribe(result => this.mistakExplaneText += result.choices[0].delta?.content ? result.choices[0].delta?.content : '');
  }
}

最後在Ion Model元件裡,我們使用內嵌繫結來顯示mistakeExplaneText變數的內容。同時,在資料正在取得的階段,我在文字的末尾加入了「」的動畫效果,用來提示API正在執行中,以增強使用者的體驗。

<ion-modal #modal [initialBreakpoint]="0.6" [breakpoints]="[0, 0.6, 1]"
  (ionModalDidPresent)="onIonModalDidPresent($event)">
  <ng-template>
    <ion-header>
      <ion-toolbar>
        <ion-title>AI文法解析</ion-title>
        <ion-buttons slot="end">
          <ion-button (click)="modal.dismiss()">
            <ion-icon slot="icon-only" name="close-outline"></ion-icon>
          </ion-button>
        </ion-buttons>
      </ion-toolbar>
    </ion-header>
    <ion-content>
      <div class="flex flex-col p-4">
        <div class="mb-2"><span class="bg-rose-400 text-white font-bold rounded-2xl px-2 py-1">錯誤句型:</span></div>
        <div class="mb-6 underline pl-2 text-gray-600">{{ (grammerStatus$ | async)?.mistakeSentence }}</div>
        <div class="mb-2"><span class="bg-lime-500 text-white font-bold rounded-2xl px-2 py-1">錯誤解析:</span></div>
        <div class="pl-2 align-baseline">
          <p class="text-gray-800">
            {{ mistakExplaneText }}
            <ng-container *ngIf="isGettingExplane">
              <span class="dot text-xl font-bold text-purple-400" style="--i:1"> .</span>
              <span class="dot text-xl font-bold text-orange-400" style="--i:2"> .</span>
              <span class="dot text-xl font-bold text-blue-400" style="--i:3"> .</span>
              <span class="dot text-xl font-bold text-amber-400" style="--i:4"> .</span>
            </ng-container>
          </p>
        </div>
      </div>
    </ion-content>
  </ng-template>
</ion-modal>

此為「」的CSS動畫:

.dot {
  opacity: 0;
  animation: fadeDot 2.5s infinite;
  animation-delay: calc(0.5s * var(--i));
}

@keyframes fadeDot {
  0%,
  80%,
  100% {
    opacity: 0;
  }

  50% {
    opacity: 1;
  }
}

 

實際測試

最後完成編譯後,在實體機上進行測試,我故意講了一段常見的錯誤文法「Where are you come from ?」,來驗證看看整個運作的效果吧!
https://github.com/MomoChenisMe/IonicWithChatGPT-iTHome2023/blob/main/%E9%90%B5%E4%BA%BA%E8%B3%BDDay24/%E5%9C%96%E6%AA%94/4.gif?raw=true

結語

今天我們終於完成了文法解析功能的整合。其實最初的想法是讓AI在對話中即時解釋文法問題,但因為【Day - 19】提到的GPT-3.5模型和Function Calling的問題,導致這功能難以實現之外,英文能力較弱的使用者,譬如「」,在同時理解對話和文法解釋時也是頗為吃力。但隨著功能的完成,我發現將文法提示做為附加資訊,可以讓使用者能更集中在聽力訓練上,並在必要時深入了解文法上的錯誤。這樣的方式不僅為應用帶來了更多的互動,也讓使用者得到了更精確且具深度的文法解析呢!



Github專案程式碼:Ionic結合ChatGPT - Day24


上一篇
【Day - 23】Sheet Modal - 設計與實現文法錯誤提示功能
下一篇
【Day - 25】Capacitor SQLite - 儲存歷史對話內容
系列文
Ionic結合ChatGPT - 30天打造AI英語口說導師APP30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言