feat: claude paid optimize (#890)

This commit is contained in:
takatost 2023-08-17 16:56:20 +08:00 committed by GitHub
parent 2f7b234cc5
commit 9adbeadeec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 69 additions and 25 deletions

View File

@ -117,10 +117,12 @@ HOSTED_AZURE_OPENAI_QUOTA_LIMIT=200
HOSTED_ANTHROPIC_ENABLED=false HOSTED_ANTHROPIC_ENABLED=false
HOSTED_ANTHROPIC_API_BASE= HOSTED_ANTHROPIC_API_BASE=
HOSTED_ANTHROPIC_API_KEY= HOSTED_ANTHROPIC_API_KEY=
HOSTED_ANTHROPIC_QUOTA_LIMIT=1000000 HOSTED_ANTHROPIC_QUOTA_LIMIT=600000
HOSTED_ANTHROPIC_PAID_ENABLED=false HOSTED_ANTHROPIC_PAID_ENABLED=false
HOSTED_ANTHROPIC_PAID_STRIPE_PRICE_ID= HOSTED_ANTHROPIC_PAID_STRIPE_PRICE_ID=
HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA=1 HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA=1000000
HOSTED_ANTHROPIC_PAID_MIN_QUANTITY=20
HOSTED_ANTHROPIC_PAID_MAX_QUANTITY=100
STRIPE_API_KEY= STRIPE_API_KEY=
STRIPE_WEBHOOK_SECRET= STRIPE_WEBHOOK_SECRET=

View File

@ -258,6 +258,8 @@ def sync_anthropic_hosted_providers():
click.echo(click.style('Start sync anthropic hosted providers.', fg='green')) click.echo(click.style('Start sync anthropic hosted providers.', fg='green'))
count = 0 count = 0
new_quota_limit = hosted_model_providers.anthropic.quota_limit
page = 1 page = 1
while True: while True:
try: try:
@ -265,6 +267,7 @@ def sync_anthropic_hosted_providers():
Provider.provider_name == 'anthropic', Provider.provider_name == 'anthropic',
Provider.provider_type == ProviderType.SYSTEM.value, Provider.provider_type == ProviderType.SYSTEM.value,
Provider.quota_type == ProviderQuotaType.TRIAL.value, Provider.quota_type == ProviderQuotaType.TRIAL.value,
Provider.quota_limit != new_quota_limit
).order_by(Provider.created_at.desc()).paginate(page=page, per_page=100) ).order_by(Provider.created_at.desc()).paginate(page=page, per_page=100)
except NotFound: except NotFound:
break break
@ -272,9 +275,9 @@ def sync_anthropic_hosted_providers():
page += 1 page += 1
for provider in providers: for provider in providers:
try: try:
click.echo('Syncing tenant anthropic hosted provider: {}'.format(provider.tenant_id)) click.echo('Syncing tenant anthropic hosted provider: {}, origin: limit {}, used {}'
.format(provider.tenant_id, provider.quota_limit, provider.quota_used))
original_quota_limit = provider.quota_limit original_quota_limit = provider.quota_limit
new_quota_limit = hosted_model_providers.anthropic.quota_limit
division = math.ceil(new_quota_limit / 1000) division = math.ceil(new_quota_limit / 1000)
provider.quota_limit = new_quota_limit if original_quota_limit == 1000 \ provider.quota_limit = new_quota_limit if original_quota_limit == 1000 \

View File

