使用C#開發微信公眾號對接ChatGPT和DALL-E

来源:https://www.cnblogs.com/royzshare/archive/2023/03/22/17245840.html
-Advertisement-
Play Games

本人是一家小公司的技術總監,工作包括寫市場分析、工作彙報、產品推廣文案及代碼開發等。在ChatGPT推出之後本人一直在工作中使用,在頭腦風暴、大綱生成、語句優化、代碼生成方面很有效果。但ChatGPT在一些常識性生成方面並不理想,比如某個省有哪些旅游景點、三角數學公式推算等,大家使用中一定要註意並仔 ...


本人是一家小公司的技術總監,工作包括寫市場分析、工作彙報、產品推廣文案及代碼開發等。在ChatGPT推出之後本人一直在工作中使用,在頭腦風暴、大綱生成、語句優化、代碼生成方面很有效果。但ChatGPT在一些常識性生成方面並不理想,比如某個省有哪些旅游景點、三角數學公式推算等,大家使用中一定要註意並仔細甄別。ChatGPT的一些使用場景可以參考用 ChatGPT 來瞭解 ChatGPT
由於我之前一直是在電腦瀏覽器中使用,且經常碰見超時需要重新進入的情況,遂產生想法把ChatGPT和DALL-E(OpenAI開發的圖片生成模型)集成到本人的公眾號上,使用更加方便和穩定。二話不說,先上效果圖和代碼。

代碼
開發中主要使用了兩個庫:
OpenAI-DotNet:一個簡單的C# .NET客戶端庫,用於通過RESTful API使用chat-gpt、GPT-4、GPT-3.5-Turbo和Dall-E。這是一個獨立開發的庫,不是官方庫,需要OpenAI API帳戶。
Senparc.Weixin —— 微信 .NET SDK:使用 Senparc.Weixin,您可以方便快速地開發微信全平臺的應用(包括微信公眾號、小程式、小游戲、企業號、開放平臺、微信支付、JS-SDK、微信硬體/藍牙,等等。

  1. 新建一個空的.net core WebAPI項目並引入Nuget包如下:
Install-Package OpenAI-DotNet

Install-Package Senparc.Weixin.AspNet
Install-Package Senparc.Weixin.MP.Middleware
  1. 在Program.cs中引入微信配置代碼
public static void Main(string[] args)
{
    ...
    
    builder.Services.AddMemoryCache();//使用本地緩存必須添加

    //Senparc.Weixin 微信註冊
    builder.Services.AddSenparcWeixinServices(builder.Configuration);

    ...
    
    //啟用微信配置
    var registerService = app.UseSenparcWeixin(app.Environment, null, null,
        register => { /* CO2NET 全局配置 */ },
        (register, weixinSetting) =>
        {
            register.RegisterMpAccount(weixinSetting);
        });

    //啟用微信消息處理中間件
    app.UseMessageHandlerForMp("/WeixinAsync", CustomMessageHandler.GenerateMessageHandler, options =>
    {
        options.AccountSettingFunc = context => Senparc.Weixin.Config.SenparcWeixinSetting;
    });
    
    //Nginx部署使用
    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
    });
    
      ...

}
  1. 新建一個類CustomMessageHandler用於處理微信消息
/// <summary>
/// 自定義消息處理器
/// </summary>
public class CustomMessageHandler : MessageHandler<DefaultMpMessageContext>
{
    private readonly ILogger _logger;
    private readonly string _openAPI_Key;
    private string appId = Config.SenparcWeixinSetting.MpSetting.WeixinAppId;

    /// <summary>
    /// 為中間件提供生成當前類的委托
    /// </summary>
    public static Func<Stream, PostModel, int, IServiceProvider, CustomMessageHandler> GenerateMessageHandler = (stream, postModel, maxRecordCount, serviceProvider)
                     => new CustomMessageHandler(stream, postModel, maxRecordCount, serviceProvider: serviceProvider);

