使用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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...