Feat/billing enhancement (#2239)

Co-authored-by: takatost <takatost@gmail.com>
This commit is contained in:
Garfield Dai 2024-01-26 18:26:15 +08:00 committed by GitHub
parent 2fc0dcc10a
commit bb5d5fc683
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 43 additions and 41 deletions

View File

@ -40,17 +40,11 @@ DEFAULTS = {
'HOSTED_OPENAI_QUOTA_LIMIT': 200, 'HOSTED_OPENAI_QUOTA_LIMIT': 200,
'HOSTED_OPENAI_TRIAL_ENABLED': 'False', 'HOSTED_OPENAI_TRIAL_ENABLED': 'False',
'HOSTED_OPENAI_PAID_ENABLED': 'False', 'HOSTED_OPENAI_PAID_ENABLED': 'False',
'HOSTED_OPENAI_PAID_INCREASE_QUOTA': 1,
'HOSTED_OPENAI_PAID_MIN_QUANTITY': 1,
'HOSTED_OPENAI_PAID_MAX_QUANTITY': 1,
'HOSTED_AZURE_OPENAI_ENABLED': 'False', 'HOSTED_AZURE_OPENAI_ENABLED': 'False',
'HOSTED_AZURE_OPENAI_QUOTA_LIMIT': 200, 'HOSTED_AZURE_OPENAI_QUOTA_LIMIT': 200,
'HOSTED_ANTHROPIC_QUOTA_LIMIT': 600000, 'HOSTED_ANTHROPIC_QUOTA_LIMIT': 600000,
'HOSTED_ANTHROPIC_TRIAL_ENABLED': 'False', 'HOSTED_ANTHROPIC_TRIAL_ENABLED': 'False',
'HOSTED_ANTHROPIC_PAID_ENABLED': 'False', 'HOSTED_ANTHROPIC_PAID_ENABLED': 'False',
'HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA': 1,
'HOSTED_ANTHROPIC_PAID_MIN_QUANTITY': 1,
'HOSTED_ANTHROPIC_PAID_MAX_QUANTITY': 1,
'HOSTED_MODERATION_ENABLED': 'False', 'HOSTED_MODERATION_ENABLED': 'False',
'HOSTED_MODERATION_PROVIDERS': '', 'HOSTED_MODERATION_PROVIDERS': '',
'CLEAN_DAY_SETTING': 30, 'CLEAN_DAY_SETTING': 30,
@ -262,10 +256,6 @@ class Config:
self.HOSTED_OPENAI_TRIAL_ENABLED = get_bool_env('HOSTED_OPENAI_TRIAL_ENABLED') self.HOSTED_OPENAI_TRIAL_ENABLED = get_bool_env('HOSTED_OPENAI_TRIAL_ENABLED')
self.HOSTED_OPENAI_QUOTA_LIMIT = int(get_env('HOSTED_OPENAI_QUOTA_LIMIT')) self.HOSTED_OPENAI_QUOTA_LIMIT = int(get_env('HOSTED_OPENAI_QUOTA_LIMIT'))
self.HOSTED_OPENAI_PAID_ENABLED = get_bool_env('HOSTED_OPENAI_PAID_ENABLED') self.HOSTED_OPENAI_PAID_ENABLED = get_bool_env('HOSTED_OPENAI_PAID_ENABLED')
self.HOSTED_OPENAI_PAID_STRIPE_PRICE_ID = get_env('HOSTED_OPENAI_PAID_STRIPE_PRICE_ID')
self.HOSTED_OPENAI_PAID_INCREASE_QUOTA = int(get_env('HOSTED_OPENAI_PAID_INCREASE_QUOTA'))
self.HOSTED_OPENAI_PAID_MIN_QUANTITY = int(get_env('HOSTED_OPENAI_PAID_MIN_QUANTITY'))
self.HOSTED_OPENAI_PAID_MAX_QUANTITY = int(get_env('HOSTED_OPENAI_PAID_MAX_QUANTITY'))
self.HOSTED_AZURE_OPENAI_ENABLED = get_bool_env('HOSTED_AZURE_OPENAI_ENABLED') self.HOSTED_AZURE_OPENAI_ENABLED = get_bool_env('HOSTED_AZURE_OPENAI_ENABLED')
self.HOSTED_AZURE_OPENAI_API_KEY = get_env('HOSTED_AZURE_OPENAI_API_KEY') self.HOSTED_AZURE_OPENAI_API_KEY = get_env('HOSTED_AZURE_OPENAI_API_KEY')
@ -277,10 +267,6 @@ class Config:
self.HOSTED_ANTHROPIC_TRIAL_ENABLED = get_bool_env('HOSTED_ANTHROPIC_TRIAL_ENABLED') self.HOSTED_ANTHROPIC_TRIAL_ENABLED = get_bool_env('HOSTED_ANTHROPIC_TRIAL_ENABLED')
self.HOSTED_ANTHROPIC_QUOTA_LIMIT = int(get_env('HOSTED_ANTHROPIC_QUOTA_LIMIT')) self.HOSTED_ANTHROPIC_QUOTA_LIMIT = int(get_env('HOSTED_ANTHROPIC_QUOTA_LIMIT'))
self.HOSTED_ANTHROPIC_PAID_ENABLED = get_bool_env('HOSTED_ANTHROPIC_PAID_ENABLED') self.HOSTED_ANTHROPIC_PAID_ENABLED = get_bool_env('HOSTED_ANTHROPIC_PAID_ENABLED')
self.HOSTED_ANTHROPIC_PAID_STRIPE_PRICE_ID = get_env('HOSTED_ANTHROPIC_PAID_STRIPE_PRICE_ID')
self.HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA = int(get_env('HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA'))
self.HOSTED_ANTHROPIC_PAID_MIN_QUANTITY = int(get_env('HOSTED_ANTHROPIC_PAID_MIN_QUANTITY'))
self.HOSTED_ANTHROPIC_PAID_MAX_QUANTITY = int(get_env('HOSTED_ANTHROPIC_PAID_MAX_QUANTITY'))
self.HOSTED_MINIMAX_ENABLED = get_bool_env('HOSTED_MINIMAX_ENABLED') self.HOSTED_MINIMAX_ENABLED = get_bool_env('HOSTED_MINIMAX_ENABLED')
self.HOSTED_SPARK_ENABLED = get_bool_env('HOSTED_SPARK_ENABLED') self.HOSTED_SPARK_ENABLED = get_bool_env('HOSTED_SPARK_ENABLED')

View File

@ -20,7 +20,7 @@ class Subscription(Resource):
parser.add_argument('interval', type=str, required=True, location='args', choices=['month', 'year']) parser.add_argument('interval', type=str, required=True, location='args', choices=['month', 'year'])
args = parser.parse_args() args = parser.parse_args()
BillingService.is_tenant_owner(current_user) BillingService.is_tenant_owner_or_admin(current_user)
return BillingService.get_subscription(args['plan'], return BillingService.get_subscription(args['plan'],
args['interval'], args['interval'],
@ -35,8 +35,8 @@ class Invoices(Resource):
@account_initialization_required @account_initialization_required
@only_edition_cloud @only_edition_cloud
def get(self): def get(self):
BillingService.is_tenant_owner(current_user) BillingService.is_tenant_owner_or_admin(current_user)
return BillingService.get_invoices(current_user.email) return BillingService.get_invoices(current_user.email, current_user.current_tenant_id)
api.add_resource(Subscription, '/billing/subscription') api.add_resource(Subscription, '/billing/subscription')

View File

@ -186,10 +186,11 @@ class ModelProviderPaymentCheckoutUrlApi(Resource):
def get(self, provider: str): def get(self, provider: str):
if provider != 'anthropic': if provider != 'anthropic':
raise ValueError(f'provider name {provider} is invalid') raise ValueError(f'provider name {provider} is invalid')
BillingService.is_tenant_owner_or_admin(current_user)
data = BillingService.get_model_provider_payment_link(provider_name=provider, data = BillingService.get_model_provider_payment_link(provider_name=provider,
tenant_id=current_user.current_tenant_id, tenant_id=current_user.current_tenant_id,
account_id=current_user.id) account_id=current_user.id,
prefilled_email=current_user.email)
return data return data

View File

@ -9,6 +9,7 @@ from pydantic import BaseModel
class QuotaUnit(Enum): class QuotaUnit(Enum):
TIMES = 'times' TIMES = 'times'
TOKENS = 'tokens' TOKENS = 'tokens'
CREDITS = 'credits'
class SystemConfigurationStatus(Enum): class SystemConfigurationStatus(Enum):

View File

@ -20,10 +20,6 @@ class TrialHostingQuota(HostingQuota):
class PaidHostingQuota(HostingQuota): class PaidHostingQuota(HostingQuota):
quota_type: ProviderQuotaType = ProviderQuotaType.PAID quota_type: ProviderQuotaType = ProviderQuotaType.PAID
stripe_price_id: str = None
increase_quota: int = 1
min_quantity: int = 20
max_quantity: int = 100
class FreeHostingQuota(HostingQuota): class FreeHostingQuota(HostingQuota):
@ -102,7 +98,7 @@ class HostingConfiguration:
) )
def init_openai(self, app_config: Config) -> HostingProvider: def init_openai(self, app_config: Config) -> HostingProvider:
quota_unit = QuotaUnit.TIMES quota_unit = QuotaUnit.CREDITS
quotas = [] quotas = []
if app_config.get("HOSTED_OPENAI_TRIAL_ENABLED"): if app_config.get("HOSTED_OPENAI_TRIAL_ENABLED"):
@ -114,6 +110,8 @@ class HostingConfiguration:
RestrictModel(model="gpt-3.5-turbo-1106", model_type=ModelType.LLM), RestrictModel(model="gpt-3.5-turbo-1106", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-instruct", model_type=ModelType.LLM), RestrictModel(model="gpt-3.5-turbo-instruct", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-16k", model_type=ModelType.LLM), RestrictModel(model="gpt-3.5-turbo-16k", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-16k-0613", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-0613", model_type=ModelType.LLM),
RestrictModel(model="text-davinci-003", model_type=ModelType.LLM), RestrictModel(model="text-davinci-003", model_type=ModelType.LLM),
RestrictModel(model="whisper-1", model_type=ModelType.SPEECH2TEXT), RestrictModel(model="whisper-1", model_type=ModelType.SPEECH2TEXT),
] ]
@ -122,10 +120,20 @@ class HostingConfiguration:
if app_config.get("HOSTED_OPENAI_PAID_ENABLED"): if app_config.get("HOSTED_OPENAI_PAID_ENABLED"):
paid_quota = PaidHostingQuota( paid_quota = PaidHostingQuota(
stripe_price_id=app_config.get("HOSTED_OPENAI_PAID_STRIPE_PRICE_ID"), restrict_models=[
increase_quota=int(app_config.get("HOSTED_OPENAI_PAID_INCREASE_QUOTA", "1")), RestrictModel(model="gpt-4", model_type=ModelType.LLM),
min_quantity=int(app_config.get("HOSTED_OPENAI_PAID_MIN_QUANTITY", "1")), RestrictModel(model="gpt-4-turbo-preview", model_type=ModelType.LLM),
max_quantity=int(app_config.get("HOSTED_OPENAI_PAID_MAX_QUANTITY", "1")) RestrictModel(model="gpt-4-32k", model_type=ModelType.LLM),
RestrictModel(model="gpt-4-1106-preview", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-16k", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-16k-0613", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-1106", model_type=ModelType.LLM),
RestrictModel(model="gpt-4-0125-preview", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-0613", model_type=ModelType.LLM),
RestrictModel(model="gpt-3.5-turbo-instruct", model_type=ModelType.LLM),
RestrictModel(model="text-davinci-003", model_type=ModelType.LLM),
]
) )
quotas.append(paid_quota) quotas.append(paid_quota)
@ -164,12 +172,7 @@ class HostingConfiguration:
quotas.append(trial_quota) quotas.append(trial_quota)
if app_config.get("HOSTED_ANTHROPIC_PAID_ENABLED"): if app_config.get("HOSTED_ANTHROPIC_PAID_ENABLED"):
paid_quota = PaidHostingQuota( paid_quota = PaidHostingQuota()
stripe_price_id=app_config.get("HOSTED_ANTHROPIC_PAID_STRIPE_PRICE_ID"),
increase_quota=int(app_config.get("HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA", "1000000")),
min_quantity=int(app_config.get("HOSTED_ANTHROPIC_PAID_MIN_QUANTITY", "20")),
max_quantity=int(app_config.get("HOSTED_ANTHROPIC_PAID_MAX_QUANTITY", "100"))
)
quotas.append(paid_quota) quotas.append(paid_quota)
if len(quotas) > 0: if len(quotas) > 0:

View File

@ -26,3 +26,4 @@ pricing:
output: '0.002' output: '0.002'
unit: '0.001' unit: '0.001'
currency: USD currency: USD
deprecated: true

View File

@ -33,6 +33,11 @@ def handle(sender, **kwargs):
if quota_unit: if quota_unit:
if quota_unit == QuotaUnit.TOKENS: if quota_unit == QuotaUnit.TOKENS:
used_quota = message.message_tokens + message.answer_tokens used_quota = message.message_tokens + message.answer_tokens
elif quota_unit == QuotaUnit.CREDITS:
used_quota = 1
if 'gpt-4' in model_config.model:
used_quota = 20
else: else:
used_quota = 1 used_quota = 1

View File

@ -34,17 +34,22 @@ class BillingService:
def get_model_provider_payment_link(cls, def get_model_provider_payment_link(cls,
provider_name: str, provider_name: str,
tenant_id: str, tenant_id: str,
account_id: str): account_id: str,
prefilled_email: str):
params = { params = {
'provider_name': provider_name, 'provider_name': provider_name,
'tenant_id': tenant_id, 'tenant_id': tenant_id,
'account_id': account_id 'account_id': account_id,
'prefilled_email': prefilled_email
} }
return cls._send_request('GET', '/model-provider/payment-link', params=params) return cls._send_request('GET', '/model-provider/payment-link', params=params)
@classmethod @classmethod
def get_invoices(cls, prefilled_email: str = ''): def get_invoices(cls, prefilled_email: str = '', tenant_id: str = ''):
params = {'prefilled_email': prefilled_email} params = {
'prefilled_email': prefilled_email,
'tenant_id': tenant_id
}
return cls._send_request('GET', '/invoices', params=params) return cls._send_request('GET', '/invoices', params=params)
@classmethod @classmethod
@ -60,7 +65,7 @@ class BillingService:
return response.json() return response.json()
@staticmethod @staticmethod
def is_tenant_owner(current_user): def is_tenant_owner_or_admin(current_user):
tenant_id = current_user.current_tenant_id tenant_id = current_user.current_tenant_id
join = db.session.query(TenantAccountJoin).filter( join = db.session.query(TenantAccountJoin).filter(
@ -68,5 +73,5 @@ class BillingService:
TenantAccountJoin.account_id == current_user.id TenantAccountJoin.account_id == current_user.id
).first() ).first()
if join.role != 'owner': if join.role not in ['owner', 'admin']:
raise ValueError('Only tenant owner can perform this action') raise ValueError('Only team owner or team admin can perform this action')