bioma-framework 1.0.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.
bioma/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """
2
+ B.I.O.M.A. — a lean efficiency & resilience micro-kernel for LLM infrastructure.
3
+
4
+ Two proven primitives, exposed from the Rust kernel (`bioma_micro`) plus a
5
+ resilient OpenRouter abstraction and a drop-in gateway:
6
+
7
+ * `kernel.HormonalBus` — lock-free in-memory signal injection (~2M sig/s, ~5μs)
8
+ * `kernel.dehydrate` / `kernel.ContextApoptosis` — autonomous context apoptosis
9
+ * `LeanOpenRouterClient` — resilient async dispatch with kernel-side apoptosis
10
+ * `bioma.gateway` — OpenAI/Anthropic drop-in gateway (`pip install bioma[gateway]`)
11
+
12
+ The optional integrations (`LeanOpenRouterClient` needs `openai`) are imported
13
+ lazily, so `import bioma` stays light for kernel-only use.
14
+ """
15
+ import bioma_micro as kernel # the compiled Rust micro-kernel
16
+
17
+ __all__ = ["kernel", "LeanOpenRouterClient", "Dispatch"]
18
+ __version__ = "1.0.0"
19
+
20
+
21
+ def __getattr__(name: str):
22
+ # PEP 562 lazy import — pull the openai-backed client only when actually used
23
+ if name in ("LeanOpenRouterClient", "Dispatch"):
24
+ from bioma.openrouter_client import Dispatch, LeanOpenRouterClient
25
+ return {"LeanOpenRouterClient": LeanOpenRouterClient, "Dispatch": Dispatch}[name]
26
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
bioma/esg.py ADDED
@@ -0,0 +1,99 @@
1
+ """
2
+ `bioma/esg.py` — the official Frugal-AI KPI of the project: **energy per token**.
3
+
4
+ Converts MEASURED token savings (the kernel's per-dispatch audit) into energy
5
+ (Wh) and emissions (gCO2e) estimates using DECLARED literature coefficients.
6
+ The tokens are ground truth; the conversion is an estimate — every function
7
+ returns (low, mid, high) bounds and never a single unqualified number.
8
+
9
+ Coefficients (declared, replace with your own measurements when available):
10
+
11
+ * Datacenter LLM inference energy: **0.5–1.3 kWh per million tokens**
12
+ (mid 0.9). Range adopted from the public literature — consistent with
13
+ Epoch AI's ~0.3 Wh per GPT-4o query (~500-token answer) and 2024–2025
14
+ meta-analyses of production frontier-model inference. Our own CPU-laptop
15
+ measurement (reports/BIOMA_ENERGY_LOCAL.md: 0.754 Wh / 7,481 tok ≈
16
+ 0.10 kWh/M tok, MARGINAL — idle and PUE excluded, 1B-parameter model,
17
+ prefill-heavy) sits below that range, as expected for a tiny model
18
+ measured marginally; the literature range targets production-scale
19
+ frontier inference. The KPI's reduction % is coefficient-independent.
20
+ * Grid carbon intensity presets (gCO2e/kWh): world ≈ 445 (IEA 2024),
21
+ EU ≈ 230 (EEA), US ≈ 385 (EPA eGRID), BR ≈ 100 (hydro-heavy, EPE/ONS).
22
+ * Prompt-caching adjustment: cached prefill tokens are not free — they are
23
+ billed/computed at a fraction of full price. `cache_adjust` shrinks the
24
+ claimed saving accordingly: saving × ((1−hit) + hit·cache_cost).
25
+
26
+ Usage:
27
+ from bioma.esg import estimate_saving, DEFAULTS
28
+ est = estimate_saving(tokens_saved=45_868) # one 16-round session
29
+ est["wh"] # (low, mid, high) Wh saved
30
+ est["gco2e"] # (low, mid, high) gCO2e saved, world grid
31
+ """
32
+ from __future__ import annotations
33
+
34
+ from typing import TypedDict
35
+
36
+ # kWh per MILLION tokens — declared literature range (see module docstring).
37
+ KWH_PER_MTOK = {"low": 0.5, "mid": 0.9, "high": 1.3}
38
+
39
+ # gCO2e per kWh — grid presets (replace with your grid's factor).
40
+ GRID_GCO2_PER_KWH = {"world": 445.0, "eu": 230.0, "us": 385.0, "br": 100.0}
41
+
42
+ DEFAULTS = {"grid": "world", "cache_hit": 0.0, "cache_cost": 0.10}
43
+
44
+
45
+ class Estimate(TypedDict):
46
+ tokens_saved: int
47
+ cache_multiplier: float
48
+ wh: tuple[float, float, float]
49
+ gco2e: tuple[float, float, float]
50
+ grid: str
51
+ kwh_per_mtok: dict
52
+
53
+
54
+ def cache_multiplier(cache_hit: float, cache_cost: float = 0.10) -> float:
55
+ """Fraction of the nominal saving that survives an honest caching baseline.
56
+
57
+ If `cache_hit` of the resent context would have been a cache hit anyway
58
+ (costing `cache_cost` of a full prefill), the counterfactual baseline is
59
+ cheaper and the claimable saving shrinks: (1−hit) + hit·cost."""
60
+ hit = min(max(cache_hit, 0.0), 1.0)
61
+ cost = min(max(cache_cost, 0.0), 1.0)
62
+ return (1.0 - hit) + hit * cost
63
+
64
+
65
+ def wh_from_tokens(tokens: float) -> tuple[float, float, float]:
66
+ """Energy (Wh) for `tokens` at the declared low/mid/high coefficients."""
67
+ return tuple(tokens / 1e6 * KWH_PER_MTOK[k] * 1000.0 for k in ("low", "mid", "high")) # type: ignore[return-value]
68
+
69
+
70
+ def estimate_saving(tokens_saved: int, *, grid: str = "world",
71
+ cache_hit: float = 0.0, cache_cost: float = 0.10) -> Estimate:
72
+ """Convert measured tokens saved into bounded Wh / gCO2e estimates."""
73
+ if grid not in GRID_GCO2_PER_KWH:
74
+ raise ValueError(f"unknown grid preset {grid!r}; options: {sorted(GRID_GCO2_PER_KWH)}")
75
+ mult = cache_multiplier(cache_hit, cache_cost)
76
+ wh = tuple(v * mult for v in wh_from_tokens(tokens_saved))
77
+ g = GRID_GCO2_PER_KWH[grid]
78
+ return Estimate(
79
+ tokens_saved=tokens_saved,
80
+ cache_multiplier=round(mult, 4),
81
+ wh=wh, # type: ignore[typeddict-item]
82
+ gco2e=tuple(v / 1000.0 * g for v in wh), # type: ignore[typeddict-item]
83
+ grid=grid,
84
+ kwh_per_mtok=dict(KWH_PER_MTOK),
85
+ )
86
+
87
+
88
+ def kpi_energy_per_token(tokens_before: int, tokens_after: int) -> dict:
89
+ """The project KPI: energy per dispatch, before vs after apoptosis.
90
+
91
+ Returns Wh (low, mid, high) for both sides plus the reduction fraction —
92
+ which is exact (independent of the coefficient, since it cancels out)."""
93
+ if tokens_before <= 0:
94
+ raise ValueError("tokens_before must be positive")
95
+ return {
96
+ "wh_before": wh_from_tokens(tokens_before),
97
+ "wh_after": wh_from_tokens(tokens_after),
98
+ "reduction": 1.0 - tokens_after / tokens_before,
99
+ }
bioma/esg_report.py ADDED
@@ -0,0 +1,123 @@
1
+ """
2
+ `bioma/esg_report.py` — the design-partner instrument: turn a deployment's REAL
3
+ gateway audit log into a per-deployment ESG + cost case report.
4
+
5
+ The gateway writes one JSONL line per request (`tokens_before`, `tokens_after`,
6
+ `reduction`, kernel μs). This reads that ground-truth log and reports, for the
7
+ deployment: tokens avoided, and — via the declared literature coefficients in
8
+ `bioma.esg` — bounded Wh / gCO2e / USD avoided. Every input is measured on the
9
+ partner's own traffic; only the energy conversion is an estimate (with bounds).
10
+
11
+ A partner runs their real workload through the gateway, then:
12
+
13
+ python -m bioma.esg_report bioma_gateway_audit.jsonl --grid eu --price-in 2.0
14
+
15
+ No third-party data is invented here — the report is empty until a real log exists.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import json
21
+ import os
22
+ import sys
23
+
24
+ from bioma.esg import GRID_GCO2_PER_KWH, KWH_PER_MTOK, estimate_saving
25
+
26
+
27
+ def read_audit(path: str) -> list[dict]:
28
+ rows = []
29
+ with open(path, encoding="utf-8") as f:
30
+ for line in f:
31
+ line = line.strip()
32
+ if not line:
33
+ continue
34
+ try:
35
+ rows.append(json.loads(line))
36
+ except json.JSONDecodeError:
37
+ continue
38
+ return rows
39
+
40
+
41
+ def build_report(rows: list[dict], *, grid: str = "world",
42
+ price_in_per_mtok: float | None = None,
43
+ cache_hit: float = 0.0) -> dict:
44
+ n = len(rows)
45
+ before = sum(int(r.get("tokens_before", 0)) for r in rows)
46
+ after = sum(int(r.get("tokens_after", 0)) for r in rows)
47
+ saved = before - after
48
+ est = estimate_saving(saved, grid=grid, cache_hit=cache_hit) if saved > 0 else None
49
+ usd = None
50
+ if price_in_per_mtok is not None and saved > 0:
51
+ usd = saved / 1e6 * price_in_per_mtok
52
+ return {
53
+ "requests": n,
54
+ "tokens_before": before, "tokens_after": after, "tokens_saved": saved,
55
+ "reduction": (1 - after / before) if before else 0.0,
56
+ "grid": grid,
57
+ "wh_avoided": est["wh"] if est else (0.0, 0.0, 0.0),
58
+ "gco2e_avoided": est["gco2e"] if est else (0.0, 0.0, 0.0),
59
+ "usd_avoided": usd,
60
+ }
61
+
62
+
63
+ def render_md(rep: dict, coeff_note: str) -> str:
64
+ wl, wm, wh = rep["wh_avoided"]
65
+ gl, gm, gh = rep["gco2e_avoided"]
66
+ L = [
67
+ "# Relatório ESG de deployment — B.I.O.M.A. (dados do seu tráfego real)",
68
+ "",
69
+ f"> Gerado de {rep['requests']:,} requests do audit do gateway. Tokens = medidos "
70
+ "no seu tráfego; Wh/gCO2e = estimativa com coeficientes declarados "
71
+ f"({coeff_note}); limites baixo/central/alto.",
72
+ "",
73
+ "| Métrica | Valor |",
74
+ "| :--- | ---: |",
75
+ f"| Requests medidos | {rep['requests']:,} |",
76
+ f"| Tokens de entrada | {rep['tokens_before']:,} → {rep['tokens_after']:,} |",
77
+ f"| **Tokens evitados** | **{rep['tokens_saved']:,}** (−{rep['reduction']*100:.1f}%) |",
78
+ f"| Energia evitada (grid {rep['grid']}) | {wl:,.1f} / **{wm:,.1f}** / {wh:,.1f} Wh |",
79
+ f"| Emissões evitadas | {gl:,.1f} / **{gm:,.1f}** / {gh:,.1f} gCO2e |",
80
+ ]
81
+ if rep["usd_avoided"] is not None:
82
+ L.append(f"| Custo de entrada evitado | **${rep['usd_avoided']:,.4f}** |")
83
+ L += ["",
84
+ "Escopo: redução auditável POR DEPLOYMENT (não global). A energia herda a "
85
+ "incerteza declarada do coeficiente; substitua pelo fator do seu grid/provedor.",
86
+ ""]
87
+ return "\n".join(L)
88
+
89
+
90
+ def _main(argv: list[str] | None = None) -> int:
91
+ try:
92
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
93
+ except Exception:
94
+ pass
95
+ ap = argparse.ArgumentParser(description="ESG deployment report from a gateway audit log.")
96
+ ap.add_argument("audit", help="path to bioma_gateway_audit.jsonl")
97
+ ap.add_argument("--grid", default="world", choices=sorted(GRID_GCO2_PER_KWH))
98
+ ap.add_argument("--price-in", type=float, default=None,
99
+ help="input price $/M tokens for your model (optional → $ avoided)")
100
+ ap.add_argument("--cache-hit", type=float, default=0.0)
101
+ ap.add_argument("--out", default=None, help="write the markdown report here")
102
+ args = ap.parse_args(argv)
103
+ if not os.path.exists(args.audit):
104
+ print(f"audit log não encontrado: {args.audit} — rode seu tráfego pelo gateway primeiro.")
105
+ return 2
106
+ rows = read_audit(args.audit)
107
+ if not rows:
108
+ print("audit vazio — nenhum request medido ainda.")
109
+ return 2
110
+ rep = build_report(rows, grid=args.grid, price_in_per_mtok=args.price_in,
111
+ cache_hit=args.cache_hit)
112
+ coeff = f"{KWH_PER_MTOK['low']}–{KWH_PER_MTOK['high']} kWh/Mtok, grid {args.grid}"
113
+ md = render_md(rep, coeff)
114
+ print(md)
115
+ if args.out:
116
+ with open(args.out, "w", encoding="utf-8") as f:
117
+ f.write(md)
118
+ print(f"📄 {args.out}")
119
+ return 0
120
+
121
+
122
+ if __name__ == "__main__":
123
+ raise SystemExit(_main())
@@ -0,0 +1,223 @@
1
+ """
2
+ `bioma/firewall_client.py` — the Cognitive Firewall: a local, provider-agnostic
3
+ defensive shim you drop in front of ANY LLM call (Anthropic, Google, OpenAI, …).
4
+
5
+ It is NOT a magic injection blocker. It is a stack of *real, measurable* controls:
6
+
7
+ 1. **Secret redaction** — vault values are scrubbed from the outbound payload AND
8
+ the model response. An injection cannot exfiltrate a secret the model never got.
9
+ 2. **Saturation detection + apoptosis** — cognitive-DDoS / floods are detected
10
+ (`saturation_scan`) → RED ALERT (`0x0F`) → dehydrated by apoptosis before dispatch.
11
+ 3. **Timeout guard** — dispatch is bounded, so a loop/hang cannot stall the pipeline.
12
+ 4. **Exponential backoff** — 429/5xx absorbed (built-in OpenRouter path).
13
+
14
+ Two ways to use it — 100% local, no hosted service:
15
+
16
+ fw = CognitiveFirewall(vault={"db": DB_PW})
17
+
18
+ # (a) PURE artifact — harden the payload, then call YOUR provider yourself:
19
+ h = fw.shield(history, "fix the bug")
20
+ # → send h.prompt / h.system to anthropic / google-genai / openai directly
21
+ # → h.telemetry has saturation, red_alert, apoptosis reduction, μs latency
22
+
23
+ # (b) Bring-your-own dispatcher (any async provider):
24
+ s = await fw.harden(history, "fix the bug", dispatch_fn=my_anthropic_call)
25
+
26
+ # (c) Convenience: built-in OpenRouter dispatch (needs OPENROUTER_API_KEY).
27
+ s = await fw.harden(history, "fix the bug", model="openai/gpt-4o")
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import asyncio
32
+ import os
33
+ import random
34
+ from dataclasses import dataclass
35
+ from typing import Awaitable, Callable, Optional
36
+
37
+ import bioma_micro as _kernel
38
+
39
+ RED_ALERT = 0x0F # invasion bit-flag broadcast on the hormonal bus
40
+ OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
41
+
42
+ _ROLE_SIGNAL = {"system": _kernel.SYSTEM, "user": _kernel.USER, "assistant": _kernel.ASSISTANT,
43
+ "tool": _kernel.TOOL, "fact": _kernel.FACT}
44
+
45
+ # A bring-your-own dispatcher: takes the hardened (prompt, system) and returns text.
46
+ Dispatcher = Callable[[str, Optional[str]], Awaitable[str]]
47
+
48
+
49
+ @dataclass
50
+ class Hardened:
51
+ """The pure output of `shield()`: a clean payload ready for ANY provider."""
52
+ prompt: str # dehydrated + redacted user payload
53
+ system: Optional[str] # redacted system prompt
54
+ saturation: float
55
+ red_alert: bool
56
+ secrets_redacted: int
57
+ outbound_clean: bool # no vault secret survived into the outbound
58
+ apoptosis_reduction: float
59
+ tokens_before: int
60
+ tokens_after: int
61
+ kernel_latency_us: float
62
+
63
+ @property
64
+ def telemetry(self) -> dict:
65
+ return {"saturation": self.saturation, "red_alert": self.red_alert,
66
+ "secrets_redacted": self.secrets_redacted, "outbound_clean": self.outbound_clean,
67
+ "apoptosis_reduction": self.apoptosis_reduction, "tokens_before": self.tokens_before,
68
+ "tokens_after": self.tokens_after, "kernel_latency_us": self.kernel_latency_us}
69
+
70
+
71
+ @dataclass
72
+ class Shield:
73
+ """The outcome of one hardened *dispatch* + full defensive telemetry."""
74
+ answer: str
75
+ model: str
76
+ saturation: float
77
+ red_alert: bool
78
+ secrets_redacted: int
79
+ outbound_clean: bool
80
+ apoptosis_reduction: float
81
+ tokens_before: int
82
+ tokens_after: int
83
+ kernel_latency_us: float
84
+ dispatched: bool
85
+ timed_out: bool
86
+ error: Optional[str] = None
87
+
88
+
89
+ class CognitiveFirewall:
90
+ """Local, provider-agnostic defensive shim. Thread-safe: kernel calls are pure."""
91
+
92
+ def __init__(self, api_key: Optional[str] = None, *, vault: Optional[dict] = None,
93
+ saturation_threshold: float = 0.85, dispatch_timeout: float = 20.0,
94
+ max_retries: int = 5, base_delay: float = 1.0, max_delay: float = 30.0,
95
+ half_life: float = 6.0, safe_threshold: float = 0.35,
96
+ referer: str = "https://bioma.ai", title: str = "B.I.O.M.A. Cognitive Firewall"):
97
+ self._vault = dict(vault or {})
98
+ self._secrets = [str(v) for v in self._vault.values() if v]
99
+ self.saturation_threshold = saturation_threshold
100
+ self.dispatch_timeout = dispatch_timeout
101
+ self.max_retries = max_retries
102
+ self.base_delay = base_delay
103
+ self.max_delay = max_delay
104
+ self.half_life = half_life
105
+ self.safe_threshold = safe_threshold
106
+ self._bus = _kernel.HormonalBus(32)
107
+ key = api_key or os.environ.get("OPENROUTER_API_KEY")
108
+ self.online = bool(key and key.startswith("sk-or"))
109
+ if self.online:
110
+ from openai import AsyncOpenAI
111
+ self._client = AsyncOpenAI(base_url=OPENROUTER_BASE_URL, api_key=key,
112
+ default_headers={"HTTP-Referer": referer, "X-Title": title})
113
+ else:
114
+ self._client = None
115
+
116
+ # ----- primitives ------------------------------------------------------ #
117
+ def _redact(self, text: str) -> tuple[str, int]:
118
+ hits = 0
119
+ for sv in self._secrets:
120
+ if sv and sv in text:
121
+ hits += text.count(sv)
122
+ text = text.replace(sv, "[REDACTED]")
123
+ return text, hits
124
+
125
+ def _leaks(self, text: str) -> bool:
126
+ return any(sv in text for sv in self._secrets if sv)
127
+
128
+ def scan(self, text: str) -> float:
129
+ """Cognitive-DDoS saturation score (0..1)."""
130
+ return _kernel.saturation_scan(text)
131
+
132
+ def alert_level(self) -> float:
133
+ return self._bus.sense(RED_ALERT)
134
+
135
+ # ----- the PURE hardening primitive (provider-agnostic) ---------------- #
136
+ def shield(self, history: list[dict], query: str, system: Optional[str] = None) -> Hardened:
137
+ """Harden a payload — scan → RED ALERT → apoptosis → secret redaction — and
138
+ return the clean prompt/system + telemetry. No network, no provider. Send the
139
+ result to Anthropic / Google / OpenAI (or anything) yourself."""
140
+ payload = "\n".join(str(m.get("content", "")) for m in history) + "\n" + query
141
+ saturation = _kernel.saturation_scan(payload)
142
+ red = saturation >= self.saturation_threshold
143
+ if red:
144
+ self._bus.secrete(RED_ALERT, min(1.0, saturation))
145
+
146
+ msgs = [(str(m.get("content", "")), _ROLE_SIGNAL.get(m.get("role", "user"), _kernel.USER))
147
+ for m in history]
148
+ audit = _kernel.dehydrate(msgs, half_life=self.half_life, safe_threshold=self.safe_threshold)
149
+ dehydrated = "\n".join(audit["kept"])
150
+
151
+ clean_ctx, h1 = self._redact(dehydrated)
152
+ clean_query, h2 = self._redact(query)
153
+ clean_system, h3 = self._redact(system) if system else ("", 0)
154
+ prompt = f"Context:\n{clean_ctx}\n\nRequest:\n{clean_query}" if clean_ctx else clean_query
155
+ outbound_clean = not (self._leaks(prompt) or self._leaks(clean_system))
156
+ return Hardened(prompt=prompt, system=(clean_system or None), saturation=round(saturation, 4),
157
+ red_alert=red, secrets_redacted=h1 + h2 + h3, outbound_clean=outbound_clean,
158
+ apoptosis_reduction=float(audit["reduction"]), tokens_before=int(audit["tokens_before"]),
159
+ tokens_after=int(audit["tokens_after"]), kernel_latency_us=float(audit["kernel_latency_us"]))
160
+
161
+ # ----- hardened dispatch (built-in OpenRouter OR your own provider) ---- #
162
+ async def harden(self, history: list[dict], query: str, *, model: str = "openai/gpt-4o",
163
+ system: Optional[str] = None, max_tokens: int = 256,
164
+ timeout: Optional[float] = None,
165
+ dispatch_fn: Optional[Dispatcher] = None) -> Shield:
166
+ hp = self.shield(history, query, system)
167
+ base = dict(saturation=hp.saturation, red_alert=hp.red_alert, secrets_redacted=hp.secrets_redacted,
168
+ outbound_clean=hp.outbound_clean, apoptosis_reduction=hp.apoptosis_reduction,
169
+ tokens_before=hp.tokens_before, tokens_after=hp.tokens_after,
170
+ kernel_latency_us=hp.kernel_latency_us)
171
+ bound = timeout if timeout is not None else self.dispatch_timeout
172
+
173
+ # No dispatcher available → hardening ran, dispatch skipped.
174
+ if dispatch_fn is None and not self.online:
175
+ return Shield(answer="", model=model, dispatched=False, timed_out=False,
176
+ error="offline (no key, no dispatch_fn) — defenses ran; dispatch skipped", **base)
177
+
178
+ try:
179
+ if dispatch_fn is not None: # bring-your-own provider (Anthropic/Google/OpenAI)
180
+ text = await asyncio.wait_for(dispatch_fn(hp.prompt, hp.system), timeout=bound)
181
+ err = None
182
+ model = "custom"
183
+ else: # built-in OpenRouter convenience
184
+ messages = ([{"role": "system", "content": hp.system}] if hp.system else []) + \
185
+ [{"role": "user", "content": hp.prompt}]
186
+ text, err = await asyncio.wait_for(self._dispatch(model, messages, max_tokens), timeout=bound)
187
+ except asyncio.TimeoutError:
188
+ return Shield(answer="", model=model, dispatched=False, timed_out=True,
189
+ error=f"dispatch exceeded {bound}s — contained by timeout guard", **base)
190
+
191
+ safe_answer, resp_hits = self._redact(text or "")
192
+ base["secrets_redacted"] += resp_hits
193
+ base["outbound_clean"] = base["outbound_clean"] and not self._leaks(safe_answer)
194
+ return Shield(answer=safe_answer, model=model, dispatched=(err is None),
195
+ timed_out=False, error=err, **base)
196
+
197
+ async def _dispatch(self, model: str, messages: list, max_tokens: int) -> tuple[str, Optional[str]]:
198
+ from openai import (APIConnectionError, APIStatusError, APITimeoutError, RateLimitError)
199
+ delay, last = self.base_delay, "unknown"
200
+ for attempt in range(self.max_retries + 1):
201
+ try:
202
+ resp = await self._client.chat.completions.create(
203
+ model=model, messages=messages, max_tokens=max_tokens,
204
+ temperature=0.2, extra_body={"usage": {"include": True}})
205
+ return resp.choices[0].message.content or "", None
206
+ except RateLimitError as exc:
207
+ last = f"429 {exc}"
208
+ except APIStatusError as exc:
209
+ code = getattr(exc, "status_code", 0)
210
+ if 500 <= code < 600:
211
+ last = f"{code} {exc}"
212
+ else:
213
+ return "", f"{code}: {exc}"
214
+ except (APIConnectionError, APITimeoutError) as exc:
215
+ last = f"conn {exc}"
216
+ if attempt < self.max_retries:
217
+ await asyncio.sleep(min(self.max_delay, delay) + random.uniform(0, 0.3 * delay))
218
+ delay *= 2
219
+ return "", f"exhausted retries: {last}"
220
+
221
+ async def close(self) -> None:
222
+ if self._client is not None:
223
+ await self._client.close()