Special: halved GPT-3 credits cost (thanks for 1.000 members!)

If no model is given, the API now defaults to gpt-3.5-turbo
We now also support gpt-3.5-turbo-0301!
Made provider code asynchronous
New dependency requirement: aiofiles
Staff now gets a notification when a provider key is invalid
Internal improvements with log webhooks for staff
Removed image model check
This commit is contained in:
nsde 2023-09-02 21:15:55 +02:00
parent 008bf56fdf
commit 6bd5dc534c
7 changed files with 70 additions and 30 deletions

View file

@ -8,23 +8,21 @@ costs:
chat-models:
gpt-4-32k: 100
gpt-4: 30
gpt-3: 10
gpt-3: 5
## Roles Explanation
# Bonuses: They are a multiplier for costs
# They work like: final_cost = cost * bonus
# Rate limits: Limit the requests of the user
# Seconds to wait between requests
roles:
owner:
bonus: 0.1
bonus: 0
admin:
bonus: 0.3
bonus: 0.2
helper:
bonus: 0.4
booster:
bonus: 0.5
bonus: 0.6
default:
bonus: 1.0

View file

@ -3,14 +3,11 @@
import os
import sys
from helpers import errors
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(project_root)
# the code above is to allow importing from the root folder
import os
import json
import hmac
import fastapi
@ -20,6 +17,7 @@ from dotenv import load_dotenv
import checks.client
from helpers import errors
from db.users import UserManager
load_dotenv()
@ -64,11 +62,13 @@ async def new_user_webhook(user: dict) -> None:
color=0x90ee90,
)
dc = user['auth']['discord']
embed.add_field(name='ID', value=str(user['_id']), inline=False)
embed.add_field(name='Discord', value=user['auth']['discord'] or '-')
embed.add_field(name='Discord', value=dc or '-')
embed.add_field(name='Github', value=user['auth']['github'] or '-')
dhook.send(embed=embed)
dhook.send(content=f'<@{dc}>', embed=embed)
@router.post('/users')
async def create_user(incoming_request: fastapi.Request):

View file

