mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
Feat/billing enhancement (#2239)
Co-authored-by: takatost <takatost@gmail.com>
This commit is contained in:
parent
2fc0dcc10a
commit
bb5d5fc683
|
@ -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')
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -26,3 +26,4 @@ pricing:
|
||||||
output: '0.002'
|
output: '0.002'
|
||||||
unit: '0.001'
|
unit: '0.001'
|
||||||
currency: USD
|
currency: USD
|
||||||
|
deprecated: true
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user