    public CustomMessageHandler(Stream inputStream, PostModel postModel, int maxRecordCount, IServiceProvider serviceProvider)
        : base(inputStream, postModel, maxRecordCount, false, null, serviceProvider)
    {
        GlobalMessageContext.ExpireMinutes = 3;
        GlobalMessageContext.MaxRecordCount = 3;
        OmitRepeatedMessage = true; //啟用消息去重功能
        _logger = serviceProvider.GetService<ILogger<CustomMessageHandler>>();
        _openAPI_Key = serviceProvider.GetService<IConfiguration>()["OpenAIKey"];
    }

    /// <summary>
    /// 回覆以文字形式發送的信息
    /// </summary>
    public override async Task<IResponseMessageBase> OnTextRequestAsync(RequestMessageText requestMessage)
    {
        #region 由於微信回覆時長5秒限制,這裡採用非同步推送客服信息,後面直接返回空消息。

        _ = Task.Factory.StartNew(async () =>
        {
            OpenAIClient api = new OpenAIClient(_openAPI_Key);

            if (requestMessage.Content.Contains("圖片", StringComparison.OrdinalIgnoreCase)
                || requestMessage.Content.Contains("圖像", StringComparison.OrdinalIgnoreCase)
                || requestMessage.Content.Contains("照片", StringComparison.OrdinalIgnoreCase)
                || requestMessage.Content.Contains("Image", StringComparison.OrdinalIgnoreCase)
                || requestMessage.Content.Contains("Photo", StringComparison.OrdinalIgnoreCase)) //DALL-E 模型
            {
                var imageRequest = requestMessage.Content.Replace("圖片", "").Replace("圖像", "").Replace("照片", "").Replace("Image", "").Replace("Photo", "").Trim();
                var results = await api.ImagesEndPoint.GenerateImageAsync(imageRequest, 1, ImageSize.Small); //調用DALL-E模型介面生成圖片
                if (results.Count > 0)
                {
                    var imageRemoteUrl = results[0];

                    #region 臨時保存圖片到本地
                    var imageLocalUrl = ServerUtility.ContentRootMapPath($"~/App_Data/TempImages/{Guid.NewGuid()}.png");
                    var client = new HttpClient();
                    client.Timeout = TimeSpan.FromSeconds(30);
                    byte[] bytes = await client.GetByteArrayAsync(imageRemoteUrl);
                    if (bytes.Length>0)
                    {
                        FileStream fs = new FileStream(imageLocalUrl, FileMode.Create);
                        BinaryWriter w = new BinaryWriter(fs);

                        try
                        {
                            w.Write(bytes);
                        }
                        finally
                        {
                            fs.Close();
                            w.Close();
                        }
                    }
                    #endregion

                    var uploadResult = MediaApi.UploadTemporaryMedia(appId, UploadMediaFileType.image, imageLocalUrl);
                    await CustomApi.SendImageAsync(appId, OpenId, uploadResult.media_id);
                }
            }
            else //GPT3_5_Turbo 模型
            {
                var currentMessageContext = await base.GetCurrentMessageContext(); //使用StorageData保存對話上下文
                if (currentMessageContext.StorageData == null || !(currentMessageContext.StorageData is List<ChatPrompt>))
                {
                    currentMessageContext.StorageData = new List<ChatPrompt>();
                }
                var chatPrompts = (List<ChatPrompt>)currentMessageContext.StorageData;

                chatPrompts.Add(new ChatPrompt("user", requestMessage.Content));

                var chatRequest = new ChatRequest(chatPrompts, model: Model.GPT3_5_Turbo);
                var result = await api.ChatEndpoint.GetCompletionAsync(chatRequest); //調用GPT3_5_Turbo模型介面生成對話
                var firstChoice = result.FirstChoice.ToString().Trim();
                await CustomApi.SendTextAsync(appId, OpenId, firstChoice);

                chatPrompts.Add(new ChatPrompt("assistant", firstChoice));
                currentMessageContext.StorageData = chatPrompts;
                GlobalMessageContext.UpdateMessageContext(currentMessageContext);//儲存到緩存
            }

        }).ContinueWith(async task =>
        {
            if (task.Exception != null)
            {
                await CustomApi.SendTextAsync(appId, OpenId, $"生成失敗,請稍後再試。");
                _logger.LogError($"生成失敗,錯誤信息:{task.Exception.InnerException}。");
            }
        });

        return new SuccessResponseMessage();

        #endregion
    }

