[isTMS]課程 - AI Agent 開發實戰
 

建立AI Agent

Lab 4(A): create a ChatGPT bot (LINE)

  1. create a line bot (messaging api) https://developers.line.biz/en/
  2. get Channel access token and Your UserID
  3. create a new webapi project
md testbot
cd testbot
dotnet new webapi -controllers
dotnet new --install isRock.Template.LineWebHook  
# https://arock.blob.core.windows.net/blogdata202310/LineBotChatGPTWebHookController.cs
dotnet add package linebotsdk
dotnet new linewebhook
code .
#編輯程式碼
dotnet run    

run ngrok command(不推薦):

ngrok authtoken 2eeb482nM____your____token____V3Ar4
ngrok http 5000 --host-header="localhost:5000" 

or DevTunnel (推薦)

參考說明
https://studyhost.blogspot.com/2023/10/devtunnel-webapi.html
安裝方式:
winget install Microsoft.devtunnel
或
Invoke-WebRequest -Uri https://aka.ms/TunnelsCliDownload/win-x64 -OutFile devtunnel.exe
.\devtunnel.exe -h 
安裝完成之後,請先關掉terminal再開,接著透過底下網址登入:
devtunnel user login
在命令列執行上面這個指令會開啟瀏覽器,請用你的 MS Account 或 GitHub 帳號登入。登入完成後,你就可以透過底下這樣的指令,把 localhost 特定的 port 公開到網際網路上(例如底下的port是5000):
devtunnel host -p 5000 --allow-anonymous

程式碼修改:

  1. AdminUserId image
  2. ChannelAccessToken
  3. Programs.cs ==> //app.UseHttpsRedirection(); image
  4. ChatGPT.CallAzureOpenAIChatAPI() 改為 ChatGPT.CallOpenAIChatAPI() image

sample code: https://gist.github.com/isdaviddong/5c630c9a97720962fc52ef766dbb7530#file-openailinebot-cs

//基本的 OpenAI ChatGPT WebHook
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;

namespace isRock.Template
{
    public class LineBotChatGPTWebHookController : isRock.LineBot.LineWebHookControllerBase
    {
        [Route("api/LineBotChatGPTWebHook")]
        [HttpPost]
        public IActionResult POST()
        {
            const string AdminUserId = "_____Admin_User_ID_________"; //👉repleace it with your Admin User Id

            try
            {
                //設定ChannelAccessToken
                this.ChannelAccessToken = "____LINE_Channel_Access_Token____"; //👉repleace it with your Channel Access Token
                //配合Line Verify
                if (ReceivedMessage.events == null || ReceivedMessage.events.Count() <= 0 ||
                    ReceivedMessage.events.FirstOrDefault().replyToken == "00000000000000000000000000000000") return Ok();
                //取得Line Event
                var LineEvent = this.ReceivedMessage.events.FirstOrDefault();
                var responseMsg = "";
                //準備回覆訊息
                if (LineEvent.type.ToLower() == "message" && LineEvent.message.type == "text")
                {
                    responseMsg = ChatGPT.getResponseFromGPT(LineEvent.message.text);
                }
                else if (LineEvent.type.ToLower() == "message")
                    responseMsg = $"收到 event : {LineEvent.type} type: {LineEvent.message.type} ";
                else
                    responseMsg = $"收到 event : {LineEvent.type} ";
                //回覆訊息
                this.ReplyMessage(LineEvent.replyToken, responseMsg);
                //response OK
                return Ok();
            }
            catch (Exception ex)
            {
                //回覆訊息
                this.PushMessage(AdminUserId, "發生錯誤:\n" + ex.Message);
                //response OK
                return Ok();
            }
        }
    }