@ -57,10 +57,12 @@ DEFAULTS = {
'HOSTED_OPENAI_PAID_INCREASE_QUOTA': 1, 'HOSTED_OPENAI_PAID_INCREASE_QUOTA': 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': 1000000, 'HOSTED_ANTHROPIC_QUOTA_LIMIT': 600000,
'HOSTED_ANTHROPIC_ENABLED': 'False', 'HOSTED_ANTHROPIC_ENABLED': 'False',
'HOSTED_ANTHROPIC_PAID_ENABLED': 'False', 'HOSTED_ANTHROPIC_PAID_ENABLED': 'False',
'HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA': 1, 'HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA': 1000000,
'HOSTED_ANTHROPIC_PAID_MIN_QUANTITY': 20,
'HOSTED_ANTHROPIC_PAID_MAX_QUANTITY': 100,
'TENANT_DOCUMENT_COUNT': 100, 'TENANT_DOCUMENT_COUNT': 100,
'CLEAN_DAY_SETTING': 30, 'CLEAN_DAY_SETTING': 30,
'UPLOAD_FILE_SIZE_LIMIT': 15, 'UPLOAD_FILE_SIZE_LIMIT': 15,
@ -211,7 +213,7 @@ class Config:
self.HOSTED_OPENAI_API_KEY = get_env('HOSTED_OPENAI_API_KEY') self.HOSTED_OPENAI_API_KEY = get_env('HOSTED_OPENAI_API_KEY')
self.HOSTED_OPENAI_API_BASE = get_env('HOSTED_OPENAI_API_BASE') self.HOSTED_OPENAI_API_BASE = get_env('HOSTED_OPENAI_API_BASE')
self.HOSTED_OPENAI_API_ORGANIZATION = get_env('HOSTED_OPENAI_API_ORGANIZATION') self.HOSTED_OPENAI_API_ORGANIZATION = get_env('HOSTED_OPENAI_API_ORGANIZATION')
self.HOSTED_OPENAI_QUOTA_LIMIT = 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_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_INCREASE_QUOTA = int(get_env('HOSTED_OPENAI_PAID_INCREASE_QUOTA'))
@ -219,15 +221,17 @@ class Config:
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')
self.HOSTED_AZURE_OPENAI_API_BASE = get_env('HOSTED_AZURE_OPENAI_API_BASE') self.HOSTED_AZURE_OPENAI_API_BASE = get_env('HOSTED_AZURE_OPENAI_API_BASE')
self.HOSTED_AZURE_OPENAI_QUOTA_LIMIT = get_env('HOSTED_AZURE_OPENAI_QUOTA_LIMIT') self.HOSTED_AZURE_OPENAI_QUOTA_LIMIT = int(get_env('HOSTED_AZURE_OPENAI_QUOTA_LIMIT'))
self.HOSTED_ANTHROPIC_ENABLED = get_bool_env('HOSTED_ANTHROPIC_ENABLED') self.HOSTED_ANTHROPIC_ENABLED = get_bool_env('HOSTED_ANTHROPIC_ENABLED')
self.HOSTED_ANTHROPIC_API_BASE = get_env('HOSTED_ANTHROPIC_API_BASE') self.HOSTED_ANTHROPIC_API_BASE = get_env('HOSTED_ANTHROPIC_API_BASE')
self.HOSTED_ANTHROPIC_API_KEY = get_env('HOSTED_ANTHROPIC_API_KEY') self.HOSTED_ANTHROPIC_API_KEY = get_env('HOSTED_ANTHROPIC_API_KEY')
self.HOSTED_ANTHROPIC_QUOTA_LIMIT = 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_STRIPE_PRICE_ID = get_env('HOSTED_ANTHROPIC_PAID_STRIPE_PRICE_ID')
self.HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA = get_env('HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA') 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.STRIPE_API_KEY = get_env('STRIPE_API_KEY') self.STRIPE_API_KEY = get_env('STRIPE_API_KEY')
self.STRIPE_WEBHOOK_SECRET = get_env('STRIPE_WEBHOOK_SECRET') self.STRIPE_WEBHOOK_SECRET = get_env('STRIPE_WEBHOOK_SECRET')

View File

@ -38,12 +38,20 @@ class StripeWebhookApi(Resource):
logging.debug(event['data']['object']['payment_status']) logging.debug(event['data']['object']['payment_status'])
logging.debug(event['data']['object']['metadata']) logging.debug(event['data']['object']['metadata'])
session = stripe.checkout.Session.retrieve(
event['data']['object']['id'],
expand=['line_items'],
)
logging.debug(session.line_items['data'][0]['quantity'])
# Fulfill the purchase... # Fulfill the purchase...
provider_checkout_service = ProviderCheckoutService() provider_checkout_service = ProviderCheckoutService()
try: try:
provider_checkout_service.fulfill_provider_order(event) provider_checkout_service.fulfill_provider_order(event, session.line_items)
except Exception as e: except Exception as e:
logging.debug(str(e)) logging.debug(str(e))
return 'success', 200 return 'success', 200

View File

@ -125,6 +125,8 @@ class BaseLLM(BaseProviderModel):
completion_tokens = self.get_num_tokens([PromptMessage(content=completion_content, type=MessageType.ASSISTANT)]) completion_tokens = self.get_num_tokens([PromptMessage(content=completion_content, type=MessageType.ASSISTANT)])
total_tokens = prompt_tokens + completion_tokens total_tokens = prompt_tokens + completion_tokens
self.model_provider.update_last_used()
if self.deduct_quota: if self.deduct_quota:
self.model_provider.deduct_quota(total_tokens) self.model_provider.deduct_quota(total_tokens)

View File

@ -183,6 +183,8 @@ class AnthropicProvider(BaseModelProvider):
return { return {
'product_id': hosted_model_providers.anthropic.paid_stripe_price_id, 'product_id': hosted_model_providers.anthropic.paid_stripe_price_id,
'increase_quota': hosted_model_providers.anthropic.paid_increase_quota, 'increase_quota': hosted_model_providers.anthropic.paid_increase_quota,
'min_quantity': hosted_model_providers.anthropic.paid_min_quantity,
'max_quantity': hosted_model_providers.anthropic.paid_max_quantity,
} }
return None return None

View File

@ -31,7 +31,9 @@ class HostedAnthropic(BaseModel):
"""Quota limit for the anthropic hosted model. 0 means unlimited.""" """Quota limit for the anthropic hosted model. 0 means unlimited."""
paid_enabled: bool = False paid_enabled: bool = False
paid_stripe_price_id: str = None paid_stripe_price_id: str = None
paid_increase_quota: int = 1 paid_increase_quota: int = 1000000
paid_min_quantity: int = 20
paid_max_quantity: int = 100
class HostedModelProviders(BaseModel): class HostedModelProviders(BaseModel):
@ -73,4 +75,6 @@ def init_app(app: Flask):
paid_enabled=app.config.get("HOSTED_ANTHROPIC_PAID_ENABLED"), paid_enabled=app.config.get("HOSTED_ANTHROPIC_PAID_ENABLED"),
paid_stripe_price_id=app.config.get("HOSTED_ANTHROPIC_PAID_STRIPE_PRICE_ID"), paid_stripe_price_id=app.config.get("HOSTED_ANTHROPIC_PAID_STRIPE_PRICE_ID"),
paid_increase_quota=app.config.get("HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA"), paid_increase_quota=app.config.get("HOSTED_ANTHROPIC_PAID_INCREASE_QUOTA"),
paid_min_quantity=app.config.get("HOSTED_ANTHROPIC_PAID_MIN_QUANTITY"),
paid_max_quantity=app.config.get("HOSTED_ANTHROPIC_PAID_MAX_QUANTITY"),
) )

