LangChain, (Microsoft) OpenAI und irreführende Rate Limit Fehler (429)

27 Feb. 2025

4 Minuten Lesezeit

Der frustrierende Fehler RateLimitError: Error code: 429 tritt häufig bei der Nutzung der (Azure) OpenAI-API auf, selbst wenn das Limit nicht überschritten wird. Es wird gezeigt, wie eine Batch-Logik dabei helfen kann, die Herausforderungen bei der Arbeit mit LLMs zu meistern.

Der Fehler liegt hier häufig in der Größe der Anfrage. Dabei ist es nicht entscheidend, ob der Zugriff direkt über die von OpenAI bereitgestellte API erfolgt, oder über einen Wrapper wie von Langchain zur Verfügung gestellt wird. Eine Lösung lässt sich meist erst nach langer Internetrecherche finden. Der Grund, ein zu großer Text-Input für einen Request führt zu der irritierenden Fehlermeldung.

Nehmen wir an es soll ein RAG-System aufgebaut werden. Dazu müssen die Informationen, welche als Text vorliegen, in einzelne Chunk zerlegt werden. Anschließend müssen die Chunks mithilfe der Embedding-API in Vektoren überführt werden. Als Input dient dabei eine Liste von Texten oder ein einzelner Text. Wie ein möglicher Anwendungsfall aussehen könnte, kann an dieser Stelle nachgelesen werden.

In Python kann die Embedding-API über den Azure OpenAI Client mit der Funktion embeddings.create() erzeugt werden.

client = AzureOpenAI(
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
    api_version="2024-09-01-preview",
    azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
    )
    
embedding = client.embeddings.create(
    input=text,
    model="EMBEDDING_MODEL",
)

Pro Request an die API darf eine maximale Anzahl an Input-Token nicht überschritten werden. Diese ist von der Länge des Eingabetextes oder der Eingabetexte abhängig. Ein Token ist dabei die kleinste Einheit, in der ein Text zerlegt wird. Ein Token kann ein Wort sein oder ein Teil eines Wortes oder ein Satzzeichen siehe Tokenizer OpenAi.

Tipp: Nicht nur die Größe des gesamten Inputs ist relevant, sondern auch eines einzelnen Textes. Dabei ist zu beachten, dass ein einzelner Text oder ein Text aus einem Array die maximal 8192 Token nicht überschreiten darf (openai-python/src/openai/resources/embeddings.py at main · openai/openai-python).

Bei den von OpenAI bereitgestellten Model zum Embedding “text-embedding-3-large", kommt es bei der Überschreitung des Tokenlimits pro Request zu der Fehlermeldung:

