本人是一家小公司的技術總監,工作包括寫市場分析、工作彙報、產品推廣文案及代碼開發等。在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、微信硬體/藍牙,等等。
- 新建一個空的.net core WebAPI項目並引入Nuget包如下:
Install-Package OpenAI-DotNet
Install-Package Senparc.Weixin.AspNet
Install-Package Senparc.Weixin.MP.Middleware
- 在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
});
...
}
- 新建一個類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;
}
}
- 在配置文件appsettings.json裡面添加對應配置
- 運行代碼,在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的操作系統上,主要有三步:
- 安裝運行時 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
- 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 選取更改。
- 設置服務自啟動
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秒的時長限制,所以最好使用客服介面進行結果回覆。而目前客服介面只適用於通過微信認知的企業公眾號而非個人公眾號。所以本人的個人公眾號【車騎數說】無法接入,目前自己也只能在個人測試號上使用。不過我也正在開發小程式版本,後續開發完成後會及時更新。