    public class ChatGPT
    {
        [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
        public enum role
        {
            assistant, user, system
        }

        public static string CallOpenAIChatAPI(object requestData)
        {
            var client = new HttpClient();

            // 設定 API 網址
            var apiUrl = $"https://api.openai.com/v1/chat/completions";

            // 設定 HTTP request headers
            client.DefaultRequestHeaders.Add("Authorization", "Bearer sk-sEoH______open_ai_api_key_____qQTZU");
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT heade
            // 將 requestData 物件序列化成 JSON 字串
            string jsonRequestData = Newtonsoft.Json.JsonConvert.SerializeObject(requestData);
            // 建立 HTTP request 內容
            var content = new StringContent(jsonRequestData, Encoding.UTF8, "application/json");
            // 傳送 HTTP POST request
            var response = client.PostAsync(apiUrl, content).Result;
            // 取得 HTTP response 內容
            var responseContent = response.Content.ReadAsStringAsync().Result;
            var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(responseContent);
            return obj.choices[0].message.content.Value;
        }


        public static string getResponseFromGPT(string Message)
        {
            return ChatGPT.CallOpenAIChatAPI(
                new
                {
                    model = "gpt-3.5-turbo",
                    messages = new[]
                    {
                        new {
                            role = ChatGPT.role.system ,
                            content = @"
                                假設你是一個專業客戶服務人員,對於客戶非常有禮貌、也能夠安撫客戶的抱怨情緒。
                                請檢視底下的客戶訊息,以最親切有禮的方式回應。

                                但回應時,請注意以下幾點:
                                * 不要說 '感謝你的來信' 之類的話,因為客戶是從對談視窗輸入訊息的,不是寫信來的
                                * 不能過度承諾
                                * 要同理客戶的情緒
                                * 要能夠盡量解決客戶的問題
                                * 不要以回覆信件的格式書寫,請直接提供對談機器人可以直接給客戶的回覆
                                ----------------------
"
                        },
                        new {
                             role = ChatGPT.role.user,
                              content = Message
                             },
                    }
                });
        }
    }
}

如果出現 307 error, 請在程式碼中槓掉 //app.UseHttpsRedirection();

如果 build error , 請在程式碼中補上 using System.Net.Http;

final version (.net 8): git clone https://github.com/isdaviddong/Sample_Net8_LINE_Bot_with_OpenAI.git

Lab 4(B): create a ChatGPT bot (MS Bot)

download VS2022

https://visualstudio.microsoft.com/zh-hant/vs/older-downloads/

更新 VS 2022:

更新工具

更新VS2022:

修改安裝

增加套件

增加SDK

Create a Echo-Bot and Show Attachment

  1. 更新VS install
  2. 更新VS 2022
  3. 🎁安裝 emulator https://github.com/microsoft/BotFramework-Emulator/releases/download/v4.11.0/BotFramework-Emulator-4.11.0-windows-setup.exe
  4. 🎁安裝 bot Templates https://marketplace.visualstudio.com/items?itemName=BotBuilder.botbuilderv4
  5. 👉建立echo bot
  6. ~~修改project file 從 6.0 改為 5.0 ~~(如果環境於vs2019)
  7. 執行
  8. 開啟 emulator
  9. 與bot對話
  10. 在VS2022中,設定中斷點
  11. 觀察中斷點中,用戶傳來訊息
  12. 修改程式碼,當用戶上傳檔案時,bot顯示檔案名稱…
        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            var replyText = $"Echo: {turnContext.Activity.Text}";
            if (turnContext.Activity.Attachments!=null && turnContext.Activity.Attachments.Count > 0)
            {
                replyText = $"you've upload a file : {turnContext.Activity.Attachments[0].Name}";
            }
            await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
        }
  1. 使用emulator測試 <=== 截圖

整合 OpenAI

  1. 加入 ChatGPT.cs 類別
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;

namespace EchoBot1.Bots
{
    public class ChatGPT
    {
        const string AzureOpenAIEndpoint = "https://____________.openai.azure.com";  //👉replace it with your Azure OpenAI Endpoint
        const string AzureOpenAIModelName = "________"; //👉repleace it with your Azure OpenAI Model Name
        const string AzureOpenAIToken = "_________________"; //👉repleace it with your Azure OpenAI Token
        const string AzureOpenAIVersion = "2023-03-15-preview";  //👉replace  it with your Azure OpenAI Model Version

        [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
        public enum role
        {
            assistant, user, system
        }

        public static string CallAzureOpenAIChatAPI(
            string endpoint, string modelName, string apiKey, string apiVersion, object requestData)
        {
            var client = new HttpClient();

            // 設定 API 網址
            var apiUrl = $"{endpoint}/openai/deployments/{modelName}/chat/completions?api-version={apiVersion}";

            // 設定 HTTP request headers
            client.DefaultRequestHeaders.Add("api-key", apiKey);
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT heade
            // 將 requestData 物件序列化成 JSON 字串
            string jsonRequestData = Newtonsoft.Json.JsonConvert.SerializeObject(requestData);
            // 建立 HTTP request 內容
            var content = new StringContent(jsonRequestData, Encoding.UTF8, "application/json");
            // 傳送 HTTP POST request
            var response = client.PostAsync(apiUrl, content).Result;
            // 取得 HTTP response 內容
            var responseContent = response.Content.ReadAsStringAsync().Result;
            var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(responseContent);
            return obj.choices[0].message.content.Value;
        }


        public static string CallOpenAIChatAPI(string apiKey, object requestData)
        {
            var client = new HttpClient();

            // 設定 API 網址
            var apiUrl = $"https://api.openai.com/v1/chat/completions";

            // 設定 HTTP request headers
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}"); //👉OpenAI key
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT header
            // 將 requestData 物件序列化成 JSON 字串
            string jsonRequestData = Newtonsoft.Json.JsonConvert.SerializeObject(requestData);
            // 建立 HTTP request 內容
            var content = new StringContent(jsonRequestData, Encoding.UTF8, "application/json");
            // 傳送 HTTP POST request
            var response = client.PostAsync(apiUrl, content).Result;
            // 取得 HTTP response 內容
            var responseContent = response.Content.ReadAsStringAsync().Result;
            var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(responseContent);
            return obj.choices[0].message.content.Value;
        }

