使用C#开发微信公众号对接ChatGPT和DALL-E
本人是一家小公司的技术总监,工作包括写市场分析、工作汇报、产品推广文案及代码开发等。在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秒的时长限制,所以最好使用客服接口进行结果回复。而目前客服接口只适用于通过微信认知的企业公众号而非个人公众号。所以本人的个人公众号【车骑数说】无法接入,目前自己也只能在个人测试号上使用。不过我也正在开发小程序版本,后续开发完成后会及时更新。
使用C#开发微信公众号对接ChatGPT和DALL-E的更多相关文章
- Java开发微信公众号(四)---微信服务器post消息体的接收及消息的处理
在前几节文章中我们讲述了微信公众号环境的搭建.如何接入微信公众平台.以及微信服务器请求消息,响应消息,事件消息以及工具处理类的封装:接下来我们重点说一下-微信服务器post消息体的接收及消息的处理,这 ...
- 小机器人自动回复(python,可扩展开发微信公众号的小机器人)
api来之图灵机器人.我们都知道微信公众号可以有自动回复,我们先用python脚本编写一个简单的自动回复的脚本,利用图灵机器人的api. http://www.tuling123.com/help/h ...
- vue+node.js+webpack开发微信公众号功能填坑——v -for循环
页面整体框架实现,实现小功能,循环出数据,整体代码是上一篇 vue+node.js+webpack开发微信公众号功能填坑--组件按需引入 修改部门代码 app.vue <yd-flexbox&g ...
- vue+node.js+webpack开发微信公众号功能填坑——组件按需引入
初次开发微信公众号,整体框架是经理搭建,小喽喽只是实现部分功能,整体页面效果 整个页面使用两个组件:布局 FlexBox,搜索框 Search,demo文档 http://vue.ydui.org/d ...
- PHP开发微信公众号
PHP开发微信公众号:配置和部署服务器及Token认证 https://zhuanlan.zhihu.com/p/28259840
- 使用vue开发微信公众号下SPA站点的填坑之旅
原文发表于本人博客,点击进入使用vue开发微信公众号下SPA站点的填坑之旅 本文为我创业过程中,开发项目的填坑之旅.作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱^ ...
- PHP开发微信公众号(一)二维码的获取
要开发微信公众号,首先进行需要注册一个,然后认证.这就不用多说了. 当然如果没有,也可以去申请一个测试号来使用,地址:https://mp.weixin.qq.com/debug/cgi-bin/sa ...
- Java开发微信公众号(五)---微信开发中如何获取access_token以及缓存access_token
获取access_token是微信api最重要的一个部分,因为调用其他api很多都需要用到access_token.比如自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等在请求的时候 ...
- Java开发微信公众号(三)---微信服务器请求消息,响应消息,事件消息以及工具处理类的封装
在前面几篇文章我们讲了微信公众号环境的配置 和微信公众号服务的接入,接下来我们来说一下微信服务器请求消息,响应消息以及事件消息的相关内容,首先我们来分析一下消息类型和返回xml格式及实体类的封装. ( ...
- Java开发微信公众号(二)---开启开发者模式,接入微信公众平台开发
接入微信公众平台开发,开发者需要按照如下步骤完成: 1.填写服务器配置 2.验证服务器地址的有效性 3.依据接口文档实现业务逻辑 资料准备: 1.一个可以访问的外网,即80的访问端口,因为微信公众号接 ...
随机推荐
- centos6.5下安装配置apache2.4.9
centos6.5下安装配置apache2.4.9 摘要: 需要下载的包 apr-1.5.0.tar.gz apr-util-1.5.3.tar.gz pcre-8.33.tar.gz httpd-2 ...
- nginx 均衡负载
前言 在此只介绍一些nginx的简单的负载均衡. 正文 在这篇之前,请看我的nginx反向代理这一篇,是接着上面的例子写的. 我在上个例子中,分别是两个a.html 和 b.html 现在我把他们的名 ...
- 重新整理.net core 计1400篇[二] (.net core 改造控制台项目)
前言 为.net core 命令行的基础上写的,如果有兴趣的话,可以去看我的.net core 前文. 下面介绍如何将.net core控制台转换为.net core web应用. 正文 如果我们要实 ...
- Oracle SQL 常用的将varchar数据处理成number的正则
Oracle SQL 常用的数据处理正则 去除所有的空格 replace(t.dxmz,chr(32),'') 匹配非纯数字 not regexp_like(t.zgbs,'^[[:digit:]]* ...
- [PHP] 小数转科学计数法, 小数保留 n 位
使用sprintf / printf 的 %e 或%E 格式说明符将其转换为科学计数法. 使用精度控制符指定保留多少位. 例如:sprintf('%.4e', 0.00000123); Link:ht ...
- [FAQ] 夏玉米 按规则查询域名靠谱吗 ?
很早就有一个网站叫 夏玉米,可以按规则查询和注册域名,那么它真如我们想的那样 可以找到好域名吗? 虽然看起来很好用,实际上夏玉米的查询只是针对它自己的数据库,不包含未在其平台注册的域名,所以大家要失望 ...
- IIncrementalGenerator 增量 Source Generator 生成代码入门 从语法到语义 获取类型完全限定名
本文告诉大家如何在使用 IIncrementalGenerator 进行增量的 Source Generator 生成代码时,如何从语法分析过程,将获取的语法 Token 转换到语义分析上,比如获取类 ...
- kubernetes-1.26安装
一.环境准备 k8s集群角色 IP 主机名 安装组件 配置 控制节点 192.168.10.10 master apiserver.controller-manager.scheduler.etcd. ...
- vue.js写悬浮广告效果
拿上一篇运行一下,感觉自己这个效果在边界处理的更好 <template> <div class="ad"> <p>vue广告悬浮</p&g ...
- 使用ChatGPT自动构建知识图谱
1.概述 本文将探讨利用OpenAI的gpt-3.5-turbo从原始文本构建知识图谱,通过LLM和RAG技术实现文本生成.问答和特定领域知识的高效提取,以获得有价值的洞察.在开始前,我们需要明确一些 ...