@ -1,5 +1,6 @@
"""Does quite a few checks and prepares the incoming request for the target endpoint, so it can be streamed"""
import os
import json
import yaml
import time
@ -23,6 +24,8 @@ models_list = json.load(open('models.json', encoding='utf8'))
with open('config/config.yml', encoding='utf8') as f:
config = yaml.safe_load(f)
moderation_debug_key_key = os.getenv('MODERATION_DEBUG_KEY')
async def handle(incoming_request: fastapi.Request):
"""
### Transfer a streaming response
@ -47,7 +50,7 @@ async def handle(incoming_request: fastapi.Request):
received_key = incoming_request.headers.get('Authorization')
if not received_key or not received_key.startswith('Bearer '):
return await errors.error(403, 'No NovaAI API key given!', 'Add \'Authorization: Bearer nv-...\' to your request headers.')
return await errors.error(401, 'No NovaAI API key given!', 'Add \'Authorization: Bearer nv-...\' to your request headers.')
key_tags = ''
@ -58,7 +61,7 @@ async def handle(incoming_request: fastapi.Request):
user = await users.user_by_api_key(received_key.split('Bearer ')[1].strip())
if not user or not user['status']['active']:
return await errors.error(403, 'Invalid or inactive NovaAI API key!', 'Create a new NovaOSS API key or reactivate your account.')
return await errors.error(418, 'Invalid or inactive NovaAI API key!', 'Create a new NovaOSS API key or reactivate your account.')
if user.get('auth', {}).get('discord'):
print(f'[bold green]>Discord[/bold green] {user["auth"]["discord"]}')
@ -86,7 +89,7 @@ async def handle(incoming_request: fastapi.Request):
return await errors.error(429, 'Not enough credits.', 'Wait or earn more credits. Learn more on our website or Discord server.')
if not 'DISABLE_VARS' in key_tags:
if 'DISABLE_VARS' not in key_tags:
payload_with_vars = json.dumps(payload)
replace_dict = {
@ -112,6 +115,8 @@ async def handle(incoming_request: fastapi.Request):
payload = json.loads(payload_with_vars)
policy_violation = False
if not (moderation_debug_key_key and moderation_debug_key_key in key_tags and 'gpt-3' in payload.get('model', '')):
if '/moderations' not in path:
inp = ''
@ -126,12 +131,14 @@ async def handle(incoming_request: fastapi.Request):
if policy_violation:
return await errors.error(
400, f'The request contains content which violates this model\'s policies for "{policy_violation}".',
400, f'The request contains content which violates this model\'s policies for <{policy_violation}>.',
'We currently don\'t support any NSFW models.'
)
if 'chat/completions' in path and not payload.get('stream', False):
payload['stream'] = False
if 'chat/completions' in path and not payload.get('model'):
payload['model'] = 'gpt-3.5-turbo'
media_type = 'text/event-stream' if payload.get('stream', False) else 'application/json'

View file

@ -27,10 +27,10 @@ async def balance_chat_request(payload: dict) -> dict:
providers_available.append(provider_module)
if not providers_available:
raise NotImplementedError(f'The model "{payload["model"]}" is not available. MODEl_UNAVAILABLE')
raise ValueError(f'The model "{payload["model"]}" is not available. MODEL_UNAVAILABLE')
provider = random.choice(providers_available)
target = provider.chat_completion(**payload)
target = await provider.chat_completion(**payload)
module_name = await _get_module_name(provider)
target['module'] = module_name
@ -61,7 +61,7 @@ async def balance_organic_request(request: dict) -> dict:
providers_available.append(provider_module)
provider = random.choice(providers_available)
target = provider.organify(request)
target = await provider.organify(request)
module_name = await _get_module_name(provider)
target['module'] = module_name

View file

@ -1,7 +1,28 @@
"""This module contains functions for authenticating with providers."""
import os
import asyncio
from dotenv import load_dotenv
from dhooks import Webhook, Embed
load_dotenv()
async def invalidation_webhook(provider_and_key: str) -> None:
"""Runs when a new user is created."""
dhook = Webhook(os.environ['DISCORD_WEBHOOK__API_ISSUE'])
embed = Embed(
description='Key Invalidated',
color=0xffee90,
)
embed.add_field(name='Provider', value=provider_and_key.split('>')[0])
embed.add_field(name='Key (censored)', value=f'||{provider_and_key.split(">")[1][:10]}...||', inline=False)
dhook.send(embed=embed)
async def invalidate_key(provider_and_key: str) -> None:
"""
@ -28,5 +49,7 @@ async def invalidate_key(provider_and_key: str) -> None:
with open(f'secret/{provider}.invalid.txt', 'a', encoding='utf8') as f:
f.write(key + '\n')
await invalidation_webhook(provider_and_key)
if __name__ == '__main__':
asyncio.run(invalidate_key('closed>cd...'))
asyncio.run(invalidate_key('closed>demo-...'))

View file

@ -140,8 +140,8 @@ async def demo():
print('[lightblue]Checking if the API works...')
print(await test_chat())
print('[lightblue]Checking if SDXL image generation works...')
print(await test_sdxl())
# print('[lightblue]Checking if SDXL image generation works...')
# print(await test_sdxl())
print('[lightblue]Checking if the moderation endpoint works...')
print(await test_api_moderation())

View file

@ -1,3 +1,13 @@
# Setup
## Requirements
- Python 3.9+
- pip
- MongoDB database
## Recommended
- `git` (for updates)
- `screen` (for production)
- Cloudflare (for security, anti-DDoS, etc.) - we fully support Cloudflare
## Install
Assuming you have a new version of Python 3.9+ and pip installed:
@ -110,6 +120,8 @@ You can also specify a port, e.g.:
python run 1337
```
## Adding a provider
## Test if it works
`python checks`