Error code: 429 - {'error': {'code': '429', 'message': 'Requests to the Embeddings_Create Operation under Azure OpenAI API version 2024-08-01-preview have exceeded call rate limit of your current OpenAI S0 pricing tier. Please retry after 86400 seconds. Please go here: Dynamics 365 Customer Voice if you would like to further increase the default rate limit.

Der Fehler hat allerdings nichts mit dem “OpenAI S0 Pricing Tier" zu tun und eine Erhöhung mithilfe des referenzierten Links hilft nicht zur Lösung des Problems. Der Hinweis, es nach 86400 Sekunden oder auch einem Tag erneut zu versuchen, kann somit nicht weiterhelfen. Der Fehler entsteht dadurch, dass ein einzelner Request auf einmal nur eine bestimmte Menge an Tokens verarbeiten kann. Die genaue Anzahl an maximalen Tokens pro Request, scheint dabei zu variieren und liegt nicht bei einem absoluten Wert.

Use Case for Error 429
Imports and Definitions
import os
import random

import requests
import tiktoken
import numpy as np
from dotenv import load_dotenv
from openai.lib.azure import AzureOpenAI

random.seed(42)

load_dotenv("secrets.env")

API_VERSION = "2024-08-01-preview"
EMBEDDING_MODEL = "text-embedding-3-large"

API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
AZURE_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")

WORDLIST_URL = "https://www.mit.edu/~ecprice/wordlist.10000"
WORDLIST_PATH = "../wordlist.10000.txt"
Define Functions
def download_wordlist(url: str, file_path: str):
    response = requests.get(url)
    response.raise_for_status()  # Ensure we notice bad responses
    with open(file_path, "w") as file:
        file.write(response.text)

def load_text_file(file_path: str) -> list[str]:
    with open(file_path, "r") as file:
        words = file.read().splitlines()
    return words

# create a stringarray with the size of n with random words
def generate_random_strings(n, words):
    random_strings = []
    for _ in range(n):
        sentence = " ".join(random.choices(words, k=130))
        random_strings.append(sentence)
    return random_strings

def calculate_tokens(texts: list[str], model: str):
    encoding = tiktoken.encoding_for_model(model)
    # Tokenize the input text
    tokens = [encoding.encode(text) for text in texts]

    # Calculate the total number of tokens
    total_tokens = sum(len(token) for token in tokens)

    # Return the total number of tokens
    return total_tokens
Create text for embedding
if not os.path.exists(WORDLIST_PATH):
    download_wordlist(WORDLIST_URL, WORDLIST_PATH)



client = AzureOpenAI(
    api_key=API_KEY,
    api_version=API_VERSION,
    azure_endpoint=AZURE_ENDPOINT,
)

words = load_text_file(WORDLIST_PATH)

# Generate random sentence to have around 256.000+ tokens
random_strings = generate_random_strings(1700, words)

# example of random strings  
print("Example of the first 10 random strings:\n")
for string in random_strings[5:10]:
    print(" ".join(string.split()[10:20]).join(["...", "..."]))

# Calculate the total number of tokens
tokens = calculate_tokens(random_strings, "text-embedding-3-large")

print(f"\n Total number of tokens for this request: {tokens}")
Example of the first 10 random strings:

...michael enquiries yahoo bryan ringtone notebook se cut llp infant...
...trek legal tales sufficiently drawing scripting helmet under lee ski...
...pharmaceutical spend organize timeline patch equivalent impact moses recording benefits...
...savage superb lived institutions wagner assurance jewelry gzip pray keys...
...sox share b substance antiques adware treated substitute moms modem...

 Total number of tokens for this request: 256035
Create a vector with the embedding-API
# The Error 429 Error happens when the Input Token is more than 256.000 tokens per request
embeddings = client.embeddings.create(
    input=random_strings,
    model=EMBEDDING_MODEL,
)
json = embeddings.model_dump()

print(json["usage"])
---------------------------------------------------------------------------
RateLimitError                            Traceback (most recent call last)
Cell In[55], line 2
      1 # The Error 429 Error happens when the Input Token is more than 256.000 tokens per request
----> 2 embeddings = client.embeddings.create(
      3     input=random_strings,
      4     model=EMBEDDING_MODEL,
      5 )
      6 json = embeddings.model_dump()
      8 print(json["usage"])

File ~/Library/Caches/pypoetry/virtualenvs/demo-429-ratelimit-eo_0Q7ut-py3.12/lib/python3.12/site-packages/openai/resources/embeddings.py:124, in Embeddings.create(self, input, model, dimensions, encoding_format, user, extra_headers, extra_query, extra_body, timeout)
    118         embedding.embedding = np.frombuffer(  # type: ignore[no-untyped-call]
    119             base64.b64decode(data), dtype="float32"
    120         ).tolist()
    122     return obj
--> 124 return self._post(
    125     "/embeddings",
    126     body=maybe_transform(params, embedding_create_params.EmbeddingCreateParams),
    127     options=make_request_options(
    128         extra_headers=extra_headers,
    129         extra_query=extra_query,
    130         extra_body=extra_body,
    131         timeout=timeout,
    132         post_parser=parser,
    133     ),
    134     cast_to=CreateEmbeddingResponse,
    135 )

File ~/Library/Caches/pypoetry/virtualenvs/demo-429-ratelimit-eo_0Q7ut-py3.12/lib/python3.12/site-packages/openai/_base_client.py:1283, in SyncAPIClient.post(self, path, cast_to, body, options, files, stream, stream_cls)
   1269 def post(
   1270     self,
   1271     path: str,
   (...)
   1278     stream_cls: type[_StreamT] | None = None,
   1279 ) -> ResponseT | _StreamT:
   1280     opts = FinalRequestOptions.construct(
   1281         method="post", url=path, json_data=body, files=to_httpx_files(files), **options
   1282     )
-> 1283     return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))

