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.
Files changed (124) hide show
  1. {deepintshield-2.7.1/src/deepintshield.egg-info → deepintshield-2.7.2}/PKG-INFO +70 -6
  2. {deepintshield-2.7.1 → deepintshield-2.7.2}/README.md +69 -5
  3. {deepintshield-2.7.1 → deepintshield-2.7.2}/pyproject.toml +1 -1
  4. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/_prompt_cache.py +16 -7
  5. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/anthropic.py +19 -3
  6. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/client.py +3 -1
  7. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/anthropic.py +24 -10
  8. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/rag.py +63 -18
  9. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/transport.py +6 -3
  10. deepintshield-2.7.2/src/deepintshield/version.py +1 -0
  11. {deepintshield-2.7.1 → deepintshield-2.7.2/src/deepintshield.egg-info}/PKG-INFO +70 -6
  12. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield.egg-info/SOURCES.txt +5 -0
  13. deepintshield-2.7.2/tests/test_anthropic_transport_compat.py +103 -0
  14. deepintshield-2.7.2/tests/test_genai_contracts.py +227 -0
  15. deepintshield-2.7.2/tests/test_model_compatibility.py +188 -0
  16. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_native_provider_headers.py +11 -6
  17. deepintshield-2.7.2/tests/test_platform_agentic_workflows.py +84 -0
  18. deepintshield-2.7.2/tests/test_platform_rag_workflows.py +177 -0
  19. deepintshield-2.7.1/src/deepintshield/version.py +0 -1
  20. {deepintshield-2.7.1 → deepintshield-2.7.2}/LICENSE +0 -0
  21. {deepintshield-2.7.1 → deepintshield-2.7.2}/NOTICE +0 -0
  22. {deepintshield-2.7.1 → deepintshield-2.7.2}/setup.cfg +0 -0
  23. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/__init__.py +0 -0
  24. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/_gemini_cache.py +0 -0
  25. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agent.py +0 -0
  26. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/__init__.py +0 -0
  27. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/actions.py +0 -0
  28. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/__init__.py +0 -0
  29. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/base.py +0 -0
  30. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/entra.py +0 -0
  31. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/oidc.py +0 -0
  32. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/credentials/zeroid.py +0 -0
  33. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/decorators.py +0 -0
  34. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/dpop.py +0 -0
  35. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/enforcement.py +0 -0
  36. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/engine.py +0 -0
  37. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/errors.py +0 -0
  38. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/execution.py +0 -0
  39. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/gate.py +0 -0
  40. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/identity.py +0 -0
  41. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/__init__.py +0 -0
  42. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/_common.py +0 -0
  43. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/autogen.py +0 -0
  44. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/crewai.py +0 -0
  45. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/google_adk.py +0 -0
  46. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/hermes.py +0 -0
  47. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/langchain.py +0 -0
  48. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/langgraph.py +0 -0
  49. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/litellm.py +0 -0
  50. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/llamaindex.py +0 -0
  51. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/openai_agents.py +0 -0
  52. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/openclaw.py +0 -0
  53. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/pydanticai.py +0 -0
  54. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/strands.py +0 -0
  55. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/integrations/temporal.py +0 -0
  56. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/manifest.py +0 -0
  57. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/obligations.py +0 -0
  58. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/registry.py +0 -0
  59. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/surface.py +0 -0
  60. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/agentic/types.py +0 -0
  61. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/client.py +0 -0
  62. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/config.py +0 -0
  63. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/errors.py +0 -0
  64. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/__init__.py +0 -0
  65. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/autogen.py +0 -0
  66. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/crewai.py +0 -0
  67. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/langgraph.py +0 -0
  68. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/llamaindex.py +0 -0
  69. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/openai_agents.py +0 -0
  70. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/frameworks/pydanticai.py +0 -0
  71. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/__init__.py +0 -0
  72. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/_errors.py +0 -0
  73. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/_native.py +0 -0
  74. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/__init__.py +0 -0
  75. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/_security.py +0 -0
  76. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/langchain.py +0 -0
  77. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/adapters/openai.py +0 -0
  78. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/mcp/tool.py +0 -0
  79. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/__init__.py +0 -0
  80. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/bedrock.py +0 -0
  81. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/genai.py +0 -0
  82. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/langchain.py +0 -0
  83. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/langgraph.py +0 -0
  84. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/litellm.py +0 -0
  85. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/openai.py +0 -0
  86. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/providers/pydanticai.py +0 -0
  87. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/streaming.py +0 -0
  88. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield/types.py +0 -0
  89. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield.egg-info/dependency_links.txt +0 -0
  90. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield.egg-info/requires.txt +0 -0
  91. {deepintshield-2.7.1 → deepintshield-2.7.2}/src/deepintshield.egg-info/top_level.txt +0 -0
  92. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agent.py +0 -0
  93. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic.py +0 -0
  94. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_blueprint_contract.py +0 -0
  95. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_callback_inventory.py +0 -0
  96. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_credentials.py +0 -0
  97. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_error_contract.py +0 -0
  98. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_execution_lifecycle.py +0 -0
  99. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_fail_closed_integrations.py +0 -0
  100. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_framework_parity.py +0 -0
  101. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_import_lifecycle.py +0 -0
  102. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_langchain.py +0 -0
  103. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_new_decide.py +0 -0
  104. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_new_discovery.py +0 -0
  105. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_new_enforcement.py +0 -0
  106. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_agentic_workload_headers.py +0 -0
  107. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_chat_streaming.py +0 -0
  108. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_client.py +0 -0
  109. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_config.py +0 -0
  110. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_error_catalog.py +0 -0
  111. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_error_surfaces.py +0 -0
  112. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_errors.py +0 -0
  113. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_gemini_cache.py +0 -0
  114. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_mcp_native_errors.py +0 -0
  115. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_mcp_native_session.py +0 -0
  116. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_mcp_preferred_api.py +0 -0
  117. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_obligation_contract.py +0 -0
  118. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_prompt_cache.py +0 -0
  119. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_providers.py +0 -0
  120. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_rag.py +0 -0
  121. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_rag_guard.py +0 -0
  122. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_rag_redaction.py +0 -0
  123. {deepintshield-2.7.1 → deepintshield-2.7.2}/tests/test_transport.py +0 -0
  124. {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.1
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-3-sonnet-20240229",
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-1.5-flash",
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 automatically participates in the gateway's two cost-reduction layers
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** - every chat client returned by `shield.openai()`,
819
- `shield.anthropic()`, etc. ships an `httpx` request hook that injects
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-3-sonnet-20240229",
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-1.5-flash",
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 automatically participates in the gateway's two cost-reduction layers
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** - every chat client returned by `shield.openai()`,
726
- `shield.anthropic()`, etc. ships an `httpx` request hook that injects
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.1"
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
- try:
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(tools: Iterable[Tool]) -> list[dict[str, Any]]:
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
- sanitized = _ANTHROPIC_NAME_RE.sub("_", tool.qualified_name)[:64]
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, build_http_client
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
- http_client = build_http_client(PROVIDER_ANTHROPIC, timeout=shield.timeout)
39
- _install_agent_selector_header_hook(http_client)
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
- return anthropic.Anthropic(
42
- base_url=kwargs.pop("base_url", base_url),
43
- api_key=kwargs.pop("api_key", shield.api_key()),
44
- default_headers=connection_headers(shield, extra=kwargs.pop("default_headers", None)),
45
- http_client=http_client,
46
- **kwargs,
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 any retriever exposing one of ``invoke`` / ``retrieve`` /
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
- method_name = next(
178
- (m for m in _RETRIEVE_METHODS if callable(getattr(retriever, m, None))), None
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
- @functools.wraps(original)
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
- wrapped._deepintshield_wrapped = True # type: ignore[attr-defined]
206
- try:
207
- setattr(retriever, method_name, wrapped)
208
- except Exception: # frozen pydantic model
209
- object.__setattr__(retriever, method_name, wrapped)
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 typing import TYPE_CHECKING, Mapping, Optional
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: httpx.Client | httpx.AsyncClient) -> None:
54
- hook = _normalize_agent_selector_headers_async if isinstance(client, httpx.AsyncClient) else _normalize_agent_selector_headers
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"