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 +44 -0
- lutflow/budget.py +161 -0
- lutflow/cli.py +699 -0
- lutflow/client.py +472 -0
- lutflow/executors/__init__.py +5 -0
- lutflow/executors/self_kill.py +191 -0
- lutflow/kafka/__init__.py +3 -0
- lutflow/pcpo.py +597 -0
- lutflow/pricing.py +211 -0
- lutflow/py.typed +1 -0
- lutflow/tinyfish.py +1318 -0
- lutflow/wrappers/__init__.py +3 -0
- lutflow/wrappers/anthropic.py +336 -0
- lutflow/wrappers/gemini.py +244 -0
- lutflow/wrappers/openai.py +276 -0
- lutflow-0.1.0.dist-info/METADATA +326 -0
- lutflow-0.1.0.dist-info/RECORD +20 -0
- lutflow-0.1.0.dist-info/WHEEL +4 -0
- lutflow-0.1.0.dist-info/entry_points.txt +2 -0
- lutflow-0.1.0.dist-info/licenses/LICENSE +107 -0
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)
|