๐Ÿ’ฐ Token ้ข„็ฎ—

ไฝ ๆ— ๆณ•ๆŽงๅˆถไฝ ๆฒกๆœ‰้ข„็ฎ—็š„ไธœ่ฅฟใ€‚Token ้ข„็ฎ— = ๆˆๆœฌ็š„ๅคฉ่Šฑๆฟโ€”โ€”ๆŒ‰็”จๆˆทใ€ๆŒ‰ไผš่ฏใ€ๆŒ‰ๅคฉ่ฎพ้™๏ผŒ่ถ…้ข„็ฎ—่‡ชๅŠจ้™็บงใ€‚

โš ๏ธ ๆฒกๆœ‰้ข„็ฎ—็š„ๅŽๆžœ๏ผšไธ€ไธชๆถๆ„็”จๆˆทๅพช็Žฏ่ฐƒ็”จ API๏ผŒไธ€ๅคœไน‹้—ดๅฏไปฅ่Šฑๆމไฝ ๆ•ฐๅƒ็พŽๅ…ƒใ€‚ไธ€ไธช bug ๅฏผ่‡ดๆ— ้™ๅพช็Žฏ่ฐƒ็”จ๏ผŒ่ดฆๅ•ๅฏ่ƒฝๅœจๅ‡ ๅฐๆ—ถๅ†…็ˆ†ๆމใ€‚Token ้ข„็ฎ—ไธไป…ๆ˜ฏ็œ้’ฑๅทฅๅ…ท๏ผŒๆ›ดๆ˜ฏๆˆๆœฌๅฎ‰ๅ…จ็š„ๅบ•็บฟใ€‚

้ข„็ฎ—ๅฑ‚็บง

้ข„็ฎ—ๅฑ‚็บง๏ผˆไปŽ็ป†ๅˆฐ็ฒ—๏ผ‰๏ผš

่ฏทๆฑ‚็บง โ†’ ไผš่ฏ็บง โ†’ ็”จๆˆท็บง โ†’ ็งŸๆˆท็บง โ†’ ๅ…จๅฑ€็บง

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๅ…จๅฑ€้ข„็ฎ—: $5000/ๆœˆ                                      โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ ็งŸๆˆทA: $2000/ๆœˆ  โ”‚  ็งŸๆˆทB: $1000/ๆœˆ  โ”‚  ...       โ”‚ โ”‚
โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”              โ”‚ โ”‚
โ”‚ โ”‚ โ”‚็”จๆˆท1: $50/ๆœˆ โ”‚ โ”‚ โ”‚็”จๆˆท1: $30/ๆœˆ โ”‚              โ”‚ โ”‚
โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚              โ”‚ โ”‚
โ”‚ โ”‚ โ”‚ โ”‚ไผš่ฏ1:    โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ไผš่ฏ1:    โ”‚ โ”‚              โ”‚ โ”‚
โ”‚ โ”‚ โ”‚ โ”‚1000tok/่ฝฎโ”‚ โ”‚ โ”‚ โ”‚ โ”‚800tok/่ฝฎ โ”‚ โ”‚              โ”‚ โ”‚
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚              โ”‚ โ”‚
โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜              โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Token ้ข„็ฎ—็ณป็ปŸๅฎž็Žฐ

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
import time

class BudgetLevel(Enum):
    REQUEST = "request"     # ๅ•ๆฌก่ฏทๆฑ‚
    SESSION = "session"     # ๅ•ไธชไผš่ฏ
    USER_DAILY = "user_daily"
    USER_MONTHLY = "user_monthly"
    TENANT_MONTHLY = "tenant_monthly"
    GLOBAL_MONTHLY = "global_monthly"

class DegradeAction(Enum):
    CHEAPER_MODEL = "cheaper_model"     # ้™็บงๅˆฐๆ›ดไพฟๅฎœ็š„ๆจกๅž‹
    REDUCE_CONTEXT = "reduce_context"   # ๅ‡ๅฐ‘ไธŠไธ‹ๆ–‡้•ฟๅบฆ
    QUEUE = "queue"                     # ๆŽ’้˜Ÿ็ญ‰ๅพ…
    REJECT = "reject"                   # ๆ‹’็ป่ฏทๆฑ‚
    NOTIFY = "notify"                   # ไป…้€š็Ÿฅ๏ผŒไธ้™ๅˆถ

