VI ▾

API LLM không kiểm duyệt tương thích OpenAIuncensoredapis.com

Trang chủLỗi

Lỗi và Giới hạn tốc độ: Hướng dẫn thực tế cho Uncensored API

Mã production dành nhiều thời gian hơn cho các đường dẫn lỗi hơn là đường dẫn thành công. Bộ hướng dẫn này liệt kê từng mã trạng thái mà Uncensored API trả về, chỉ ra những mã nào nên thử lại, và cung cấp cách giảm tải phía máy khách cho giới hạn 300 yêu cầu mỗi phút trong Python asyncio và Node, cùng một cách nhẹ nhàng để hiển thị lỗi 402 cho người dùng của bạn.

Cập nhật

Điểm chính

  1. Chỉ thử lại 429, 503 và lỗi truyền tải; sửa 400, 401, 403, 404 và 402 tại nguyên nhân gốc.
  2. Lỗi được trả về dưới dạng JSON với một đối tượng lỗi chứa mã và thông báo, vì vậy hãy rẽ nhánh dựa trên trạng thái trước, sau đó mới đến mã.
  3. Một bucket token nạp lại 5 token mỗi giây giữ một khóa dưới 300 yêu cầu mỗi phút mà không bị từ chối hoặc giảm tốc độ.
  4. 402 có nghĩa là số dư trả trước đã hết hoặc thời gian dùng thử đã kết thúc; hãy coi đó là trạng thái sản phẩm, không phải lỗi sập.

Bao lỗi

Mỗi lỗi trả về một thân JSON với một đối tượng error cấp cao duy nhất. Hai trường quan trọng: code, một chuỗi máy ngắn, và message, văn bản dùng cho nhật ký và con người.

{
  "error": {
    "code": "no_credit",
    "message": "Balance used up or trial expired."
  }
}

Hãy quyết định theo thứ tự này. Trước tiên hãy xem trạng thái HTTP vì nó cho bạn biết lớp vấn đề. Chỉ sử dụng code để phân biệt các trường hợp có cùng trạng thái, và không bao giờ phân tích message cho logic, vì cách diễn đạt có thể thay đổi. Để biết cấu trúc của phản hồi thành công, hãy xem tài liệu tham chiếu API.

Mã trạng thái, nguyên nhân và hành động

Trạng tháiMãNguyên nhân điển hìnhThử lại?Hành động
400yêu cầu không hợp lệJSON không hợp lệ, hoặc số token prompt cộng với max_tokens lớn hơn 100.000KhôngSửa payload; cắt ngắn lịch sử hoặc giảm max_tokens
401khóa không hợp lệThiếu header, lỗi chính tả hoặc khóa đã được tạo lạiKhôngTải lại khóa từ kho bí mật của bạn
402no_creditSố dư đã hết hoặc thời gian dùng thử miễn phí đã hết hạnKhôngNạp tiền vào số dư trả trước, sau đó tiếp tục
403content_blockedYêu cầu chạm vào nội dung tình dục liên quan đến trẻ vị thành niênKhôngKhông gửi lại; hãy hiển thị thông báo chính sách của riêng bạn
404không tìm thấyĐường dẫn sai, chẳng hạn thiếu tiền tố /v1KhôngKiểm tra Base URL
429giới hạn tốc độHơn 300 yêu cầu trong một phút trên một khóaCóBackoff với jitter; thêm giới hạn tốc độ phía máy khách
503upstream_busyÁp lực dung lượng tạm thờiCóThử lại sau vài giây

Lỗi 403 cần được nhấn mạnh: đây là quyết định về chính sách, không phải lỗi tạm thời. Thử lại cùng một yêu cầu sẽ lãng phí quota và sẽ trả về cùng một kết quả. Trường hợp 400 khiến nhiều người ngạc nhiên nhất là tổng số token. Một prompt 60.000 token với max_tokens là 8.000 sẽ tràn thêm 4.000 token và thất bại mặc dù mỗi con số đều hợp lệ khi đứng riêng.

Một chính sách thử lại hoạt động

Sử dụng một chính sách thử lại nhỏ, có giới hạn và chỉ áp dụng ở những nơi việc thử lại thực sự hữu ích.

  1. Cho phép thử lại các lỗi có thể thử lại: 429, 503, kết nối bị đặt lại và quá thời gian chờ. Mọi lỗi khác sẽ được ném ra ngay lập tức.
  2. Giãn nở theo cấp số nhân với nhiễu: chờ 1, 2, 4, 8 giây cộng thêm một phần ngẫu nhiên của một giây, giới hạn tối đa ở 30. Nhiễu ngăn nhiều worker thử lại cùng một lúc.
  3. Giới hạn số lần thử lại ở năm. Vượt quá mức đó, sự cố hệ thống có khả năng cao hơn là do xui xẻo, và công việc bị xếp hàng nên bị lỗi rõ ràng.
  4. Tắt tự động thử lại của SDK (max_retries=0) khi bạn chạy vòng lặp của riêng mình, nếu không các lần thử sẽ nhân lên và bộ giới hạn tốc độ của bạn sẽ mất kiểm soát.
  5. Thử lại luồng từ đầu. Một luồng bị lỗi ở giữa không thể tiếp tục; hãy loại bỏ văn bản bị lỗi và gửi lại yêu cầu, hoặc giữ lại và yêu cầu phần tiếp theo.

