1515FREE_TIER_LIMIT = 5
1616PRO_TIER_LIMIT = 20
1717
18+ CC_FREE_TIER_LIMIT = 1
19+ CC_PRO_TIER_LIMIT = 5
20+
1821LUA_RATE_LIMIT = """
1922local count = redis.call('INCR', KEYS[1])
2023local ttl = redis.call('TTL', KEYS[1])
2528return {count, ttl}
2629"""
2730
31+ LUA_CHECK_LIMIT = """
32+ local count = tonumber(redis.call('GET', KEYS[1]) or '0')
33+ local ttl = redis.call('TTL', KEYS[1])
34+ return {count, ttl}
35+ """
36+
2837
2938async def resolve_ai_client (
3039 developer_id : str | None ,
3140 plan : str | None ,
3241 encrypted_byok : dict | None ,
42+ feature : str = "query-builder" ,
43+ increment : bool = True ,
3344) -> ChatGroq :
3445 """Resolve the appropriate AI client based on BYOK and plan.
3546
@@ -42,14 +53,16 @@ async def resolve_ai_client(
4253 developer_id: The developer's MongoDB ObjectId string.
4354 plan: The developer's plan ('free' or 'pro').
4455 encrypted_byok: Transit-encrypted BYOK payload from Node, or None.
56+ feature: The feature being used ("query-builder" or "collection-creator").
57+ increment: Whether to increment the usage count.
4558
4659 Returns:
4760 An initialized ChatGroq client.
4861
4962 Raises:
5063 HTTPException: 401 if BYOK key is invalid, 403 if rate limited, 500 if no platform key.
5164 """
52- logger .info ("Resolving AI client | developer_id =%s, plan=%s, has_byok=%s" , developer_id , plan , bool (encrypted_byok and encrypted_byok .get ("groqKey" )))
65+ logger .info ("Resolving AI client | feature =%s, dev_id=%s, plan=%s, has_byok=%s, increment=%s " , feature , developer_id , plan , bool (encrypted_byok and encrypted_byok .get ("groqKey" )), increment )
5366
5467 # ── 1. Try BYOK ──
5568 if encrypted_byok and encrypted_byok .get ("groqKey" ):
@@ -83,18 +96,33 @@ async def resolve_ai_client(
8396 detail = "developer_id is required for platform key rate limiting"
8497 )
8598
86- limit = PRO_TIER_LIMIT if plan == "pro" else FREE_TIER_LIMIT
99+ if feature == "collection-creator" :
100+ limit = CC_PRO_TIER_LIMIT if plan == "pro" else CC_FREE_TIER_LIMIT
101+ key_prefix = "ai:cc:count"
102+ else :
103+ limit = PRO_TIER_LIMIT if plan == "pro" else FREE_TIER_LIMIT
104+ key_prefix = "ai:gen:count"
105+
87106 month = datetime .datetime .now (datetime .UTC ).strftime ("%Y-%m" )
88- key = f"ai:gen:count:{ developer_id } :{ month } "
107+ key = f"{ key_prefix } :{ developer_id } :{ month } "
108+
109+ logger .info ("🏢 Using Platform Groq key. Checking Redis rate limit at '%s' (limit=%d/month, increment=%s)..." , key , limit , increment )
89110
90- logger .info ("🏢 Using Platform Groq key. Checking Redis rate limit at '%s' (limit=%d/month)..." , key , limit )
111+ if increment :
112+ count , ttl = await redis_client .eval (LUA_RATE_LIMIT , 1 , key , 32 * 86400 )
113+ else :
114+ count , ttl = await redis_client .eval (LUA_CHECK_LIMIT , 1 , key )
115+ # If we are just checking, we still enforce the limit but don't increment.
116+ # Actually, if count >= limit, we should block.
117+ # Wait, if count == limit and we check, it's allowed.
118+ # If count > limit, it's blocked.
119+ # So we just use count.
91120
92- # Atomic script execution: INCR + EXPIRE (if new key)
93- count , ttl = await redis_client .eval (LUA_RATE_LIMIT , 1 , key , 32 * 86400 )
94121 logger .info ("📊 Monthly usage for developer_id=%s: %d/%d (TTL: %ds remaining in window)" , developer_id , count , limit , ttl )
95122
96123 if count > limit :
97124 tier_name = "Pro" if plan == "pro" else "Free"
125+ feature_name = "Schema Designs" if feature == "collection-creator" else "AI usages"
98126 upgrade_msg = (
99127 "Add your own Groq key in Settings to get unlimited usage."
100128 if plan == "pro"
@@ -103,7 +131,7 @@ async def resolve_ai_client(
103131 logger .warning ("🚫 Rate limit exceeded for developer_id=%s: %d > %d allowed in %s tier" , developer_id , count , limit , tier_name )
104132 raise HTTPException (
105133 status_code = 403 ,
106- detail = f"{ tier_name } tier AI limit reached ({ limit } /month). { upgrade_msg } " ,
134+ detail = f"{ tier_name } tier limit reached ({ limit } { feature_name } /month). { upgrade_msg } " ,
107135 )
108136
109137 # ── 4. Return platform client ──
0 commit comments