Compare commits

..

No commits in common. "169f5469a91d931a8cc3060d40b6e432b119b4d2" and "003a7d3d716d62677281aa808aa9ddfef74af331" have entirely different histories.

22 changed files with 79 additions and 96 deletions

View file

@ -167,7 +167,7 @@ You can also just add the *beginning* of an API address, like `12.123.` (without
### Core Keys ### Core Keys
`CORE_API_KEY` specifies the **very secret key** for which need to access the entire user database etc. `CORE_API_KEY` specifies the **very secret key** for which need to access the entire user database etc.
`NOVA_KEY` is the API key the which is used in tests. It should be one with tons of credits. `TEST_NOVA_KEY` is the API key the which is used in tests. It should be one with tons of credits.
### Webhooks ### Webhooks
`DISCORD_WEBHOOK__USER_CREATED` is the Discord webhook URL for when a user is created. `DISCORD_WEBHOOK__USER_CREATED` is the Discord webhook URL for when a user is created.

View file

@ -1,8 +1,6 @@
import os import os
import json import json
import asyncio import asyncio
import aiofiles
import aiofiles.os
from sys import argv from sys import argv
from bson import json_util from bson import json_util
@ -20,7 +18,8 @@ async def main(output_dir: str):
async def make_backup(output_dir: str): async def make_backup(output_dir: str):
output_dir = os.path.join(FILE_DIR, '..', 'backups', output_dir) output_dir = os.path.join(FILE_DIR, '..', 'backups', output_dir)
await aiofiles.os.makedirs(output_dir, exist_ok=True) if not os.path.exists(output_dir):
os.makedirs(output_dir)
client = AsyncIOMotorClient(MONGO_URI) client = AsyncIOMotorClient(MONGO_URI)
databases = await client.list_database_names() databases = await client.list_database_names()
@ -30,22 +29,22 @@ async def make_backup(output_dir: str):
if database == 'local': if database == 'local':
continue continue
await aiofiles.os.makedirs(os.path.join(output_dir, database), exist_ok=True) if not os.path.exists(f'{output_dir}/{database}'):
os.mkdir(f'{output_dir}/{database}')
for collection in databases[database]: for collection in databases[database]:
print(f'Initiated database backup for {database}/{collection}') print(f'Initiated database backup for {database}/{collection}')
await make_backup_for_collection(database, collection, output_dir) await make_backup_for_collection(database, collection, output_dir)
async def make_backup_for_collection(database, collection, output_dir): async def make_backup_for_collection(database, collection, output_dir):
path = os.path.join(output_dir, database, f'{collection}.json') path = f'{output_dir}/{database}/{collection}.json'
client = AsyncIOMotorClient(MONGO_URI) client = AsyncIOMotorClient(MONGO_URI)
collection = client[database][collection] collection = client[database][collection]
documents = await collection.find({}).to_list(length=None) documents = await collection.find({}).to_list(length=None)
async with aiofiles.open(path, 'w') as f: with open(path, 'w') as f:
for chunk in json.JSONEncoder(default=json_util.default).iterencode(documents): json.dump(documents, f, default=json_util.default)
await f.write(chunk)
if __name__ == '__main__': if __name__ == '__main__':
if len(argv) < 2 or len(argv) > 2: if len(argv) < 2 or len(argv) > 2:

View file

@ -13,7 +13,6 @@ import json
import hmac import hmac
import httpx import httpx
import fastapi import fastapi
import aiofiles
import functools import functools
from dhooks import Webhook, Embed from dhooks import Webhook, Embed
@ -131,7 +130,7 @@ async def run_checks(incoming_request: fastapi.Request):
checks.client.test_chat_non_stream_gpt4, checks.client.test_chat_non_stream_gpt4,
checks.client.test_chat_stream_gpt3, checks.client.test_chat_stream_gpt3,
checks.client.test_function_calling, checks.client.test_function_calling,
# checks.client.test_image_generation, checks.client.test_image_generation,
# checks.client.test_speech_to_text, # checks.client.test_speech_to_text,
checks.client.test_models checks.client.test_models
] ]
@ -149,14 +148,11 @@ async def run_checks(incoming_request: fastapi.Request):
async def get_crypto_price(cryptocurrency: str) -> float: async def get_crypto_price(cryptocurrency: str) -> float:
"""Gets the price of a cryptocurrency using coinbase's API.""" """Gets the price of a cryptocurrency using coinbase's API."""
cache_path = os.path.join('cache', 'crypto_prices.json') if os.path.exists('cache/crypto_prices.json'):
try: with open('cache/crypto_prices.json', 'r') as f:
async with aiofiles.open(cache_path) as f: cache = json.load(f)
content = await f.read()
except FileNotFoundError:
cache = {}
else: else:
cache = json.loads(content) cache = {}
is_old = time.time() - cache.get('_last_updated', 0) > 60 * 60 is_old = time.time() - cache.get('_last_updated', 0) > 60 * 60
@ -168,9 +164,8 @@ async def get_crypto_price(cryptocurrency: str) -> float:
cache[cryptocurrency] = usd_price cache[cryptocurrency] = usd_price
cache['_last_updated'] = time.time() cache['_last_updated'] = time.time()
async with aiofiles.open(cache_path, 'w') as f: with open('cache/crypto_prices.json', 'w') as f:
for chunk in json.JSONEncoder().iterencode(cache): json.dump(cache, f)
await f.write(chunk)
return cache[cryptocurrency] return cache[cryptocurrency]

