deepintshield 2.7.0__tar.gz → 2.7.2__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.
- {deepintshield-2.7.0/src/deepintshield.egg-info → deepintshield-2.7.2}/PKG-INFO +70 -6
- {deepintshield-2.7.0 → deepintshield-2.7.2}/README.md +69 -5
- {deepintshield-2.7.0 → deepintshield-2.7.2}/pyproject.toml +1 -1
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/_prompt_cache.py +16 -7
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/enforcement.py +11 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/registry.py +17 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/anthropic.py +19 -3
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/client.py +35 -3
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/anthropic.py +28 -12
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/genai.py +32 -1
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/openai.py +5 -3
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/rag.py +149 -30
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/transport.py +46 -8
- deepintshield-2.7.2/src/deepintshield/version.py +1 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2/src/deepintshield.egg-info}/PKG-INFO +70 -6
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield.egg-info/SOURCES.txt +8 -0
- deepintshield-2.7.2/tests/test_agentic_import_lifecycle.py +56 -0
- deepintshield-2.7.2/tests/test_anthropic_transport_compat.py +103 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_error_surfaces.py +34 -0
- deepintshield-2.7.2/tests/test_genai_contracts.py +227 -0
- deepintshield-2.7.2/tests/test_model_compatibility.py +188 -0
- deepintshield-2.7.2/tests/test_native_provider_headers.py +108 -0
- deepintshield-2.7.2/tests/test_platform_agentic_workflows.py +84 -0
- deepintshield-2.7.2/tests/test_platform_rag_workflows.py +177 -0
- deepintshield-2.7.2/tests/test_rag_redaction.py +127 -0
- deepintshield-2.7.0/src/deepintshield/version.py +0 -1
- {deepintshield-2.7.0 → deepintshield-2.7.2}/LICENSE +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/NOTICE +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/setup.cfg +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/__init__.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/_gemini_cache.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agent.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/__init__.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/actions.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/__init__.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/base.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/entra.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/oidc.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/zeroid.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/decorators.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/dpop.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/engine.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/errors.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/execution.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/gate.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/identity.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/__init__.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/_common.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/autogen.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/crewai.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/google_adk.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/hermes.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/langchain.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/langgraph.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/litellm.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/llamaindex.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/openai_agents.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/openclaw.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/pydanticai.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/strands.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/temporal.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/manifest.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/obligations.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/surface.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/agentic/types.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/client.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/config.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/errors.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/__init__.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/autogen.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/crewai.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/langgraph.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/llamaindex.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/openai_agents.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/frameworks/pydanticai.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/__init__.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/_errors.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/_native.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/__init__.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/_security.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/langchain.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/openai.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/mcp/tool.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/__init__.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/bedrock.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/langchain.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/langgraph.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/litellm.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/providers/pydanticai.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/streaming.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield/types.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield.egg-info/dependency_links.txt +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield.egg-info/requires.txt +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/src/deepintshield.egg-info/top_level.txt +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agent.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_blueprint_contract.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_callback_inventory.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_credentials.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_error_contract.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_execution_lifecycle.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_fail_closed_integrations.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_framework_parity.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_langchain.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_new_decide.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_new_discovery.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_new_enforcement.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_agentic_workload_headers.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_chat_streaming.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_client.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_config.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_error_catalog.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_errors.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_gemini_cache.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_mcp_native_errors.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_mcp_native_session.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_mcp_preferred_api.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_obligation_contract.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_prompt_cache.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_providers.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_rag.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_rag_guard.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_transport.py +0 -0
- {deepintshield-2.7.0 → deepintshield-2.7.2}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepintshield
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.2
|
|
4
4
|
Summary: Unified Python SDK for routing chat, RAG, agentic tool-gating, identity, and MCP traffic through DeepintShield - drop-in across the top agentic frameworks.
|
|
5
5
|
Author: DeepintShield
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -98,6 +98,8 @@ Dynamic: license-file
|
|
|
98
98
|
|
|
99
99
|
Unified Python SDK for DeepIntShield - one import, any provider, any agent framework.
|
|
100
100
|
|
|
101
|
+
Current release: **2.7.2**, aligned with DeepIntShield Server **2.7.2**.
|
|
102
|
+
|
|
101
103
|
`deepintshield` lets you keep writing idiomatic OpenAI / Anthropic / Bedrock /
|
|
102
104
|
Google GenAI code **and** native agent-framework code (LangGraph, CrewAI,
|
|
103
105
|
OpenAI Agents SDK, LlamaIndex, AutoGen, PydanticAI, Temporal, AWS Strands,
|
|
@@ -134,6 +136,9 @@ and you're done.
|
|
|
134
136
|
|
|
135
137
|
## Install
|
|
136
138
|
|
|
139
|
+
For a reproducible installation of this release, use `pip install "deepintshield==2.7.2"`.
|
|
140
|
+
Add the provider and framework extras your application needs:
|
|
141
|
+
|
|
137
142
|
```bash
|
|
138
143
|
pip install deepintshield # core (chat, RAG, agentic)
|
|
139
144
|
pip install 'deepintshield[openai]' # + OpenAI SDK
|
|
@@ -268,17 +273,40 @@ response = openai.chat.completions.create(
|
|
|
268
273
|
)
|
|
269
274
|
```
|
|
270
275
|
|
|
276
|
+
The same client exposes the native Responses API:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
response = openai.responses.create(
|
|
280
|
+
model="gpt-4o-mini",
|
|
281
|
+
input="Explain this design in one sentence.",
|
|
282
|
+
store=False,
|
|
283
|
+
)
|
|
284
|
+
print(response.output_text)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Responses uses `max_output_tokens` and a `reasoning` object where supported;
|
|
288
|
+
Chat Completions uses its model's supported token-limit field and
|
|
289
|
+
`reasoning_effort`. For manual Responses continuations, retain the full output
|
|
290
|
+
items, including tool calls and opaque reasoning state, and replay them only
|
|
291
|
+
with the same provider and model. The Playground performs this mapping and
|
|
292
|
+
preserves compatible response state in saved sessions.
|
|
293
|
+
|
|
271
294
|
### Anthropic
|
|
272
295
|
|
|
273
296
|
```python
|
|
274
297
|
anthropic = shield.anthropic()
|
|
275
298
|
response = anthropic.messages.create(
|
|
276
|
-
model="claude-
|
|
299
|
+
model="claude-sonnet-5",
|
|
277
300
|
max_tokens=256,
|
|
278
301
|
messages=[{"role": "user", "content": "hello"}],
|
|
279
302
|
)
|
|
280
303
|
```
|
|
281
304
|
|
|
305
|
+
`shield.anthropic()` uses the installed Anthropic SDK's default transport class,
|
|
306
|
+
including SDK releases backed by `httpx2`, and retains automatic prompt-cache
|
|
307
|
+
hooks. A caller-supplied `http_client` must be compatible with that installed SDK
|
|
308
|
+
and remains responsible for its own prompt-cache hooks.
|
|
309
|
+
|
|
282
310
|
### Bedrock
|
|
283
311
|
|
|
284
312
|
```python
|
|
@@ -294,11 +322,30 @@ response = bedrock.converse(
|
|
|
294
322
|
```python
|
|
295
323
|
genai = shield.genai()
|
|
296
324
|
response = genai.models.generate_content(
|
|
297
|
-
model="gemini-
|
|
325
|
+
model="gemini-3.5-flash",
|
|
298
326
|
contents="hello",
|
|
327
|
+
config={"automatic_function_calling": {"disable": True}},
|
|
299
328
|
)
|
|
329
|
+
print(response.text)
|
|
300
330
|
```
|
|
301
331
|
|
|
332
|
+
Disabling automatic function calling (AFC) is optional for this text-only call.
|
|
333
|
+
Recent Google SDK versions warn about direct AFC use even when no callable tools
|
|
334
|
+
are supplied; that warning alone does not mean the request failed. When using
|
|
335
|
+
Python callable tools, Google recommends the chat interface. Multi-turn tool
|
|
336
|
+
workflows also depend on the gateway preserving tool roles and thought signatures;
|
|
337
|
+
a successful text-only request does not verify those conversions.
|
|
338
|
+
|
|
339
|
+
The gateway's native Gemini conversion preserves model/user tool roles, per-call
|
|
340
|
+
thought signatures, and distinct IDs for parallel calls to the same function.
|
|
341
|
+
SDK regression tests cover direct and chat calls, sync/async streaming, and
|
|
342
|
+
callable-tool continuations. A timeout-only `http_options` override retains the
|
|
343
|
+
gateway destination in SDK 2.7.2 (fixed in 2.7.1); SDK 2.7.0 does not merge that override correctly.
|
|
344
|
+
|
|
345
|
+
For streaming, use `genai.models.generate_content_stream(...)` or
|
|
346
|
+
`chat.send_message_stream(...)` and read each chunk's `text`. The native async
|
|
347
|
+
interfaces remain available under `genai.aio`.
|
|
348
|
+
|
|
302
349
|
### LangChain
|
|
303
350
|
|
|
304
351
|
```python
|
|
@@ -370,6 +417,12 @@ retriever = shield.rag.guard_retriever(my_retriever) # mutates in place
|
|
|
370
417
|
docs = retriever.invoke("what is the Q2 ledger?") # only allowed chunks
|
|
371
418
|
```
|
|
372
419
|
|
|
420
|
+
Async retrievers are supported too: use `await retriever.ainvoke(query)` or
|
|
421
|
+
the retriever's native async retrieval method. Filtering finishes before
|
|
422
|
+
documents are returned; delegated retrieval methods filter once per call.
|
|
423
|
+
Allowed documents keep their order and original framework objects. Redacted
|
|
424
|
+
content is returned in copies, leaving the retriever's source documents intact.
|
|
425
|
+
|
|
373
426
|
### Guard an embedder (pre-embedding, Portkey-parity "before request")
|
|
374
427
|
|
|
375
428
|
Screen input text for PII / injection / toxicity **before** it is vectorised:
|
|
@@ -808,15 +861,21 @@ OpenAI/Anthropic/LangChain conversion-loop helpers remain deprecated 2.x
|
|
|
808
861
|
compatibility shims. Their removal is planned for SDK 3.0; new code should use
|
|
809
862
|
the official session or a maintained third-party adapter.
|
|
810
863
|
|
|
864
|
+
For an existing Anthropic conversion loop, use the same `shield.mcp` instance
|
|
865
|
+
for `to_anthropic(tools)` and `run_anthropic_tool_uses(response.content)`.
|
|
866
|
+
Provider-safe aliases distinguish qualified names that contain unsupported
|
|
867
|
+
characters or exceed Anthropic's length limit; that client retains the mapping
|
|
868
|
+
back to the original tool names for execution.
|
|
869
|
+
|
|
811
870
|
---
|
|
812
871
|
|
|
813
872
|
## Cost Optimization
|
|
814
873
|
|
|
815
|
-
The SDK
|
|
874
|
+
The SDK participates in the gateway's caching mechanisms
|
|
816
875
|
(both controlled by workspace switches under **Cost Optimization**):
|
|
817
876
|
|
|
818
|
-
- **Provider prompt caching** -
|
|
819
|
-
|
|
877
|
+
- **Provider prompt caching** - SDK-created OpenAI and Anthropic transports
|
|
878
|
+
include request hooks that inject
|
|
820
879
|
Anthropic `cache_control` markers and an OpenAI `prompt_cache_key` so the
|
|
821
880
|
provider reuses KV state for the static prompt prefix. Eligible cached tokens
|
|
822
881
|
use the provider's current cached-input rate; verify model-specific pricing
|
|
@@ -830,6 +889,11 @@ The SDK automatically participates in the gateway's two cost-reduction layers
|
|
|
830
889
|
threshold. The SDK doesn't need any code change to benefit; results flow
|
|
831
890
|
back through the normal API.
|
|
832
891
|
|
|
892
|
+
Cost dashboards show recorded numeric totals, using `$0.00` when no display
|
|
893
|
+
value is available. Missing prices remain nullable in API log records and can
|
|
894
|
+
still be found through the missing-cost filter. Estimated savings remain signed
|
|
895
|
+
and are separate from the actual recorded cost.
|
|
896
|
+
|
|
833
897
|
### Per-request cache overrides
|
|
834
898
|
|
|
835
899
|
Workspace settings are the default, but any individual call can override
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
Unified Python SDK for DeepIntShield - one import, any provider, any agent framework.
|
|
7
7
|
|
|
8
|
+
Current release: **2.7.2**, aligned with DeepIntShield Server **2.7.2**.
|
|
9
|
+
|
|
8
10
|
`deepintshield` lets you keep writing idiomatic OpenAI / Anthropic / Bedrock /
|
|
9
11
|
Google GenAI code **and** native agent-framework code (LangGraph, CrewAI,
|
|
10
12
|
OpenAI Agents SDK, LlamaIndex, AutoGen, PydanticAI, Temporal, AWS Strands,
|
|
@@ -41,6 +43,9 @@ and you're done.
|
|
|
41
43
|
|
|
42
44
|
## Install
|
|
43
45
|
|
|
46
|
+
For a reproducible installation of this release, use `pip install "deepintshield==2.7.2"`.
|
|
47
|
+
Add the provider and framework extras your application needs:
|
|
48
|
+
|
|
44
49
|
```bash
|
|
45
50
|
pip install deepintshield # core (chat, RAG, agentic)
|
|
46
51
|
pip install 'deepintshield[openai]' # + OpenAI SDK
|
|
@@ -175,17 +180,40 @@ response = openai.chat.completions.create(
|
|
|
175
180
|
)
|
|
176
181
|
```
|
|
177
182
|
|
|
183
|
+
The same client exposes the native Responses API:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
response = openai.responses.create(
|
|
187
|
+
model="gpt-4o-mini",
|
|
188
|
+
input="Explain this design in one sentence.",
|
|
189
|
+
store=False,
|
|
190
|
+
)
|
|
191
|
+
print(response.output_text)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Responses uses `max_output_tokens` and a `reasoning` object where supported;
|
|
195
|
+
Chat Completions uses its model's supported token-limit field and
|
|
196
|
+
`reasoning_effort`. For manual Responses continuations, retain the full output
|
|
197
|
+
items, including tool calls and opaque reasoning state, and replay them only
|
|
198
|
+
with the same provider and model. The Playground performs this mapping and
|
|
199
|
+
preserves compatible response state in saved sessions.
|
|
200
|
+
|
|
178
201
|
### Anthropic
|
|
179
202
|
|
|
180
203
|
```python
|
|
181
204
|
anthropic = shield.anthropic()
|
|
182
205
|
response = anthropic.messages.create(
|
|
183
|
-
model="claude-
|
|
206
|
+
model="claude-sonnet-5",
|
|
184
207
|
max_tokens=256,
|
|
185
208
|
messages=[{"role": "user", "content": "hello"}],
|
|
186
209
|
)
|
|
187
210
|
```
|
|
188
211
|
|
|
212
|
+
`shield.anthropic()` uses the installed Anthropic SDK's default transport class,
|
|
213
|
+
including SDK releases backed by `httpx2`, and retains automatic prompt-cache
|
|
214
|
+
hooks. A caller-supplied `http_client` must be compatible with that installed SDK
|
|
215
|
+
and remains responsible for its own prompt-cache hooks.
|
|
216
|
+
|
|
189
217
|
### Bedrock
|
|
190
218
|
|
|
191
219
|
```python
|
|
@@ -201,11 +229,30 @@ response = bedrock.converse(
|
|
|
201
229
|
```python
|
|
202
230
|
genai = shield.genai()
|
|
203
231
|
response = genai.models.generate_content(
|
|
204
|
-
model="gemini-
|
|
232
|
+
model="gemini-3.5-flash",
|
|
205
233
|
contents="hello",
|
|
234
|
+
config={"automatic_function_calling": {"disable": True}},
|
|
206
235
|
)
|
|
236
|
+
print(response.text)
|
|
207
237
|
```
|
|
208
238
|
|
|
239
|
+
Disabling automatic function calling (AFC) is optional for this text-only call.
|
|
240
|
+
Recent Google SDK versions warn about direct AFC use even when no callable tools
|
|
241
|
+
are supplied; that warning alone does not mean the request failed. When using
|
|
242
|
+
Python callable tools, Google recommends the chat interface. Multi-turn tool
|
|
243
|
+
workflows also depend on the gateway preserving tool roles and thought signatures;
|
|
244
|
+
a successful text-only request does not verify those conversions.
|
|
245
|
+
|
|
246
|
+
The gateway's native Gemini conversion preserves model/user tool roles, per-call
|
|
247
|
+
thought signatures, and distinct IDs for parallel calls to the same function.
|
|
248
|
+
SDK regression tests cover direct and chat calls, sync/async streaming, and
|
|
249
|
+
callable-tool continuations. A timeout-only `http_options` override retains the
|
|
250
|
+
gateway destination in SDK 2.7.2 (fixed in 2.7.1); SDK 2.7.0 does not merge that override correctly.
|
|
251
|
+
|
|
252
|
+
For streaming, use `genai.models.generate_content_stream(...)` or
|
|
253
|
+
`chat.send_message_stream(...)` and read each chunk's `text`. The native async
|
|
254
|
+
interfaces remain available under `genai.aio`.
|
|
255
|
+
|
|
209
256
|
### LangChain
|
|
210
257
|
|
|
211
258
|
```python
|
|
@@ -277,6 +324,12 @@ retriever = shield.rag.guard_retriever(my_retriever) # mutates in place
|
|
|
277
324
|
docs = retriever.invoke("what is the Q2 ledger?") # only allowed chunks
|
|
278
325
|
```
|
|
279
326
|
|
|
327
|
+
Async retrievers are supported too: use `await retriever.ainvoke(query)` or
|
|
328
|
+
the retriever's native async retrieval method. Filtering finishes before
|
|
329
|
+
documents are returned; delegated retrieval methods filter once per call.
|
|
330
|
+
Allowed documents keep their order and original framework objects. Redacted
|
|
331
|
+
content is returned in copies, leaving the retriever's source documents intact.
|
|
332
|
+
|
|
280
333
|
### Guard an embedder (pre-embedding, Portkey-parity "before request")
|
|
281
334
|
|
|
282
335
|
Screen input text for PII / injection / toxicity **before** it is vectorised:
|
|
@@ -715,15 +768,21 @@ OpenAI/Anthropic/LangChain conversion-loop helpers remain deprecated 2.x
|
|
|
715
768
|
compatibility shims. Their removal is planned for SDK 3.0; new code should use
|
|
716
769
|
the official session or a maintained third-party adapter.
|
|
717
770
|
|
|
771
|
+
For an existing Anthropic conversion loop, use the same `shield.mcp` instance
|
|
772
|
+
for `to_anthropic(tools)` and `run_anthropic_tool_uses(response.content)`.
|
|
773
|
+
Provider-safe aliases distinguish qualified names that contain unsupported
|
|
774
|
+
characters or exceed Anthropic's length limit; that client retains the mapping
|
|
775
|
+
back to the original tool names for execution.
|
|
776
|
+
|
|
718
777
|
---
|
|
719
778
|
|
|
720
779
|
## Cost Optimization
|
|
721
780
|
|
|
722
|
-
The SDK
|
|
781
|
+
The SDK participates in the gateway's caching mechanisms
|
|
723
782
|
(both controlled by workspace switches under **Cost Optimization**):
|
|
724
783
|
|
|
725
|
-
- **Provider prompt caching** -
|
|
726
|
-
|
|
784
|
+
- **Provider prompt caching** - SDK-created OpenAI and Anthropic transports
|
|
785
|
+
include request hooks that inject
|
|
727
786
|
Anthropic `cache_control` markers and an OpenAI `prompt_cache_key` so the
|
|
728
787
|
provider reuses KV state for the static prompt prefix. Eligible cached tokens
|
|
729
788
|
use the provider's current cached-input rate; verify model-specific pricing
|
|
@@ -737,6 +796,11 @@ The SDK automatically participates in the gateway's two cost-reduction layers
|
|
|
737
796
|
threshold. The SDK doesn't need any code change to benefit; results flow
|
|
738
797
|
back through the normal API.
|
|
739
798
|
|
|
799
|
+
Cost dashboards show recorded numeric totals, using `$0.00` when no display
|
|
800
|
+
value is available. Missing prices remain nullable in API log records and can
|
|
801
|
+
still be found through the missing-cost filter. Estimated savings remain signed
|
|
802
|
+
and are separate from the actual recorded cost.
|
|
803
|
+
|
|
740
804
|
### Per-request cache overrides
|
|
741
805
|
|
|
742
806
|
Workspace settings are the default, but any individual call can override
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepintshield"
|
|
7
|
-
version = "2.7.
|
|
7
|
+
version = "2.7.2"
|
|
8
8
|
description = "Unified Python SDK for routing chat, RAG, agentic tool-gating, identity, and MCP traffic through DeepintShield - drop-in across the top agentic frameworks."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -25,6 +25,7 @@ from __future__ import annotations
|
|
|
25
25
|
|
|
26
26
|
import hashlib
|
|
27
27
|
import json
|
|
28
|
+
from functools import lru_cache
|
|
28
29
|
from typing import Any, Callable, Iterable
|
|
29
30
|
|
|
30
31
|
import httpx
|
|
@@ -33,6 +34,20 @@ import httpx
|
|
|
33
34
|
PROVIDER_ANTHROPIC = "anthropic"
|
|
34
35
|
PROVIDER_OPENAI = "openai"
|
|
35
36
|
|
|
37
|
+
|
|
38
|
+
@lru_cache(maxsize=8)
|
|
39
|
+
def _request_byte_stream_type(request_type: type) -> type:
|
|
40
|
+
"""Keep rewritten request streams in the HTTP library that owns the request."""
|
|
41
|
+
for cls in request_type.__mro__:
|
|
42
|
+
package = cls.__module__.partition(".")[0]
|
|
43
|
+
if package == "httpx2":
|
|
44
|
+
# Optional: only imported for a request from an installed httpx2 SDK.
|
|
45
|
+
from httpx2 import ByteStream
|
|
46
|
+
return ByteStream
|
|
47
|
+
if package == "httpx":
|
|
48
|
+
return httpx.ByteStream
|
|
49
|
+
return httpx.ByteStream
|
|
50
|
+
|
|
36
51
|
# Anthropic accepts up to 4 cache_control markers per request. We place them in
|
|
37
52
|
# priority order: system → tools → last static user/assistant block.
|
|
38
53
|
_DEFAULT_BREAKPOINTS: tuple[str, ...] = ("system", "tools")
|
|
@@ -209,13 +224,7 @@ def build_request_hook(
|
|
|
209
224
|
# the announced byte count). Reattach the stream so the new body
|
|
210
225
|
# is what actually gets sent.
|
|
211
226
|
request._content = encoded # type: ignore[attr-defined]
|
|
212
|
-
|
|
213
|
-
from httpx._content import ByteStream # type: ignore
|
|
214
|
-
except ImportError: # httpx <0.24 fallback - module path drifted.
|
|
215
|
-
from httpx import _content # type: ignore
|
|
216
|
-
ByteStream = getattr(_content, "ByteStream", None)
|
|
217
|
-
if ByteStream is not None:
|
|
218
|
-
request.stream = ByteStream(encoded) # type: ignore[attr-defined]
|
|
227
|
+
request.stream = _request_byte_stream_type(type(request))(encoded)
|
|
219
228
|
|
|
220
229
|
return hook
|
|
221
230
|
|
|
@@ -267,6 +267,17 @@ def install_all(*, client: Any = None) -> list[str]:
|
|
|
267
267
|
with _install_lock:
|
|
268
268
|
if mod_name in _installed:
|
|
269
269
|
continue
|
|
270
|
+
# Nested imports return before the enclosing framework has
|
|
271
|
+
# defined its execution classes. Inspecting it at that point
|
|
272
|
+
# causes circular imports and falsely reports an unsupported
|
|
273
|
+
# version. The enclosing import's post-hook retries once all
|
|
274
|
+
# framework modules have finished initializing.
|
|
275
|
+
if any(
|
|
276
|
+
(name == mod_name or name.startswith(mod_name + "."))
|
|
277
|
+
and getattr(getattr(module, "__spec__", None), "_initializing", False)
|
|
278
|
+
for name, module in tuple(sys.modules.items())
|
|
279
|
+
):
|
|
280
|
+
continue
|
|
270
281
|
# Keep check → patch → mark atomic. Two clients initialized in
|
|
271
282
|
# parallel must not wrap the same framework method twice and
|
|
272
283
|
# therefore run two PDP decisions for one tool invocation.
|
|
@@ -3358,6 +3358,23 @@ def ensure_registration_capture(
|
|
|
3358
3358
|
if time.monotonic() < _capture_retry_at(engine, tool_key):
|
|
3359
3359
|
return False
|
|
3360
3360
|
selector = str(getattr(engine, "_agent_subject_selector", "") or "")
|
|
3361
|
+
if not selector:
|
|
3362
|
+
# No explicit agent_name was given. The gateway's own
|
|
3363
|
+
# credential-info carries the subject this virtual key is bound to,
|
|
3364
|
+
# and ``AgenticEngine.agent_subject`` documents that value as
|
|
3365
|
+
# authoritative - it is already what /decide is told the principal
|
|
3366
|
+
# is. Registering under it keeps one identity across the decision
|
|
3367
|
+
# and the registry instead of refusing a VK that the server can
|
|
3368
|
+
# name perfectly well.
|
|
3369
|
+
#
|
|
3370
|
+
# This is NOT the invented shared name warned about below: it is
|
|
3371
|
+
# server-issued and VK-bound, so it cannot collide across keys.
|
|
3372
|
+
try:
|
|
3373
|
+
selector = str(getattr(engine, "agent_subject", "") or "").strip()
|
|
3374
|
+
except Exception:
|
|
3375
|
+
# Credential discovery is allowed to fail here; the selector
|
|
3376
|
+
# check below then reports the missing name as before.
|
|
3377
|
+
selector = ""
|
|
3361
3378
|
agent_key = _key(selector.removeprefix("agent:"))
|
|
3362
3379
|
if not selector.startswith("agent:") or not agent_key:
|
|
3363
3380
|
# Proof-less registration is deliberately limited to an explicit
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
|
+
import hashlib
|
|
5
6
|
from typing import TYPE_CHECKING, Any, Iterable, Mapping
|
|
6
7
|
|
|
7
8
|
from ..tool import Tool
|
|
@@ -15,11 +16,23 @@ if TYPE_CHECKING:
|
|
|
15
16
|
_ANTHROPIC_NAME_RE = re.compile(r"[^a-zA-Z0-9_-]")
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def to_anthropic(
|
|
19
|
+
def to_anthropic(
|
|
20
|
+
tools: Iterable[Tool], *, name_map: dict[str, str] | None = None,
|
|
21
|
+
) -> list[dict[str, Any]]:
|
|
19
22
|
"""Convert ``Tool`` objects to Anthropic's Messages API tools array."""
|
|
20
23
|
out: list[dict[str, Any]] = []
|
|
24
|
+
aliases = dict(name_map or {})
|
|
21
25
|
for tool in tools:
|
|
22
|
-
|
|
26
|
+
qualified = tool.qualified_name
|
|
27
|
+
sanitized = _ANTHROPIC_NAME_RE.sub("_", qualified)
|
|
28
|
+
if sanitized != qualified or len(sanitized) > 64:
|
|
29
|
+
# Truncation/replacement alone can send two different tools to
|
|
30
|
+
# the same server name. Keep a stable, provider-valid alias.
|
|
31
|
+
digest = hashlib.sha256(qualified.encode("utf-8")).hexdigest()[:16]
|
|
32
|
+
sanitized = f"{sanitized[:47]}_{digest}"
|
|
33
|
+
if sanitized in aliases and aliases[sanitized] != qualified:
|
|
34
|
+
raise ValueError("Anthropic MCP tool aliases collide")
|
|
35
|
+
aliases[sanitized] = qualified
|
|
23
36
|
out.append(
|
|
24
37
|
{
|
|
25
38
|
"name": sanitized,
|
|
@@ -27,6 +40,8 @@ def to_anthropic(tools: Iterable[Tool]) -> list[dict[str, Any]]:
|
|
|
27
40
|
"input_schema": tool.schema or {"type": "object", "properties": {}},
|
|
28
41
|
}
|
|
29
42
|
)
|
|
43
|
+
if name_map is not None:
|
|
44
|
+
name_map.update(aliases)
|
|
30
45
|
return out
|
|
31
46
|
|
|
32
47
|
|
|
@@ -35,6 +50,7 @@ def run_tool_uses(
|
|
|
35
50
|
content: Iterable[Any],
|
|
36
51
|
*,
|
|
37
52
|
extra_headers: Mapping[str, str] | None = None,
|
|
53
|
+
name_map: Mapping[str, str] | None = None,
|
|
38
54
|
) -> list[dict[str, Any]]:
|
|
39
55
|
"""Execute every ``tool_use`` block in an assistant content array.
|
|
40
56
|
|
|
@@ -50,7 +66,7 @@ def run_tool_uses(
|
|
|
50
66
|
continue
|
|
51
67
|
try:
|
|
52
68
|
result = client.call_qualified(
|
|
53
|
-
name,
|
|
69
|
+
(name_map or {}).get(name, name),
|
|
54
70
|
args or {},
|
|
55
71
|
call_id=tool_use_id,
|
|
56
72
|
extra_headers=extra_headers,
|
|
@@ -41,6 +41,7 @@ class MCPClient:
|
|
|
41
41
|
|
|
42
42
|
def __init__(self, shield: "DeepintShield") -> None:
|
|
43
43
|
self._shield = shield
|
|
44
|
+
self._anthropic_tool_names: dict[str, str] = {}
|
|
44
45
|
|
|
45
46
|
# ───────────────────── preferred native MCP boundary ───────────────────
|
|
46
47
|
|
|
@@ -193,7 +194,7 @@ class MCPClient:
|
|
|
193
194
|
"POST",
|
|
194
195
|
"/v1/mcp/tool/execute",
|
|
195
196
|
json_body=payload,
|
|
196
|
-
extra_headers=extra_headers,
|
|
197
|
+
extra_headers=self._agent_identity_headers(extra_headers),
|
|
197
198
|
error_code=ErrorCode.MCP_EXECUTION_FAILED,
|
|
198
199
|
require_object=True,
|
|
199
200
|
)
|
|
@@ -258,6 +259,36 @@ class MCPClient:
|
|
|
258
259
|
)
|
|
259
260
|
return self.call(server=server, tool=tool, arguments=args_dict, **kwargs)
|
|
260
261
|
|
|
262
|
+
# ───────────────────────── agent identity headers ────────────────────────
|
|
263
|
+
|
|
264
|
+
def _agent_identity_headers(
|
|
265
|
+
self, extra_headers: Mapping[str, str] | None
|
|
266
|
+
) -> dict[str, str]:
|
|
267
|
+
"""Attach the client's agent selector to a gateway MCP call.
|
|
268
|
+
|
|
269
|
+
A DeepintShield client represents one agent identity. The PDP decide
|
|
270
|
+
path already sends ``X-Agent-Subject``; the brokered MCP path used to
|
|
271
|
+
send nothing, so a virtual key bound to more than one active agent was
|
|
272
|
+
refused with ``mcp_tool_authorization_unavailable`` on execute while
|
|
273
|
+
its decide calls succeeded. The selector is derived locally (no
|
|
274
|
+
discovery round-trip). Workload tokens stay request-scoped: pass
|
|
275
|
+
``X-Agent-Token`` through ``extra_headers`` as before. Explicit caller
|
|
276
|
+
headers always win; nothing is added for a client without an
|
|
277
|
+
``agent_name``.
|
|
278
|
+
"""
|
|
279
|
+
headers = dict(extra_headers or {})
|
|
280
|
+
agent_name = str(getattr(self._shield, "agent_name", "") or "").strip()
|
|
281
|
+
if not agent_name:
|
|
282
|
+
return headers
|
|
283
|
+
if any(str(key).lower() == "x-agent-subject" for key in headers):
|
|
284
|
+
return headers
|
|
285
|
+
from ..agentic.registry import _registry_key
|
|
286
|
+
|
|
287
|
+
agent_key = _registry_key(agent_name)
|
|
288
|
+
if agent_key:
|
|
289
|
+
headers["X-Agent-Subject"] = f"agent:{agent_key}"
|
|
290
|
+
return headers
|
|
291
|
+
|
|
261
292
|
# ─────────────────────────── discovery (optional) ────────────────────────
|
|
262
293
|
|
|
263
294
|
def list_tools(
|
|
@@ -274,7 +305,7 @@ class MCPClient:
|
|
|
274
305
|
unavailable in your environment, supply tool definitions manually.
|
|
275
306
|
"""
|
|
276
307
|
self._warn_legacy("list_tools")
|
|
277
|
-
headers: dict[str, str] =
|
|
308
|
+
headers: dict[str, str] = self._agent_identity_headers(None)
|
|
278
309
|
if admin_token:
|
|
279
310
|
headers["Authorization"] = f"Bearer {admin_token}"
|
|
280
311
|
payload = self._shield.request(
|
|
@@ -338,7 +369,7 @@ class MCPClient:
|
|
|
338
369
|
"""Convert tools to Anthropic Messages API ``tools=`` array shape."""
|
|
339
370
|
self._warn_legacy("to_anthropic")
|
|
340
371
|
from .adapters import anthropic as _anthropic
|
|
341
|
-
return _anthropic.to_anthropic(tools)
|
|
372
|
+
return _anthropic.to_anthropic(tools, name_map=self._anthropic_tool_names)
|
|
342
373
|
|
|
343
374
|
def run_anthropic_tool_uses(
|
|
344
375
|
self,
|
|
@@ -356,6 +387,7 @@ class MCPClient:
|
|
|
356
387
|
self,
|
|
357
388
|
content,
|
|
358
389
|
extra_headers=extra_headers,
|
|
390
|
+
name_map=self._anthropic_tool_names,
|
|
359
391
|
)
|
|
360
392
|
|
|
361
393
|
def to_langchain(self, tools: Iterable[Tool]) -> list[Any]:
|
|
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
-
from .._prompt_cache import PROVIDER_ANTHROPIC,
|
|
5
|
+
from .._prompt_cache import PROVIDER_ANTHROPIC, build_request_hook
|
|
6
6
|
from ..errors import ErrorCode, _dependency_error
|
|
7
|
+
from ..transport import connection_headers, _install_agent_selector_header_hook
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from ..client import DeepintShield
|
|
@@ -19,8 +20,8 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
|
|
|
19
20
|
Provider Prompt Caching switch - disabled workspaces have the markers
|
|
20
21
|
stripped at the gateway before they reach Anthropic.
|
|
21
22
|
|
|
22
|
-
Pass a custom ``http_client`` to
|
|
23
|
-
|
|
23
|
+
Pass a custom ``http_client`` to manage caching yourself; only
|
|
24
|
+
case-insensitive agent selector override handling is added to that client.
|
|
24
25
|
"""
|
|
25
26
|
try:
|
|
26
27
|
import anthropic
|
|
@@ -33,13 +34,28 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
|
|
|
33
34
|
|
|
34
35
|
base_url = shield.anthropic_passthrough_base_url() if passthrough else shield.anthropic_base_url()
|
|
35
36
|
http_client = kwargs.pop("http_client", None)
|
|
37
|
+
owns_http_client = http_client is None
|
|
36
38
|
if http_client is None:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
# Use the installed SDK's public transport class: older releases use
|
|
40
|
+
# httpx, while current releases require httpx2 and reject httpx.Client.
|
|
41
|
+
http_client = anthropic.DefaultHttpxClient(
|
|
42
|
+
timeout=shield.timeout,
|
|
43
|
+
follow_redirects=False,
|
|
44
|
+
event_hooks={"request": [build_request_hook(PROVIDER_ANTHROPIC)]},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
client = anthropic.Anthropic(
|
|
49
|
+
base_url=kwargs.pop("base_url", base_url),
|
|
50
|
+
api_key=kwargs.pop("api_key", shield.api_key()),
|
|
51
|
+
default_headers=connection_headers(shield, extra=kwargs.pop("default_headers", None)),
|
|
52
|
+
http_client=http_client,
|
|
53
|
+
**kwargs,
|
|
54
|
+
)
|
|
55
|
+
except Exception:
|
|
56
|
+
if owns_http_client:
|
|
57
|
+
http_client.close()
|
|
58
|
+
raise
|
|
59
|
+
# Let the SDK validate caller-supplied transports before mutating hooks.
|
|
60
|
+
_install_agent_selector_header_hook(http_client)
|
|
61
|
+
return client
|
|
@@ -4,6 +4,12 @@ from typing import TYPE_CHECKING, Any
|
|
|
4
4
|
|
|
5
5
|
from .._gemini_cache import GenaiCachedClient, GeminiCacheManager, env_ttl_seconds
|
|
6
6
|
from ..errors import ErrorCode, _dependency_error
|
|
7
|
+
from ..transport import (
|
|
8
|
+
connection_headers,
|
|
9
|
+
_install_agent_selector_header_hook,
|
|
10
|
+
_normalize_agent_selector_headers,
|
|
11
|
+
_normalize_agent_selector_headers_async,
|
|
12
|
+
)
|
|
7
13
|
|
|
8
14
|
if TYPE_CHECKING:
|
|
9
15
|
from ..client import DeepintShield
|
|
@@ -22,9 +28,34 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
|
|
|
22
28
|
) from exc
|
|
23
29
|
|
|
24
30
|
base_url = shield.genai_passthrough_base_url() if passthrough else shield.genai_base_url()
|
|
31
|
+
supplied_options = kwargs.pop("http_options", None)
|
|
32
|
+
if isinstance(supplied_options, HttpOptions):
|
|
33
|
+
options = supplied_options.model_copy()
|
|
34
|
+
else:
|
|
35
|
+
options = HttpOptions(**(supplied_options or {}))
|
|
36
|
+
options.base_url = options.base_url or base_url
|
|
37
|
+
options.headers = connection_headers(shield, extra=options.headers)
|
|
38
|
+
# Preserve caller options, transports, and hooks. Native sync/async clients
|
|
39
|
+
# use the same stateless selector normalization, including stream requests.
|
|
40
|
+
for client_attr, args_attr, hook in (
|
|
41
|
+
("httpx_client", "client_args", _normalize_agent_selector_headers),
|
|
42
|
+
("httpx_async_client", "async_client_args", _normalize_agent_selector_headers_async),
|
|
43
|
+
):
|
|
44
|
+
native_http_client = getattr(options, client_attr, None)
|
|
45
|
+
if native_http_client is not None:
|
|
46
|
+
_install_agent_selector_header_hook(native_http_client)
|
|
47
|
+
else:
|
|
48
|
+
client_args = dict(getattr(options, args_attr, None) or {})
|
|
49
|
+
event_hooks = dict(client_args.get("event_hooks") or {})
|
|
50
|
+
request_hooks = list(event_hooks.get("request") or [])
|
|
51
|
+
if hook not in request_hooks:
|
|
52
|
+
request_hooks.append(hook)
|
|
53
|
+
event_hooks["request"] = request_hooks
|
|
54
|
+
client_args["event_hooks"] = event_hooks
|
|
55
|
+
setattr(options, args_attr, client_args)
|
|
25
56
|
return genai.Client(
|
|
26
57
|
api_key=kwargs.pop("api_key", shield.api_key()),
|
|
27
|
-
http_options=
|
|
58
|
+
http_options=options,
|
|
28
59
|
**kwargs,
|
|
29
60
|
)
|
|
30
61
|
|
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
4
4
|
|
|
5
5
|
from .._prompt_cache import PROVIDER_OPENAI, build_http_client
|
|
6
6
|
from ..errors import ErrorCode, _dependency_error
|
|
7
|
+
from ..transport import connection_headers, _install_agent_selector_header_hook
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from ..client import DeepintShield
|
|
@@ -19,8 +20,8 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
|
|
|
19
20
|
on the gateway - if the workspace has it disabled the gateway strips the
|
|
20
21
|
key before forwarding.
|
|
21
22
|
|
|
22
|
-
Callers passing their own ``http_client``
|
|
23
|
-
|
|
23
|
+
Callers passing their own ``http_client`` manage caching themselves; only
|
|
24
|
+
case-insensitive agent selector override handling is added to that client.
|
|
24
25
|
"""
|
|
25
26
|
try:
|
|
26
27
|
from openai import OpenAI
|
|
@@ -35,11 +36,12 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
|
|
|
35
36
|
http_client = kwargs.pop("http_client", None)
|
|
36
37
|
if http_client is None:
|
|
37
38
|
http_client = build_http_client(PROVIDER_OPENAI, timeout=shield.timeout)
|
|
39
|
+
_install_agent_selector_header_hook(http_client)
|
|
38
40
|
|
|
39
41
|
return OpenAI(
|
|
40
42
|
base_url=kwargs.pop("base_url", base_url),
|
|
41
43
|
api_key=kwargs.pop("api_key", shield.api_key()),
|
|
42
|
-
default_headers=
|
|
44
|
+
default_headers=connection_headers(shield, extra=kwargs.pop("default_headers", None)),
|
|
43
45
|
http_client=http_client,
|
|
44
46
|
**kwargs,
|
|
45
47
|
)
|