gavio 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.
- gavio-0.1.0/.gitignore +65 -0
- gavio-0.1.0/PKG-INFO +140 -0
- gavio-0.1.0/README.md +93 -0
- gavio-0.1.0/gavio/__init__.py +63 -0
- gavio-0.1.0/gavio/_ids.py +64 -0
- gavio-0.1.0/gavio/context.py +44 -0
- gavio-0.1.0/gavio/exceptions.py +53 -0
- gavio-0.1.0/gavio/gateway.py +203 -0
- gavio-0.1.0/gavio/interceptors/__init__.py +8 -0
- gavio-0.1.0/gavio/interceptors/audit/__init__.py +16 -0
- gavio-0.1.0/gavio/interceptors/audit/interceptor.py +87 -0
- gavio-0.1.0/gavio/interceptors/audit/record.py +69 -0
- gavio-0.1.0/gavio/interceptors/audit/sink.py +19 -0
- gavio-0.1.0/gavio/interceptors/audit/sinks/__init__.py +7 -0
- gavio-0.1.0/gavio/interceptors/audit/sinks/stdout.py +41 -0
- gavio-0.1.0/gavio/interceptors/base.py +45 -0
- gavio-0.1.0/gavio/interceptors/cache/__init__.py +8 -0
- gavio-0.1.0/gavio/interceptors/cache/backend.py +31 -0
- gavio-0.1.0/gavio/interceptors/cache/backends/__init__.py +7 -0
- gavio-0.1.0/gavio/interceptors/cache/backends/memory.py +44 -0
- gavio-0.1.0/gavio/interceptors/chain.py +63 -0
- gavio-0.1.0/gavio/interceptors/pii/__init__.py +39 -0
- gavio-0.1.0/gavio/interceptors/pii/context.py +24 -0
- gavio-0.1.0/gavio/interceptors/pii/guard.py +170 -0
- gavio-0.1.0/gavio/interceptors/pii/match.py +32 -0
- gavio-0.1.0/gavio/interceptors/pii/scanner.py +63 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/__init__.py +37 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/bsn.py +42 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/credit_card.py +48 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/email.py +32 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/iban.py +47 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/ip_address.py +45 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/phone.py +45 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/secret.py +51 -0
- gavio-0.1.0/gavio/interceptors/pii/scanners/ssn.py +32 -0
- gavio-0.1.0/gavio/interceptors/reliability/__init__.py +15 -0
- gavio-0.1.0/gavio/interceptors/reliability/fallback.py +63 -0
- gavio-0.1.0/gavio/interceptors/reliability/policy.py +30 -0
- gavio-0.1.0/gavio/interceptors/reliability/retry.py +91 -0
- gavio-0.1.0/gavio/interceptors/reliability/timeout.py +41 -0
- gavio-0.1.0/gavio/pricing.py +73 -0
- gavio-0.1.0/gavio/providers/__init__.py +43 -0
- gavio-0.1.0/gavio/providers/_http.py +51 -0
- gavio-0.1.0/gavio/providers/anthropic.py +99 -0
- gavio-0.1.0/gavio/providers/base.py +60 -0
- gavio-0.1.0/gavio/providers/mock.py +69 -0
- gavio-0.1.0/gavio/providers/openai.py +82 -0
- gavio-0.1.0/gavio/py.typed +0 -0
- gavio-0.1.0/gavio/request.py +57 -0
- gavio-0.1.0/gavio/response.py +49 -0
- gavio-0.1.0/gavio/testing/__init__.py +9 -0
- gavio-0.1.0/gavio/testing/fixtures.py +28 -0
- gavio-0.1.0/gavio/testing/harness.py +99 -0
- gavio-0.1.0/gavio/types.py +75 -0
- gavio-0.1.0/pyproject.toml +61 -0
- gavio-0.1.0/tests/unit/test_audit_and_testkit.py +66 -0
- gavio-0.1.0/tests/unit/test_chain_and_retry.py +118 -0
- gavio-0.1.0/tests/unit/test_gateway.py +99 -0
- gavio-0.1.0/tests/unit/test_ids.py +22 -0
- gavio-0.1.0/tests/unit/test_pii_guard.py +80 -0
- gavio-0.1.0/tests/unit/test_pii_scanners.py +79 -0
- gavio-0.1.0/tests/unit/test_vectors.py +72 -0
gavio-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# ─── OS / editor ──────────────────────────────────────────────
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
*.swp
|
|
5
|
+
*~
|
|
6
|
+
.idea/
|
|
7
|
+
.vscode/
|
|
8
|
+
*.iml
|
|
9
|
+
|
|
10
|
+
# ─── Node / TypeScript (gavio-js) ─────────────────────────────
|
|
11
|
+
node_modules/
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
out/
|
|
15
|
+
coverage/
|
|
16
|
+
*.tsbuildinfo
|
|
17
|
+
.npm/
|
|
18
|
+
.pnpm-store/
|
|
19
|
+
.yarn/
|
|
20
|
+
npm-debug.log*
|
|
21
|
+
yarn-error.log*
|
|
22
|
+
.vitest/
|
|
23
|
+
|
|
24
|
+
# ─── Python (gavio-py) ────────────────────────────────────────
|
|
25
|
+
.venv/
|
|
26
|
+
venv/
|
|
27
|
+
env/
|
|
28
|
+
__pycache__/
|
|
29
|
+
*.py[cod]
|
|
30
|
+
*.egg-info/
|
|
31
|
+
.eggs/
|
|
32
|
+
.pytest_cache/
|
|
33
|
+
.mypy_cache/
|
|
34
|
+
.ruff_cache/
|
|
35
|
+
.tox/
|
|
36
|
+
.coverage
|
|
37
|
+
.coverage.*
|
|
38
|
+
htmlcov/
|
|
39
|
+
|
|
40
|
+
# ─── Java / Maven / Gradle (gavio-java) ───────────────────────
|
|
41
|
+
target/
|
|
42
|
+
.gradle/
|
|
43
|
+
*.class
|
|
44
|
+
*.jar
|
|
45
|
+
*.war
|
|
46
|
+
hs_err_pid*
|
|
47
|
+
|
|
48
|
+
# ─── Secrets / local env ──────────────────────────────────────
|
|
49
|
+
.env
|
|
50
|
+
.env.*
|
|
51
|
+
!.env.example
|
|
52
|
+
*.pem
|
|
53
|
+
*.key
|
|
54
|
+
|
|
55
|
+
# ─── SigMap local artifacts ───────────────────────────────────
|
|
56
|
+
# Generated context (copilot-instructions.md) is committed on purpose;
|
|
57
|
+
# only the local metrics/cache are ignored.
|
|
58
|
+
.context/
|
|
59
|
+
.sigmap-cache/
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ---docs--- #
|
|
63
|
+
MASTER_*.md
|
|
64
|
+
SDK_*.md
|
|
65
|
+
*PLAN.md
|
gavio-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gavio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The open standard AI gateway for production systems — PII protection, audit trails, reliability, and cost control as composable interceptors.
|
|
5
|
+
Project-URL: Homepage, https://gavio.io
|
|
6
|
+
Project-URL: Repository, https://github.com/gavio-ai/gavio
|
|
7
|
+
Project-URL: Changelog, https://github.com/gavio-ai/gavio/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Gavio core team
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: ai,anthropic,audit,gateway,interceptor,llm,openai,pii
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Provides-Extra: all
|
|
20
|
+
Requires-Dist: elasticsearch; extra == 'all'
|
|
21
|
+
Requires-Dist: opentelemetry-exporter-otlp; extra == 'all'
|
|
22
|
+
Requires-Dist: opentelemetry-sdk; extra == 'all'
|
|
23
|
+
Requires-Dist: pgvector; extra == 'all'
|
|
24
|
+
Requires-Dist: presidio-analyzer; extra == 'all'
|
|
25
|
+
Requires-Dist: psycopg2; extra == 'all'
|
|
26
|
+
Requires-Dist: redis; extra == 'all'
|
|
27
|
+
Requires-Dist: spacy; extra == 'all'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
33
|
+
Provides-Extra: elasticsearch
|
|
34
|
+
Requires-Dist: elasticsearch; extra == 'elasticsearch'
|
|
35
|
+
Provides-Extra: otel
|
|
36
|
+
Requires-Dist: opentelemetry-exporter-otlp; extra == 'otel'
|
|
37
|
+
Requires-Dist: opentelemetry-sdk; extra == 'otel'
|
|
38
|
+
Provides-Extra: pgvector
|
|
39
|
+
Requires-Dist: pgvector; extra == 'pgvector'
|
|
40
|
+
Requires-Dist: psycopg2; extra == 'pgvector'
|
|
41
|
+
Provides-Extra: presidio
|
|
42
|
+
Requires-Dist: presidio-analyzer; extra == 'presidio'
|
|
43
|
+
Requires-Dist: spacy; extra == 'presidio'
|
|
44
|
+
Provides-Extra: redis
|
|
45
|
+
Requires-Dist: redis; extra == 'redis'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# Gavio — Python SDK
|
|
49
|
+
|
|
50
|
+
> The open standard AI gateway for production systems. PII protection, audit
|
|
51
|
+
> trails, reliability, and cost control as composable interceptors.
|
|
52
|
+
|
|
53
|
+
`gavio` sits between your application and any LLM provider. The same request
|
|
54
|
+
passes through a pre/post interceptor chain — PII redaction, retries, cost
|
|
55
|
+
tracking, audit logging — before and after the provider call.
|
|
56
|
+
|
|
57
|
+
Part of the [Gavio](https://gavio.io) project. MIT licensed.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install gavio # zero mandatory dependencies
|
|
63
|
+
pip install gavio[dev] # + pytest, ruff, mypy
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requires Python 3.10+.
|
|
67
|
+
|
|
68
|
+
## Quick start (dev mode — no API key, no network)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
from gavio import Gateway
|
|
73
|
+
from gavio.interceptors.pii import PiiGuard
|
|
74
|
+
|
|
75
|
+
gw = (
|
|
76
|
+
Gateway.builder()
|
|
77
|
+
.dev_mode(True) # MockProvider + stdout audit
|
|
78
|
+
.use(PiiGuard()) # redact PII before it leaves the process
|
|
79
|
+
.build()
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
async def main():
|
|
83
|
+
resp = await gw.complete(
|
|
84
|
+
messages=[{"role": "user", "content": "Email jan@example.com about NL91ABNA0417164300"}],
|
|
85
|
+
agent_id="demo",
|
|
86
|
+
)
|
|
87
|
+
print(resp.content) # PII restored in the reply
|
|
88
|
+
print(f"cost=${resp.cost_usd:.6f} latency={resp.latency_ms}ms")
|
|
89
|
+
print("pii types:", resp.audit.pii_entity_types)
|
|
90
|
+
|
|
91
|
+
asyncio.run(main())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Real providers
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from gavio import Gateway, Provider
|
|
98
|
+
from gavio.interceptors.pii import PiiGuard
|
|
99
|
+
from gavio.interceptors.audit import AuditInterceptor
|
|
100
|
+
from gavio.interceptors.reliability import RetryInterceptor, TimeoutPolicy
|
|
101
|
+
|
|
102
|
+
gw = (
|
|
103
|
+
Gateway.builder()
|
|
104
|
+
.provider(Provider.ANTHROPIC) # reads ANTHROPIC_API_KEY
|
|
105
|
+
.model("claude-sonnet-4-6")
|
|
106
|
+
.use(PiiGuard(sensitivity="strict"))
|
|
107
|
+
.use(AuditInterceptor(sink="stdout://"))
|
|
108
|
+
.use(TimeoutPolicy(timeout_seconds=30))
|
|
109
|
+
.use(RetryInterceptor(max_attempts=3))
|
|
110
|
+
.build()
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
resp = await gw.complete(messages=[{"role": "user", "content": "Hi"}])
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`OPENAI_API_KEY` / `Provider.OPENAI` work the same way.
|
|
117
|
+
|
|
118
|
+
## What ships in v0.1.0
|
|
119
|
+
|
|
120
|
+
- **Core** — `Gateway` fluent builder, `InterceptorChain`, `GavioRequest` /
|
|
121
|
+
`GavioResponse`, UUID v7 `trace_id`, `agent_id` / `parent_trace_id`.
|
|
122
|
+
- **PII Guard (F-SEC-01)** — Email, IBAN (mod-97), BSN (11-proef),
|
|
123
|
+
CreditCard (Luhn), Phone, IP, SSN scanners, redact/mask/tag/block, restore.
|
|
124
|
+
- **Secret Scanner (F-SEC-04)** — API keys, JWTs, PEM keys, DB URLs.
|
|
125
|
+
- **Reliability** — retry with backoff (F-REL-01), fallback chain (F-REL-02),
|
|
126
|
+
timeout (F-REL-07).
|
|
127
|
+
- **Cost tracking (F-GOV-01)** — per-request `cost_usd`.
|
|
128
|
+
- **Audit (F-OBS-01)** — `AuditRecord` + `StdoutSink` (F-OBS-05).
|
|
129
|
+
- **Dev mode (F-DX-01)** and **dry-run mode (F-DX-02)**.
|
|
130
|
+
- **Providers** — OpenAI, Anthropic, Mock.
|
|
131
|
+
|
|
132
|
+
See the [Python guide](../../docs/packages/python.md) and [CHANGELOG.md](../../CHANGELOG.md).
|
|
133
|
+
|
|
134
|
+
## Tests
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install -e ".[dev]"
|
|
138
|
+
pytest tests/unit -v
|
|
139
|
+
ruff check gavio
|
|
140
|
+
```
|
gavio-0.1.0/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Gavio — Python SDK
|
|
2
|
+
|
|
3
|
+
> The open standard AI gateway for production systems. PII protection, audit
|
|
4
|
+
> trails, reliability, and cost control as composable interceptors.
|
|
5
|
+
|
|
6
|
+
`gavio` sits between your application and any LLM provider. The same request
|
|
7
|
+
passes through a pre/post interceptor chain — PII redaction, retries, cost
|
|
8
|
+
tracking, audit logging — before and after the provider call.
|
|
9
|
+
|
|
10
|
+
Part of the [Gavio](https://gavio.io) project. MIT licensed.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install gavio # zero mandatory dependencies
|
|
16
|
+
pip install gavio[dev] # + pytest, ruff, mypy
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Python 3.10+.
|
|
20
|
+
|
|
21
|
+
## Quick start (dev mode — no API key, no network)
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import asyncio
|
|
25
|
+
from gavio import Gateway
|
|
26
|
+
from gavio.interceptors.pii import PiiGuard
|
|
27
|
+
|
|
28
|
+
gw = (
|
|
29
|
+
Gateway.builder()
|
|
30
|
+
.dev_mode(True) # MockProvider + stdout audit
|
|
31
|
+
.use(PiiGuard()) # redact PII before it leaves the process
|
|
32
|
+
.build()
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
async def main():
|
|
36
|
+
resp = await gw.complete(
|
|
37
|
+
messages=[{"role": "user", "content": "Email jan@example.com about NL91ABNA0417164300"}],
|
|
38
|
+
agent_id="demo",
|
|
39
|
+
)
|
|
40
|
+
print(resp.content) # PII restored in the reply
|
|
41
|
+
print(f"cost=${resp.cost_usd:.6f} latency={resp.latency_ms}ms")
|
|
42
|
+
print("pii types:", resp.audit.pii_entity_types)
|
|
43
|
+
|
|
44
|
+
asyncio.run(main())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Real providers
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from gavio import Gateway, Provider
|
|
51
|
+
from gavio.interceptors.pii import PiiGuard
|
|
52
|
+
from gavio.interceptors.audit import AuditInterceptor
|
|
53
|
+
from gavio.interceptors.reliability import RetryInterceptor, TimeoutPolicy
|
|
54
|
+
|
|
55
|
+
gw = (
|
|
56
|
+
Gateway.builder()
|
|
57
|
+
.provider(Provider.ANTHROPIC) # reads ANTHROPIC_API_KEY
|
|
58
|
+
.model("claude-sonnet-4-6")
|
|
59
|
+
.use(PiiGuard(sensitivity="strict"))
|
|
60
|
+
.use(AuditInterceptor(sink="stdout://"))
|
|
61
|
+
.use(TimeoutPolicy(timeout_seconds=30))
|
|
62
|
+
.use(RetryInterceptor(max_attempts=3))
|
|
63
|
+
.build()
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
resp = await gw.complete(messages=[{"role": "user", "content": "Hi"}])
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`OPENAI_API_KEY` / `Provider.OPENAI` work the same way.
|
|
70
|
+
|
|
71
|
+
## What ships in v0.1.0
|
|
72
|
+
|
|
73
|
+
- **Core** — `Gateway` fluent builder, `InterceptorChain`, `GavioRequest` /
|
|
74
|
+
`GavioResponse`, UUID v7 `trace_id`, `agent_id` / `parent_trace_id`.
|
|
75
|
+
- **PII Guard (F-SEC-01)** — Email, IBAN (mod-97), BSN (11-proef),
|
|
76
|
+
CreditCard (Luhn), Phone, IP, SSN scanners, redact/mask/tag/block, restore.
|
|
77
|
+
- **Secret Scanner (F-SEC-04)** — API keys, JWTs, PEM keys, DB URLs.
|
|
78
|
+
- **Reliability** — retry with backoff (F-REL-01), fallback chain (F-REL-02),
|
|
79
|
+
timeout (F-REL-07).
|
|
80
|
+
- **Cost tracking (F-GOV-01)** — per-request `cost_usd`.
|
|
81
|
+
- **Audit (F-OBS-01)** — `AuditRecord` + `StdoutSink` (F-OBS-05).
|
|
82
|
+
- **Dev mode (F-DX-01)** and **dry-run mode (F-DX-02)**.
|
|
83
|
+
- **Providers** — OpenAI, Anthropic, Mock.
|
|
84
|
+
|
|
85
|
+
See the [Python guide](../../docs/packages/python.md) and [CHANGELOG.md](../../CHANGELOG.md).
|
|
86
|
+
|
|
87
|
+
## Tests
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install -e ".[dev]"
|
|
91
|
+
pytest tests/unit -v
|
|
92
|
+
ruff check gavio
|
|
93
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Gavio — the open standard AI gateway for production systems.
|
|
2
|
+
|
|
3
|
+
Public API surface (v0.1.0):
|
|
4
|
+
|
|
5
|
+
from gavio import Gateway, GavioRequest, GavioResponse, Provider
|
|
6
|
+
|
|
7
|
+
See https://gavio.io for documentation. MIT licensed.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .context import InterceptorContext
|
|
13
|
+
from .exceptions import (
|
|
14
|
+
BudgetExceededError,
|
|
15
|
+
ConfigurationError,
|
|
16
|
+
GavioError,
|
|
17
|
+
GuardrailViolationError,
|
|
18
|
+
PiiBlockedError,
|
|
19
|
+
ProviderError,
|
|
20
|
+
ProviderUnavailableError,
|
|
21
|
+
RateLimitError,
|
|
22
|
+
ServerError,
|
|
23
|
+
)
|
|
24
|
+
from .gateway import Gateway, GatewayBuilder
|
|
25
|
+
from .request import GavioRequest
|
|
26
|
+
from .response import GavioResponse
|
|
27
|
+
from .types import (
|
|
28
|
+
CacheType,
|
|
29
|
+
GuardrailOutcome,
|
|
30
|
+
Message,
|
|
31
|
+
PiiMode,
|
|
32
|
+
Provider,
|
|
33
|
+
Sensitivity,
|
|
34
|
+
TokenUsage,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__version__ = "0.1.0"
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"__version__",
|
|
41
|
+
"Gateway",
|
|
42
|
+
"GatewayBuilder",
|
|
43
|
+
"GavioRequest",
|
|
44
|
+
"GavioResponse",
|
|
45
|
+
"InterceptorContext",
|
|
46
|
+
"Provider",
|
|
47
|
+
"Message",
|
|
48
|
+
"TokenUsage",
|
|
49
|
+
"CacheType",
|
|
50
|
+
"PiiMode",
|
|
51
|
+
"Sensitivity",
|
|
52
|
+
"GuardrailOutcome",
|
|
53
|
+
# exceptions
|
|
54
|
+
"GavioError",
|
|
55
|
+
"ConfigurationError",
|
|
56
|
+
"ProviderError",
|
|
57
|
+
"ProviderUnavailableError",
|
|
58
|
+
"RateLimitError",
|
|
59
|
+
"ServerError",
|
|
60
|
+
"PiiBlockedError",
|
|
61
|
+
"BudgetExceededError",
|
|
62
|
+
"GuardrailViolationError",
|
|
63
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""UUID v7 generation — time-sortable, unique identifiers for traces.
|
|
2
|
+
|
|
3
|
+
Python's stdlib only gains ``uuid.uuid7`` in 3.14, so we ship a compliant
|
|
4
|
+
generator here. UUID v7 layout (RFC 9562): 48-bit Unix millisecond timestamp,
|
|
5
|
+
4-bit version, 12 bits of randomness, 2-bit variant, 62 bits of randomness.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
import uuid
|
|
14
|
+
|
|
15
|
+
_lock = threading.Lock()
|
|
16
|
+
_last_ms = -1
|
|
17
|
+
_seq = 0 # 12-bit per-millisecond sequence in rand_a, for monotonicity
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _next_timestamp_and_seq() -> tuple[int, int]:
|
|
21
|
+
"""Return a (unix_ms, sequence) pair that is monotonically non-decreasing.
|
|
22
|
+
|
|
23
|
+
Within a single millisecond the 12-bit sequence increments so IDs stay
|
|
24
|
+
strictly ordered (RFC 9562 method 1). If the sequence overflows, the
|
|
25
|
+
timestamp is nudged forward.
|
|
26
|
+
"""
|
|
27
|
+
global _last_ms, _seq
|
|
28
|
+
with _lock:
|
|
29
|
+
now_ms = int(time.time() * 1000)
|
|
30
|
+
if now_ms > _last_ms:
|
|
31
|
+
_last_ms = now_ms
|
|
32
|
+
_seq = int.from_bytes(os.urandom(2), "big") & 0x0FFF
|
|
33
|
+
else:
|
|
34
|
+
_seq += 1
|
|
35
|
+
if _seq > 0x0FFF:
|
|
36
|
+
_last_ms += 1
|
|
37
|
+
_seq = 0
|
|
38
|
+
return _last_ms, _seq
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def uuid7() -> uuid.UUID:
|
|
42
|
+
"""Return a new UUID version 7 (time-ordered, monotonic within a process)."""
|
|
43
|
+
unix_ms, rand_a = _next_timestamp_and_seq()
|
|
44
|
+
|
|
45
|
+
# 48 bits of millisecond timestamp.
|
|
46
|
+
time_high = (unix_ms >> 16) & 0xFFFFFFFF
|
|
47
|
+
time_low = unix_ms & 0xFFFF
|
|
48
|
+
|
|
49
|
+
rand_b = int.from_bytes(os.urandom(8), "big") & 0x3FFFFFFFFFFFFFFF # 62 bits
|
|
50
|
+
|
|
51
|
+
value = (
|
|
52
|
+
(time_high << 96)
|
|
53
|
+
| (time_low << 80)
|
|
54
|
+
| (0x7 << 76) # version 7
|
|
55
|
+
| (rand_a << 64)
|
|
56
|
+
| (0b10 << 62) # variant
|
|
57
|
+
| rand_b
|
|
58
|
+
)
|
|
59
|
+
return uuid.UUID(int=value)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def new_trace_id() -> str:
|
|
63
|
+
"""Return a fresh trace id as a string."""
|
|
64
|
+
return str(uuid7())
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Per-request context passed through the interceptor pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class InterceptorContext:
|
|
11
|
+
"""Mutable scratch space shared by all interceptors within one request.
|
|
12
|
+
|
|
13
|
+
One instance per request — never shared across requests or threads.
|
|
14
|
+
Interceptors stash signals here (PII findings, cache decisions, risk
|
|
15
|
+
scores) for the audit interceptor to collect at the end of the chain.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
trace_id: str
|
|
19
|
+
agent_id: str | None = None
|
|
20
|
+
parent_trace_id: str | None = None
|
|
21
|
+
session_id: str | None = None
|
|
22
|
+
dry_run: bool = False
|
|
23
|
+
|
|
24
|
+
# Signals accumulated by interceptors during the request.
|
|
25
|
+
interceptors_fired: list[str] = field(default_factory=list)
|
|
26
|
+
pii_entity_types: list[str] = field(default_factory=list)
|
|
27
|
+
pii_entity_counts: dict[str, int] = field(default_factory=dict)
|
|
28
|
+
cache_hit: bool = False
|
|
29
|
+
cache_type: str | None = None
|
|
30
|
+
risk_score: float | None = None
|
|
31
|
+
guardrail_outcome: str | None = None
|
|
32
|
+
|
|
33
|
+
# Arbitrary inter-interceptor state (e.g. PII replacement map for restore).
|
|
34
|
+
state: dict[str, Any] = field(default_factory=dict)
|
|
35
|
+
|
|
36
|
+
def mark_fired(self, name: str) -> None:
|
|
37
|
+
if name not in self.interceptors_fired:
|
|
38
|
+
self.interceptors_fired.append(name)
|
|
39
|
+
|
|
40
|
+
def record_pii(self, entity_types: list[str]) -> None:
|
|
41
|
+
for et in entity_types:
|
|
42
|
+
self.pii_entity_counts[et] = self.pii_entity_counts.get(et, 0) + 1
|
|
43
|
+
if et not in self.pii_entity_types:
|
|
44
|
+
self.pii_entity_types.append(et)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Gavio exception hierarchy.
|
|
2
|
+
|
|
3
|
+
All Gavio errors derive from :class:`GavioError` so callers can catch the
|
|
4
|
+
whole family with a single ``except``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GavioError(Exception):
|
|
11
|
+
"""Base class for every error raised by Gavio."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigurationError(GavioError):
|
|
15
|
+
"""Raised when the gateway is misconfigured (e.g. no provider set)."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProviderError(GavioError):
|
|
19
|
+
"""Base class for provider-adapter failures."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ProviderUnavailableError(ProviderError):
|
|
23
|
+
"""The provider could not be reached (network / health-check failure)."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RateLimitError(ProviderError):
|
|
27
|
+
"""The provider returned a rate-limit (HTTP 429) signal."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ServerError(ProviderError):
|
|
31
|
+
"""The provider returned a 5xx server error."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TimeoutError(ProviderError): # noqa: A001 - intentional domain name
|
|
35
|
+
"""A request exceeded its configured timeout."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PiiBlockedError(GavioError):
|
|
39
|
+
"""PiiGuard is in BLOCK mode and detected PII in the request."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, entity_types: list[str]) -> None:
|
|
42
|
+
self.entity_types = entity_types
|
|
43
|
+
super().__init__(
|
|
44
|
+
f"Request blocked: PII detected ({', '.join(sorted(set(entity_types)))})"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BudgetExceededError(GavioError):
|
|
49
|
+
"""A hard budget cap was exceeded. Never swallow this — surface to user."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class GuardrailViolationError(GavioError):
|
|
53
|
+
"""Output failed a guardrail validator with on_failure='error'."""
|