Skip to content

Commit a187fdb

Browse files
authored
Merge pull request #41 from azlaan428/feat/openai-example
docs: add no-API-key example for the OpenAI adapter
2 parents 057d043 + 7f0f562 commit a187fdb

2 files changed

Lines changed: 89 additions & 1 deletion

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,9 @@ response = guarded_completion(guard, client, model="gpt-4o", messages=[...])
414414

415415
`guarded_completion` reserves the budget before the call (raising
416416
`BudgetExceeded` so a blocked call never reaches OpenAI) and records spend after.
417-
Use `guarded_acompletion` with an `AsyncOpenAI` client for async.
417+
Use `guarded_acompletion` with an `AsyncOpenAI` client for async. See
418+
[`examples/openai_adapter.py`](examples/openai_adapter.py) for a runnable
419+
hard-stop demo (no API key needed).
418420

419421
### Anthropic
420422

examples/openai_adapter.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""floe-guard + the OpenAI adapter — runs with NO API key, NO account, NO network.
2+
3+
``examples/runaway_loop.py`` drives the guard directly (``check()`` /
4+
``record()``). This demo instead goes through
5+
``floe_guard.integrations.openai.guarded_completion`` — the real
6+
reserve-before / settle-after wrapper you'd point at a live ``openai.OpenAI``
7+
client — so it exercises the actual hard-stop contract: a blocked call raises
8+
``BudgetExceeded`` *before* ``client.chat.completions.create`` ever runs.
9+
10+
The client below is a duck-typed stub (same shape the adapter's own tests use)
11+
that returns a fixed ``usage`` block instead of calling OpenAI, so this needs
12+
no ``openai`` install and no ``OPENAI_API_KEY``.
13+
14+
Run: python examples/openai_adapter.py
15+
"""
16+
17+
from __future__ import annotations
18+
19+
from dataclasses import dataclass
20+
21+
from floe_guard import BudgetExceeded, BudgetGuard
22+
from floe_guard.integrations.openai import guarded_completion
23+
24+
MODEL = "gpt-4o"
25+
26+
27+
@dataclass
28+
class _Usage:
29+
prompt_tokens: int
30+
completion_tokens: int
31+
32+
33+
@dataclass
34+
class _Response:
35+
model: str
36+
usage: _Usage
37+
38+
39+
class _Completions:
40+
"""Stub of ``client.chat.completions`` — no network, fixed token usage."""
41+
42+
def __init__(self) -> None:
43+
self.call_count = 0
44+
45+
def create(self, **kwargs: object) -> _Response:
46+
self.call_count += 1
47+
# A real call reached here — this is what the hard-stop must prevent
48+
# once the budget is exhausted.
49+
return _Response(model=MODEL, usage=_Usage(prompt_tokens=1_000, completion_tokens=1_000))
50+
51+
52+
class _Chat:
53+
def __init__(self, completions: _Completions) -> None:
54+
self.completions = completions
55+
56+
57+
class _Client:
58+
"""Duck-typed stand-in for ``openai.OpenAI`` — same shape, no network."""
59+
60+
def __init__(self) -> None:
61+
self.chat = _Chat(_Completions())
62+
63+
64+
def main() -> None:
65+
# gpt-4o at 1k in + 1k out ~= $0.0125/call, so a $0.05 ceiling clears
66+
# exactly 4 calls before the 5th is blocked pre-flight.
67+
guard = BudgetGuard(limit_usd=0.05)
68+
client = _Client()
69+
70+
print(f"Starting with a ${guard.limit_usd:.2f} budget against a stub OpenAI client...\n")
71+
call = 0
72+
while True:
73+
call += 1
74+
messages = [{"role": "user", "content": "go"}]
75+
try:
76+
response = guarded_completion(guard, client, model=MODEL, messages=messages)
77+
except BudgetExceeded:
78+
calls_made = client.chat.completions.call_count
79+
print(f"\nCall #{call} blocked before reaching the client.")
80+
print(f"client.chat.completions.create was invoked {calls_made} times (not {call}).")
81+
break
82+
print(f" call #{call}: served by {response.model} (running total ${guard.spent_usd:.4f})")
83+
84+
85+
if __name__ == "__main__":
86+
main()

0 commit comments

Comments
 (0)