File ~/Library/Caches/pypoetry/virtualenvs/demo-429-ratelimit-eo_0Q7ut-py3.12/lib/python3.12/site-packages/openai/_base_client.py:960, in SyncAPIClient.request(self, cast_to, options, remaining_retries, stream, stream_cls)
    957 else:
    958     retries_taken = 0
--> 960 return self._request(
    961     cast_to=cast_to,
    962     options=options,
    963     stream=stream,
    964     stream_cls=stream_cls,
    965     retries_taken=retries_taken,
    966 )

File ~/Library/Caches/pypoetry/virtualenvs/demo-429-ratelimit-eo_0Q7ut-py3.12/lib/python3.12/site-packages/openai/_base_client.py:1049, in SyncAPIClient._request(self, cast_to, options, retries_taken, stream, stream_cls)
   1047 if remaining_retries > 0 and self._should_retry(err.response):
   1048     err.response.close()
-> 1049     return self._retry_request(
   1050         input_options,
   1051         cast_to,
   1052         retries_taken=retries_taken,
   1053         response_headers=err.response.headers,
   1054         stream=stream,
   1055         stream_cls=stream_cls,
   1056     )
   1058 # If the response is streamed then we need to explicitly read the response
   1059 # to completion before attempting to access the response text.
   1060 if not err.response.is_closed:

File ~/Library/Caches/pypoetry/virtualenvs/demo-429-ratelimit-eo_0Q7ut-py3.12/lib/python3.12/site-packages/openai/_base_client.py:1098, in SyncAPIClient._retry_request(self, options, cast_to, retries_taken, response_headers, stream, stream_cls)
   1094 # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
   1095 # different thread if necessary.
   1096 time.sleep(timeout)
-> 1098 return self._request(
   1099     options=options,
   1100     cast_to=cast_to,
   1101     retries_taken=retries_taken + 1,
   1102     stream=stream,
   1103     stream_cls=stream_cls,
   1104 )

File ~/Library/Caches/pypoetry/virtualenvs/demo-429-ratelimit-eo_0Q7ut-py3.12/lib/python3.12/site-packages/openai/_base_client.py:1049, in SyncAPIClient._request(self, cast_to, options, retries_taken, stream, stream_cls)
   1047 if remaining_retries > 0 and self._should_retry(err.response):
   1048     err.response.close()
-> 1049     return self._retry_request(
   1050         input_options,
   1051         cast_to,
   1052         retries_taken=retries_taken,
   1053         response_headers=err.response.headers,
   1054         stream=stream,
   1055         stream_cls=stream_cls,
   1056     )
   1058 # If the response is streamed then we need to explicitly read the response
   1059 # to completion before attempting to access the response text.
   1060 if not err.response.is_closed:

File ~/Library/Caches/pypoetry/virtualenvs/demo-429-ratelimit-eo_0Q7ut-py3.12/lib/python3.12/site-packages/openai/_base_client.py:1098, in SyncAPIClient._retry_request(self, options, cast_to, retries_taken, response_headers, stream, stream_cls)
   1094 # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
   1095 # different thread if necessary.
   1096 time.sleep(timeout)
-> 1098 return self._request(
   1099     options=options,
   1100     cast_to=cast_to,
   1101     retries_taken=retries_taken + 1,
   1102     stream=stream,
   1103     stream_cls=stream_cls,
   1104 )

