【.Net/C#之ChatGPT开发系列】四、ChatGPT多KEY动态轮询,自动删除无效KEY

ChatGPT是一种基于Token数量计费的语言模型,它可以生成高质量的文本。然而,每个新账号只有一个有限的初始配额,用完后就需要付费才能继续使用。为此,我们可能存在使用多KEY的情况,并在每个KEY达到额度上限后,自动将其删除。那么,我们应该如何实现这个功能呢?还请大家扫个小关。👇

ChatGPT多KEY轮询

为了实现多KEY管理,我们通常需要把所有密钥保存在数据库中,但为了简化演示,这里我使用Redis来进行存储和管理多个KEY。同样,我将重新创建一个名为ChatGPT.Demo4的项目,代码和ChatGPT.Demo3相同。

一、Redis密钥管理

1、定义IChatGPTKeyService接口

在根目录下,创建一个名为Extensions的文件夹,然后右键点击它,新建一个IChatGPTKeyService.cs接口文件,并写入以下代码:

public interface IChatGPTKeyService
{//初始话密钥public Task InitAsync();//随机获取密钥KEYpublic Task<string> GetRandomAsync();//获取所有密钥Task<string[]> GetAllAsync();//移除密钥Task RemoveAsync(string apiKey);
}

InitAsync方法用以初始化密钥,GetRandomAsync方法用于随机读取一个密钥,GetAllAsync方法用于读取所有密钥,RemoveAsync方法用于删除指定密钥。

2、实现IChatGPTKeyService服务

安装StackExchange.Redis库,这是一个用于访问和操作Redis数据库的.NET客户端。

PM> Install-Package StackExchange.Redis

右键点击Extensions文件夹,新建一个ChatGPTKeyService.cs文件,并在文件中写入以下代码:

using StackExchange.Redis;public class ChatGPTKeyService : IChatGPTKeyService
{private ConnectionMultiplexer? _connection;private IDatabase? _cache;private readonly string _configuration;private const string _redisKey = "ChatGPTKey";public ChatGPTKeyService(string configuration){_configuration = configuration;}private async Task ConnectAsync(){if (_cache != null) return;_connection = await ConnectionMultiplexer.ConnectAsync(_configuration);_cache = _connection.GetDatabase();}public async Task InitAsync(){await ConnectAsync();//使用Set对象存储密钥await _cache!.SetAddAsync(_redisKey, new RedisValue[] {"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1","sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2",});}public async Task<string> GetRandomAsync(){await ConnectAsync();//使用Set随机返回一个密钥var redisValue = await _cache!.SetRandomMemberAsync(_redisKey);return redisValue.ToString();}public async Task<string[]> GetAllAsync(){await ConnectAsync();//读取所有密钥var redisValues = await _cache!.SetMembersAsync(_redisKey);return redisValues.Select(m => m.ToString()).ToArray();}public async Task RemoveAsync(string apiKey){await ConnectAsync();await _cache!.SetRemoveAsync(_redisKey, apiKey);}
}

为了保存KEY,我们选择使用Redis的Set数据结构,它可以存储不重复的元素,并且可以随机返回一个元素。这样,我们就可以实现密钥的随机轮换功能。ConnectAsync方法是用来建立和Redis数据库的连接。

接下来,我们打开Program.cs文件注册ChatGPTKeyService服务。另外,为了演示效果,我们需要在项目启动的时候,调用InitAsync方法来初始化数据:

using ChatGPT.Demo4.Extensions;
//注册IChatGPTKeyService单例服务
builder.Services.AddSingleton<IChatGPTKeyService>(new ChatGPTKeyService("localhost"));var app = builder.Build();
//初始化redis数据库
var _chatGPTKeyService = app.Services.GetRequiredService<IChatGPTKeyService>();
_chatGPTKeyService.InitAsync().Wait();

Betalgo.OpenAI提供了两种使用方式,一种是依赖注入,一种是非依赖注入。之前我们采用的是依赖注入方式,大家会发现,依赖注入并不支持多KEY的设置,为此,我们先来看看如何使用非依赖注入的方式实现。

//Betalgo.OpenAI地址:https://github.com/betalgo/openai

二、 非依赖注入实现密钥轮换

1、取消IOpenAIService服务注册

我们先打开Program.cs文件,把IOpenAIService服务的注册代码注释掉。

2、取消IOpenAIService依赖注入

打开Controllers/ChatController.cs文件,在文件开头添加IChatGPTKeyService服务的命名空间,然后在构造函数中注入该服务。同时,我们把IOpenAIService服务的注入也注释掉。

using ChatGPT.Demo4.Extensions;//private readonly IOpenAIService _openAiService;
private readonly IChatGPTKeyService _chatGPTKeyService;public ChatController(/*IOpenAIService openAiService,*/ IChatGPTKeyService chatGPTKeyService)
{//_openAiService = openAiService;_chatGPTKeyService = chatGPTKeyService;
}

3、手动实例化IOpenAIService

接着修改Input方法,先调用IChatGPTKeyService中的GetRandomAsync方法,获取一个随机的密钥。然后,使用这个密钥来手动创建一个IOpenAIService服务的实例。

string apiKey = await _chatGPTKeyService.GetRandomAsync();
IOpenAIService _openAiService = new OpenAIService(new OpenAiOptions
{ApiKey = apiKey
});

这样,通过非依赖注入方式,我们已经实现了ChatGPT的多KEY动态轮询功能,但是这种方式没有利用.Net Core的依赖注入机制,无法发挥它的优势。那么,有没有可能用依赖注入的方式来达到同样的效果呢?答案是肯定的,让我们继续。

三、 依赖注入实现密钥轮换

Betalgo.OpenAI请求是基于HttpClient来实现的,这给我们实现多KEY切换带来了希望。

DelegatingHandler是一个抽象类,它继承自HttpMessageHandler,用于处理HTTP请求和响应。它的特点是可以将请求和响应的处理委托给另一个处理程序,称为内部处理程序。通常,一系列的DelegatingHandler被链接在一起,形成一个处理程序链。第一个处理程序接收一个HTTP请求,做一些处理,然后将请求传递给下一个处理程序,这种模式被称为委托处理程序模式。

HttpClient默认使用HttpClientHandler处理程序来处理请求,HttpClientHandler继承自HttpMessageHandler,它重写了HttpMessageHandler的Send方法,负责将请求通过网络发送到服务器并获取服务器的响应。因此,我们可以在管道中插入自定义的DelegatingHandler,来拦截修改请求头中的密钥,实现多KEY轮换的功能。

1、创建DelegatingHandler

要编写一个自定义的DelegatingHandler,我们需要继承System.Net.Http.DelegatingHandler类,并重写它的Send方法。

我们在Extensions文件夹中创建一个名为ChatGPTHttpMessageHandler.cs的文件,然后在其中添加以下代码:

    public class ChatGPTHttpMessageHandler : DelegatingHandler
{private readonly IChatGPTKeyService _chatGPTKeyService;public ChatGPTHttpMessageHandler(IChatGPTKeyService  chatGPTKeyService){_chatGPTKeyService = chatGPTKeyService;}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){var apiKey = await _chatGPTKeyService.GetRandomAsync();request.Headers.Remove("Authorization");request.Headers.Add("Authorization", $"Bearer {apiKey}");return await base.SendAsync(request, cancellationToken);}protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken){var apiKey = _chatGPTKeyService.GetRandomAsync().Result;request.Headers.Remove("Authorization");request.Headers.Add("Authorization", $"Bearer {apiKey}");return base.Send(request, cancellationToken);}}

在ChatGPTHttpMessageHandler中,我们通过依赖注入的方式获取IChatGPTKeyService密钥服务的实例,然后重写了Send方法,调用IChatGPTKeyService的GetRandomAsync方法随机获取一个KEY,接着使用HttpHeaders的Remove方法移除默认的KEY,再使用HttpHeaders的Add方法添加获取的KEY,最后我们调用base.SendAsync方法将请求传递给内部处理程序进行后续的处理。这样我们就完成了KEY的切换。