View File

@ -5,10 +5,11 @@
], ],
"system_config": { "system_config": {
"supported_quota_types": [ "supported_quota_types": [
"paid",
"trial" "trial"
], ],
"quota_unit": "times", "quota_unit": "tokens",
"quota_limit": 1000 "quota_limit": 600000
}, },
"model_flexibility": "fixed" "model_flexibility": "fixed"
} }

View File

@ -39,6 +39,8 @@ class ProviderCheckoutService:
raise ValueError(f'provider name {provider_name} not support payment') raise ValueError(f'provider name {provider_name} not support payment')
payment_product_id = payment_info['product_id'] payment_product_id = payment_info['product_id']
payment_min_quantity = payment_info['min_quantity']
payment_max_quantity = payment_info['max_quantity']
# create provider order # create provider order
provider_order = ProviderOrder( provider_order = ProviderOrder(
@ -53,18 +55,29 @@ class ProviderCheckoutService:
db.session.add(provider_order) db.session.add(provider_order)
db.session.flush() db.session.flush()
line_item = {
'price': f'{payment_product_id}',
'quantity': payment_min_quantity
}
if payment_min_quantity > 1 and payment_max_quantity != payment_min_quantity:
line_item['adjustable_quantity'] = {
'enabled': True,
'minimum': payment_min_quantity,
'maximum': payment_max_quantity
}
try: try:
# create stripe checkout session # create stripe checkout session
checkout_session = stripe.checkout.Session.create( checkout_session = stripe.checkout.Session.create(
line_items=[ line_items=[
{ line_item
'price': f'{payment_product_id}',
'quantity': 1,
},
], ],
mode='payment', mode='payment',
success_url=current_app.config.get("CONSOLE_WEB_URL") + '?provider_payment=succeeded', success_url=current_app.config.get("CONSOLE_WEB_URL")
cancel_url=current_app.config.get("CONSOLE_WEB_URL") + '?provider_payment=cancelled', + f'?provider_name={provider_name}&payment_result=succeeded',
cancel_url=current_app.config.get("CONSOLE_WEB_URL")
+ f'?provider_name={provider_name}&payment_result=cancelled',
automatic_tax={'enabled': True}, automatic_tax={'enabled': True},
) )
except Exception as e: except Exception as e:
@ -76,7 +89,7 @@ class ProviderCheckoutService:
return ProviderCheckout(checkout_session) return ProviderCheckout(checkout_session)
def fulfill_provider_order(self, event): def fulfill_provider_order(self, event, line_items):
provider_order = db.session.query(ProviderOrder) \ provider_order = db.session.query(ProviderOrder) \
.filter(ProviderOrder.payment_id == event['data']['object']['id']) \ .filter(ProviderOrder.payment_id == event['data']['object']['id']) \
.first() .first()
@ -85,7 +98,8 @@ class ProviderCheckoutService:
raise ValueError(f'provider order not found, payment id: {event["data"]["object"]["id"]}') raise ValueError(f'provider order not found, payment id: {event["data"]["object"]["id"]}')
if provider_order.payment_status != ProviderOrderPaymentStatus.WAIT_PAY.value: if provider_order.payment_status != ProviderOrderPaymentStatus.WAIT_PAY.value:
raise ValueError(f'provider order payment status is not wait pay, payment id: {event["data"]["object"]["id"]}') raise ValueError(
f'provider order payment status is not wait pay, payment id: {event["data"]["object"]["id"]}')
provider_order.transaction_id = event['data']['object']['payment_intent'] provider_order.transaction_id = event['data']['object']['payment_intent']
provider_order.currency = event['data']['object']['currency'] provider_order.currency = event['data']['object']['currency']
@ -110,10 +124,12 @@ class ProviderCheckoutService:
model_provider = model_provider_class(provider=provider) model_provider = model_provider_class(provider=provider)
payment_info = model_provider.get_payment_info() payment_info = model_provider.get_payment_info()
quantity = line_items['data'][0]['quantity']
if not payment_info: if not payment_info:
increase_quota = 0 increase_quota = 0
else: else:
increase_quota = int(payment_info['increase_quota']) increase_quota = int(payment_info['increase_quota']) * quantity
if increase_quota > 0: if increase_quota > 0:
provider.quota_limit += increase_quota provider.quota_limit += increase_quota

View File

@ -133,12 +133,14 @@ class ProviderService:
provider_parameter_dict[key]['is_valid'] = provider.is_valid provider_parameter_dict[key]['is_valid'] = provider.is_valid
provider_parameter_dict[key]['quota_used'] = provider.quota_used provider_parameter_dict[key]['quota_used'] = provider.quota_used
provider_parameter_dict[key]['quota_limit'] = provider.quota_limit provider_parameter_dict[key]['quota_limit'] = provider.quota_limit
provider_parameter_dict[key]['last_used'] = provider.last_used provider_parameter_dict[key]['last_used'] = int(provider.last_used.timestamp()) \
if provider.last_used else None
elif provider.provider_type == ProviderType.CUSTOM.value \ elif provider.provider_type == ProviderType.CUSTOM.value \
and ProviderType.CUSTOM.value in provider_parameter_dict: and ProviderType.CUSTOM.value in provider_parameter_dict:
# if custom # if custom
key = ProviderType.CUSTOM.value key = ProviderType.CUSTOM.value
provider_parameter_dict[key]['last_used'] = provider.last_used provider_parameter_dict[key]['last_used'] = int(provider.last_used.timestamp()) \
if provider.last_used else None
provider_parameter_dict[key]['is_valid'] = provider.is_valid provider_parameter_dict[key]['is_valid'] = provider.is_valid
if model_provider_rule['model_flexibility'] == 'fixed': if model_provider_rule['model_flexibility'] == 'fixed':