Với mã 503, hướng dẫn là chờ vài giây, vì vậy hãy bắt đầu độ trễ đầu tiên ở mức một hoặc hai giây thay vì gửi yêu cầu liên tục. Với mã 429, giải pháp là giảm tốc độ, điều này sẽ được tự động hóa ở phần tiếp theo.

Giữ dưới 300 yêu cầu mỗi phút

Giới hạn là 300 yêu cầu mỗi phút trên mỗi khóa, tương đương năm yêu cầu mỗi giây. Một bucket token thực hiện chính xác điều đó: nó giữ tối đa một lượng nhỏ token, nạp lại năm token mỗi giây và mỗi yêu cầu sẽ tiêu tốn một token. Nếu bucket trống, người gọi sẽ chờ cho đến khi có token.

Phiên bản Python sử dụng asyncio. Tất cả hai mươi tác vụ chia sẻ một bucket và một khóa, vì vậy các coroutine đồng thời không thể rút quá mức. Lưu ý rằng bucket đếm mọi lần thử, bao gồm cả các lần thử lại.

import asyncio, os, random, time
from openai import AsyncOpenAI, APIStatusError, APIConnectionError

class TokenBucket:
    """Allows `rate` acquisitions per `per` seconds, with a small burst."""
    def __init__(self, rate=300, per=60.0, burst=10):
        self.fill = rate / per          # tokens added per second (5.0)
        self.cap = burst
        self.tokens = burst
        self.stamp = time.monotonic()
        self.lock = asyncio.Lock()

    async def acquire(self):
        async with self.lock:
            while True:
                now = time.monotonic()
                self.tokens = min(self.cap, self.tokens + (now - self.stamp) * self.fill)
                self.stamp = now
                if self.tokens >= 1:
                    self.tokens -= 1
                    return
                await asyncio.sleep((1 - self.tokens) / self.fill)

client = AsyncOpenAI(base_url="https://api.uncensoredapis.com/v1",
                     api_key=os.environ["API_KEY"], max_retries=0)
bucket = TokenBucket()

RETRYABLE = {429, 503}

async def ask(prompt, attempts=5):
    for n in range(attempts):
        await bucket.acquire()
        try:
            r = await client.chat.completions.create(
                model="uncensored",
                messages=[{"role": "user", "content": prompt}],
                max_tokens=200,
            )
            return r.choices[0].message.content
        except APIStatusError as e:
            if e.status_code not in RETRYABLE or n == attempts - 1:
                raise
        except APIConnectionError:
            if n == attempts - 1:
                raise
        await asyncio.sleep(min(30, 2 ** n) + random.uniform(0, 1))

async def main():
    prompts = [f"Give one-line fact number {i} about tides." for i in range(1, 21)]
    results = await asyncio.gather(*(ask(p) for p in prompts))
    print(len(results), "answers; first:", results[0])

asyncio.run(main())

Chạy nó với API_KEY đã được xuất. Hai mươi yêu cầu vừa với giới hạn burst cộng thêm vài giây nạp lại, vì vậy bạn sẽ thấy nó kết thúc nhanh chóng; tăng số lượng lên 600 và thời gian chạy kéo dài khoảng hai phút, đây là giới hạn đang hoạt động đúng chức năng của nó.

Phiên bản Node, sử dụng gói chính thức trong một ES module, chuỗi các lần mua thông qua một hàng đợi promise để các caller được phục vụ theo thứ tự.

import OpenAI from "openai";

class TokenBucket {
  constructor(rate = 300, perMs = 60000, burst = 10) {
    this.fillPerMs = rate / perMs;
    this.cap = burst;
    this.tokens = burst;
    this.stamp = Date.now();
    this.queue = Promise.resolve();
  }
  acquire() {
    // Serialize callers so the bucket math never races.
    this.queue = this.queue.then(async () => {
      for (;;) {
        const now = Date.now();
        this.tokens = Math.min(this.cap, this.tokens + (now - this.stamp) * this.fillPerMs);
        this.stamp = now;
        if (this.tokens >= 1) { this.tokens -= 1; return; }
        await new Promise((r) => setTimeout(r, Math.ceil((1 - this.tokens) / this.fillPerMs)));
      }
    });
    return this.queue;
  }
}