    /// <summary>
    /// 預設消息
    /// </summary>
    public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
    {
        var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
        responseMessage.Content = $"歡迎來到車騎數說,該公眾號已集成【ChatGPT 對話生成】和【DALL-E 圖片生成】功能:\r\n1、如若使用ChatGPT 對話生成功能請直接輸入想問的問題。\r\n1、如若使用DALL-E 圖片生成請在描述文字後以“圖片”結尾,比如“一匹馬在月球上圖片”。";
        return responseMessage;
    }
}
  1. 在配置文件appsettings.json裡面添加對應配置
  2. 運行代碼,在url後加上/WeixinAsync尾碼,如果出現以下頁面即代表開發成功。

OpenAI介面賬號
註冊完OpenAI賬號後(具體註冊過程請自行百度),打開https://platform.openai.com/,在用戶管理裡面的View API Keys裡面創建一個New Secret Key,並保存下來用於API調用。每個用戶有18美元的免費額度用於調用。具體使用額度可以在Usage頁面查看。

後端部署
由於OpenAI及介面不允許中國地區訪問,所以在部署代碼的時候有兩種方案:

  • 國內伺服器+安裝國外代理
  • 國外伺服器

同時對接微信公眾號一定是需要功能變數名稱的,目前功能變數名稱和國外伺服器綁定不需要備案,而和國內伺服器綁定需要備案,如果考量備案時間關係建議使用國外伺服器,而且部分國外伺服器並不貴,這裡推薦RackNerd,RackNerd是一家美國VPS服務商,其公司於2015年註冊,可選機房有聖何塞、芝加哥、達拉斯、亞特蘭大、紐約、阿什本等10個機房。一直以價格低廉、守候到位、伺服器穩定而火爆市場。付款方式支持支付寶。
促銷款便宜套餐入口:套餐1 套餐2 套餐3 2023年最新優惠碼:15OFFDEDI 官方網站:racknerd
本人購買的是套餐2,一年大概170元。

我是部署在Ubuntu 20.4的操作系統上,主要有三步:

  1. 安裝運行時 aspnetcore-runtime

添加 Microsoft 包存儲庫

wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb

安裝運行時

sudo apt-get update && \
  sudo apt-get install -y aspnetcore-runtime-6.0
  1. Nginx 安裝配置

由於請求通過反向代理轉發,因此使用 Microsoft.AspNetCore.HttpOverrides 包中的轉接頭中間件。 此中間件使用 X-Forwarded-Proto 標頭來更新
Request.Scheme,使重定向 URI 和其他安全策略能夠正常工作。

using Microsoft.AspNetCore.HttpOverrides;

...

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

app.UseAuthentication();

安裝 Nginx
sudo apt-get install nginx
首次安裝 Nginx,通過運行以下命令顯式啟動後確認瀏覽器顯示 Nginx 的預設登陸頁。
sudo service nginx start
配置 Nginx
修改 /etc/nginx/sites-available/default。 在文本編輯器中打開它,並將內容替換為以下代碼片段,Nginx 將匹配的請求轉發到 http://127.0.0.1:5000 中的 Kestrel上運行的 ASP.NET Core 應用。