2、注册DelegatingHandler

接下来,我们需要在Program.cs文件中,将ChatGPTHttpMessageHandler处理程序注册到OpenAIService的请求管道中。

builder.Services.AddTransient<ChatGPTHttpMessageHandler>();
builder.Services.AddHttpClient<IOpenAIService, OpenAIService>().AddHttpMessageHandler<ChatGPTHttpMessageHandler>();

3、重新注册IOpenAIService服务

同时取消Program.cs文件中OpenAIService服务的注释。

4、恢复IOpenAIService依赖注入

最后在Controllers/ChatController.cs中,我们重新使用依赖注入的方式获取OpenAIService服务的实例,同时注释掉手动创建OpenAIService的代码。

动态删除无效KEY

当ChatGPT账号使用达到额度上限时,KEY将会失效,为此,我们需要及时删除无效的KEY,避免影响请求的正常发送。但比较遗憾,OpenAI官方并没有提供直接的API来查询额度,那么,我们怎么知道KEY是否还有效呢?

幸运的是,有大神通过抓包分析发现了两个可用的接口,可以用来查询KEY的相关信息,一个是账单查询API,用来查询KEY的过期时间和剩余额度,它接受GET请求,在Header中带上授权Token(API KEY)即可。

//账单查询API:https://api.openai.com/v1/dashboard/billing/subscription

另一个是账单明细查询,用来查询已使用的额度和具体的请求记录,它也是一个GET请求,在Header中同样需要携带授权Token(API KEY),另外还可以通过参数指定要查询的日期范围。

//账单明细:https://api.openai.com/v1/v1/dashboard/billing/usage?start_date=2023-07-01&end_date=2023-07-02

1、创建ChatGPT账单查询服务

我们在Extensions文件夹中创建IChatGPTBillService.cs接口和ChatGPTBillService.cs服务两个文件,IChatGPTBillService接口声明了账单及明细查询两个方法,代码如下:

public interface IChatGPTBillService
{/// <summary>/// 查询账单/// </summary>/// <param name="apiKey">api密钥</param>/// <returns></returns>Task<ChatGPTBillModel?> QueryAsync(string apiKey);/// <summary>/// 账单明细/// </summary>/// <param name="apiKey">api密钥</param>/// <param name="startTime">开始日期</param>/// <param name="endTime">结束日期</param>/// <returns></returns>Task<ChatGPTBillDetailsModel?> QueryDetailsAsync(string apiKey, DateTimeOffset startTime, DateTimeOffset endTime);
}

ChatGPTBillService服务是IChatGPTBillService接口的实现,代码如下所示:

public class ChatGPTBillService : IChatGPTBillService
{private readonly IHttpClientFactory _httpClientFactory;public ChatGPTBillService(IHttpClientFactory httpClientFactory){_httpClientFactory = httpClientFactory;}public async Task<ChatGPTBillModel?> QueryAsync(string apiKey){string url = "https://api.openai.com/v1/dashboard/billing/subscription";var client = _httpClientFactory.CreateClient();client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");var response = await client.GetFromJsonAsync<ChatGPTBillModel>(url);return response;}public async Task<ChatGPTBillDetailsModel?> QueryDetailsAsync(string apiKey, DateTimeOffset startTime, DateTimeOffset endTime){string url = $"https://api.openai.com/dashboard/billing/usage?start_date={startTime:yyyy-MM-dd}&end_date={endTime:yyyy-MM-dd}";var client = _httpClientFactory.CreateClient();client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");var response = await client.GetFromJsonAsync<ChatGPTBillDetailsModel>(url);return response;}
}

ChatGPTBillService通过使用IHttpClientFactory工厂创建HttpClient来发送请求,并在请求头中添加ChatGPT的授权Token,即API KEY,从而实现对ChatGPT的账单和明细的查询功能。考虑到篇幅长度,这里不再给出账单类ChatGPTBillModel和账单明细类ChatGPTBillDetailsModel的具体定义。

2、创建后台任务过滤无效KEY

我们使用BackgroundService来实现自动过滤任务,BackgroundService是.NET Core中的一个抽象基类,它实现了IHostedService接口,用于执行后台任务或长时间运行的服务。BackgroundService类提供了以下方法:

