contextwall-sdk 0.1.1__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.
- contextwall_sdk-0.1.1/.gitignore +29 -0
- contextwall_sdk-0.1.1/PKG-INFO +57 -0
- contextwall_sdk-0.1.1/README.md +32 -0
- contextwall_sdk-0.1.1/pyproject.toml +39 -0
- contextwall_sdk-0.1.1/src/contextwall_sdk/__init__.py +59 -0
- contextwall_sdk-0.1.1/src/contextwall_sdk/_anthropic.py +269 -0
- contextwall_sdk-0.1.1/src/contextwall_sdk/_openai.py +268 -0
- contextwall_sdk-0.1.1/src/contextwall_sdk/client.py +475 -0
- contextwall_sdk-0.1.1/src/contextwall_sdk/exceptions.py +53 -0
- contextwall_sdk-0.1.1/tests/test_sdk.py +279 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Secrets — never commit these
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
*.pyo
|
|
10
|
+
*.pyd
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
dist/
|
|
15
|
+
build/
|
|
16
|
+
*.egg-info/
|
|
17
|
+
.venv/
|
|
18
|
+
venv/
|
|
19
|
+
|
|
20
|
+
# Runtime data
|
|
21
|
+
.ctxfw/
|
|
22
|
+
*.db
|
|
23
|
+
*.pid
|
|
24
|
+
|
|
25
|
+
# Editor
|
|
26
|
+
.vscode/
|
|
27
|
+
.idea/
|
|
28
|
+
*.swp
|
|
29
|
+
.DS_Store
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: contextwall-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: ContextWall SDK - drop-in wrapper for Anthropic and OpenAI with context firewall enforcement
|
|
5
|
+
Author-email: Context Firewall <sumesh.punakkal@gmail.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Keywords: ai,anthropic,context-firewall,governance,openai,security
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: pydantic>=2.0
|
|
11
|
+
Provides-Extra: all
|
|
12
|
+
Requires-Dist: anthropic>=0.25; extra == 'all'
|
|
13
|
+
Requires-Dist: openai>=1.0; extra == 'all'
|
|
14
|
+
Provides-Extra: anthropic
|
|
15
|
+
Requires-Dist: anthropic>=0.25; extra == 'anthropic'
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: anthropic>=0.25; extra == 'dev'
|
|
18
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
19
|
+
Requires-Dist: openai>=1.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
22
|
+
Provides-Extra: openai
|
|
23
|
+
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# contextwall-sdk
|
|
27
|
+
|
|
28
|
+
Drop-in wrapper for Anthropic and OpenAI that routes calls through a local [ContextWall](https://contextwall.io) daemon, enforcing your context firewall policy with no other code changes.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install 'contextwall-sdk[anthropic]'
|
|
34
|
+
pip install 'contextwall-sdk[openai]'
|
|
35
|
+
pip install 'contextwall-sdk[all]'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from contextwall_sdk import SafeAnthropic
|
|
42
|
+
|
|
43
|
+
client = SafeAnthropic(api_key="sk-ant-...", ctxfw_url="http://localhost:8080")
|
|
44
|
+
# use exactly like the standard Anthropic client
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from contextwall_sdk import SafeOpenAI
|
|
49
|
+
|
|
50
|
+
client = SafeOpenAI(api_key="sk-...", ctxfw_url="http://localhost:8080")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Blocked requests raise `contextwall_sdk.ContextWallBlockedError` with the policy violation detail.
|
|
54
|
+
|
|
55
|
+
## Daemon setup
|
|
56
|
+
|
|
57
|
+
The SDK requires a running ContextWall daemon. See the [quickstart](https://contextwall.io/quickstart) or the [GitHub repo](https://github.com/bytewise-ca/context-wall).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# contextwall-sdk
|
|
2
|
+
|
|
3
|
+
Drop-in wrapper for Anthropic and OpenAI that routes calls through a local [ContextWall](https://contextwall.io) daemon, enforcing your context firewall policy with no other code changes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install 'contextwall-sdk[anthropic]'
|
|
9
|
+
pip install 'contextwall-sdk[openai]'
|
|
10
|
+
pip install 'contextwall-sdk[all]'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from contextwall_sdk import SafeAnthropic
|
|
17
|
+
|
|
18
|
+
client = SafeAnthropic(api_key="sk-ant-...", ctxfw_url="http://localhost:8080")
|
|
19
|
+
# use exactly like the standard Anthropic client
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from contextwall_sdk import SafeOpenAI
|
|
24
|
+
|
|
25
|
+
client = SafeOpenAI(api_key="sk-...", ctxfw_url="http://localhost:8080")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Blocked requests raise `contextwall_sdk.ContextWallBlockedError` with the policy violation detail.
|
|
29
|
+
|
|
30
|
+
## Daemon setup
|
|
31
|
+
|
|
32
|
+
The SDK requires a running ContextWall daemon. See the [quickstart](https://contextwall.io/quickstart) or the [GitHub repo](https://github.com/bytewise-ca/context-wall).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "contextwall-sdk"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "ContextWall SDK - drop-in wrapper for Anthropic and OpenAI with context firewall enforcement"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Context Firewall", email = "sumesh.punakkal@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "security", "context-firewall", "anthropic", "openai", "governance"]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"httpx>=0.27",
|
|
19
|
+
"pydantic>=2.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
anthropic = ["anthropic>=0.25"]
|
|
24
|
+
openai = ["openai>=1.0"]
|
|
25
|
+
all = ["anthropic>=0.25", "openai>=1.0"]
|
|
26
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.24", "anthropic>=0.25", "openai>=1.0", "httpx>=0.27"]
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.wheel]
|
|
29
|
+
packages = ["src/contextwall_sdk"]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.metadata]
|
|
32
|
+
allow-direct-references = true
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
asyncio_mode = "auto"
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
target-version = "py311"
|
|
39
|
+
line-length = 100
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""CRE SDK - drop-in context firewall for Anthropic and OpenAI agents.
|
|
2
|
+
|
|
3
|
+
Quick start::
|
|
4
|
+
|
|
5
|
+
pip install 'contextwall-sdk[anthropic]'
|
|
6
|
+
|
|
7
|
+
from contextwall_sdk import SafeAnthropic, CREBlockedError
|
|
8
|
+
|
|
9
|
+
client = SafeAnthropic() # reads CRE_KEY and CRE_URL from env
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
response = client.messages.create(
|
|
13
|
+
model="claude-opus-4-5",
|
|
14
|
+
max_tokens=1024,
|
|
15
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
16
|
+
)
|
|
17
|
+
except CREBlockedError as e:
|
|
18
|
+
print(f"Blocked: {e.violations}")
|
|
19
|
+
|
|
20
|
+
Provisioning a key (admin)::
|
|
21
|
+
|
|
22
|
+
from contextwall_sdk import CREClient
|
|
23
|
+
|
|
24
|
+
cre = CREClient(api_key="...", base_url="http://localhost:8080")
|
|
25
|
+
result = cre.keys.create(
|
|
26
|
+
project_id="my-agent",
|
|
27
|
+
upstream_key="sk-ant-...",
|
|
28
|
+
)
|
|
29
|
+
print(result.key) # sk-cre-xxx - store this securely
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from .exceptions import CREError, CREBlockedError, CREUnavailableError, CREAuthError
|
|
33
|
+
from ._anthropic import SafeAnthropic, AsyncSafeAnthropic
|
|
34
|
+
from ._openai import SafeOpenAI, AsyncSafeOpenAI
|
|
35
|
+
from .client import CREClient, AsyncCREClient, Source, ProxyKeyResult, HealthStatus, AnalyticsSummary
|
|
36
|
+
|
|
37
|
+
__version__ = "0.1.0"
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Exceptions
|
|
41
|
+
"CREError",
|
|
42
|
+
"CREBlockedError",
|
|
43
|
+
"CREUnavailableError",
|
|
44
|
+
"CREAuthError",
|
|
45
|
+
# Anthropic wrappers
|
|
46
|
+
"SafeAnthropic",
|
|
47
|
+
"AsyncSafeAnthropic",
|
|
48
|
+
# OpenAI wrappers
|
|
49
|
+
"SafeOpenAI",
|
|
50
|
+
"AsyncSafeOpenAI",
|
|
51
|
+
# Admin client
|
|
52
|
+
"CREClient",
|
|
53
|
+
"AsyncCREClient",
|
|
54
|
+
# Response models
|
|
55
|
+
"Source",
|
|
56
|
+
"ProxyKeyResult",
|
|
57
|
+
"HealthStatus",
|
|
58
|
+
"AnalyticsSummary",
|
|
59
|
+
]
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""SafeAnthropic - drop-in Anthropic client with CRE enforcement.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
# Before
|
|
6
|
+
import anthropic
|
|
7
|
+
client = anthropic.Anthropic(api_key="sk-ant-...")
|
|
8
|
+
|
|
9
|
+
# After (one line change, everything else identical)
|
|
10
|
+
from contextwall_sdk import SafeAnthropic
|
|
11
|
+
client = SafeAnthropic(cre_key="sk-cre-...", cre_url="http://localhost:8080")
|
|
12
|
+
|
|
13
|
+
# Same API
|
|
14
|
+
response = client.messages.create(
|
|
15
|
+
model="claude-opus-4-5",
|
|
16
|
+
max_tokens=1024,
|
|
17
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
Environment variables (no code changes needed)::
|
|
21
|
+
|
|
22
|
+
CRE_KEY=sk-cre-...
|
|
23
|
+
CRE_URL=http://localhost:8080
|
|
24
|
+
|
|
25
|
+
from contextwall_sdk import SafeAnthropic
|
|
26
|
+
client = SafeAnthropic() # reads from env
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
from typing import Any, Iterator, AsyncIterator
|
|
33
|
+
|
|
34
|
+
from .exceptions import CREBlockedError, CREUnavailableError, CREAuthError
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _check_cre_block(body: Any) -> None:
|
|
38
|
+
"""Raise CREBlockedError if the response body is a CRE policy violation."""
|
|
39
|
+
if not isinstance(body, dict):
|
|
40
|
+
return
|
|
41
|
+
err = body.get("error", {})
|
|
42
|
+
if isinstance(err, dict) and err.get("type") == "cre_policy_violation":
|
|
43
|
+
raise CREBlockedError(
|
|
44
|
+
blocked_reason=err.get("message", "policy violation"),
|
|
45
|
+
violations=err.get("violations", []),
|
|
46
|
+
raw_body=body,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _wrap_exception(exc: Exception, cre_url: str) -> None:
|
|
51
|
+
"""Convert underlying SDK exceptions into CRE-specific ones where applicable."""
|
|
52
|
+
try:
|
|
53
|
+
import httpx
|
|
54
|
+
if isinstance(exc, httpx.ConnectError):
|
|
55
|
+
raise CREUnavailableError(cre_url, cause=exc) from exc
|
|
56
|
+
except ImportError:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
# Anthropic SDK exception inspection
|
|
60
|
+
exc_type = type(exc).__name__
|
|
61
|
+
if exc_type in ("AuthenticationError",):
|
|
62
|
+
raise CREAuthError() from exc
|
|
63
|
+
|
|
64
|
+
if exc_type in ("BadRequestError", "APIStatusError"):
|
|
65
|
+
body = getattr(exc, "body", None)
|
|
66
|
+
_check_cre_block(body)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _StreamContextWrapper:
|
|
70
|
+
"""Wraps an Anthropic stream context manager to catch CRE blocks on entry."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, stream_cm: Any, cre_url: str) -> None:
|
|
73
|
+
self._cm = stream_cm
|
|
74
|
+
self._cre_url = cre_url
|
|
75
|
+
|
|
76
|
+
def __enter__(self) -> Any:
|
|
77
|
+
try:
|
|
78
|
+
return self._cm.__enter__()
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
_wrap_exception(exc, self._cre_url)
|
|
81
|
+
raise
|
|
82
|
+
|
|
83
|
+
def __exit__(self, *args: Any) -> Any:
|
|
84
|
+
return self._cm.__exit__(*args)
|
|
85
|
+
|
|
86
|
+
def __getattr__(self, name: str) -> Any:
|
|
87
|
+
return getattr(self._cm, name)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class _AsyncStreamContextWrapper:
|
|
91
|
+
def __init__(self, stream_cm: Any, cre_url: str) -> None:
|
|
92
|
+
self._cm = stream_cm
|
|
93
|
+
self._cre_url = cre_url
|
|
94
|
+
|
|
95
|
+
async def __aenter__(self) -> Any:
|
|
96
|
+
try:
|
|
97
|
+
return await self._cm.__aenter__()
|
|
98
|
+
except Exception as exc:
|
|
99
|
+
_wrap_exception(exc, self._cre_url)
|
|
100
|
+
raise
|
|
101
|
+
|
|
102
|
+
async def __aexit__(self, *args: Any) -> Any:
|
|
103
|
+
return await self._cm.__aexit__(*args)
|
|
104
|
+
|
|
105
|
+
def __getattr__(self, name: str) -> Any:
|
|
106
|
+
return getattr(self._cm, name)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _MessagesWrapper:
|
|
110
|
+
"""Wraps anthropic.resources.Messages to surface CRE-specific errors."""
|
|
111
|
+
|
|
112
|
+
def __init__(self, messages: Any, cre_url: str, fallback: bool) -> None:
|
|
113
|
+
self._messages = messages
|
|
114
|
+
self._cre_url = cre_url
|
|
115
|
+
self._fallback = fallback
|
|
116
|
+
|
|
117
|
+
def create(self, **kwargs: Any) -> Any:
|
|
118
|
+
try:
|
|
119
|
+
return self._messages.create(**kwargs)
|
|
120
|
+
except Exception as exc:
|
|
121
|
+
_wrap_exception(exc, self._cre_url)
|
|
122
|
+
raise
|
|
123
|
+
|
|
124
|
+
def stream(self, **kwargs: Any) -> _StreamContextWrapper:
|
|
125
|
+
# stream() returns a context manager; errors from CRE surface on __enter__
|
|
126
|
+
return _StreamContextWrapper(self._messages.stream(**kwargs), self._cre_url)
|
|
127
|
+
|
|
128
|
+
def __getattr__(self, name: str) -> Any:
|
|
129
|
+
return getattr(self._messages, name)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class _AsyncMessagesWrapper:
|
|
133
|
+
def __init__(self, messages: Any, cre_url: str, fallback: bool) -> None:
|
|
134
|
+
self._messages = messages
|
|
135
|
+
self._cre_url = cre_url
|
|
136
|
+
self._fallback = fallback
|
|
137
|
+
|
|
138
|
+
async def create(self, **kwargs: Any) -> Any:
|
|
139
|
+
try:
|
|
140
|
+
return await self._messages.create(**kwargs)
|
|
141
|
+
except Exception as exc:
|
|
142
|
+
_wrap_exception(exc, self._cre_url)
|
|
143
|
+
raise
|
|
144
|
+
|
|
145
|
+
def stream(self, **kwargs: Any) -> _AsyncStreamContextWrapper:
|
|
146
|
+
return _AsyncStreamContextWrapper(self._messages.stream(**kwargs), self._cre_url)
|
|
147
|
+
|
|
148
|
+
def __getattr__(self, name: str) -> Any:
|
|
149
|
+
return getattr(self._messages, name)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class SafeAnthropic:
|
|
153
|
+
"""Drop-in replacement for ``anthropic.Anthropic`` with CRE enforcement.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
cre_key: Your ``sk-cre-xxx`` key. Falls back to
|
|
157
|
+
``CRE_KEY`` then ``ANTHROPIC_API_KEY`` env vars.
|
|
158
|
+
cre_url: CRE daemon URL. Falls back to ``CRE_URL`` env var,
|
|
159
|
+
then ``http://localhost:8080``.
|
|
160
|
+
fallback_on_unavailable: If True and CRE is unreachable, raises
|
|
161
|
+
``CREUnavailableError`` (default False = fail fast).
|
|
162
|
+
**kwargs: Passed through to ``anthropic.Anthropic()``.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
CREBlockedError: When CRE blocks the request (policy violation).
|
|
166
|
+
CREUnavailableError: When CRE cannot be reached (and fallback is off).
|
|
167
|
+
CREAuthError: When the CRE key is rejected.
|
|
168
|
+
ImportError: If ``anthropic`` package is not installed.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
cre_key: str | None = None,
|
|
174
|
+
cre_url: str | None = None,
|
|
175
|
+
fallback_on_unavailable: bool = False,
|
|
176
|
+
**kwargs: Any,
|
|
177
|
+
) -> None:
|
|
178
|
+
try:
|
|
179
|
+
import anthropic
|
|
180
|
+
except ImportError as e:
|
|
181
|
+
raise ImportError(
|
|
182
|
+
"anthropic package is required: pip install 'contextwall-sdk[anthropic]'"
|
|
183
|
+
) from e
|
|
184
|
+
|
|
185
|
+
self._cre_url = (
|
|
186
|
+
cre_url or os.environ.get("CRE_URL") or "http://localhost:8080"
|
|
187
|
+
).rstrip("/")
|
|
188
|
+
self._fallback = fallback_on_unavailable
|
|
189
|
+
|
|
190
|
+
key = (
|
|
191
|
+
cre_key
|
|
192
|
+
or os.environ.get("CRE_KEY")
|
|
193
|
+
or os.environ.get("ANTHROPIC_API_KEY")
|
|
194
|
+
or ""
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self._client = anthropic.Anthropic(
|
|
198
|
+
api_key=key,
|
|
199
|
+
base_url=f"{self._cre_url}/proxy/anthropic",
|
|
200
|
+
**kwargs,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def messages(self) -> _MessagesWrapper:
|
|
205
|
+
return _MessagesWrapper(self._client.messages, self._cre_url, self._fallback)
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def beta(self) -> Any:
|
|
209
|
+
return self._client.beta
|
|
210
|
+
|
|
211
|
+
def __getattr__(self, name: str) -> Any:
|
|
212
|
+
return getattr(self._client, name)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class AsyncSafeAnthropic:
|
|
216
|
+
"""Async version of SafeAnthropic. Drop-in for ``anthropic.AsyncAnthropic``.
|
|
217
|
+
|
|
218
|
+
Example::
|
|
219
|
+
|
|
220
|
+
client = AsyncSafeAnthropic()
|
|
221
|
+
response = await client.messages.create(
|
|
222
|
+
model="claude-opus-4-5",
|
|
223
|
+
max_tokens=1024,
|
|
224
|
+
messages=[{"role": "user", "content": "Hello"}],
|
|
225
|
+
)
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
def __init__(
|
|
229
|
+
self,
|
|
230
|
+
cre_key: str | None = None,
|
|
231
|
+
cre_url: str | None = None,
|
|
232
|
+
fallback_on_unavailable: bool = False,
|
|
233
|
+
**kwargs: Any,
|
|
234
|
+
) -> None:
|
|
235
|
+
try:
|
|
236
|
+
import anthropic
|
|
237
|
+
except ImportError as e:
|
|
238
|
+
raise ImportError(
|
|
239
|
+
"anthropic package is required: pip install 'contextwall-sdk[anthropic]'"
|
|
240
|
+
) from e
|
|
241
|
+
|
|
242
|
+
self._cre_url = (
|
|
243
|
+
cre_url or os.environ.get("CRE_URL") or "http://localhost:8080"
|
|
244
|
+
).rstrip("/")
|
|
245
|
+
self._fallback = fallback_on_unavailable
|
|
246
|
+
|
|
247
|
+
key = (
|
|
248
|
+
cre_key
|
|
249
|
+
or os.environ.get("CRE_KEY")
|
|
250
|
+
or os.environ.get("ANTHROPIC_API_KEY")
|
|
251
|
+
or ""
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
self._client = anthropic.AsyncAnthropic(
|
|
255
|
+
api_key=key,
|
|
256
|
+
base_url=f"{self._cre_url}/proxy/anthropic",
|
|
257
|
+
**kwargs,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def messages(self) -> _AsyncMessagesWrapper:
|
|
262
|
+
return _AsyncMessagesWrapper(self._client.messages, self._cre_url, self._fallback)
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def beta(self) -> Any:
|
|
266
|
+
return self._client.beta
|
|
267
|
+
|
|
268
|
+
def __getattr__(self, name: str) -> Any:
|
|
269
|
+
return getattr(self._client, name)
|