const client = new OpenAI({
  baseURL: "https://api.uncensoredapis.com/v1",
  apiKey: process.env.API_KEY,
  maxRetries: 0,
});
const bucket = new TokenBucket();

async function ask(prompt, attempts = 5) {
  for (let n = 0; n < attempts; n++) {
    await bucket.acquire();
    try {
      const r = await client.chat.completions.create({
        model: "uncensored",
        messages: [{ role: "user", content: prompt }],
        max_tokens: 200,
      });
      return r.choices[0].message.content;
    } catch (err) {
      const retryable = err.status === 429 || err.status === 503 || err.status === undefined;
      if (!retryable || n === attempts - 1) throw err;
      await new Promise((r) => setTimeout(r, Math.min(30000, 2 ** n * 1000) + Math.random() * 1000));
    }
  }
}

const prompts = Array.from({ length: 20 }, (_, i) => `Give one-line fact number ${i + 1} about tides.`);
const answers = await Promise.all(prompts.map((p) => ask(p)));
console.log(answers.length, "answers; first:", answers[0]);

Đặt kích thước burst một cách thận trọng. Một burst 10 cho phép một đỉnh ngắn trong khi tốc độ duy trì vẫn ở năm yêu cầu mỗi giây. Nếu nhiều quy trình chia sẻ một khóa, hãy chia tốc độ cho chúng, ví dụ 100 mỗi phút trên ba worker, hoặc định tuyến tất cả lưu lượng qua một hàng đợi.

Xử lý lỗi 402 một cách khéo léo trong sản phẩm của bạn

Lỗi 402 với mã no_credit có nghĩa là số dư trả trước đã đạt mức không hoặc tín dụng dùng thử miễn phí đã hết hạn. Đây là lỗi duy nhất mà người dùng cuối của bạn không bao giờ nên thấy dưới dạng dấu vết ngăn xếp (stack trace), và nó thường báo hiệu điều gì đó chỉ bạn mới có thể khắc phục: nạp tiền.

Xây dựng ba lớp. Đầu tiên, detect nó tập trung trong một lớp bao (wrapper) để không vị trí gọi nào phải ghi nhớ. Thứ hai, translate nó: trình duyệt hoặc ứng dụng di động nhận được trạng thái trung tính như capacity_paused và hiển thị một màn hình thân thiện, không bao giờ là thông báo từ phía máy chủ. Thứ ba, alert chính bạn, vì mọi người dùng đều bị ảnh hưởng cùng một lúc; hãy gửi thông báo cho chủ tài khoản và bật cờ tính năng (feature flag) để giao diện người dùng ngừng gửi yêu cầu.

# server side: translate the upstream 402 into a product state
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from openai import AsyncOpenAI, APIStatusError
import os

app = FastAPI()
client = AsyncOpenAI(base_url="https://api.uncensoredapis.com/v1", api_key=os.environ["API_KEY"])

@app.post("/api/reply")
async def reply(body: dict):
    try:
        r = await client.chat.completions.create(
            model="uncensored", messages=body["messages"], max_tokens=400)
        return {"text": r.choices[0].message.content}
    except APIStatusError as e:
        if e.status_code == 402:
            # Tell the client app to show a "temporarily unavailable" screen,
            # and page yourself to top up the prepaid balance.
            return JSONResponse({"state": "capacity_paused"}, status_code=503)
        if e.status_code == 429 or e.status_code == 503:
            return JSONResponse({"state": "busy_retry"}, status_code=503)
        raise

Thêm bảo vệ chủ động. Tổng usage.total_tokens cho mỗi yêu cầu vào sổ cái của riêng bạn và cảnh báo khi đạt ngưỡng bạn chọn, để việc nạp tiền xảy ra trước khi người dùng bị chặn. Hãy nhớ rằng tín dụng dùng thử miễn phí là $0,50 và kéo dài 7 ngày, vì vậy một nguyên mẫu có thể gặp lỗi 402 chỉ do hết hạn trong khi tiền vẫn chưa được chi tiêu. Tỷ lệ để lập kế hoạch có trên trang bảng giá.

Khi số dư lại dương, hãy phát lại công việc bạn đã lên hàng đợi trong khi tạm dừng. Lớp bao của bạn nên xóa cờ tạm dừng khi thành công đầu tiên.

Những gì cần ghi log và cảnh báo

Nhật ký tốt làm cho sự cố ngắn lại. Hãy ghi log, cho mỗi yêu cầu: id yêu cầu của riêng bạn, completion id khi có, trạng thái HTTP, lỗi code, số lần thử và mức sử dụng token. Không bao giờ ghi log tiêu đề Authorization hoặc các prompt đầy đủ chứa dữ liệu cá nhân.