  • StartAsync (CancellationToken):在服务启动时调用,可以用于执行一些初始化操作。
  • StopAsync (CancellationToken):在服务停止时调用,可以用于执行一些清理操作。
  • ExecuteAsync (CancellationToken):在服务运行时调用,包含了后台任务的主要逻辑,必须被重写

我们创建一个后台定时任务,在ExecuteAsync方法中执行ChatGPT的密钥过滤。在Extensions文件夹中新建一个名为ChatGPTBillBackgroundService.cs的文件,并在其中添加如下代码:

public class ChatGPTBillBackgroundService : BackgroundService
{private readonly IChatGPTKeyService _chatGPTKeyService;private readonly IChatGPTBillService _chatGPTBillService;public ChatGPTBillBackgroundService(IChatGPTKeyService chatGPTKeyService, IChatGPTBillService chatGPTBillService){_chatGPTKeyService = chatGPTKeyService;_chatGPTBillService = chatGPTBillService;}protected override async Task ExecuteAsync(CancellationToken stoppingToken){while (!stoppingToken.IsCancellationRequested){var apiKeys = await _chatGPTKeyService.GetAllAsync();foreach (var apiKey in apiKeys){var bill = await _chatGPTBillService.QueryAsync(apiKey);if (bill == null) continue;var dt = DateTimeOffset.Now;//判断key是否到期或是否有额度if (bill.AccessUntil < dt.ToUnixTimeSeconds() || bill.HardLimitUsd == 0){await _chatGPTKeyService.RemoveAsync(apiKey);continue;}//查询99天以内的账单明细var billDetails = await _chatGPTBillService.QueryDetailsAsync(apiKey, dt.AddDays(-99), dt.AddDays(1));if (billDetails == null) continue;//判断已使用额度大于等于总额度if (billDetails.TotalUsage >= bill.HardLimitUsd){await _chatGPTKeyService.RemoveAsync(apiKey);continue;}}// 创建一个异步的任务,该任务在指定1分钟间隔后完成await Task.Delay(1 * 60 * 1000, stoppingToken);}}
}

ChatGPTBillBackgroundService类继承自BackgroundService,并通过构造函数注入了IChatGPTKeyService密钥服务和IChatGPTBillService账单服务,然后重写了ExecuteAsync方法,通过使用while循环和Task.Delay方法间接实现每分钟执行一次的定时任务,任务的逻辑是:从缓存中获取所有密钥,然后对每个密钥进行以下操作:

  • 调用IChatGPTBillService服务,查询密钥的有效期和总额度。
  • 如果密钥已过期或总额度为零,就从缓存中移除该密钥。
  • 如果密钥仍有效,就继续调用IChatGPTBillService服务,查询密钥的已使用额度。
  • 如果已使用额度大于或等于总额度,就从缓存中移除该密钥。

为了让这个后台服务能够在系统启动时运行,我们还需要在Program.cs文件中注册它。打Program.cs文件,加入下面的代码:

//注册账单服务
builder.Services.AddSingleton<IChatGPTBillService, ChatGPTBillService>();
//注册后台任务
builder.Services.AddHostedService<ChatGPTBillBackgroundService>();

至此,我们完成了ChatGPT的多KEY动态轮询,和自动删除无效KEY的功能实现。

写作不易,转载请注明博文地址,否则禁转!!!

//源码地址:https://github.com/ynanech/ChatGPT.Demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/14505.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.3 - 操作系统 - firewalld防火墙iptables防火墙

「作者简介」:CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「订阅专栏」:此文章已录入专栏《网络安全入门到精通》 Linux防火墙 Frewalld1、常用操作2、开放/关闭服务3、开放/关闭端口4、IP访问端口规则5、安全域Iptables1、常用操作2、四表五链cent…

再见操作系统!ChatGPT和Windows直接在一起了

来源&#xff1a;水木学堂 二十世纪初&#xff0c;微软曾发起过一场“浏览器战争”&#xff0c;用 IE 4.0 成功打赢了浏览器市场份额超过 90% 以上的网景浏览器。当时&#xff0c;微软用的就是“将 IE 放在 Windows 98 ”这样的“禁忌武器”&#xff0c;还因此遭到了日后的反垄…

ChatGPT封杀潮,禁入学校,AI顶会特意改规则,LeCun:要不咱把小模型也禁了?...

2023 点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 计算机视觉研究院专栏 作者&#xff1a;Edison_G 狂欢之后&#xff0c;事情的走向开始进入封杀这一过程。 公众号ID&#xff5c;ComputerVisionGzq 学习群&#xff5c;扫码在主页获取加入方式 转自《机器之心》…

VPS(Linux)解决ChatGPT Access Denied(错误码1020)方法

本文参考了GitHub的一个开源项目&#xff0c;项目地址:https://github.com/fscarmen/warp 操作方法: 以下两个脚本二选一&#xff0c;部署完成后记得重启VPS VPS嵌套WARP后&#xff0c;建议开启BBR&#xff0c;能够有效降低延迟 WARP部署脚本: wget -N https://raw.githubu…

奶奶版ChatGPT炸了!背刺微软泄露Win11秘钥!

教坏一个大模型的成本实在太低了&#xff01; 大家都知道&#xff0c;ChatGPT本身可以制造“幻觉”&#xff0c;却也原来如此容易被“情感”所利用&#xff01;只要故事讲的好&#xff0c;让ChatGPT为你摘星星都没问题&#xff01;万万没想到&#xff0c;通过让ChatGPT扮演一个…

第一批因ChatGPT坐牢的人,已经上路了

大家好&#xff0c;我是 Jack。 ChatGPT 的火爆有目共睹&#xff0c;有人靠着它赚了第一桶金&#xff0c;也有人靠着它即将吃上第一顿牢饭。 任何一件东西的火爆&#xff0c;总会给一些聪明人带来机会。 艾尔登法环火的时候&#xff0c;一堆淘宝卖魂的&#xff1b;羊了个羊火…

机器学习--最小二乘法

补充&#xff1a; 一. 简介 最小二乘法&#xff08;又称最小平方法&#xff09;是一种数学优化技术。它通过最小化误差的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数据&#xff0c;并使得这些求得的数据与实际数据之间误差的平方和为最小。最小二乘法…

java最后问面试官什么问题,大量教程

魔鬼面试官必问:ConcurrentHashMap 线程安全吗?但面对魔鬼面试官时,我们更在乎的是这些真的正确吗? 1 线程重用导致用户信息错乱生产环境中,有时获取到的用户信息是别人的。查看代码后 为方便观察问题,我们输出了这个Map一开始和最后的元素个数。 师兄大厂面试遇到面试官的Ka…

计算机网络参考模型及协议

目录 一、计算机网络概述 1.1计算机网络与通信 1.2计算机IP地址与Mac地址 1.3计算机网络相关术语 1.4计算机网络相关设备 1.5计算机网络分类 二、计算机网络分层 2.1计算机网络分层的必要性 三、OSI七层参考模型 3.1应用层 3.2表示层 3.3会话层 3.4传输层 3.5网络…

纪念成为博客专家

一、前言 是的&#xff0c;一直到现在都有点蒙蒙的。从上周六提交申请之后&#xff0c;一直没有消息&#xff0c;博主自己都放弃了。这是第四次申请&#xff0c;虽然申请了很多次&#xff0c;但内心一直有些打退堂鼓&#xff0c;觉得自己的技术水平并不到位&#xff0c;其他的专…

你们都去养猪,我还写前端,然后自费送签名书

文/北妈 阅读本文需要 2.1分钟 这是北妈第 221篇 原创文章 一 我们的口号是&#xff1f; 忘了&#xff1f;周五不加班&#xff0c;周五不加班&#xff0c;不加班&#xff01; 今天北妈我就说两点&#xff0c;说完去吃饭。 1、最近朋友圈、大V们&#xff0c;人人都在养猪&#x…

惊呆了!Java程序员最常犯的错竟然是这10个

和绝大多数的程序员一样&#xff0c;我也非常的宅。周末最奢侈的享受就是逛一逛技术型网站&#xff0c;比如说 programcreek&#xff0c;这个小网站上有一些非常有意思的主题。比如说&#xff1a;Java 程序员最常犯的错竟然是这 10 个&#xff0c;像这类令人好奇心想害死猫的主…

spring boot从0到实战 全

前言&#xff1a;看到这篇博客的小可爱们&#xff0c;这篇博客是我自己从0到实战的笔记&#xff0c;后面我会附上我整个过程的源码给大家参考&#xff0c;一起加油把。 同时&#xff0c;这是上一篇博客&#xff0c;SpringBoot详解&#xff0c;完整版。从0到1&#xff01;&#…

FFT蝶形算法的verilog实现专题——从FFT算法的定义开始入手

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 FFT蝶形算法的verilog实现专题——从FFT算法的定义开始入手 先从FFT的定义入手&#xff1a; 一个N 点序列x(n) 的DFT 定义为: 下面用MATLAB程序来面熟一下上述这个式子&…

SAP 电商云 Spartacus UI 修改 Delivery Mode 触发的三个 HTTP 请求

LoaderState&#xff1a; loading 状态在 true 和 false 之间的切换&#xff0c;通过 loader .reducer.ts 里的 reducer 函数进行。每次通过 store.dispatch 往 store 里投递新的 action 时&#xff0c;都会触发该 reducer 的执行。 添加上打印日志&#xff1a; 设置 delive…

再送签名书20本、红包封面10000个

大家好&#xff0c;我是北妈 福利又来了&#xff0c;由于北妈红包封面抢手&#xff0c;又增加了 10000个&#xff0c;没领到的赶紧转发&#xff0c;让亲朋好友来领取。 然后下面送签名书喽。 1、送书 又到了送北妈签名书的时候&#xff0c;凑着要放假&#xff0c;赶紧上车关门&…

vue3.0 非常面熟的错误

1&#xff0c;标签错误 ​​​​ 错误文件所在的目录 \src\views\HomeView.vue:2:3 没有结束标签~ 2.编译问题 Compiled with problems: 编译问题 C:\myel\src\views\HomeView.vue 错误出现文件 3:1 error Mixed spaces and tabs no-mixed-spaces-and-tabs 4:1 error M…

Chat GPT5的主要介绍

Chat GPT-5是一种基于人工智能技术的对话系统&#xff0c;用于进行自然语言处理和对话&#xff0c;以提供更好的服务。 它是由OpenAI公司开发的&#xff0c;是GPT系列的最新版本。 GPT代表着"生成式预训练"&#xff0c;因此Chat GPT-5基于神经网络&#xff0c;通过预…

ChatGPT:探索RLHF与GPT的完美结合

前言 ChatGPT已经发布一周了热度依旧不减&#xff0c;ChatGPT也各种大显神通&#xff0c;为各大网友“出谋划策”&#xff0c;有写周报的&#xff0c;有写绩效的甚至还有写论文的&#xff0c;作为一个NLP从业者&#xff0c;除了好好体验下其中的乐趣&#xff0c;其背后的原理当…

一场场网络时代的“墨攻”

编辑&#xff1a;阿冒 设计&#xff1a;沐由 公输般为楚造云梯之械成&#xff0c;将以攻宋。子墨子闻之&#xff0c;起于鲁&#xff0c;行十日十夜&#xff0c;而至于郢&#xff0c;见公输般。 子墨子解带为城&#xff0c;以牒为械&#xff0c;公输般九设攻城之机变&#xff0c…