View file

@ -3,8 +3,6 @@ import time
import random import random
import asyncio import asyncio
import aiofiles
import aiofiles.os
from aiocache import cached from aiocache import cached
from dotenv import load_dotenv from dotenv import load_dotenv
from cachetools import TTLCache from cachetools import TTLCache
@ -74,10 +72,10 @@ class KeyManager:
db = await self._get_collection('providerkeys') db = await self._get_collection('providerkeys')
num = 0 num = 0
for filename in await aiofiles.os.listdir(os.path.join('api', 'secret')): for filename in os.listdir('api/secret'):
if filename.endswith('.txt'): if filename.endswith('.txt'):
async with aiofiles.open(os.path.join('api', 'secret', filename)) as f: with open(f'api/secret/{filename}') as f:
async for line in f: for line in f.readlines():
if not line.strip(): if not line.strip():
continue continue

View file

@ -14,7 +14,7 @@ except ImportError:
load_dotenv() load_dotenv()
with open(os.path.join(helpers.root, 'api', 'config', 'config.yml'), encoding='utf8') as f: with open(helpers.root + '/api/config/config.yml', encoding='utf8') as f:
credits_config = yaml.safe_load(f) credits_config = yaml.safe_load(f)
## MONGODB Setup ## MONGODB Setup

View file

@ -19,11 +19,10 @@ from helpers import tokens, errors, network
load_dotenv() load_dotenv()
users = UserManager() users = UserManager()
with open(os.path.join('cache', 'models.json'), encoding='utf8') as f: models_list = json.load(open('cache/models.json', encoding='utf8'))
models_list = json.load(f)
models = [model['id'] for model in models_list['data']] models = [model['id'] for model in models_list['data']]
with open(os.path.join('config', 'config.yml'), encoding='utf8') as f: with open('config/config.yml', encoding='utf8') as f:
config = yaml.safe_load(f) config = yaml.safe_load(f)
moderation_debug_key_key = os.getenv('MODERATION_DEBUG_KEY') moderation_debug_key_key = os.getenv('MODERATION_DEBUG_KEY')
@ -39,10 +38,7 @@ async def handle(incoming_request: fastapi.Request):
ip_address = await network.get_ip(incoming_request) ip_address = await network.get_ip(incoming_request)
if '/dashboard' in path: if '/models' in path:
return errors.error(404, 'You can\'t access /dashboard.', 'This is a private endpoint.')
if path.startswith('/v1/models'):
return fastapi.responses.JSONResponse(content=models_list) return fastapi.responses.JSONResponse(content=models_list)
try: try:
@ -98,6 +94,7 @@ async def handle(incoming_request: fastapi.Request):
if user['credits'] < cost: if user['credits'] < cost:
return await errors.error(429, 'Not enough credits.', 'Wait or earn more credits. Learn more on our website or Discord server.') return await errors.error(429, 'Not enough credits.', 'Wait or earn more credits. Learn more on our website or Discord server.')
if 'DISABLE_VARS' not in key_tags: if 'DISABLE_VARS' not in key_tags:
payload_with_vars = json.dumps(payload) payload_with_vars = json.dumps(payload)