server {
    listen        80;
    #server_name   example.com *.example.com;
    location / {
        proxy_pass         http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

完成配置 Nginx 後,運行 sudo nginx -t 來驗證配置文件的語法。 如果配置文件測試成功,可以通過運行 sudo nginx -s reload 強制 Nginx 選取更改。

  1. 設置服務自啟動

systemd 可用於創建服務文件以啟動和監視基礎 Web 應用。 systemd 是一個初始系統,可以提供啟動、停止和管理進程的許多強大的功能。
創建服務定義文件:
sudo nano /etc/systemd/system/wx-ai.service
並填充以下內容

[Unit]
Description=wx-ai service

[Service]
WorkingDirectory=/var/www/wx-ai
ExecStart=/usr/bin/dotnet /var/www/wx-ai/OpenAI.Examples.WX.dll
Restart=always
# Restart service after 90 seconds if the dotnet service crashes:
RestartSec=90
KillSignal=SIGINT
SyslogIdentifier=wx-ai
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

systemd常用命令如下:

sudo systemctl enable wx-ai.service  #開機啟動
sudo systemctl start wx-ai.service  #啟動服務
sudo systemctl stop wx-ai.service #停止服務
sudo systemctl status wx-ai.service #查看服務狀態
sudo systemctl restart wx-ai.service  #重新啟動服務

查看日誌命令如下:
sudo journalctl -fu wx-ai.service --since "2016-10-18" --until "2016-10-18 04:00"

至此部署完成,最後需要申請功能變數名稱綁定伺服器IP。

微信集成
這裡以微信測試公眾號為例子,打開https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index,找到appID和appsecret放在ASP.NET Core 應用的配置文件里,把ASP.NET Core應用部署的地址+/WeixinAsync填入介面配置信息,並填寫自定義Token,保證Token在微信配置和ASP.NET Core 應用的配置文件里一直。接下來就可以正式測試了。

正式公眾號配置類似,但這裡有個比較尷尬的地方,ChatGPT和DALL-E和介面數據返回時間一般比較長,大於微信公眾號普通回覆介面5秒的時長限制,所以最好使用客服介面進行結果回覆。而目前客服介面只適用於通過微信認知的企業公眾號而非個人公眾號。所以本人的個人公眾號【車騎數說】無法接入,目前自己也只能在個人測試號上使用。不過我也正在開發小程式版本,後續開發完成後會及時更新。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Tcl語言線上運行編譯,是一款可線上編程編輯器,在編輯器上輸入Tcl語言代碼,點擊運行,可線上編譯運行Tcl語言,Tcl語言代碼線上運行調試,Tcl語言線上編譯,可快速線上測試您的Tcl語言代碼,線上編譯Tcl語言代碼發現是否存在錯誤,如果代碼測試通過,將會輸出編譯後的結果。 該線上工具由IT寶庫提 ...
  • 直入主題: Q1:為什麼要用分散式鎖? 在分散式系統中,多個進程或線程可能會同時訪問共用資源,這可能會導致數據不一致、併發性問題、性能下降等問題。為瞭解決這些問題,我們通常會使用分散式鎖來協調多個進程或線程對共用資源的訪問。 分散式鎖是一種協調機制,它通過在共用資源上設置鎖來防止多個進程或線程同時訪 ...
  • 一.註解 1. 註解入門 Annotation是從JDK5.0開始引入的新技術 Annotation的作用: 不是程式本身,可以對程式做出解釋(這一點和註釋(comment)沒什麼區別) 可以被其他程式(比如:編譯器等)讀取 Annotation的格式: 註解是以“@註解名”在代碼中存在的,還可以添 ...
  • 原文:blog.csdn.net/qunqunstyle99/article/details/94717256 ThreadLocal是什麼 ThreadLocal是一個本地線程副本變數工具類。主要用於將私有線程和該線程存放的副本對象做一個映射,各個線程之間的變數互不幹擾,在高併發場景下,可以實現無 ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。 1. 使用前的準備 參考本人另一篇博客 安裝 Visual Leak Detector 下載 vld-2.5.1-setup.exe 並按步驟安裝 VLD。這一種使用方式的特點是,在一臺電腦上安裝完成後,將 VLD 安裝目錄下的 lib ...
  • ​1 導庫 import re 2 使用模板 re_pattern = re.compile(pattern, flags=0) result = re.findall(re_pattern,string) 3 說明 參數 描述 pattern 匹配的正則表達式,如 r'abc' 可用來匹配abc ...
  • Salt安裝部署 一:salt簡介 ​ 早期運維人員會根據自己的生產環境來寫特定腳本完成大量重覆性工作,這些腳本複雜且難以維護。系統管理員面臨的問題主要是1、系統配置管理,2、遠程執行命令,因此誕生了很多開源軟體,系統維護方面有fabric、puppet、chef、ansible、saltstack ...
  • """ 需求1:會員卡充值 金額只能輸入數字和浮點數、不能輸入負數 充值100(包含100)金額提示:成功充值N元,恭喜你成為普通會員 充值100.01(包含100.01)至200金額提示:成功充值N元,恭喜你成為鉑金會員 充值200.01(包含100.01)至300金額提示:成功充值N元,恭喜你成 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...