File ~/Library/Caches/pypoetry/virtualenvs/demo-429-ratelimit-eo_0Q7ut-py3.12/lib/python3.12/site-packages/openai/_base_client.py:1064, in SyncAPIClient._request(self, cast_to, options, retries_taken, stream, stream_cls)
   1061         err.response.read()
   1063     log.debug("Re-raising status error")
-> 1064     raise self._make_status_error_from_response(err.response) from None
   1066 return self._process_response(
   1067     cast_to=cast_to,
   1068     options=options,
   (...)
   1072     retries_taken=retries_taken,
   1073 )

RateLimitError: Error code: 429 - {'error': {'code': '429', 'message': 'Requests to the Embeddings_Create Operation under Azure OpenAI API version 2024-08-01-preview have exceeded call rate limit of your current OpenAI S0 pricing tier. Please retry after 86400 seconds. Please go here: https://aka.ms/oai/quotaincrease if you would like to further increase the default rate limit.'}}

Um das Problem mit den zu großen Input-Token zu lösen, sollte die Anzahl der Input-Token für einen Request klein genug sein, um den Fehler zu umgehen. Beispielsweise kann so die Anzahl der Token für einen Request aus dem Notebook oben mithilfe eines Batch-Jobs beschränkt werden. Der einzelne Request kann nun von dem Embedding Modell verarbeitet werden.

Solution to fix the issue for the the misleading error message
# Important: Split the array into parts that do not overwhelm the API in one request
split_random_strings = np.array_split(random_strings, 10)

embeddings = []

for i, split in enumerate(split_random_strings):
    embedding = client.embeddings.create(
        input=split,
        model=EMBEDDING_MODEL,
    )
    embeddings.append(embedding)
    
    print(f"Request {i+1} of {len(split_random_strings)}")
    print(embedding.model_dump()["usage"])
    print("\n")

split_random_strings = np.array_split(random_strings, 10)
Request 1 of 10
{'prompt_tokens': 25751, 'total_tokens': 25751}


Request 2 of 10
{'prompt_tokens': 25686, 'total_tokens': 25686}


Request 3 of 10
{'prompt_tokens': 25528, 'total_tokens': 25528}


Request 4 of 10
{'prompt_tokens': 25531, 'total_tokens': 25531}


Request 5 of 10
{'prompt_tokens': 25523, 'total_tokens': 25523}


Request 6 of 10
{'prompt_tokens': 25655, 'total_tokens': 25655}


Request 7 of 10
{'prompt_tokens': 25553, 'total_tokens': 25553}


Request 8 of 10
{'prompt_tokens': 25528, 'total_tokens': 25528}


Request 9 of 10
{'prompt_tokens': 25579, 'total_tokens': 25579}


Request 10 of 10
{'prompt_tokens': 25701, 'total_tokens': 25701}

Fazit

Abschließend lässt sich festhalten, dass die ausgegebene Fehlermeldung häufig auf eine Überschreitung des Tokenlimits hinweist. Dies kann frustrierend sein, insbesondere wenn sie fälschlicherweise, wie im Fehlercode erklärt, mit dem “Pricing Tier” in Verbindung gebracht wird.

Um diese Probleme zu vermeiden, ist es ratsam, die Token-Anzahl im Voraus zu überprüfen und gegebenenfalls Texte zu kürzen oder aufzuteilen. So kann sichergestellt werden, dass Anfragen effizient bearbeitet werden und die volle Funktionalität der API genutzt werden kann.

Letztlich ist ein proaktiver Umgang mit den Token-Beschränkungen der Schlüssel zu einer erfolgreichen Integration der OpenAI API in Projekte.

Bei Fragen stehen wir Ihnen gerne zur Verfügung. Weitere Informationen zu unseren Angeboten finden Sie außerdem hier.

Vorheriger Artikel

Künstliche Intelligenz: Kurzfristige Chancen auf dem Weg zur digitalen Transformation

Nächster Artikel

Robot Framework Usergroup - Gute Keywords