View file

@ -7,10 +7,10 @@ from rich import print
from dotenv import load_dotenv from dotenv import load_dotenv
from bson.objectid import ObjectId from bson.objectid import ObjectId
from fastapi.middleware.cors import CORSMiddleware
from slowapi.errors import RateLimitExceeded from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware from slowapi.middleware import SlowAPIMiddleware
from fastapi.middleware.cors import CORSMiddleware
from slowapi.util import get_remote_address from slowapi.util import get_remote_address
from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi import Limiter, _rate_limit_exceeded_handler

View file

@ -1,2 +1,12 @@
from . import azure, webraft from . import \
MODULES = [azure, webraft] azure \
# closed, \
# closed4
# closed432
MODULES = [
azure,
# closed,
# closed4,
# closed432,
]

View file

@ -3,13 +3,14 @@ import sys
import aiohttp import aiohttp
import asyncio import asyncio
import importlib import importlib
import aiofiles.os
from rich import print from rich import print
def remove_duplicate_keys(file): def remove_duplicate_keys(file):
with open(file, 'r', encoding='utf8') as f: with open(file, 'r', encoding='utf8') as f:
unique_lines = set(f) lines = f.readlines()
unique_lines = set(lines)
with open(file, 'w', encoding='utf8') as f: with open(file, 'w', encoding='utf8') as f:
f.writelines(unique_lines) f.writelines(unique_lines)
@ -21,7 +22,7 @@ async def main():
except IndexError: except IndexError:
print('List of available providers:') print('List of available providers:')
for file_name in await aiofiles.os.listdir(os.path.dirname(__file__)): for file_name in os.listdir(os.path.dirname(__file__)):
if file_name.endswith('.py') and not file_name.startswith('_'): if file_name.endswith('.py') and not file_name.startswith('_'):
print(file_name.split('.')[0]) print(file_name.split('.')[0])

View file

@ -2,6 +2,7 @@ from .helpers import utils
AUTH = True AUTH = True
ORGANIC = False ORGANIC = False
CONTEXT = True
STREAMING = True STREAMING = True
MODERATIONS = False MODERATIONS = False
ENDPOINT = 'https://nova-00001.openai.azure.com' ENDPOINT = 'https://nova-00001.openai.azure.com'
@ -11,7 +12,7 @@ MODELS = [
'gpt-4', 'gpt-4',
'gpt-4-32k' 'gpt-4-32k'
] ]
MODELS += [f'{model}-azure' for model in MODELS] # MODELS = [f'{model}-azure' for model in MODELS]
AZURE_API = '2023-08-01-preview' AZURE_API = '2023-08-01-preview'

View file

@ -2,6 +2,7 @@ from .helpers import utils
AUTH = True AUTH = True
ORGANIC = True ORGANIC = True
CONTEXT = True
STREAMING = True STREAMING = True
MODERATIONS = True MODERATIONS = True
ENDPOINT = 'https://api.openai.com' ENDPOINT = 'https://api.openai.com'

View file

