5.1 应对OpenAI接口请求限制

5.1.1 OpenAI限速场景

OpenAI的速率限制是 API 对用户或客户端在指定时间段内访问服务次数施加的限制。 官方称这些限制“有助于预防 API 滥用和保证公平访问”,但是实际上更重要的原因是计算能力在用户数量面前太有限了。

其限制的方式包括:

  • 每分钟请求数(RPM)

  • 每分钟令牌数(TPM)

  • 每天请求数(RPD)

  • 每分钟图像数(IPM)

速率限制是在组织级别实施的,而不是用户级别,并且会根据所使用的模型而有所不同。使用者还会受到每月在 API 上花费的总额的限制,这也称为“使用限制”。

随着使用者对 OpenAI API 的使用增加以及在 API 上的支出增加,OpenAI 会自动将使用者升级到下一个使用层。这通常会增加大多数模型的速率限制。更高层次的使用者还可以访问更低延迟的模型。

我们不考虑达芬奇和其他模型,主要考虑gpt-3.5-turbo和gpt-4这两个常用模型,一个便宜,另一个功能强大,能够覆盖绝大多数场景。 简单的讲一下前期的限制:

MODELRPMRPDTPM

gpt-4

3

200

10,000

gpt-3.5-turbo

3

200

20,000

text-embedding-ada-002

3

200

150,000

whisper-1

3

200

-

tts-1

3

200

-

dall-e-2

5

-

-

dall-e-3

1

-

-

这个阶段比较麻烦,需要多个初级账号做成集群,系统轮流使用这些账号访问接口。 但是好在产生5美金消费以后,就会得到提升。

MODELRPMRPDTPM

gpt-4

500

10000

10000

gpt-3.5-turbo

3500

10000

40000

text-embedding-ada-002

500

10000

1000000

whisper-1

50

-

-

tts-1

50

-

-

dall-e-2

50 img/min

-

-

dall-e-3

5 img/min

-

-

至于怎么到下一个等级,可以看这里:

等级资格金额限制

未消费

用户必须位于允许的地区

每月 100 美元

等级 1

支付 5 美元

每月 100 美元

等级 2

支付 50 美元且距离首次成功付款至少 7 天

每月 500 美元

等级 3

支付 100 美元且距离首次成功付款至少 7 天

每月 1,000 美元

等级 4

支付 250 美元且距离首次成功付款至少 14 天

每月 5,000 美元

等级 5

支付 1,000 美元且距离首次成功付款至少 30 天

每月 10,000 美元

这意味着,其实消费不是重点,主要是受时间限制。 7天,14天是2个不同的档位。 7天消费达到$50以后,就不再有每天请求数(RPD)的限制。 然而到达等级4以后,其实限制只有金额的区别,下面是等级4的限制,等级5的RPM和TPM与其一致。

MODELRPMRPDTPM

gpt-4

10,000

-

300,000

gpt-3.5-turbo

10,000

-

1,000,000

text-embedding-ada-002

10,000

-

5,000,000

whisper-1

100

-

-

tts-1

100

-

-

dall-e-2

100 img/min

-

-

dall-e-3

15 img/min

-

-

个人使用肯定是够用,但生产环境平台级应用显然是无法靠单个账号满足的,同时我们还要规避异常带来的拥堵和用户负面体验。

  • 如果我遇到了速率限制错误会怎样? 速率限制错误看起来像这样: Rate limit reached for default-text-davinci-002 in organization org-{id} on requests per min. Limit: 20.000000 / min. Current: 24.000000 / min. 如果你遇到了速度上线问题,则意味着你在短时间内进行了过多的申请,并且 API 拒绝履行进一步申请直至经过指定时间。

所以通过一套轮询机制和锁机制来调控就显得非常有必要。

5.1.2 Goruntine经典方案

在 Go 中,协程操作效率远高于进程和线程,为了测试阶段的成本考虑,我们先假设同一时间只允许30个调用OpenAI接口的请求。

这时可以使用一个有缓冲的通道来控制同时进行的请求数量,通道可以当作信号量(semaphore),以限制可以同时运行的 goroutine 的数量。

我们看一段简单的实现代码:

package main

import (
    "sync"
    "time"
)

// 请求函数,模拟发送请求
func sendRequest(requestNum int, semaphore chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()

    // 获取信号量的一个位置
    semaphore <- struct{}{}
    
    // 在这里发送 API 请求
    // ...

    println("Request", requestNum, "is being processed.")

    // 模拟请求处理时间
    time.Sleep(10 * time.Second)

    // 请求完成后,从信号量释放位置
    <-semaphore
}

func main() {
    var wg sync.WaitGroup
    semaphore := make(chan struct{}, 30) // 限制为30个并发

    // 模拟大量请求
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go sendRequest(i, semaphore, &wg)
    }

    wg.Wait()
}

在这个代码中:

  • sendRequest的函数来模拟发送请求。

  • 我们创建了一个缓冲为30的通道semaphore,这意味着最多可以有30个空结构体在通道中。

  • 每个 goroutine 在开始处理之前,都会向semaphore发送一个空结构体。如果通道已满,goroutine 会阻塞,直到通道中有空位。

  • 当请求处理完成后,goroutine 会从通道中接收一个值,这会释放一个位置,允许另一个 goroutine 开始执行。

这种方式是利用通道的阻塞特性来实现并发控制,即实现信号量的作用。

这些锁操作只是为了防止触发rate limit,而真要解决问题,还需要靠多账号轮询。

5.1.3 使用账号池轮询来调取API

Last updated