@dataclass
class BudgetLimit:
    level: BudgetLevel
    max_tokens: int
    max_cost: float          # USD
    degrade_action: DegradeAction
    warning_threshold: float = 0.8  # 80% ๆ—ถๅ‘Š่ญฆ

@dataclass
class BudgetUsage:
    level: BudgetLevel
    key: str                 # ๆ ‡่ฏ†๏ผˆuser_id, session_id ็ญ‰๏ผ‰
    tokens_used: int = 0
    cost_used: float = 0.0
    period_start: datetime = None
    period_end: datetime = None

class TokenBudgetManager:
    """Token ้ข„็ฎ—็ฎก็†ๅ™จ"""
    
    # ้ป˜่ฎค้ข„็ฎ—็ญ–็•ฅ
    DEFAULT_LIMITS = {
        BudgetLevel.REQUEST: BudgetLimit(
            level=BudgetLevel.REQUEST,
            max_tokens=8000,
            max_cost=0.50,
            degrade_action=DegradeAction.REDUCE_CONTEXT
        ),
        BudgetLevel.SESSION: BudgetLimit(
            level=BudgetLevel.SESSION,
            max_tokens=100000,
            max_cost=5.00,
            degrade_action=DegradeAction.CHEAPER_MODEL
        ),
        BudgetLevel.USER_DAILY: BudgetLimit(
            level=BudgetLevel.USER_DAILY,
            max_tokens=500000,
            max_cost=10.00,
            degrade_action=DegradeAction.CHEAPER_MODEL
        ),
        BudgetLevel.USER_MONTHLY: BudgetLimit(
            level=BudgetLevel.USER_MONTHLY,
            max_tokens=10000000,
            max_cost=50.00,
            degrade_action=DegradeAction.REJECT
        ),
    }
    
    def __init__(self, redis_client, custom_limits: dict = None):
        self.redis = redis_client
        self.limits = custom_limits or self.DEFAULT_LIMITS
        self.alerts = []
    
    async def check_budget(self, user_id: str, session_id: str,
                           estimated_tokens: int = 0) -> dict:
        """ๆฃ€ๆŸฅ้ข„็ฎ—ๆ˜ฏๅฆๅ……่ถณ"""
        checks = {}
        
        # ๆฃ€ๆŸฅๅ„ๅฑ‚็บง้ข„็ฎ—
        for level in [BudgetLevel.REQUEST, BudgetLevel.SESSION, 
                      BudgetLevel.USER_DAILY, BudgetLevel.USER_MONTHLY]:
            limit = self.limits[level]
            usage = await self._get_usage(level, user_id, session_id)
            
            # ๆฃ€ๆŸฅ token ้ข„็ฎ—
            token_pct = (usage.tokens_used + estimated_tokens) / limit.max_tokens
            # ๆฃ€ๆŸฅๆˆๆœฌ้ข„็ฎ—
            cost_pct = usage.cost_used / limit.max_cost if limit.max_cost > 0 else 0
            
            max_pct = max(token_pct, cost_pct)
            over_budget = max_pct > 1.0
            near_limit = max_pct > limit.warning_threshold
            
            checks[level.value] = {
                'over_budget': over_budget,
                'usage_pct': max_pct,
                'near_limit': near_limit,
                'degrade_action': limit.degrade_action.value if over_budget else None
            }
            
            # ๅ‘Š่ญฆ
            if near_limit and not over_budget:
                await self._send_alert(level, user_id, max_pct)
        
        # ๆฑ‡ๆ€ปๅ†ณ็ญ–
        over_levels = [k for k, v in checks.items() if v['over_budget']]
        if over_levels:
            # ๅ–ๆœ€ไธฅๆ ผ็š„้™็บง็ญ–็•ฅ
            most_restrictive = self._most_restrictive_action(over_levels)
            return {
                'allowed': most_restrictive != DegradeAction.REJECT,
                'action': most_restrictive.value,
                'checks': checks
            }
        
        return {'allowed': True, 'action': None, 'checks': checks}
    
    async def record_usage(self, user_id: str, session_id: str,
                           tokens_used: int, cost: float):
        """่ฎฐๅฝ•ไฝฟ็”จ้‡"""
        for level in [BudgetLevel.REQUEST, BudgetLevel.SESSION,
                      BudgetLevel.USER_DAILY, BudgetLevel.USER_MONTHLY]:
            key = self._usage_key(level, user_id, session_id)
            # Redis INCRBY + ่ฎพ็ฝฎ่ฟ‡ๆœŸๆ—ถ้—ด
            pipe = self.redis.pipeline()
            pipe.incrby(f"{key}:tokens", tokens_used)
            pipe.incrbyfloat(f"{key}:cost", cost)
            
            # ่ฎพ็ฝฎ่ฟ‡ๆœŸๆ—ถ้—ด
            if level == BudgetLevel.USER_DAILY:
                pipe.expire(f"{key}:tokens", 86400)
                pipe.expire(f"{key}:cost", 86400)
            elif level == BudgetLevel.USER_MONTHLY:
                pipe.expire(f"{key}:tokens", 30*86400)
                pipe.expire(f"{key}:cost", 30*86400)
            
            await pipe.execute()
    
    async def _get_usage(self, level: BudgetLevel, user_id: str, 
                          session_id: str) -> BudgetUsage:
        """่Žทๅ–ๅฝ“ๅ‰ไฝฟ็”จ้‡"""
        key = self._usage_key(level, user_id, session_id)
        tokens = int(await self.redis.get(f"{key}:tokens") or 0)
        cost = float(await self.redis.get(f"{key}:cost") or 0)
        
        return BudgetUsage(
            level=level,
            key=key,
            tokens_used=tokens,
            cost_used=cost
        )
    
    def _usage_key(self, level: BudgetLevel, user_id: str, 
                    session_id: str) -> str:
        """็”Ÿๆˆ Redis key"""
        today = datetime.now().strftime('%Y-%m-%d')
        month = datetime.now().strftime('%Y-%m')
        
        if level == BudgetLevel.REQUEST:
            return f"budget:rq:{session_id}"
        elif level == BudgetLevel.SESSION:
            return f"budget:ss:{session_id}"
        elif level == BudgetLevel.USER_DAILY:
            return f"budget:ud:{user_id}:{today}"
        elif level == BudgetLevel.USER_MONTHLY:
            return f"budget:um:{user_id}:{month}"
    
    def _most_restrictive_action(self, levels: list) -> DegradeAction:
        """้€‰ๆ‹ฉๆœ€ไธฅๆ ผ็š„้™็บง็ญ–็•ฅ"""
        priority = [DegradeAction.REJECT, DegradeAction.CHEAPER_MODEL,
                    DegradeAction.REDUCE_CONTEXT, DegradeAction.QUEUE]
        for action in priority:
            for level in levels:
                limit = self.limits.get(BudgetLevel(level))
                if limit and limit.degrade_action == action:
                    return action
        return DegradeAction.NOTIFY
    
    async def _send_alert(self, level: BudgetLevel, user_id: str, pct: float):
        """ๅ‘้€้ข„็ฎ—ๅ‘Š่ญฆ"""
        alert = {
            'level': level.value,
            'user_id': user_id,
            'usage_pct': pct,
            'timestamp': datetime.now().isoformat()
        }
        self.alerts.append(alert)
        # ๅฎž้™…ๅฎž็Žฐ๏ผšๅ‘ๅˆฐ Slack/Email/Webhook
        print(f"โš ๏ธ ้ข„็ฎ—ๅ‘Š่ญฆ: {level.value} - ็”จๆˆท {user_id} ๅทฒไฝฟ็”จ {pct*100:.0f}%")

