lutflow 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lutflow/__init__.py ADDED
@@ -0,0 +1,44 @@
1
+ """
2
+ Lutflow — GPU Financial Firewall for AI inference.
3
+
4
+ Track costs, enforce budgets, and kill runaway GPU spending in real time.
5
+
6
+ Example:
7
+ >>> from lutflow import Client
8
+ >>> client = Client(tenant_id="acme", budget_usd=10.00)
9
+ >>> wrapped = client.wrap(openai.OpenAI())
10
+ >>> response = wrapped.chat.completions.create(model="gpt-4o", messages=[...])
11
+
12
+ Example with budget_session context manager:
13
+ >>> from lutflow import budget_session
14
+ >>> with budget_session(budget_usd=0.50, tenant_id="acme") as session:
15
+ ... wrapped = session.wrap(openai.OpenAI())
16
+ ... response = wrapped.chat.completions.create(...)
17
+ """
18
+
19
+ from lutflow.client import Client, budget_session
20
+ from lutflow.budget import BudgetStrategy, BudgetExceededError
21
+ from lutflow.pricing import PricingMode
22
+ from lutflow.pcpo import (
23
+ CostPredictor,
24
+ CostPrediction,
25
+ PredictiveBudgetGuard,
26
+ UsageEstimate,
27
+ UseCase,
28
+ MessageLength,
29
+ )
30
+
31
+ __version__ = "0.1.0"
32
+ __all__ = [
33
+ "Client",
34
+ "budget_session",
35
+ "BudgetStrategy",
36
+ "BudgetExceededError",
37
+ "PricingMode",
38
+ "CostPredictor",
39
+ "CostPrediction",
40
+ "PredictiveBudgetGuard",
41
+ "UsageEstimate",
42
+ "UseCase",
43
+ "MessageLength",
44
+ ]
lutflow/budget.py ADDED
@@ -0,0 +1,161 @@
1
+ """Budget management and enforcement strategies."""
2
+
3
+ from enum import Enum
4
+ from typing import Callable, Optional
5
+ import logging
6
+
7
+ logger = logging.getLogger("lutflow")
8
+
9
+
10
+ class BudgetStrategy(Enum):
11
+ """Strategy for handling budget exceeded events."""
12
+
13
+ SELF_KILL = "self_kill"
14
+ """Terminate the process immediately when budget is exceeded."""
15
+
16
+ RAISE_ERROR = "raise_error"
17
+ """Raise BudgetExceededError when budget is exceeded."""
18
+
19
+ WARN_ONLY = "warn_only"
20
+ """Log a warning but continue execution."""
21
+
22
+ CALLBACK = "callback"
23
+ """Call a custom callback function."""
24
+
25
+
26
+ class BudgetExceededError(Exception):
27
+ """Raised when the budget limit has been exceeded."""
28
+
29
+ def __init__(
30
+ self,
31
+ budget_usd: float,
32
+ spent_usd: float,
33
+ tenant_id: str,
34
+ deployment_id: Optional[str] = None,
35
+ ):
36
+ self.budget_usd = budget_usd
37
+ self.spent_usd = spent_usd
38
+ self.tenant_id = tenant_id
39
+ self.deployment_id = deployment_id
40
+ super().__init__(
41
+ f"Budget exceeded: spent ${spent_usd:.4f} of ${budget_usd:.4f} "
42
+ f"(tenant={tenant_id}, deployment={deployment_id})"
43
+ )
44
+
45
+
46
+ class BudgetGuard:
47
+ """
48
+ Local budget guard that enforces spending limits.
49
+
50
+ This is the SDK-side implementation of the budget guard.
51
+ It tracks accumulated costs and triggers the configured strategy
52
+ when the budget is exceeded.
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ budget_usd: float,
58
+ tenant_id: str,
59
+ deployment_id: Optional[str] = None,
60
+ strategy: BudgetStrategy = BudgetStrategy.SELF_KILL,
61
+ on_exceeded_callback: Optional[Callable[[float, float], None]] = None,
62
+ ):
63
+ self.budget_usd = budget_usd
64
+ self.tenant_id = tenant_id
65
+ self.deployment_id = deployment_id
66
+ self.strategy = strategy
67
+ self.on_exceeded_callback = on_exceeded_callback
68
+ self._accumulated_cost_usd = 0.0
69
+ self._triggered = False
70
+
71
+ @property
72
+ def accumulated_cost_usd(self) -> float:
73
+ """Total cost accumulated so far."""
74
+ return self._accumulated_cost_usd
75
+
76
+ @property
77
+ def remaining_budget_usd(self) -> float:
78
+ """Remaining budget available."""
79
+ return max(0.0, self.budget_usd - self._accumulated_cost_usd)
80
+
81
+ @property
82
+ def budget_utilization_percent(self) -> float:
83
+ """Percentage of budget used (0-100+)."""
84
+ if self.budget_usd <= 0:
85
+ return 100.0
86
+ return (self._accumulated_cost_usd / self.budget_usd) * 100
87
+
88
+ def add_cost(self, cost_usd: float) -> bool:
89
+ """
90
+ Add cost to the accumulated total and check budget.
91
+
92
+ Args:
93
+ cost_usd: Cost to add in USD.
94
+
95
+ Returns:
96
+ True if budget is still available, False if exceeded.
97
+ """
98
+ self._accumulated_cost_usd += cost_usd
99
+
100
+ if self._accumulated_cost_usd >= self.budget_usd and not self._triggered:
101
+ self._triggered = True
102
+ self._handle_budget_exceeded()
103
+ return False
104
+
105
+ return True
106
+
107
+ def check_budget(self) -> bool:
108
+ """
109
+ Check if budget is still available.
110
+
111
+ Returns:
112
+ True if budget is available, False if exceeded.
113
+ """
114
+ return self._accumulated_cost_usd < self.budget_usd
115
+
116
+ def _handle_budget_exceeded(self) -> None:
117
+ """Handle budget exceeded based on configured strategy."""
118
+ logger.warning(
119
+ "LUTFLOW: Budget exceeded - spent=$%.4f budget=$%.4f tenant=%s",
120
+ self._accumulated_cost_usd,
121
+ self.budget_usd,
122
+ self.tenant_id,
123
+ )
124
+
125
+ if self.strategy == BudgetStrategy.SELF_KILL:
126
+ self._execute_self_kill()
127
+ elif self.strategy == BudgetStrategy.RAISE_ERROR:
128
+ raise BudgetExceededError(
129
+ budget_usd=self.budget_usd,
130
+ spent_usd=self._accumulated_cost_usd,
131
+ tenant_id=self.tenant_id,
132
+ deployment_id=self.deployment_id,
133
+ )
134
+ elif self.strategy == BudgetStrategy.WARN_ONLY:
135
+ logger.warning(
136
+ "LUTFLOW: Continuing despite budget exceeded (WARN_ONLY strategy)"
137
+ )
138
+ elif self.strategy == BudgetStrategy.CALLBACK:
139
+ if self.on_exceeded_callback:
140
+ self.on_exceeded_callback(
141
+ self._accumulated_cost_usd,
142
+ self.budget_usd,
143
+ )
144
+
145
+ def _execute_self_kill(self) -> None:
146
+ """Terminate the current process."""
147
+ import os
148
+ import signal
149
+
150
+ logger.critical(
151
+ "LUTFLOW: Self-terminating process (budget exceeded). "
152
+ "Spent=$%.4f Budget=$%.4f",
153
+ self._accumulated_cost_usd,
154
+ self.budget_usd,
155
+ )
156
+
157
+ # Flush logs before killing
158
+ logging.shutdown()
159
+
160
+ # Send SIGKILL to self
161
+ os.kill(os.getpid(), signal.SIGKILL)