floe-guard 0.1.0__tar.gz
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.
- floe_guard-0.1.0/.gitignore +19 -0
- floe_guard-0.1.0/CONTRIBUTING.md +54 -0
- floe_guard-0.1.0/LICENSE +21 -0
- floe_guard-0.1.0/PKG-INFO +204 -0
- floe_guard-0.1.0/README.md +154 -0
- floe_guard-0.1.0/SECURITY.md +27 -0
- floe_guard-0.1.0/docs/stop-the-loop.gif.PLACEHOLDER.txt +6 -0
- floe_guard-0.1.0/examples/runaway_loop.py +58 -0
- floe_guard-0.1.0/pyproject.toml +52 -0
- floe_guard-0.1.0/src/floe_guard/__init__.py +37 -0
- floe_guard-0.1.0/src/floe_guard/cost_map.json +734 -0
- floe_guard-0.1.0/src/floe_guard/errors.py +52 -0
- floe_guard-0.1.0/src/floe_guard/guard.py +145 -0
- floe_guard-0.1.0/src/floe_guard/hosted.py +40 -0
- floe_guard-0.1.0/src/floe_guard/integrations/__init__.py +7 -0
- floe_guard-0.1.0/src/floe_guard/integrations/crewai.py +77 -0
- floe_guard-0.1.0/src/floe_guard/integrations/litellm.py +137 -0
- floe_guard-0.1.0/src/floe_guard/pricing.py +107 -0
- floe_guard-0.1.0/tests/test_example.py +33 -0
- floe_guard-0.1.0/tests/test_guard.py +133 -0
- floe_guard-0.1.0/tests/test_hosted.py +17 -0
- floe_guard-0.1.0/tests/test_litellm_adapter.py +84 -0
- floe_guard-0.1.0/tests/test_pricing.py +54 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Contributing to floe-guard
|
|
2
|
+
|
|
3
|
+
`floe-guard` is a local budget guardrail for AI agents: it hard-stops an agent
|
|
4
|
+
before its next LLM call when it would cross a spend ceiling. It runs in-process
|
|
5
|
+
with no account. Hosted Floe is the upgrade path — enforcement moves server-side
|
|
6
|
+
so the ceiling becomes un-bypassable and cross-vendor.
|
|
7
|
+
|
|
8
|
+
Contributions are welcome.
|
|
9
|
+
|
|
10
|
+
## Development setup
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
git clone https://github.com/Floe-Labs/floe-guard.git
|
|
14
|
+
cd floe-guard
|
|
15
|
+
pip install -e ".[dev]"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Run the checks before opening a PR:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pytest # tests
|
|
22
|
+
ruff check . # lint
|
|
23
|
+
ruff format . # format
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The optional adapters need their extras to run/import:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install -e ".[crewai]" # CrewAI adapter
|
|
30
|
+
pip install -e ".[litellm]" # LiteLLM adapter
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Contribution flow
|
|
34
|
+
|
|
35
|
+
1. Fork the repo and create a branch off `main` (e.g. `feat/your-change`).
|
|
36
|
+
2. Make your change with tests, and keep `pytest` + `ruff` green.
|
|
37
|
+
3. Open a **draft pull request** against `main` and describe what changed and why.
|
|
38
|
+
|
|
39
|
+
## Code style
|
|
40
|
+
|
|
41
|
+
- Python 3.10+ with type hints.
|
|
42
|
+
- Formatting and linting are handled by `ruff` (config in `pyproject.toml`) — run
|
|
43
|
+
`ruff format .` and `ruff check .` before pushing.
|
|
44
|
+
- Keep the core (`src/floe_guard/`) dependency-free; framework integrations belong
|
|
45
|
+
in `src/floe_guard/integrations/` behind an optional extra.
|
|
46
|
+
- Prefer small, focused changes with tests that describe behavior.
|
|
47
|
+
|
|
48
|
+
## Areas we'd love help with
|
|
49
|
+
|
|
50
|
+
- **LangChain adapter** (callback-based enforcement).
|
|
51
|
+
- **Vercel AI SDK adapter** (TypeScript middleware / port).
|
|
52
|
+
- **Cost-map coverage** — keeping the bundled pricing map current and broad.
|
|
53
|
+
|
|
54
|
+
Open an issue first if you want to discuss a larger change.
|
floe_guard-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Floe Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: floe-guard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local budget guardrail for AI agents — hard-stops a runaway loop before its next LLM call crosses a spend ceiling. No account, no network.
|
|
5
|
+
Project-URL: Homepage, https://floelabs.xyz
|
|
6
|
+
Project-URL: Dashboard, https://dev-dashboard.floelabs.xyz
|
|
7
|
+
Project-URL: Source, https://github.com/Floe-Labs/floe-guard
|
|
8
|
+
Author: Floe Labs
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Floe Labs
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: agents,anthropic,budget,cost,crewai,guardrail,litellm,llm,openai
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
40
|
+
Requires-Python: >=3.10
|
|
41
|
+
Provides-Extra: crewai
|
|
42
|
+
Requires-Dist: crewai>=0.30; extra == 'crewai'
|
|
43
|
+
Requires-Dist: litellm>=1.0; extra == 'crewai'
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
46
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
47
|
+
Provides-Extra: litellm
|
|
48
|
+
Requires-Dist: litellm>=1.0; extra == 'litellm'
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# floe-guard
|
|
52
|
+
|
|
53
|
+
**A local budget guardrail for AI agents.** It hard-stops your agent *before its
|
|
54
|
+
next LLM call* when it would cross a spend ceiling — so a runaway loop dies at
|
|
55
|
+
$0.10 instead of $4,000. No account, no signup, no network. Runs in your process.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install floe-guard
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from floe_guard import BudgetGuard
|
|
63
|
+
|
|
64
|
+
guard = BudgetGuard(limit_usd=5.00) # your ceiling
|
|
65
|
+
guard.check() # before each LLM call — raises if it'd cross
|
|
66
|
+
response = call_your_llm(...) # your existing call
|
|
67
|
+
guard.record("gpt-4o", response.usage.prompt_tokens, response.usage.completion_tokens)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
When the next call would cross the ceiling, the guard raises `BudgetExceeded` and
|
|
71
|
+
prints:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
BUDGET EXCEEDED — call blocked
|
|
75
|
+
spent so far: $5.001250 | ceiling: $5.000000
|
|
76
|
+
The next call would cross your budget; floe-guard stopped your agent before it ran.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
_Animated demo coming — run `python examples/runaway_loop.py` to watch it stop a loop live._
|
|
80
|
+
<!-- TODO: record docs/stop-the-loop.gif and restore the embed:  -->
|
|
81
|
+
|
|
82
|
+
## See it stop a loop (no API key needed)
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
python examples/runaway_loop.py
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This rigs a loop against a **stub LLM** — no real API key, no account, no network.
|
|
89
|
+
It prices each fake `gpt-4o` call offline and the guard halts the loop after a few
|
|
90
|
+
iterations. This is the reproducible "stop the loop" demo.
|
|
91
|
+
|
|
92
|
+
## How it works
|
|
93
|
+
|
|
94
|
+
The guard sits **in the call path**, not on an event bus. A passive listener is
|
|
95
|
+
told about spend *after the fact* and can't halt anything — so enforcement has to
|
|
96
|
+
be the thing standing in front of the next call:
|
|
97
|
+
|
|
98
|
+
- **`check()`** runs before each LLM call. It predicts the next call's cost from
|
|
99
|
+
the last one and raises `BudgetExceeded` if that would cross your ceiling — the
|
|
100
|
+
call never runs. (A running-total check also catches an overshoot if an estimate
|
|
101
|
+
came in low.)
|
|
102
|
+
- **`record(model, prompt_tokens, completion_tokens)`** runs after each response.
|
|
103
|
+
It prices the tokens **offline** from a bundled
|
|
104
|
+
[LiteLLM cost map](src/floe_guard/cost_map.json) and adds the USD to a running
|
|
105
|
+
total.
|
|
106
|
+
|
|
107
|
+
### Unpriceable models fail closed
|
|
108
|
+
|
|
109
|
+
If a model isn't in the cost map and you didn't supply a price, the guard **warns
|
|
110
|
+
loudly and refuses** (`UnpriceableModelError`) rather than silently treat it as
|
|
111
|
+
free — *you can't cap spend you can't measure.* Give it a price to enforce it:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from floe_guard import BudgetGuard, ManualPrice
|
|
115
|
+
|
|
116
|
+
guard = BudgetGuard(
|
|
117
|
+
limit_usd=5.00,
|
|
118
|
+
price_overrides={"my-self-hosted-model": ManualPrice(1e-6, 2e-6)}, # USD/token
|
|
119
|
+
)
|
|
120
|
+
# or, set fail_closed=False to warn-and-skip for models you accept un-metered.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Framework adapters (optional extras)
|
|
124
|
+
|
|
125
|
+
### CrewAI
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pip install floe-guard[crewai]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from crewai import Crew
|
|
133
|
+
from floe_guard import BudgetGuard
|
|
134
|
+
from floe_guard.integrations.crewai import guard_crew
|
|
135
|
+
|
|
136
|
+
guard = BudgetGuard(limit_usd=1.00)
|
|
137
|
+
guard_crew(guard) # one line — enforces across the whole crew
|
|
138
|
+
Crew(agents=[...], tasks=[...]).kickoff()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
CrewAI runs on LiteLLM, so one callback caps every agent and task under a single
|
|
142
|
+
budget.
|
|
143
|
+
|
|
144
|
+
### LiteLLM
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install floe-guard[litellm]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from floe_guard import BudgetGuard
|
|
152
|
+
from floe_guard.integrations.litellm import guarded_completion
|
|
153
|
+
|
|
154
|
+
guard = BudgetGuard(limit_usd=1.00)
|
|
155
|
+
response = guarded_completion(guard, model="gpt-4o", messages=[...])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Prefer the LiteLLM-native callback? Register `budget_guard_callback(guard)` on
|
|
159
|
+
`litellm.callbacks`.
|
|
160
|
+
|
|
161
|
+
### Coming next
|
|
162
|
+
|
|
163
|
+
LangChain (callback) and the Vercel AI SDK (TypeScript middleware) are next. Open
|
|
164
|
+
an issue if you want one sooner.
|
|
165
|
+
|
|
166
|
+
## Honest about what this is
|
|
167
|
+
|
|
168
|
+
floe-guard is a **local, estimate-based** guardrail. It prices tokens from a
|
|
169
|
+
vendored cost map *inside your process*:
|
|
170
|
+
|
|
171
|
+
- The cost map can drift as vendors change prices — refresh it like any snapshot.
|
|
172
|
+
- It only sees the vendors you instrument.
|
|
173
|
+
- A determined agent or a bug could route around an in-process check.
|
|
174
|
+
|
|
175
|
+
It's genuinely useful on its own, and it's honest about its limits. No inflated
|
|
176
|
+
metrics, no "zero defaults" claims — it's a free local stop, not a vault.
|
|
177
|
+
|
|
178
|
+
## Upgrade to hosted Floe
|
|
179
|
+
|
|
180
|
+
When you need the ceiling to be **un-bypassable** and **cross-vendor**, hosted
|
|
181
|
+
Floe moves enforcement server-side against a real credit line:
|
|
182
|
+
|
|
183
|
+
- **Un-bypassable** — enforced at the spend rail, not in your process.
|
|
184
|
+
- **Cross-vendor** — one budget over LLM tokens *and* paid (x402) tool calls.
|
|
185
|
+
- **Team budgets + analytics** — shared ceilings, per-agent isolation, spend history.
|
|
186
|
+
|
|
187
|
+
Set `FLOE_API_KEY` and floe-guard exposes a hook to delegate enforcement to
|
|
188
|
+
hosted Floe (see [`src/floe_guard/hosted.py`](src/floe_guard/hosted.py) — wiring
|
|
189
|
+
the live endpoint is in progress; the local guard is fully functional today).
|
|
190
|
+
|
|
191
|
+
→ **[dev-dashboard.floelabs.xyz](https://dev-dashboard.floelabs.xyz)** ·
|
|
192
|
+
**[floelabs.xyz](https://floelabs.xyz)**
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
pip install -e ".[dev]"
|
|
198
|
+
pytest
|
|
199
|
+
ruff check .
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# floe-guard
|
|
2
|
+
|
|
3
|
+
**A local budget guardrail for AI agents.** It hard-stops your agent *before its
|
|
4
|
+
next LLM call* when it would cross a spend ceiling — so a runaway loop dies at
|
|
5
|
+
$0.10 instead of $4,000. No account, no signup, no network. Runs in your process.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install floe-guard
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from floe_guard import BudgetGuard
|
|
13
|
+
|
|
14
|
+
guard = BudgetGuard(limit_usd=5.00) # your ceiling
|
|
15
|
+
guard.check() # before each LLM call — raises if it'd cross
|
|
16
|
+
response = call_your_llm(...) # your existing call
|
|
17
|
+
guard.record("gpt-4o", response.usage.prompt_tokens, response.usage.completion_tokens)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
When the next call would cross the ceiling, the guard raises `BudgetExceeded` and
|
|
21
|
+
prints:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
BUDGET EXCEEDED — call blocked
|
|
25
|
+
spent so far: $5.001250 | ceiling: $5.000000
|
|
26
|
+
The next call would cross your budget; floe-guard stopped your agent before it ran.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
_Animated demo coming — run `python examples/runaway_loop.py` to watch it stop a loop live._
|
|
30
|
+
<!-- TODO: record docs/stop-the-loop.gif and restore the embed:  -->
|
|
31
|
+
|
|
32
|
+
## See it stop a loop (no API key needed)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python examples/runaway_loop.py
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This rigs a loop against a **stub LLM** — no real API key, no account, no network.
|
|
39
|
+
It prices each fake `gpt-4o` call offline and the guard halts the loop after a few
|
|
40
|
+
iterations. This is the reproducible "stop the loop" demo.
|
|
41
|
+
|
|
42
|
+
## How it works
|
|
43
|
+
|
|
44
|
+
The guard sits **in the call path**, not on an event bus. A passive listener is
|
|
45
|
+
told about spend *after the fact* and can't halt anything — so enforcement has to
|
|
46
|
+
be the thing standing in front of the next call:
|
|
47
|
+
|
|
48
|
+
- **`check()`** runs before each LLM call. It predicts the next call's cost from
|
|
49
|
+
the last one and raises `BudgetExceeded` if that would cross your ceiling — the
|
|
50
|
+
call never runs. (A running-total check also catches an overshoot if an estimate
|
|
51
|
+
came in low.)
|
|
52
|
+
- **`record(model, prompt_tokens, completion_tokens)`** runs after each response.
|
|
53
|
+
It prices the tokens **offline** from a bundled
|
|
54
|
+
[LiteLLM cost map](src/floe_guard/cost_map.json) and adds the USD to a running
|
|
55
|
+
total.
|
|
56
|
+
|
|
57
|
+
### Unpriceable models fail closed
|
|
58
|
+
|
|
59
|
+
If a model isn't in the cost map and you didn't supply a price, the guard **warns
|
|
60
|
+
loudly and refuses** (`UnpriceableModelError`) rather than silently treat it as
|
|
61
|
+
free — *you can't cap spend you can't measure.* Give it a price to enforce it:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from floe_guard import BudgetGuard, ManualPrice
|
|
65
|
+
|
|
66
|
+
guard = BudgetGuard(
|
|
67
|
+
limit_usd=5.00,
|
|
68
|
+
price_overrides={"my-self-hosted-model": ManualPrice(1e-6, 2e-6)}, # USD/token
|
|
69
|
+
)
|
|
70
|
+
# or, set fail_closed=False to warn-and-skip for models you accept un-metered.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Framework adapters (optional extras)
|
|
74
|
+
|
|
75
|
+
### CrewAI
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install floe-guard[crewai]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from crewai import Crew
|
|
83
|
+
from floe_guard import BudgetGuard
|
|
84
|
+
from floe_guard.integrations.crewai import guard_crew
|
|
85
|
+
|
|
86
|
+
guard = BudgetGuard(limit_usd=1.00)
|
|
87
|
+
guard_crew(guard) # one line — enforces across the whole crew
|
|
88
|
+
Crew(agents=[...], tasks=[...]).kickoff()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
CrewAI runs on LiteLLM, so one callback caps every agent and task under a single
|
|
92
|
+
budget.
|
|
93
|
+
|
|
94
|
+
### LiteLLM
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install floe-guard[litellm]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from floe_guard import BudgetGuard
|
|
102
|
+
from floe_guard.integrations.litellm import guarded_completion
|
|
103
|
+
|
|
104
|
+
guard = BudgetGuard(limit_usd=1.00)
|
|
105
|
+
response = guarded_completion(guard, model="gpt-4o", messages=[...])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Prefer the LiteLLM-native callback? Register `budget_guard_callback(guard)` on
|
|
109
|
+
`litellm.callbacks`.
|
|
110
|
+
|
|
111
|
+
### Coming next
|
|
112
|
+
|
|
113
|
+
LangChain (callback) and the Vercel AI SDK (TypeScript middleware) are next. Open
|
|
114
|
+
an issue if you want one sooner.
|
|
115
|
+
|
|
116
|
+
## Honest about what this is
|
|
117
|
+
|
|
118
|
+
floe-guard is a **local, estimate-based** guardrail. It prices tokens from a
|
|
119
|
+
vendored cost map *inside your process*:
|
|
120
|
+
|
|
121
|
+
- The cost map can drift as vendors change prices — refresh it like any snapshot.
|
|
122
|
+
- It only sees the vendors you instrument.
|
|
123
|
+
- A determined agent or a bug could route around an in-process check.
|
|
124
|
+
|
|
125
|
+
It's genuinely useful on its own, and it's honest about its limits. No inflated
|
|
126
|
+
metrics, no "zero defaults" claims — it's a free local stop, not a vault.
|
|
127
|
+
|
|
128
|
+
## Upgrade to hosted Floe
|
|
129
|
+
|
|
130
|
+
When you need the ceiling to be **un-bypassable** and **cross-vendor**, hosted
|
|
131
|
+
Floe moves enforcement server-side against a real credit line:
|
|
132
|
+
|
|
133
|
+
- **Un-bypassable** — enforced at the spend rail, not in your process.
|
|
134
|
+
- **Cross-vendor** — one budget over LLM tokens *and* paid (x402) tool calls.
|
|
135
|
+
- **Team budgets + analytics** — shared ceilings, per-agent isolation, spend history.
|
|
136
|
+
|
|
137
|
+
Set `FLOE_API_KEY` and floe-guard exposes a hook to delegate enforcement to
|
|
138
|
+
hosted Floe (see [`src/floe_guard/hosted.py`](src/floe_guard/hosted.py) — wiring
|
|
139
|
+
the live endpoint is in progress; the local guard is fully functional today).
|
|
140
|
+
|
|
141
|
+
→ **[dev-dashboard.floelabs.xyz](https://dev-dashboard.floelabs.xyz)** ·
|
|
142
|
+
**[floelabs.xyz](https://floelabs.xyz)**
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install -e ".[dev]"
|
|
148
|
+
pytest
|
|
149
|
+
ruff check .
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Only the latest released version of `floe-guard` is supported with security fixes.
|
|
6
|
+
Please upgrade to the latest version before reporting an issue.
|
|
7
|
+
|
|
8
|
+
## Reporting a vulnerability
|
|
9
|
+
|
|
10
|
+
**Do not open a public GitHub issue for security vulnerabilities.**
|
|
11
|
+
|
|
12
|
+
Report them privately by email to **security@floelabs.xyz**. If you do not get a
|
|
13
|
+
response, use **hello@floelabs.xyz** as a fallback contact.
|
|
14
|
+
|
|
15
|
+
Please include:
|
|
16
|
+
|
|
17
|
+
- a description of the vulnerability and its impact,
|
|
18
|
+
- steps to reproduce (a minimal proof of concept helps),
|
|
19
|
+
- the affected version.
|
|
20
|
+
|
|
21
|
+
## What to expect
|
|
22
|
+
|
|
23
|
+
- We aim to acknowledge your report within **3 business days**.
|
|
24
|
+
- We will keep you updated on our assessment and the fix.
|
|
25
|
+
- Please give us a reasonable window to release a fix before any public disclosure.
|
|
26
|
+
|
|
27
|
+
Thank you for helping keep `floe-guard` and its users safe.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
TODO: record this GIF.
|
|
2
|
+
|
|
3
|
+
This is a placeholder for docs/stop-the-loop.gif — a short screen recording of
|
|
4
|
+
`python examples/runaway_loop.py` running and the guard printing
|
|
5
|
+
`BUDGET EXCEEDED — call blocked`. Replace this file with the recorded .gif
|
|
6
|
+
(keep the filename so the README reference resolves).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Stop-the-loop demo — runs with NO API key and NO account.
|
|
2
|
+
|
|
3
|
+
A naive agent loop that calls an LLM forever. The only thing standing between you
|
|
4
|
+
and a five-figure overnight bill is ``floe-guard``: it hard-stops the loop before
|
|
5
|
+
the call that would cross your $0.10 ceiling.
|
|
6
|
+
|
|
7
|
+
Run it::
|
|
8
|
+
|
|
9
|
+
python examples/runaway_loop.py
|
|
10
|
+
|
|
11
|
+
The "LLM" here is a stub that returns fixed token usage — no network, no key, no
|
|
12
|
+
crewai/litellm needed. The cost is computed offline from the bundled cost map,
|
|
13
|
+
exactly as it would be for a real ``gpt-4o`` call.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from floe_guard import BudgetExceeded, BudgetGuard
|
|
19
|
+
|
|
20
|
+
MODEL = "gpt-4o"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def stub_llm(prompt: str) -> dict[str, object]:
|
|
24
|
+
"""A fake LLM call. No network, no API key — returns fixed token usage."""
|
|
25
|
+
return {
|
|
26
|
+
"model": MODEL,
|
|
27
|
+
"text": "...thinking... let me call myself again...",
|
|
28
|
+
"prompt_tokens": 1_000,
|
|
29
|
+
"completion_tokens": 1_000,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main() -> None:
|
|
34
|
+
# $0.10 ceiling. gpt-4o at 1k in + 1k out ≈ $0.0125/call, so the guard should
|
|
35
|
+
# stop the loop after a handful of iterations — well before any real damage.
|
|
36
|
+
guard = BudgetGuard(limit_usd=0.10)
|
|
37
|
+
|
|
38
|
+
print(f"Starting a runaway loop with a ${guard.limit_usd:.2f} budget...\n")
|
|
39
|
+
call = 0
|
|
40
|
+
while True: # a real runaway loop never decides to stop on its own
|
|
41
|
+
call += 1
|
|
42
|
+
try:
|
|
43
|
+
guard.check() # <-- the kill-switch: raises before the crossing call
|
|
44
|
+
except BudgetExceeded:
|
|
45
|
+
print(f"\nLoop stopped at call #{call}. The agent never got to spend past the budget.")
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
response = stub_llm("keep going")
|
|
49
|
+
cost = guard.record(
|
|
50
|
+
str(response["model"]),
|
|
51
|
+
int(response["prompt_tokens"]), # type: ignore[arg-type]
|
|
52
|
+
int(response["completion_tokens"]), # type: ignore[arg-type]
|
|
53
|
+
)
|
|
54
|
+
print(f" call #{call}: +${cost:.4f} (running total ${guard.spent_usd:.4f})")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
main()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "floe-guard"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Local budget guardrail for AI agents — hard-stops a runaway loop before its next LLM call crosses a spend ceiling. No account, no network."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Floe Labs" }]
|
|
13
|
+
keywords = ["llm", "agents", "budget", "guardrail", "crewai", "litellm", "cost", "openai", "anthropic"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
]
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
litellm = ["litellm>=1.0"]
|
|
28
|
+
crewai = ["crewai>=0.30", "litellm>=1.0"]
|
|
29
|
+
dev = ["pytest>=7.0", "ruff>=0.4"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://floelabs.xyz"
|
|
33
|
+
Dashboard = "https://dev-dashboard.floelabs.xyz"
|
|
34
|
+
Source = "https://github.com/Floe-Labs/floe-guard"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["src/floe_guard"]
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
40
|
+
"src/floe_guard/cost_map.json" = "floe_guard/cost_map.json"
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 100
|
|
44
|
+
target-version = "py310"
|
|
45
|
+
src = ["src", "tests", "examples"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint]
|
|
48
|
+
select = ["E", "F", "I", "UP", "B", "W"]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
52
|
+
filterwarnings = ["error::floe_guard.errors.UnpriceableModelWarning"]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""floe-guard — a local, framework-agnostic budget guardrail for AI agents.
|
|
2
|
+
|
|
3
|
+
Hard-stops an agent before its next LLM call when it would cross a spend ceiling.
|
|
4
|
+
Zero account, no network, runs in-process. Hosted Floe is the un-bypassable,
|
|
5
|
+
cross-vendor upgrade path (see the README).
|
|
6
|
+
|
|
7
|
+
from floe_guard import BudgetGuard
|
|
8
|
+
|
|
9
|
+
guard = BudgetGuard(limit_usd=5.00)
|
|
10
|
+
guard.check() # before each LLM call (may raise)
|
|
11
|
+
guard.record("gpt-4o", 1200, 350) # after each response
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from .errors import (
|
|
17
|
+
BudgetExceeded,
|
|
18
|
+
FloeGuardError,
|
|
19
|
+
UnpriceableModelError,
|
|
20
|
+
UnpriceableModelWarning,
|
|
21
|
+
)
|
|
22
|
+
from .guard import BudgetGuard
|
|
23
|
+
from .pricing import ManualPrice, PricedModel, price_tokens, resolve_price
|
|
24
|
+
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"BudgetGuard",
|
|
29
|
+
"BudgetExceeded",
|
|
30
|
+
"FloeGuardError",
|
|
31
|
+
"UnpriceableModelError",
|
|
32
|
+
"UnpriceableModelWarning",
|
|
33
|
+
"ManualPrice",
|
|
34
|
+
"PricedModel",
|
|
35
|
+
"price_tokens",
|
|
36
|
+
"resolve_price",
|
|
37
|
+
]
|