Các cảnh báo hữu ích, theo thứ tự ưu tiên: bất kỳ lỗi 402 nào (ngay lập tức), một chuỗi liên tục các lỗi 401 (một khóa đã được tạo lại chưa được triển khai ở mọi nơi), tỷ lệ 429 cao hơn một vài phần trăm lệnh gọi (bộ giới hạn tốc độ của bạn không phù hợp) và cụm lỗi 503 (giảm tốc độ rộng hơn, hãy cân nhắc một hàng đợi). Một bảng điều khiển hiển thị số lượng trạng thái mỗi phút giúp tất cả bốn loại lỗi này hiển thị rõ ràng trong một cái nhìn tổng quan. Được xử lý tốt, các mã này trở thành tín hiệu thường lệ thay vì sự cố.

Kiểm tra các đường dẫn lỗi của bạn trước khi phát hành

Xử lý lỗi chưa từng chạy là một phỏng đoán. Bạn có thể kiểm tra hầu hết các nhánh mà không cần chờ sự cố thực sự, bằng cách sử dụng các yêu cầu rẻ tiền và các đối tượng giả của riêng bạn.

  • 401: gọi GET /v1/models với một khóa sai cố ý và xác nhận ứng dụng của bạn hiển thị lỗi cấu hình cho nhà điều hành, không phải cho khách hàng.
  • 404: yêu cầu /v1/chat/completion (số ít) một lần và xác nhận dòng nhật ký đặt tên cho đường dẫn, vì vậy một lỗi chính tả trong URL cơ bản sẽ rõ ràng trong vài phút.
  • 400: gửi max_tokens là 32.000 với một prompt được đệm đến khoảng 50.000 token. Tổng vượt quá 100.000, và bạn có thể theo dõi đường dẫn xác thực kích hoạt.
  • 429 và 503: bạn không thể kích hoạt các lỗi này theo yêu cầu, vì vậy hãy giả lập chúng. Bao bọc máy khách HTTP của bạn trong một giả (fake) để nâng trạng thái cho hai lần gọi đầu tiên, sau đó thành công, và xác nhận rằng vòng lặp của bạn ngủ, thử lại và trả về câu trả lời cuối cùng.
  • 402: giả lập nó và xác nhận giao diện người dùng hiển thị màn hình tạm dừng, cảnh báo kích hoạt và không có gì thử lại trong một vòng lặp chặt chẽ.

Viết chúng dưới dạng các bài kiểm tra tự động chạy trong tích hợp liên tục. Bài kiểm tra dựa trên bảng với một hàng cho mỗi trạng thái từ bảng trên mất vài phút để viết và bảo vệ chính sách khỏi các lần tái cấu trúc đầy thiện ý. Cuối cùng, chạy một bài kiểm tra tải ngắn chống lại bộ giới hạn tốc độ của bạn với API bị giả lập: gửi 1.000 yêu cầu và xác nhận rằng không có cửa sổ 60 giây nào chứa nhiều hơn 300 yêu cầu cộng với burst. Điều đó chứng minh phép tính trước khi lưu lượng thực sự phụ thuộc vào nó. Khi bạn sau đó đọc hướng dẫn vòng lặp agent, hãy sử dụng lại cùng một lớp bọc ở đó, vì agent nhân lên số lượng yêu cầu và chạm vào các giới hạn này trước.

Hỏi đáp

Mã 429 có tính vào số dư của tôi không?

Hãy kiểm tra các số liệu sử dụng trong sổ cái của riêng bạn thay vì giả định. Dù bằng cách nào, giải pháp là giảm tốc độ và thử lại với backoff.

Tôi có nên thử lại phản hồi 403 content_blocked không?

Không. Đó là một khối chính sách và một yêu cầu giống hệt nhau sẽ trả về cùng một kết quả. Hãy hiển thị thông báo của riêng bạn và tiếp tục.

Khóa của tôi đột ngột trả về 401. Điều gì đã thay đổi?

Thường nhất là khóa đã được tạo lại, điều này vô hiệu hóa khóa cũ ngay lập tức. Sao chép khóa mới từ tài khoản của bạn và triển khai lại nó.

Có bao nhiêu worker có thể chia sẻ một khóa?

Tất cả đều chia sẻ giới hạn 300 yêu cầu mỗi phút. Hãy chia nhỏ tỷ lệ này cho các worker hoặc điều hướng các lệnh gọi qua một hàng đợi duy nhất.

Khóa của bạn chỉ cách một biểu mẫu

Tạo một tài khoản, sao chép khóa, thay đổi base URL. Đó là toàn bộ thiết lập.