@ -2,6 +2,7 @@ from .helpers import utils
AUTH = True AUTH = True
ORGANIC = False ORGANIC = False
CONTEXT = True
STREAMING = True STREAMING = True
MODERATIONS = True MODERATIONS = True
ENDPOINT = 'https://api.openai.com' ENDPOINT = 'https://api.openai.com'

View file

@ -2,6 +2,7 @@ from .helpers import utils
AUTH = True AUTH = True
ORGANIC = False ORGANIC = False
CONTEXT = True
STREAMING = True STREAMING = True
MODERATIONS = False MODERATIONS = False
ENDPOINT = 'https://api.openai.com' ENDPOINT = 'https://api.openai.com'

View file

@ -23,5 +23,15 @@ GPT_4_32K = GPT_4 + [
'gpt-4-32k-0613', 'gpt-4-32k-0613',
] ]
async def conversation_to_prompt(conversation: list) -> str:
text = ''
for message in conversation:
text += f'<|{message["role"]}|>: {message["content"]}\n'
text += '<|assistant|>:'
return text
async def random_secret_for(name: str) -> str: async def random_secret_for(name: str) -> str:
return await providerkeys.manager.get_key(name) return await providerkeys.manager.get_key(name)

View file

@ -2,6 +2,7 @@ from .helpers import utils
AUTH = True AUTH = True
ORGANIC = False ORGANIC = False
CONTEXT = True
STREAMING = True STREAMING = True
MODELS = ['llama-2-7b-chat'] MODELS = ['llama-2-7b-chat']
@ -11,7 +12,7 @@ async def chat_completion(**kwargs):
return { return {
'method': 'POST', 'method': 'POST',
'url': 'https://api.mandrillai.tech/v1/chat/completions', 'url': f'https://api.mandrillai.tech/v1/chat/completions',
'payload': payload, 'payload': payload,
'headers': { 'headers': {
'Authorization': f'Bearer {key}' 'Authorization': f'Bearer {key}'

View file

@ -1,25 +0,0 @@
from .helpers import utils
AUTH = True
ORGANIC = False
STREAMING = True
MODELS = [
'gpt-3.5-turbo-0613',
'gpt-3.5-turbo-0301',
'gpt-3.5-turbo-16k-0613'
]
async def chat_completion(**kwargs):
payload = kwargs
key = await utils.random_secret_for('webraft')
return {
'method': 'POST',
'url': 'https://thirdparty.webraft.in/v1/chat/completions',
'payload': payload,
'headers': {
'Content-Type': 'application/json',
'Authorization': f'Bearer {key}'
},
'provider_auth': f'webraft>{key}'
}

View file

@ -96,7 +96,7 @@ proxies_in_files = []
for proxy_type in ['http', 'socks4', 'socks5']: for proxy_type in ['http', 'socks4', 'socks5']:
try: try:
with open(os.path.join('secret', 'proxies', f'{proxy_type}.txt')) as f: with open(f'secret/proxies/{proxy_type}.txt') as f:
for line in f: for line in f:
clean_line = line.split('#', 1)[0].strip() clean_line = line.split('#', 1)[0].strip()
if clean_line: if clean_line:

View file

@ -63,14 +63,7 @@ async def respond(
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
skipped_errors = { for i in range(5):
'insufficient_quota': 0,
'billing_not_active': 0,
'critical_provider_error': 0,
'timeout': 0
}
for _ in range(5):
try: try:
if is_chat: if is_chat:
target_request = await load_balancing.balance_chat_request(payload) target_request = await load_balancing.balance_chat_request(payload)
@ -137,13 +130,11 @@ async def respond(
if error_code == 'insufficient_quota': if error_code == 'insufficient_quota':
print('[!] insufficient quota') print('[!] insufficient quota')
await keymanager.rate_limit_key(provider_name, provider_key, 86400) await keymanager.rate_limit_key(provider_name, provider_key, 86400)
skipped_errors['insufficient_quota'] += 1
continue continue
if error_code == 'billing_not_active': if error_code == 'billing_not_active':
print('[!] billing not active') print('[!] billing not active')
await keymanager.deactivate_key(provider_name, provider_key, 'billing_not_active') await keymanager.deactivate_key(provider_name, provider_key, 'billing_not_active')
skipped_errors['billing_not_active'] += 1
continue continue
critical_error = False critical_error = False
@ -153,14 +144,23 @@ async def respond(
critical_error = True critical_error = True
if critical_error: if critical_error:
print('[!] critical provider error') print('[!] critical error')
skipped_errors['critical_provider_error'] += 1
continue continue
if response.ok: if response.ok:
server_json_response = client_json_response server_json_response = client_json_response
else:
continue
if is_stream: if is_stream:
try:
response.raise_for_status()
except Exception as exc:
if 'Too Many Requests' in str(exc):
print('[!] too many requests')
continue
chunk_no = 0 chunk_no = 0
buffer = '' buffer = ''
@ -170,7 +170,7 @@ async def respond(
chunk = chunk.decode('utf8') chunk = chunk.decode('utf8')
if 'azure' in provider_name: if 'azure' in provider_name:
chunk = chunk.replace('data: ', '', 1) chunk = chunk.replace('data: ', '')
if not chunk or chunk_no == 1: if not chunk or chunk_no == 1:
continue continue
@ -178,26 +178,19 @@ async def respond(
subchunks = chunk.split('\n\n') subchunks = chunk.split('\n\n')
buffer += subchunks[0] buffer += subchunks[0]
for subchunk in [buffer] + subchunks[1:-1]: yield buffer + '\n\n'
if not subchunk.startswith('data: '): buffer = subchunks[-1]
subchunk = 'data: ' + subchunk
for subchunk in subchunks[1:-1]:
yield subchunk + '\n\n' yield subchunk + '\n\n'
buffer = subchunks[-1]
break break
except aiohttp.client_exceptions.ServerTimeoutError: except aiohttp.client_exceptions.ServerTimeoutError:
skipped_errors['timeout'] += 1
continue continue
else: else:
skipped_errors = {k: v for k, v in skipped_errors.items() if v > 0} yield await errors.yield_error(500, 'Sorry, our API seems to have issues connecting to our provider(s).', 'This most likely isn\'t your fault. Please try again later.')
skipped_errors = ujson.dumps(skipped_errors, indent=4)
yield await errors.yield_error(500,
'Sorry, our API seems to have issues connecting to our provider(s).',
f'Please send this info to support: {skipped_errors}'
)
return return
if (not is_stream) and server_json_response: if (not is_stream) and server_json_response:

View file

@ -100,7 +100,7 @@ async def test_chat_stream_gpt3() -> float:
async for chunk in response.aiter_text(): async for chunk in response.aiter_text():
for subchunk in chunk.split('\n\n'): for subchunk in chunk.split('\n\n'):
chunk = subchunk.replace('data: ', '', 1).strip() chunk = subchunk.replace('data: ', '').strip()
if chunk == '[DONE]': if chunk == '[DONE]':
break break

View file

@ -1,4 +1,3 @@
aiofiles==23.2.1
aiohttp==3.8.5 aiohttp==3.8.5
aiohttp_socks==0.8.0 aiohttp_socks==0.8.0
dhooks==1.1.4 dhooks==1.1.4

View file

@ -51,7 +51,7 @@ async def update_roles():
def launch(): def launch():
asyncio.run(main()) asyncio.run(main())
with open(os.path.join('rewards', 'last_update.txt'), 'w', encoding='utf8') as f: with open('rewards/last_update.txt', 'w', encoding='utf8') as f:
f.write(str(time.time())) f.write(str(time.time()))
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -14,6 +14,7 @@ Runs for production on the speicified port.
import os import os
import sys import sys
import time
port = sys.argv[1] if len(sys.argv) > 1 else 2332 port = sys.argv[1] if len(sys.argv) > 1 else 2332
dev = True dev = True