deepintshield 2.7.1__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.1/src/deepintshield.egg-info → deepintshield-2.7.2}/PKG-INFO +70 -6
- {deepintshield-2.7.1 → deepintshield-2.7.2}/README.md +69 -5
- {deepintshield-2.7.1 → deepintshield-2.7.2}/pyproject.toml +1 -1
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/_prompt_cache.py +16 -7
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/anthropic.py +19 -3
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/client.py +3 -1
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/anthropic.py +24 -10
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/rag.py +63 -18
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/transport.py +6 -3
- deepintshield-2.7.2/src/deepintshield/version.py +1 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2/src/deepintshield.egg-info}/PKG-INFO +70 -6
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield.egg-info/SOURCES.txt +5 -0
- deepintshield-2.7.2/tests/test_anthropic_transport_compat.py +103 -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.1 → deepintshield-2.7.2}/tests/test_native_provider_headers.py +11 -6
- 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.1/src/deepintshield/version.py +0 -1
- {deepintshield-2.7.1 → deepintshield-2.7.2}/LICENSE +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/NOTICE +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/setup.cfg +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/__init__.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/_gemini_cache.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agent.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/__init__.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/actions.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/__init__.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/base.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/entra.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/oidc.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/zeroid.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/decorators.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/dpop.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/enforcement.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/engine.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/errors.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/execution.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/gate.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/identity.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/__init__.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/_common.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/autogen.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/crewai.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/google_adk.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/hermes.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/langchain.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/langgraph.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/litellm.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/llamaindex.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/openai_agents.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/openclaw.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/pydanticai.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/strands.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/temporal.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/manifest.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/obligations.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/registry.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/surface.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/types.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/client.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/config.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/errors.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/__init__.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/autogen.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/crewai.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/langgraph.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/llamaindex.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/openai_agents.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/pydanticai.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/__init__.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/_errors.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/_native.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/__init__.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/_security.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/langchain.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/openai.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/tool.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/__init__.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/bedrock.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/genai.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/langchain.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/langgraph.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/litellm.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/openai.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/pydanticai.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/streaming.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/types.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield.egg-info/dependency_links.txt +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield.egg-info/requires.txt +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield.egg-info/top_level.txt +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agent.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_blueprint_contract.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_callback_inventory.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_credentials.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_error_contract.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_execution_lifecycle.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_fail_closed_integrations.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_framework_parity.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_import_lifecycle.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_langchain.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_new_decide.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_new_discovery.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_new_enforcement.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_workload_headers.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_chat_streaming.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_client.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_config.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_error_catalog.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_error_surfaces.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_errors.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_gemini_cache.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_mcp_native_errors.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_mcp_native_session.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_mcp_preferred_api.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_obligation_contract.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_prompt_cache.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_providers.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_rag.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_rag_guard.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_rag_redaction.py +0 -0
- {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_transport.py +0 -0
- {deepintshield-2.7.1 → 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
|
|
|
@@ -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
|
|
|
@@ -368,7 +369,7 @@ class MCPClient:
|
|
|
368
369
|
"""Convert tools to Anthropic Messages API ``tools=`` array shape."""
|
|
369
370
|
self._warn_legacy("to_anthropic")
|
|
370
371
|
from .adapters import anthropic as _anthropic
|
|
371
|
-
return _anthropic.to_anthropic(tools)
|
|
372
|
+
return _anthropic.to_anthropic(tools, name_map=self._anthropic_tool_names)
|
|
372
373
|
|
|
373
374
|
def run_anthropic_tool_uses(
|
|
374
375
|
self,
|
|
@@ -386,6 +387,7 @@ class MCPClient:
|
|
|
386
387
|
self,
|
|
387
388
|
content,
|
|
388
389
|
extra_headers=extra_headers,
|
|
390
|
+
name_map=self._anthropic_tool_names,
|
|
389
391
|
)
|
|
390
392
|
|
|
391
393
|
def to_langchain(self, tools: Iterable[Tool]) -> list[Any]:
|
|
@@ -2,7 +2,7 @@ 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
7
|
from ..transport import connection_headers, _install_agent_selector_header_hook
|
|
8
8
|
|
|
@@ -34,14 +34,28 @@ def build_client(shield: "DeepintShield", *, passthrough: bool = False, **kwargs
|
|
|
34
34
|
|
|
35
35
|
base_url = shield.anthropic_passthrough_base_url() if passthrough else shield.anthropic_base_url()
|
|
36
36
|
http_client = kwargs.pop("http_client", None)
|
|
37
|
+
owns_http_client = http_client is None
|
|
37
38
|
if http_client is None:
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
)
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import functools
|
|
5
|
+
import inspect
|
|
6
|
+
from contextvars import ContextVar
|
|
4
7
|
from copy import deepcopy
|
|
5
8
|
from dataclasses import replace
|
|
6
9
|
from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping
|
|
@@ -13,6 +16,7 @@ if TYPE_CHECKING:
|
|
|
13
16
|
|
|
14
17
|
# Retrieve-style methods we know how to wrap, in priority order.
|
|
15
18
|
_RETRIEVE_METHODS = ("invoke", "retrieve", "_get_relevant_documents", "get_relevant_documents")
|
|
19
|
+
_ASYNC_RETRIEVE_METHODS = ("ainvoke", "aretrieve", "_aget_relevant_documents", "aget_relevant_documents")
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
def _query_text(query: Any) -> str:
|
|
@@ -166,33 +170,31 @@ class RAGSurface:
|
|
|
166
170
|
"""Wrap a framework retriever so every retrieved chunk is filtered
|
|
167
171
|
through the gateway's RAG-security evaluate before it reaches the LLM.
|
|
168
172
|
|
|
169
|
-
Works with
|
|
170
|
-
``_get_relevant_documents`` (LangChain ``BaseRetriever``, LlamaIndex
|
|
173
|
+
Works with retrievers exposing ``invoke`` / ``retrieve`` /
|
|
174
|
+
``_get_relevant_documents`` or their async counterparts (LangChain ``BaseRetriever``, LlamaIndex
|
|
171
175
|
retrievers, or a custom callable object). Mutates the retriever in
|
|
172
176
|
place and returns it, so existing graph/chain wiring is unchanged.
|
|
173
177
|
|
|
174
178
|
Unauthorised chunks are dropped and redacted documents are copied
|
|
175
179
|
with sanitized content. Source documents and allowed order are preserved.
|
|
176
180
|
"""
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if method_name is None:
|
|
181
|
+
method_names = [m for m in (*_RETRIEVE_METHODS, *_ASYNC_RETRIEVE_METHODS)
|
|
182
|
+
if callable(getattr(retriever, m, None))]
|
|
183
|
+
if not method_names:
|
|
181
184
|
raise _annotate_error(
|
|
182
185
|
TypeError(
|
|
183
186
|
"guard_retriever: retriever exposes no known retrieve method "
|
|
184
|
-
f"({', '.join(_RETRIEVE_METHODS)})"
|
|
187
|
+
f"({', '.join((*_RETRIEVE_METHODS, *_ASYNC_RETRIEVE_METHODS))})"
|
|
185
188
|
),
|
|
186
189
|
ErrorCode.RAG_RETRIEVER_UNSUPPORTED,
|
|
187
190
|
)
|
|
188
|
-
original = getattr(retriever, method_name)
|
|
189
|
-
if getattr(original, "_deepintshield_wrapped", False):
|
|
190
|
-
return retriever
|
|
191
191
|
surface = self
|
|
192
|
+
# Framework entry points often delegate to one another, including
|
|
193
|
+
# ainvoke -> a worker thread -> invoke. Filter once at the outer call;
|
|
194
|
+
# independent async tasks must retain their own evaluation context.
|
|
195
|
+
retrieving: ContextVar[bool] = ContextVar("deepintshield_retrieving", default=False)
|
|
192
196
|
|
|
193
|
-
|
|
194
|
-
def wrapped(query: Any, *args: Any, **kwargs: Any) -> Any:
|
|
195
|
-
docs = original(query, *args, **kwargs)
|
|
197
|
+
def filter_result(query: Any, docs: Any) -> Any:
|
|
196
198
|
if not isinstance(docs, (list, tuple)) or not docs:
|
|
197
199
|
return docs
|
|
198
200
|
return surface._filter_documents(
|
|
@@ -202,11 +204,54 @@ class RAGSurface:
|
|
|
202
204
|
**eval_kwargs,
|
|
203
205
|
)
|
|
204
206
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
def wrap(original: Callable[..., Any], asynchronous: bool) -> Callable[..., Any]:
|
|
208
|
+
@functools.wraps(original)
|
|
209
|
+
async def async_wrapped(query: Any, *args: Any, **kwargs: Any) -> Any:
|
|
210
|
+
nested = retrieving.get()
|
|
211
|
+
token = retrieving.set(True)
|
|
212
|
+
try:
|
|
213
|
+
docs = original(query, *args, **kwargs)
|
|
214
|
+
if inspect.isawaitable(docs):
|
|
215
|
+
docs = await docs
|
|
216
|
+
finally:
|
|
217
|
+
retrieving.reset(token)
|
|
218
|
+
if nested:
|
|
219
|
+
return docs
|
|
220
|
+
return await asyncio.to_thread(filter_result, query, docs)
|
|
221
|
+
|
|
222
|
+
@functools.wraps(original)
|
|
223
|
+
def sync_wrapped(query: Any, *args: Any, **kwargs: Any) -> Any:
|
|
224
|
+
if retrieving.get():
|
|
225
|
+
return original(query, *args, **kwargs)
|
|
226
|
+
token = retrieving.set(True)
|
|
227
|
+
try:
|
|
228
|
+
docs = original(query, *args, **kwargs)
|
|
229
|
+
finally:
|
|
230
|
+
retrieving.reset(token)
|
|
231
|
+
if inspect.isawaitable(docs):
|
|
232
|
+
async def complete() -> Any:
|
|
233
|
+
token = retrieving.set(True)
|
|
234
|
+
try:
|
|
235
|
+
resolved = await docs
|
|
236
|
+
finally:
|
|
237
|
+
retrieving.reset(token)
|
|
238
|
+
return await asyncio.to_thread(filter_result, query, resolved)
|
|
239
|
+
return complete()
|
|
240
|
+
return filter_result(query, docs)
|
|
241
|
+
|
|
242
|
+
wrapped = async_wrapped if asynchronous else sync_wrapped
|
|
243
|
+
wrapped._deepintshield_wrapped = True # type: ignore[attr-defined]
|
|
244
|
+
return wrapped
|
|
245
|
+
|
|
246
|
+
for method_name in method_names:
|
|
247
|
+
original = getattr(retriever, method_name)
|
|
248
|
+
if getattr(original, "_deepintshield_wrapped", False):
|
|
249
|
+
continue
|
|
250
|
+
wrapped = wrap(original, inspect.iscoroutinefunction(original) or method_name in _ASYNC_RETRIEVE_METHODS)
|
|
251
|
+
try:
|
|
252
|
+
setattr(retriever, method_name, wrapped)
|
|
253
|
+
except Exception: # frozen pydantic model
|
|
254
|
+
object.__setattr__(retriever, method_name, wrapped)
|
|
210
255
|
return retriever
|
|
211
256
|
|
|
212
257
|
def _filter_documents(
|
|
@@ -14,7 +14,8 @@ consumed by :mod:`deepintshield.frameworks` and re-exported on the client as
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from
|
|
17
|
+
from inspect import iscoroutinefunction
|
|
18
|
+
from typing import TYPE_CHECKING, Any, Mapping, Optional
|
|
18
19
|
|
|
19
20
|
import httpx
|
|
20
21
|
|
|
@@ -50,8 +51,10 @@ async def _normalize_agent_selector_headers_async(request: httpx.Request) -> Non
|
|
|
50
51
|
_normalize_agent_selector_headers(request)
|
|
51
52
|
|
|
52
53
|
|
|
53
|
-
def _install_agent_selector_header_hook(client:
|
|
54
|
-
|
|
54
|
+
def _install_agent_selector_header_hook(client: Any) -> None:
|
|
55
|
+
# Both httpx and httpx2 expose async send methods, but their AsyncClient
|
|
56
|
+
# classes are unrelated. Match the operation instead of one package type.
|
|
57
|
+
hook = _normalize_agent_selector_headers_async if iscoroutinefunction(client.send) else _normalize_agent_selector_headers
|
|
55
58
|
hooks = client.event_hooks.setdefault("request", [])
|
|
56
59
|
if hook not in hooks:
|
|
57
60
|
hooks.append(hook)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.7.2"
|