deepintshield 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.
@@ -0,0 +1,54 @@
1
+ """
2
+ DeepintShield — unified Python SDK.
3
+
4
+ Quick start
5
+ -----------
6
+
7
+ from deepintshield import DeepintShield
8
+
9
+ shield = DeepintShield(virtual_key="sk-...")
10
+ openai_client = shield.openai()
11
+ response = openai_client.chat.completions.create(
12
+ model="gpt-4o-mini",
13
+ messages=[{"role": "user", "content": "hello"}],
14
+ )
15
+
16
+ The gateway defaults to ``https://app.deepintshield.com`` and is overridable via
17
+ ``base_url=...`` or the ``DEEPINTSHIELD_BASE_URL`` env var.
18
+ """
19
+
20
+ from .client import DeepintShield
21
+ from .config import DEFAULT_BASE_URL, ShieldConfig
22
+ from .errors import DeepintShieldBlockedError, DeepintShieldError
23
+ from .rag import allowed_chunk_ids, build_chunk, filter_chunks
24
+ from .types import (
25
+ NON_BLOCKING_DECISIONS,
26
+ GuardrailDecision,
27
+ GuardrailResult,
28
+ GuardrailStage,
29
+ RetrievedChunk,
30
+ ToolInvocation,
31
+ )
32
+ from .version import __version__
33
+
34
+ # Backwards-compatible alias.
35
+ DeepintShieldClient = DeepintShield
36
+
37
+ __all__ = [
38
+ "__version__",
39
+ "DEFAULT_BASE_URL",
40
+ "DeepintShield",
41
+ "DeepintShieldClient",
42
+ "DeepintShieldBlockedError",
43
+ "DeepintShieldError",
44
+ "GuardrailDecision",
45
+ "GuardrailResult",
46
+ "GuardrailStage",
47
+ "NON_BLOCKING_DECISIONS",
48
+ "RetrievedChunk",
49
+ "ShieldConfig",
50
+ "ToolInvocation",
51
+ "allowed_chunk_ids",
52
+ "build_chunk",
53
+ "filter_chunks",
54
+ ]
deepintshield/agent.py ADDED
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import json
5
+ from typing import TYPE_CHECKING, Any, Callable, Mapping
6
+
7
+ from .errors import DeepintShieldBlockedError
8
+ from .types import GuardrailResult, ToolInvocation
9
+
10
+ if TYPE_CHECKING:
11
+ from .client import DeepintShield
12
+
13
+
14
+ class AgentSurface:
15
+ """
16
+ Agentic guardrails: input/output scanning plus tool/MCP evaluation.
17
+
18
+ >>> shield = DeepintShield.from_env()
19
+ >>>
20
+ >>> @shield.agent.tool
21
+ >>> def read_file(path: str) -> str: ...
22
+ >>>
23
+ >>> shield.agent.check_input("user message")
24
+ >>> shield.agent.check_output("assistant reply")
25
+ >>> shield.agent.evaluate_tool(name="read_file", args={"path": "/tmp"})
26
+ """
27
+
28
+ def __init__(self, client: "DeepintShield") -> None:
29
+ self._client = client
30
+
31
+ # ── stage helpers ────────────────────────────────────────────────────────
32
+
33
+ def check_input(self, text: str, **kwargs: Any) -> GuardrailResult:
34
+ return self._client.guard(stage="input", input=text, **kwargs)
35
+
36
+ def check_output(self, text: str, **kwargs: Any) -> GuardrailResult:
37
+ return self._client.guard(stage="output", output=text, **kwargs)
38
+
39
+ # ── tool invocation ─────────────────────────────────────────────────────
40
+
41
+ def evaluate_tool(
42
+ self,
43
+ invocation: ToolInvocation | Mapping[str, Any] | None = None,
44
+ *,
45
+ name: str | None = None,
46
+ args: Any = None,
47
+ server_label: str = "",
48
+ action_class: str = "read",
49
+ domains: list[str] | None = None,
50
+ metadata: Mapping[str, Any] | None = None,
51
+ actor_type: str = "agent",
52
+ raise_on_block: bool = True,
53
+ **kwargs: Any,
54
+ ) -> GuardrailResult:
55
+ if invocation is None:
56
+ if name is None:
57
+ raise ValueError("evaluate_tool requires either invocation or name=...")
58
+ invocation = ToolInvocation(
59
+ tool_name=name,
60
+ tool_input=args if args is not None else {},
61
+ server_label=server_label,
62
+ action_class=action_class,
63
+ domains=list(domains or []),
64
+ metadata=dict(metadata or {}),
65
+ )
66
+ tool = invocation if isinstance(invocation, ToolInvocation) else ToolInvocation(**dict(invocation))
67
+ tool_input = (
68
+ tool.tool_input
69
+ if isinstance(tool.tool_input, str)
70
+ else json.dumps(tool.tool_input, default=str, sort_keys=True)
71
+ )
72
+ return self._client.guard(
73
+ stage="mcp" if tool.server_label else "action",
74
+ actor_type=actor_type,
75
+ tool_input=tool_input,
76
+ server_label=tool.server_label or None,
77
+ tool_name=tool.tool_name,
78
+ action_class=tool.action_class,
79
+ domains=tool.domains,
80
+ metadata=tool.metadata,
81
+ raise_on_block=raise_on_block,
82
+ **kwargs,
83
+ )
84
+
85
+ # ── decorator ───────────────────────────────────────────────────────────
86
+
87
+ def tool(
88
+ self,
89
+ func: Callable | None = None,
90
+ *,
91
+ action_class: str = "read",
92
+ server_label: str = "",
93
+ name: str | None = None,
94
+ ) -> Callable:
95
+ """
96
+ Decorator that guards a function call through DeepintShield before execution.
97
+
98
+ >>> @shield.agent.tool(action_class="write")
99
+ ... def write_file(path: str, content: str) -> None: ...
100
+ """
101
+
102
+ def decorate(fn: Callable) -> Callable:
103
+ tool_name = name or fn.__name__
104
+
105
+ @functools.wraps(fn)
106
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
107
+ self.evaluate_tool(
108
+ name=tool_name,
109
+ args={"args": list(args), "kwargs": dict(kwargs)},
110
+ action_class=action_class,
111
+ server_label=server_label,
112
+ )
113
+ return fn(*args, **kwargs)
114
+
115
+ return wrapper
116
+
117
+ if func is not None and callable(func):
118
+ return decorate(func)
119
+ return decorate
120
+
121
+ # ── full loop helper ────────────────────────────────────────────────────
122
+
123
+ def guard_turn(
124
+ self,
125
+ *,
126
+ user_input: str,
127
+ model_output: str | None = None,
128
+ tool_calls: list[Mapping[str, Any]] | None = None,
129
+ raise_on_block: bool = True,
130
+ ) -> dict[str, GuardrailResult]:
131
+ """
132
+ Evaluate a single agent turn: input, tool calls (if any), output (if any).
133
+ Returns a dict of stage -> result.
134
+ """
135
+ results: dict[str, GuardrailResult] = {}
136
+ results["input"] = self.check_input(user_input, **{"raise_on_block": raise_on_block}) # type: ignore[arg-type]
137
+ for call in tool_calls or []:
138
+ key = f"tool:{call.get('name') or call.get('tool_name')}"
139
+ results[key] = self.evaluate_tool(
140
+ name=call.get("name") or call.get("tool_name", "unknown"),
141
+ args=call.get("args") or call.get("tool_input"),
142
+ action_class=call.get("action_class", "read"),
143
+ server_label=call.get("server_label", ""),
144
+ raise_on_block=raise_on_block,
145
+ )
146
+ if model_output is not None:
147
+ results["output"] = self.check_output(model_output, raise_on_block=raise_on_block)
148
+ return results
@@ -0,0 +1,291 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from typing import Any, Iterable, Mapping
6
+
7
+ import httpx
8
+
9
+ from .config import DEFAULT_BASE_URL, ShieldConfig
10
+ from .errors import DeepintShieldBlockedError, DeepintShieldError
11
+ from .types import GuardrailResult, RetrievedChunk, ToolInvocation
12
+
13
+
14
+ class DeepintShield:
15
+ """
16
+ Unified DeepintShield client.
17
+
18
+ >>> shield = DeepintShield(virtual_key="sk-...")
19
+ >>> openai = shield.openai() # native openai.OpenAI pointed at the gateway
20
+ >>> resp = shield.chat(model="gpt-4o-mini", messages=[...])
21
+ >>> shield.rag.filter(query="...", chunks=[...])
22
+ >>> shield.agent.evaluate_tool(name="read_file", args={"path": "/tmp"})
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ virtual_key: str | None = None,
28
+ *,
29
+ base_url: str = DEFAULT_BASE_URL,
30
+ timeout: float = 30.0,
31
+ default_headers: Mapping[str, str] | None = None,
32
+ app_name: str = "deepintshield",
33
+ agent_name: str = "deepintshield-agent",
34
+ requester: str = "sdk-user",
35
+ requester_role: str = "member",
36
+ persist: bool = True,
37
+ ) -> None:
38
+ self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
39
+ self.virtual_key = (virtual_key or "").strip() or None
40
+ self.timeout = timeout
41
+ self.default_headers = dict(default_headers or {})
42
+ self.app_name = app_name
43
+ self.agent_name = agent_name
44
+ self.requester = requester
45
+ self.requester_role = requester_role
46
+ self.persist = persist
47
+ self._client = httpx.Client(timeout=timeout)
48
+
49
+ from .rag import RAGSurface
50
+ from .agent import AgentSurface
51
+ from .providers import ProviderRegistry
52
+
53
+ self.rag = RAGSurface(self)
54
+ self.agent = AgentSurface(self)
55
+ self.providers = ProviderRegistry(self)
56
+
57
+ # ─────────────────────────── constructors / context ──────────────────────
58
+
59
+ @classmethod
60
+ def from_env(cls) -> "DeepintShield":
61
+ cfg = ShieldConfig.from_env()
62
+ return cls(
63
+ virtual_key=cfg.virtual_key,
64
+ base_url=cfg.base_url,
65
+ timeout=cfg.timeout,
66
+ app_name=cfg.app_name,
67
+ agent_name=cfg.agent_name,
68
+ requester=cfg.requester,
69
+ requester_role=cfg.requester_role,
70
+ persist=cfg.persist,
71
+ )
72
+
73
+ @classmethod
74
+ def from_config(cls, config: ShieldConfig) -> "DeepintShield":
75
+ return cls(
76
+ virtual_key=config.virtual_key,
77
+ base_url=config.base_url,
78
+ timeout=config.timeout,
79
+ default_headers=config.default_headers,
80
+ app_name=config.app_name,
81
+ agent_name=config.agent_name,
82
+ requester=config.requester,
83
+ requester_role=config.requester_role,
84
+ persist=config.persist,
85
+ )
86
+
87
+ def __enter__(self) -> "DeepintShield":
88
+ return self
89
+
90
+ def __exit__(self, *_args: object) -> None:
91
+ self.close()
92
+
93
+ def close(self) -> None:
94
+ self._client.close()
95
+
96
+ # ─────────────────────────── keys and headers ────────────────────────────
97
+
98
+ def virtual_key_or_raise(self) -> str:
99
+ if not self.virtual_key:
100
+ raise DeepintShieldError("DEEPINTSHIELD_VIRTUAL_KEY is required")
101
+ return self.virtual_key
102
+
103
+ def api_key(self) -> str:
104
+ return self.virtual_key_or_raise()
105
+
106
+ def headers(self, extra: Mapping[str, str] | None = None) -> dict[str, str]:
107
+ out = {"content-type": "application/json", **self.default_headers}
108
+ if self.virtual_key:
109
+ out["x-bf-vk"] = self.virtual_key
110
+ if extra:
111
+ out.update(dict(extra))
112
+ return out
113
+
114
+ # ─────────────────────────── provider endpoints ──────────────────────────
115
+
116
+ def endpoint(self, provider: str) -> str:
117
+ return f"{self.base_url}/{provider.strip('/')}"
118
+
119
+ def openai_base_url(self) -> str:
120
+ return self.endpoint("openai")
121
+
122
+ def anthropic_base_url(self) -> str:
123
+ return self.endpoint("anthropic")
124
+
125
+ def bedrock_endpoint_url(self) -> str:
126
+ return self.endpoint("bedrock")
127
+
128
+ def genai_base_url(self) -> str:
129
+ return self.endpoint("genai")
130
+
131
+ def langchain_base_url(self) -> str:
132
+ return self.endpoint("langchain")
133
+
134
+ def litellm_base_url(self) -> str:
135
+ return self.endpoint("litellm")
136
+
137
+ def pydanticai_base_url(self) -> str:
138
+ return self.endpoint("pydanticai")
139
+
140
+ def openai_passthrough_base_url(self) -> str:
141
+ return f"{self.base_url}/openai_passthrough/v1"
142
+
143
+ def anthropic_passthrough_base_url(self) -> str:
144
+ return f"{self.base_url}/anthropic_passthrough"
145
+
146
+ def genai_passthrough_base_url(self) -> str:
147
+ return f"{self.base_url}/genai_passthrough"
148
+
149
+ # ─────────────────────────── provider shortcuts ──────────────────────────
150
+
151
+ def openai(self, *, passthrough: bool = False, **kwargs: Any):
152
+ from .providers.openai import build_client
153
+ return build_client(self, passthrough=passthrough, **kwargs)
154
+
155
+ def anthropic(self, *, passthrough: bool = False, **kwargs: Any):
156
+ from .providers.anthropic import build_client
157
+ return build_client(self, passthrough=passthrough, **kwargs)
158
+
159
+ def bedrock(self, **kwargs: Any):
160
+ from .providers.bedrock import build_client
161
+ return build_client(self, **kwargs)
162
+
163
+ def genai(self, *, passthrough: bool = False, **kwargs: Any):
164
+ from .providers.genai import build_client
165
+ return build_client(self, passthrough=passthrough, **kwargs)
166
+
167
+ def langchain(self, model: str = "gpt-4o-mini", **kwargs: Any):
168
+ from .providers.langchain import build_client
169
+ return build_client(self, model=model, **kwargs)
170
+
171
+ def langgraph(self):
172
+ from .providers.langgraph import LangGraphShield
173
+ return LangGraphShield(self)
174
+
175
+ def litellm(self):
176
+ from .providers.litellm import LiteLLMShield
177
+ return LiteLLMShield(self)
178
+
179
+ def pydanticai(self, model: str = "gpt-4o-mini", **kwargs: Any):
180
+ from .providers.pydanticai import build_agent
181
+ return build_agent(self, model=model, **kwargs)
182
+
183
+ # ─────────────────────────── HTTP + guardrails ───────────────────────────
184
+
185
+ def request(
186
+ self,
187
+ method: str,
188
+ path: str,
189
+ *,
190
+ json_body: Mapping[str, Any] | None = None,
191
+ extra_headers: Mapping[str, str] | None = None,
192
+ ) -> dict[str, Any]:
193
+ response = self._client.request(
194
+ method=method,
195
+ url=f"{self.base_url}{path}",
196
+ headers=self.headers(extra_headers),
197
+ json=json_body,
198
+ )
199
+ try:
200
+ payload = response.json()
201
+ except ValueError:
202
+ payload = {"raw": response.text}
203
+ if response.status_code >= 400:
204
+ raise DeepintShieldError.from_response(response.status_code, payload)
205
+ return payload
206
+
207
+ def chat(
208
+ self,
209
+ *,
210
+ model: str,
211
+ messages: list[dict[str, Any]],
212
+ stream: bool = False,
213
+ extra_headers: Mapping[str, str] | None = None,
214
+ **kwargs: Any,
215
+ ) -> dict[str, Any]:
216
+ """Unified chat completion via the gateway OpenAI-compatible endpoint."""
217
+ return self.request(
218
+ "POST",
219
+ "/v1/chat/completions",
220
+ json_body={"model": model, "messages": messages, "stream": stream, **kwargs},
221
+ extra_headers=extra_headers,
222
+ )
223
+
224
+ def evaluate_guardrail(
225
+ self,
226
+ *,
227
+ stage: str,
228
+ actor_type: str = "sdk_user",
229
+ actor_id: str | None = None,
230
+ actor_role: str | None = None,
231
+ actor_customer_id: str | None = None,
232
+ actor_team_id: str | None = None,
233
+ model: str | None = None,
234
+ provider: str | None = None,
235
+ input: str | None = None,
236
+ output: str | None = None,
237
+ tool_input: str | None = None,
238
+ server_label: str | None = None,
239
+ tool_name: str | None = None,
240
+ action_class: str | None = None,
241
+ domains: list[str] | None = None,
242
+ app_name: str | None = None,
243
+ agent_name: str | None = None,
244
+ metadata: Mapping[str, Any] | None = None,
245
+ persist: bool | None = None,
246
+ ) -> GuardrailResult:
247
+ body: dict[str, Any] = {
248
+ "stage": stage,
249
+ "actor_type": actor_type,
250
+ "actor_id": actor_id or self.requester,
251
+ "actor_role": actor_role or self.requester_role,
252
+ "model": model,
253
+ "provider": provider,
254
+ "input": input,
255
+ "output": output,
256
+ "tool_input": tool_input,
257
+ "server_label": server_label,
258
+ "tool_name": tool_name,
259
+ "action_class": action_class,
260
+ "domains": domains or [],
261
+ "app_name": app_name or self.app_name,
262
+ "agent_name": agent_name or self.agent_name,
263
+ "persist": self.persist if persist is None else persist,
264
+ }
265
+ if actor_customer_id:
266
+ body["actor_customer_id"] = actor_customer_id
267
+ if actor_team_id:
268
+ body["actor_team_id"] = actor_team_id
269
+ if metadata:
270
+ body["metadata"] = dict(metadata)
271
+ payload = self.request("POST", "/api/guardrails/evaluate", json_body=body)
272
+ return GuardrailResult.from_response(stage, payload)
273
+
274
+ def guard(
275
+ self,
276
+ *,
277
+ stage: str,
278
+ raise_on_block: bool = True,
279
+ **kwargs: Any,
280
+ ) -> GuardrailResult:
281
+ """Evaluate a guardrail and optionally raise on block."""
282
+ result = self.evaluate_guardrail(stage=stage, **kwargs)
283
+ if raise_on_block and result.blocked:
284
+ raise DeepintShieldBlockedError(
285
+ f"DeepintShield blocked at stage={stage}: {result.reason or result.decision}",
286
+ stage=stage,
287
+ decision=result.decision,
288
+ reason=result.reason,
289
+ payload=result.raw,
290
+ )
291
+ return result
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass, field
5
+ from typing import Any
6
+
7
+ DEFAULT_BASE_URL = "https://app.deepintshield.com"
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class ShieldConfig:
12
+ virtual_key: str = ""
13
+ base_url: str = DEFAULT_BASE_URL
14
+ timeout: float = 30.0
15
+ app_name: str = "deepintshield"
16
+ agent_name: str = "deepintshield-agent"
17
+ requester: str = "sdk-user"
18
+ requester_role: str = "member"
19
+ persist: bool = True
20
+ default_headers: dict[str, str] = field(default_factory=dict)
21
+ metadata: dict[str, Any] = field(default_factory=dict)
22
+
23
+ @classmethod
24
+ def from_env(cls) -> "ShieldConfig":
25
+ return cls(
26
+ virtual_key=(os.getenv("DEEPINTSHIELD_VIRTUAL_KEY") or "").strip(),
27
+ base_url=os.getenv("DEEPINTSHIELD_BASE_URL", DEFAULT_BASE_URL).rstrip("/"),
28
+ timeout=float(os.getenv("DEEPINTSHIELD_TIMEOUT", "30")),
29
+ app_name=os.getenv("DEEPINTSHIELD_APP_NAME", "deepintshield"),
30
+ agent_name=os.getenv("DEEPINTSHIELD_AGENT_NAME", "deepintshield-agent"),
31
+ requester=os.getenv("DEEPINTSHIELD_REQUESTER", "sdk-user"),
32
+ requester_role=os.getenv("DEEPINTSHIELD_REQUESTER_ROLE", "member"),
33
+ persist=os.getenv("DEEPINTSHIELD_PERSIST", "true").lower() not in {"0", "false", "no"},
34
+ )
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ class DeepintShieldError(Exception):
7
+ def __init__(self, message: str, status_code: int | None = None, payload: dict | None = None) -> None:
8
+ super().__init__(message)
9
+ self.message = message
10
+ self.status_code = status_code
11
+ self.payload = payload or {}
12
+
13
+ @classmethod
14
+ def from_response(cls, status_code: int, payload: dict | None) -> "DeepintShieldError":
15
+ error = (payload or {}).get("error", {})
16
+ message = (
17
+ error.get("message")
18
+ or (payload or {}).get("message")
19
+ or f"DeepintShield request failed with status {status_code}"
20
+ )
21
+ return cls(message=message, status_code=status_code, payload=payload or {})
22
+
23
+
24
+ class DeepintShieldBlockedError(DeepintShieldError):
25
+ def __init__(
26
+ self,
27
+ message: str,
28
+ *,
29
+ stage: str | None = None,
30
+ decision: str | None = None,
31
+ reason: str | None = None,
32
+ status_code: int | None = None,
33
+ payload: dict[str, Any] | None = None,
34
+ ) -> None:
35
+ super().__init__(message=message, status_code=status_code, payload=payload)
36
+ self.stage = stage
37
+ self.decision = decision
38
+ self.reason = reason
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from ..client import DeepintShield
7
+
8
+
9
+ class ProviderRegistry:
10
+ """Lazy accessor for provider builders, so optional deps stay optional."""
11
+
12
+ def __init__(self, client: "DeepintShield") -> None:
13
+ self._client = client
14
+
15
+ def openai(self, **kwargs: Any):
16
+ from . import openai as mod
17
+ return mod.build_client(self._client, **kwargs)
18
+
19
+ def anthropic(self, **kwargs: Any):
20
+ from . import anthropic as mod
21
+ return mod.build_client(self._client, **kwargs)
22
+
23
+ def bedrock(self, **kwargs: Any):
24
+ from . import bedrock as mod
25
+ return mod.build_client(self._client, **kwargs)
26
+
27
+ def genai(self, **kwargs: Any):
28
+ from . import genai as mod
29
+ return mod.build_client(self._client, **kwargs)
30
+
31
+ def langchain(self, **kwargs: Any):
32
+ from . import langchain as mod
33
+ return mod.build_client(self._client, **kwargs)
34
+
35
+ def langgraph(self):
36
+ from .langgraph import LangGraphShield
37
+ return LangGraphShield(self._client)
38
+
39
+ def litellm(self):
40
+ from .litellm import LiteLLMShield
41
+ return LiteLLMShield(self._client)
42
+
43
+ def pydanticai(self, **kwargs: Any):
44
+ from . import pydanticai as mod
45
+ return mod.build_agent(self._client, **kwargs)
46
+
47
+
48
+ __all__ = ["ProviderRegistry"]
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ from ..client import DeepintShield
7
+
8
+
9
+ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs: Any):
10
+ """Return a native ``anthropic.Anthropic`` client pointed at the gateway."""
11
+ try:
12
+ import anthropic
13
+ except ImportError as exc: # pragma: no cover
14
+ raise ImportError("Install anthropic: pip install 'deepintshield[anthropic]'") from exc
15
+
16
+ base_url = shield.anthropic_passthrough_base_url() if passthrough else shield.anthropic_base_url()
17
+ return anthropic.Anthropic(
18
+ base_url=kwargs.pop("base_url", base_url),
19
+ api_key=kwargs.pop("api_key", shield.api_key()),
20
+ default_headers={**shield.headers(), **(kwargs.pop("default_headers", None) or {})},
21
+ **kwargs,
22
+ )
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ from ..client import DeepintShield
8
+
9
+
10
+ def build_client(shield: "DeepintShield", *, region_name: str | None = None, **kwargs: Any):
11
+ """Return a boto3 ``bedrock-runtime`` client routed through the gateway."""
12
+ try:
13
+ import boto3
14
+ except ImportError as exc: # pragma: no cover
15
+ raise ImportError("Install boto3: pip install 'deepintshield[bedrock]'") from exc
16
+
17
+ key = shield.api_key()
18
+ client = boto3.client(
19
+ service_name="bedrock-runtime",
20
+ endpoint_url=kwargs.pop("endpoint_url", shield.bedrock_endpoint_url()),
21
+ region_name=region_name or os.getenv("AWS_REGION", "us-west-2"),
22
+ aws_access_key_id=kwargs.pop("aws_access_key_id", key),
23
+ aws_secret_access_key=kwargs.pop("aws_secret_access_key", key),
24
+ **kwargs,
25
+ )
26
+ headers = shield.headers()
27
+
28
+ def _inject_headers(request, **_kwargs):
29
+ for name, value in headers.items():
30
+ request.headers.add_header(name, value)
31
+
32
+ client.meta.events.register_first("before-sign.bedrock-runtime.*", _inject_headers)
33
+ return client