        public static string getResponseFromGPT(string Message)
        {
            return ChatGPT.CallOpenAIChatAPI(
               "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",
                //ref: https://learn.microsoft.com/en-us/azure/cognitive-services/openai/reference#chat-completions
                new
                {
                    model = "gpt-3.5-turbo",
                    messages = new[]
                    {
                        new {
                            role = ChatGPT.role.system ,
                            content = @"
                                假設你是一個專業的客戶服務人員,對於客戶非常有禮貌、也能夠安撫客戶的抱怨情緒、
                                盡量讓客戶感到被尊重、節盡可能的回覆客戶的疑問。

                                請檢視底下的客戶訊息,以最親切有禮的方式回應。

                                但回應時,請注意以下幾點:
                                * 不要說 '感謝你的來信' 之類的話,因為客戶是從對談視窗輸入訊息的,不是寫信來的
                                * 不能過度承諾
                                * 要同理客戶的情緒
                                * 要能夠盡量解決客戶的問題
                                * 不要以回覆信件的格式書寫,請直接提供對談機器人可以直接給客戶的回覆
                                ----------------------
"
                        },
                        new {
                             role = ChatGPT.role.user,
                             content = Message
                        },
                    }
                });
        }
    }

}

  1. 修改主程式:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.18.1

using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace EchoBot1.Bots
{
    public class EchoBot : ActivityHandler
    {
        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            var ret = ChatGPT.getResponseFromGPT(turnContext.Activity.Text);
            //var replyText = $"Echo: {turnContext.Activity.Text}";
            await turnContext.SendActivityAsync(MessageFactory.Text(ret, ret), cancellationToken);
        }

        protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
        {
            var welcomeText = "Hello and welcome!";
            foreach (var member in membersAdded)
            {
                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
                }
            }
        }
    }
}

Lab 5 : 加入企業資料(包含菜單的點餐機器人)

  1. 使用上一個Lab做出的 Chat bot
  2. 修改prompt
你是餐飲店的客服機器人,會回答客戶詢問的餐點問題,並且推薦讓店裡獲利最高的產品。你也能處裡客戶的抱怨和申訴,安撫客戶情緒。
目前店裡面所販賣的產品有底下這些:
大亨堡,45元
麥香雞,36元
蛋餅,27元
可麗餅,50元
飯糰,45元
而飲料有底下這些:
可樂,55元
紅茶,35元
奶茶,45元
店裡沒有套餐,只能把餐點和飲料搭配銷售,這樣搭配沒有折扣。此外,跟客人推薦餐點時,必須是上述的清單中的餐點。
目前唯一的優惠是周日早上買一送一,只限單點大亨堡才有這個優惠。
----------------------
  1. 加入history
user: 請告訴我最便宜的餐點價格。
assistant: 你可以點選蛋餅,只需要27元
user: 有什麼好吃的餐點嗎?
assistant: 這個問題很主觀,不同的人有不同的口味喜好,以下是我個人的推薦:
1. 大亨堡:這是我們餐廳的招牌餐點,肉質鮮美,搭配特製的醬料非常美味
2. 麥香雞:這是一個很受歡迎的餐點,雞肉鮮嫩,外皮酥脆,非常適合當作午餐或晚餐 
3. 可麗餅:這是一個很受歡迎的早餐餐點,口感軟嫩,搭配楓糖漿和奶油非常美味
user: 我想要吃飽一點,你可以推薦我豐盛的套餐嗎?
assistant: 很抱歉,我們店裡沒有套餐,只能把餐點和飲料搭配銷售。
但是,如果你想吃得飽一點,我建議你可以考慮以下搭配:
1. 大亨堡 + 可樂:肉質鮮美,搭配可樂口感更佳。
2. 麥香雞 + 紅茶:雞肉鮮嫩,搭配紅茶口感更清爽。
3. 飯糰 + 奶茶:飯糰營養豐富,搭配奶茶口感更香濃。
希望這些搭配可以滿足你的食慾需求!
user: 這些搭配套餐分別是多少錢?
assistant: 很抱歉,我剛才的回答有點不清楚,我重新提供給你更詳細的價格資訊:
1. 大亨堡 + 可樂 = 100元 (大亨堡 45元 + 可樂 55元)
2. 麥香雞 + 紅茶 = 71元 (麥香雞 36元 + 紅茶 35元)
3. 飯糰 + 奶茶 = 90元 (飯糰 45元 + 奶茶 45元)
希望這些資訊可以幫助到你!

reference code: https://gist.github.com/isdaviddong/511d74e5d8620b990460a2af003442f0

  1. 如果今天想吃素? 該怎麼辦?
  2. 如果換成 GPT-4 表現會更好嗎? https://platform.openai.com/docs/models/gpt-4-turbo-and-gpt-4
  3. 如果採用 gpt-3.5-turbo 模型,如何調整 system prompt (or hostory) 讓用戶花100元買大亨堡時候,永遠得到4個...