้™็บง็ญ–็•ฅๅฎž็Žฐ

class DegradeStrategy:
    """้™็บง็ญ–็•ฅ"""
    
    # ๆจกๅž‹้™็บง้“พ
    MODEL_DEGRADE_CHAIN = {
        "claude-opus-4-20250514": "claude-sonnet-4-20250514",
        "claude-sonnet-4-20250514": "claude-3-5-haiku-20241022",
        "gpt-5.5": "gpt-5.4",
        "gpt-5.4": "gpt-5.4-mini",
        "gpt-4o": "gpt-4o-mini",
        "gpt-4o-mini": "deepseek-chat",
    }
    
    def get_cheaper_model(self, current_model: str) -> str:
        """่Žทๅ–ไธ‹ไธ€็บงๆ›ดไพฟๅฎœ็š„ๆจกๅž‹"""
        return self.MODEL_DEGRADE_CHAIN.get(current_model, "deepseek-chat")
    
    def reduce_context(self, messages: list, max_tokens: int) -> list:
        """ๅ‡ๅฐ‘ไธŠไธ‹ๆ–‡้•ฟๅบฆ"""
        total = sum(len(m['content']) for m in messages)
        if total <= max_tokens * 4:  # ็ฒ—ไผฐ
            return messages
        
        # ไฟ็•™็ณป็ปŸๆถˆๆฏ + ๆœ€่ฟ‘็š„ๅฏน่ฏ
        system_msgs = [m for m in messages if m['role'] == 'system']
        other_msgs = [m for m in messages if m['role'] != 'system']
        
        # ไปŽๆ—งๅˆฐๆ–ฐๆˆชๆ–ญ
        budget = max_tokens * 4
        system_size = sum(len(m['content']) for m in system_msgs)
        remaining = budget - system_size
        
        kept = []
        for msg in reversed(other_msgs):
            if remaining <= 0:
                break
            kept.append(msg)
            remaining -= len(msg['content'])
        
        return system_msgs + list(reversed(kept))

