From e76ea11cdc5109ce069798b11b535923770f33d4 Mon Sep 17 00:00:00 2001 From: based Date: Fri, 23 Feb 2024 20:45:47 +1000 Subject: [PATCH] implemented openrouter support --- APIKey.py | 8 ++++++++ OpenRouter.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + main.py | 32 ++++++++++++++++++++++++++++---- 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 OpenRouter.py diff --git a/APIKey.py b/APIKey.py index 2204780..f144188 100644 --- a/APIKey.py +++ b/APIKey.py @@ -48,6 +48,13 @@ class APIKey: elif provider == Provider.MAKERSUITE: self.models = [] + elif provider == Provider.OPENROUTER: + self.usage = 0 + self.credit_limit = 0 + self.rpm = 0 + self.balance = 0 + self.limit_reached = False + self.bought_credits = False class Provider(Enum): OPENAI = 1, @@ -58,3 +65,4 @@ class Provider(Enum): AZURE = 6 VERTEXAI = 7 MISTRAL = 8 + OPENROUTER = 9 \ No newline at end of file diff --git a/OpenRouter.py b/OpenRouter.py new file mode 100644 index 0000000..ffd035a --- /dev/null +++ b/OpenRouter.py @@ -0,0 +1,51 @@ +import APIKey + + +async def check_openrouter(key: APIKey, session): + async with session.get(f'https://openrouter.ai/api/v1/auth/key', headers={'Authorization': f'Bearer {key.api_key}'}) as response: + if response.status != 200: + return + response = await response.json() + data = response['data'] + if data is None: + return + + key.usage = data['usage'] + key.credit_limit = data['limit'] + key.bought_credits = not data['is_free_tier'] + key.limit_reached = key.credit_limit is not None and key.usage >= key.credit_limit + key.rpm = int(data['rate_limit']['requests']) // int(data['rate_limit']['interval'].replace('s', '')) * 60 + key.balance = await get_key_balance(key, session) + + return True + + +async def get_key_balance(key: APIKey, session): + async with session.get(f'https://openrouter.ai/api/v1/models', headers={'Authorization': f'Bearer {key.api_key}'}) as response: + if response.status != 200: + return 0 + data = await response.json() + for model in data['data']: + if model['id'] == 'openai/gpt-4-turbo-preview': + prompt_tokens = int(model['per_request_limits']['prompt_tokens']) + prompt_price = float(model['pricing']['prompt']) + balance = prompt_tokens * prompt_price + return balance + return 0 + + +def pretty_print_openrouter_keys(keys): + print('-' * 90) + premium_keys = {key for key in keys if key.balance > 0} + non_premium_keys = set(keys) - premium_keys + + print(f'Validated {len(keys)} OpenRouter keys:') + print(f'{len(premium_keys)} keys with balance:') + for key in premium_keys: + print(f'{key.api_key} | estimated balance - ${format(key.balance, ".4f")} | usage - ${format(key.usage, ".4f")}' + (' - LIMIT REACHED' if key.limit_reached else "") + (f' | cred limit - ${key.credit_limit}' if key.credit_limit else "") + f' | RPM - {key.rpm}' + (' | purchased credits' if key.bought_credits else "")) + + print(f'\n{len(non_premium_keys)} keys without balance:') + for key in non_premium_keys: + print(f'{key.api_key} | usage - ${format(key.usage, ".4f")}' + (' - LIMIT REACHED' if key.limit_reached else "") + (f' | cred limit - ${key.credit_limit}' if key.credit_limit else "") + f' | RPM - {key.rpm}' + (' | purchased credits' if key.bought_credits else "")) + + print(f'\n--- Total Valid OpenRouter Keys: {len(keys)} ({len(premium_keys)} that have the balance to use premium models) ---\n') diff --git a/README.md b/README.md index 054efe4..b4d41ab 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Currently supports and validates keys for the services below, and checks for the - Azure - (Auto-fetch all deployments, auto-fetch best deployment/model, filter status) - Google Cloud Vertex AI - (Requires a key file since oauth tokens expire hourly. Good luck scraping for those.) - MistralAI - (Subscription status) +- OpenRouter - (Estimated balance, usage in $, credit limit, RPM, has purchased any credits) Always open to adding more services, although I think I've covered all the ones people care about. Nonetheless, feel free to open an issue or PR if you want something else added. diff --git a/main.py b/main.py index 579096a..9971486 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ from AWS import check_aws, pretty_print_aws_keys from Azure import check_azure, pretty_print_azure_keys from VertexAI import check_vertexai, pretty_print_vertexai_keys from Mistral import check_mistral, pretty_print_mistral_keys +from OpenRouter import check_openrouter, pretty_print_openrouter_keys from APIKey import APIKey, Provider from concurrent.futures import ThreadPoolExecutor, as_completed @@ -107,6 +108,16 @@ async def validate_makersuite(key: APIKey, sem): api_keys.add(key) +async def validate_openrouter(key: APIKey, sem): + async with sem, aiohttp.ClientSession() as session: + IO.conditional_print(f"Checking OpenRouter: {key.api_key}", args.verbose) + if await check_openrouter(key, session) is None: + IO.conditional_print(f"Invalid OpenRouter key: {key.api_key}", args.verbose) + return + IO.conditional_print(f"OpenRouter key '{key.api_key}' is valid", args.verbose) + api_keys.add(key) + + def validate_aws(key: APIKey): IO.conditional_print(f"Checking AWS key: {key.api_key}", args.verbose) if check_aws(key) is None: @@ -140,6 +151,7 @@ ai21_and_mistral_regex = re.compile('[A-Za-z0-9]{32}') makersuite_regex = re.compile(r'AIzaSy[A-Za-z0-9\-_]{33}') aws_regex = re.compile(r'^(AKIA[0-9A-Z]{16}):([A-Za-z0-9+/]{40})$') azure_regex = re.compile(r'^(.+):([a-z0-9]{32})$') +openrouter_regex = re.compile(r'sk-or-v1-[a-z0-9]{64}') # vertex_regex = re.compile(r'^(.+):(ya29.[A-Za-z0-9\-_]{469})$') regex for the oauth tokens, useless since they expire hourly executor = ThreadPoolExecutor(max_workers=100) concurrent_connections = asyncio.Semaphore(1500) @@ -167,6 +179,12 @@ async def validate_keys(): continue key_obj = APIKey(Provider.MAKERSUITE, key) tasks.append(validate_makersuite(key_obj, concurrent_connections)) + elif "sk-or-v1-" in key: + match = openrouter_regex.match(key) + if not match: + continue + key_obj = APIKey(Provider.OPENROUTER, key) + tasks.append(validate_openrouter(key_obj, concurrent_connections)) elif "sk-" in key: match = oai_regex.match(key) if not match: @@ -201,7 +219,7 @@ async def validate_keys(): futures.clear() -def get_invalid_keys(valid_oai_keys, valid_anthropic_keys, valid_ai21_keys, valid_makersuite_keys, valid_aws_keys, valid_azure_keys, valid_vertexai_keys, valid_mistral_keys): +def get_invalid_keys(valid_oai_keys, valid_anthropic_keys, valid_ai21_keys, valid_makersuite_keys, valid_aws_keys, valid_azure_keys, valid_vertexai_keys, valid_mistral_keys, valid_openrouter_keys): valid_oai_keys_set = set([key.api_key for key in valid_oai_keys]) valid_anthropic_keys_set = set([key.api_key for key in valid_anthropic_keys]) valid_ai21_keys_set = set([key.api_key for key in valid_ai21_keys]) @@ -210,8 +228,9 @@ def get_invalid_keys(valid_oai_keys, valid_anthropic_keys, valid_ai21_keys, vali valid_azure_keys_set = set([key.api_key for key in valid_azure_keys]) valid_vertexai_keys_set = set([key.api_key for key in valid_vertexai_keys]) valid_mistral_keys_set = set([key.api_key for key in valid_mistral_keys]) + valid_openrouter_keys_set = set([key.api_key for key in valid_openrouter_keys]) - invalid_keys = inputted_keys - valid_oai_keys_set - valid_anthropic_keys_set - valid_ai21_keys_set - valid_makersuite_keys_set - valid_aws_keys_set - valid_azure_keys_set - valid_vertexai_keys_set - valid_mistral_keys_set + invalid_keys = inputted_keys - valid_oai_keys_set - valid_anthropic_keys_set - valid_ai21_keys_set - valid_makersuite_keys_set - valid_aws_keys_set - valid_azure_keys_set - valid_vertexai_keys_set - valid_mistral_keys_set - valid_openrouter_keys_set if len(invalid_keys) < 1: return print('\nInvalid Keys:') @@ -230,6 +249,7 @@ def output_keys(): valid_azure_keys = [] valid_vertexai_keys = [] valid_mistral_keys = [] + valid_openrouter_keys = [] for key in api_keys: if key.provider == Provider.OPENAI: @@ -248,6 +268,8 @@ def output_keys(): valid_vertexai_keys.append(key) elif key.provider == Provider.MISTRAL: valid_mistral_keys.append(key) + elif key.provider == Provider.OPENROUTER: + valid_openrouter_keys.append(key) if should_write: output_filename = "key_snapshots.txt" sys.stdout = IO(output_filename) @@ -257,7 +279,7 @@ def output_keys(): print(f"Key snapshot from {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print("#" * 90) print(f'\n--- Checked {len(inputted_keys)} keys | {len(inputted_keys) - len(api_keys)} were invalid ---') - get_invalid_keys(valid_oai_keys, valid_anthropic_keys, valid_ai21_keys, valid_makersuite_keys, valid_aws_keys, valid_azure_keys, valid_vertexai_keys, valid_mistral_keys) + get_invalid_keys(valid_oai_keys, valid_anthropic_keys, valid_ai21_keys, valid_makersuite_keys, valid_aws_keys, valid_azure_keys, valid_vertexai_keys, valid_mistral_keys, valid_openrouter_keys) print() if valid_oai_keys: pretty_print_oai_keys(valid_oai_keys) @@ -275,8 +297,10 @@ def output_keys(): pretty_print_vertexai_keys(valid_vertexai_keys) if valid_mistral_keys: pretty_print_mistral_keys(valid_mistral_keys) + if valid_openrouter_keys: + pretty_print_openrouter_keys(valid_openrouter_keys) else: - # ai21 and vertex keys aren't supported in proxies so no point outputting them, filtered azure keys should be excluded. + # ai21, openrouter and vertex keys aren't supported in proxies so no point outputting them, filtered azure keys should be excluded. print("OPENAI_KEY=" + ','.join(key.api_key for key in valid_oai_keys)) print("ANTHROPIC_KEY=" + ','.join(key.api_key for key in valid_anthropic_keys)) print("AWS_CREDENTIALS=" + ','.join(f"{key.api_key}:{region}" for key in valid_aws_keys if not key.useless and key.bedrock_enabled for region in [key.region] + key.alt_regions))