# ้›†ๆˆๅˆฐ่ฏทๆฑ‚ๆต็จ‹
degrade = DegradeStrategy()

async def chat_with_budget(user_id: str, session_id: str, 
                            messages: list, model: str = "gpt-4o"):
    # 1. ๆฃ€ๆŸฅ้ข„็ฎ—
    budget_check = await budget_manager.check_budget(
        user_id, session_id, estimated_tokens=1000
    )
    
    current_model = model
    current_messages = messages
    
    if not budget_check['allowed']:
        return {"error": "ๅทฒ่ถ…ๅ‡บไฝฟ็”จ้ข„็ฎ—๏ผŒ่ฏทๆ˜Žๅคฉๅ†่ฏ•"}
    
    if budget_check['action'] == 'cheaper_model':
        current_model = degrade.get_cheaper_model(model)
    elif budget_check['action'] == 'reduce_context':
        current_messages = degrade.reduce_context(messages, max_tokens=4000)
    
    # 2. ่ฐƒ็”จ LLM
    response = await call_llm(model=current_model, messages=current_messages)
    
    # 3. ่ฎฐๅฝ•ไฝฟ็”จ้‡
    tokens = response.usage.total_tokens
    cost = calculate_cost(current_model, response.usage)
    await budget_manager.record_usage(user_id, session_id, tokens, cost)
    
    return {
        "response": response.choices[0].message.content,
        "model_used": current_model,
        "degraded": current_model != model,
        "tokens_used": tokens,
        "cost": cost
    }

ๆŒ‰็”จๆˆท็ฑปๅž‹่ฎพ็ฝฎ้ข„็ฎ—

็”จๆˆท็ฑปๅž‹ๆฏๆ—ฅ Tokenๆฏๆ—ฅๆˆๆœฌๆจกๅž‹้™็บง็ญ–็•ฅ
ๅ…่ดน็”จๆˆท50K$0.50GPT-4o-mini่ถ…้™ๆ‹’็ป
ๅŸบ็ก€ไป˜่ดน500K$5.00GPT-4o้™็บงๅˆฐ mini
Pro ไป˜่ดน2M$20.00GPT-4o/Claude้™็บงๅˆฐ GPT-4o
ไผไธš็”จๆˆท่‡ชๅฎšไน‰่‡ชๅฎšไน‰่‡ชๅฎšไน‰้€š็Ÿฅ็ฎก็†ๅ‘˜

ๆˆๆœฌๆŠฅ่ญฆ

class CostAlertManager:
    """ๆˆๆœฌๆŠฅ่ญฆ็ฎก็†"""
    
    ALERT_LEVELS = [
        {'threshold': 0.5, 'level': 'info', 'message': 'ๅทฒไฝฟ็”จ50%้ข„็ฎ—'},
        {'threshold': 0.8, 'level': 'warning', 'message': 'ๅทฒไฝฟ็”จ80%้ข„็ฎ—'},
        {'threshold': 0.95, 'level': 'critical', 'message': 'ๅทฒไฝฟ็”จ95%้ข„็ฎ—'},
        {'threshold': 1.0, 'level': 'emergency', 'message': 'ๅทฒ่ถ…ๅ‡บ้ข„็ฎ—'},
    ]
    
    async def check_and_alert(self, scope: str, usage_pct: float):
        """ๆฃ€ๆŸฅๅนถๅ‘้€ๆŠฅ่ญฆ"""
        for alert in self.ALERT_LEVELS:
            if usage_pct >= alert['threshold']:
                await self._send_alert(scope, alert, usage_pct)
    
    async def _send_alert(self, scope: str, alert: dict, pct: float):
        """ๅ‘้€ๆŠฅ่ญฆ๏ผˆๅคšๆธ ้“๏ผ‰"""
        message = f"[{alert['level'].upper()}] {scope}: {alert['message']} ({pct*100:.1f}%)"
        
        # Slack ๆŠฅ่ญฆ
        if alert['level'] in ['critical', 'emergency']:
            await send_slack("#cost-alerts", message)
        
        # Email ๆŠฅ่ญฆ
        if alert['level'] == 'emergency':
            await send_email("admin@company.com", 
                           "๐Ÿšจ LLM ๆˆๆœฌ่ถ…้ข„็ฎ—", message)
        
        # ่‡ชๅŠจ้™็บง
        if alert['level'] == 'emergency':
            await self._auto_degrade(scope)
    
    async def _auto_degrade(self, scope: str):
        """่‡ชๅŠจ้™็บงโ€”โ€”็ดงๆ€ฅไฟๆŠค"""
        # ๅ…จๅฑ€้™็บงๅˆฐๆœ€ไพฟๅฎœ็š„ๆจกๅž‹
        await set_global_default_model("deepseek-chat")
        await send_slack("#cost-alerts", 
                        f"๐Ÿšจ ๅทฒ่‡ชๅŠจ้™็บงๅˆฐ deepseek-chat ไปฅๆŽงๅˆถๆˆๆœฌ")
โœ… Token ้ข„็ฎ—ๆœ€ไฝณๅฎž่ทต๏ผš
  1. ไปŽๆœ€็ฒ—็ฒ’ๅบฆๅผ€ๅง‹๏ผšๅ…ˆ่ฎพๅ…จๅฑ€ๆœˆๅบฆ้ข„็ฎ—๏ผŒๅ†้€ๆญฅ็ป†ๅŒ–ๅˆฐ็”จๆˆท็บง
  2. ๅฎๅฏๅคš่ฎพไธ€ๅฑ‚๏ผšๆฏไธชๅฑ‚็บง้ƒฝๆœ‰้ข„็ฎ— = ไปปไฝ•ไธ€ๅฑ‚่ขซ็ช็ ด้ƒฝๆœ‰ไฟๆŠค
  3. ้™็บง > ๆ‹’็ป๏ผš็”จๆˆทไฝ“้ชŒไธŠ๏ผŒ้™็บงๅˆฐๅฐๆจกๅž‹ๆฏ”็›ดๆŽฅๆ‹’็ปๅฅฝๅพ—ๅคš
  4. ๅฎžๆ—ถ็›‘ๆŽง๏ผš80% ๅ‘Š่ญฆ่ฎฉไฝ ๆœ‰ๆ—ถ้—ดๅๅบ”๏ผŒ100% ๅฐฑๆฅไธๅŠไบ†

โ† ไธŠไธ€็ฏ‡๏ผšBatch API | ไธ‹ไธ€็ฏ‡๏ผšEmbedding ๆˆๆœฌ โ†’ | ่ฟ”ๅ›ž้ฆ–้กต