Skip to content

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

deepintshield

Unified Python SDK for DeepIntShield - one import, any provider, any agent framework.

deepintshield lets you keep writing idiomatic OpenAI / Anthropic / Bedrock / Google GenAI code and native agent-framework code (LangGraph, CrewAI, OpenAI Agents SDK, LlamaIndex, AutoGen, PydanticAI, Temporal, AWS Strands, Google ADK, Hermes Agent, OpenClaw) while automatically routing traffic through the DeepIntShield gateway for guardrails, RAG filtering, agentic tool control, and agent identity.

You pass a virtual key and base URL, plus a stable agent_name when the agent differs from the SDK default (deepintshield-agent). The Agentic registry owns the canonical subject, optional identity-provider selection, policy attributes, and many-to-many Virtual Key associations; the selected provider configuration supplies tenant and scope details. The SDK discovers that profile automatically, so no identity GUIDs appear in your code.

Protection comes in two layers:

  • Transparent - point any framework's native client at the gateway (base_url + a one-line header injector). Chat, embeddings, input/output and RAG-prompt guardrails, observability and identity all run server-side with zero changes to your agent code. Works for Python frameworks and any OpenAI-compatible platform (n8n, Flowise, Dify, raw HTTP).
  • Enforcement - supported Python agent frameworks are guarded automatically at their ordinary compile/run boundary; explicit native hooks cover durable runtimes. This adds local tool gating (DENY / MASK / human approval) and chunk-level RAG filtering - the parts that physically can't be done at the wire because the tool runs in your process.

Traffic defaults to https://app.deepintshield.com. Override the gateway with base_url= (or DEEPINTSHIELD_BASE_URL). Set DEEPINTSHIELD_VIRTUAL_KEY and you're done.


Install

pip install deepintshield                       # core (chat, RAG, agentic)
pip install 'deepintshield[openai]'             # + OpenAI SDK
pip install 'deepintshield[anthropic]'
pip install 'deepintshield[anthropic-mcp]'       # Anthropic's maintained MCP helpers
pip install 'deepintshield[bedrock]'
pip install 'deepintshield[genai]'
pip install 'deepintshield[langchain]'
pip install 'deepintshield[langchain-mcp]'       # maintained LangChain MCP adapter
pip install 'deepintshield[langgraph]'
pip install 'deepintshield[crewai]'              # CrewAI bind + tool gating
pip install 'deepintshield[openai-agents]'       # OpenAI Agents SDK
pip install 'deepintshield[llamaindex]'
pip install 'deepintshield[autogen]'             # AutoGen / AG2
pip install 'deepintshield[litellm]'
pip install 'deepintshield[pydanticai]'
pip install 'deepintshield[temporal]'            # Temporal durable-agent runtime
pip install 'deepintshield[strands]'             # AWS Strands integration
pip install 'deepintshield[google-adk]'          # Google ADK integration
pip install 'deepintshield[azure]'               # azure-identity for Entra agent identity
pip install 'deepintshield[mcp]'                # official MCP Python client
pip install 'deepintshield[all]'                # everything

Configure

export DEEPINTSHIELD_VIRTUAL_KEY="<virtual-key>"
# Optional - point at a self-hosted or staging gateway.
export DEEPINTSHIELD_BASE_URL="https://gateway.example.com"
# Stable Agentic registry key and acting principal.
export DEEPINTSHIELD_AGENT_NAME="my-agent"
export DEEPINTSHIELD_REQUESTER="user@example.com"

Or pass explicitly:

from deepintshield import DeepintShield

shield = DeepintShield(virtual_key="<virtual-key>")

# Self-hosted / staging override (default: https://app.deepintshield.com)
shield = DeepintShield(
    virtual_key="<virtual-key>",
    base_url="https://gateway.example.com",
)

Chat

Gateway provider breadth

The SDK's native client facades remain OpenAI, Anthropic, classic Bedrock, and Google GenAI. They do not grow one method per gateway provider. OpenAI-compatible clients can instead select any of the gateway's 29 built-in provider identities with a provider-qualified model. DeepSeek, Amazon Bedrock Mantle, Sarvam AI, and Wafer are now available through that routing surface; operation support differs by provider and model.

Configuration and capability matrices live in the canonical gateway guides: DeepSeek, Amazon Bedrock Mantle, Sarvam AI, and Wafer.

Core client (including streaming)

from deepintshield import DeepintShield

shield = DeepintShield.from_env()
response = shield.chat(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hello"}],
)

# Streaming is lazy and yields each decoded SSE data object. The terminal
# [DONE] event is consumed by the SDK and is not yielded.
with shield.chat(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hello"}],
    stream=True,
) as stream:
    for chunk in stream:
        print(chunk)

Consume the stream fully, use it as a context manager, or call stream.close() when stopping early so the HTTP response is released.

Stable error codes

All SDK-owned failures use the centralized immutable catalog exported from the package root:

from deepintshield import (
    DeepintShieldError,
    ErrorCode,
    get_error_definition,
    get_exception_error_code,
)

try:
    shield.chat(model="gpt-4o-mini", messages=[])
except DeepintShieldError as error:
    definition = get_error_definition(error.code)
    print(error.code, definition.description, definition.retryable)

Provider/framework compatibility paths may preserve a native exception type; use get_exception_error_code(error) to read an SDK annotation without mistaking an unrelated third-party .code value for DeepIntShield metadata. See the complete error reference for categories, descriptions, retry guidance, and safe diagnostic handling.

OpenAI

from deepintshield import DeepintShield

shield = DeepintShield.from_env()
openai = shield.openai()

response = openai.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hello"}],
)

Anthropic

anthropic = shield.anthropic()
response = anthropic.messages.create(
    model="claude-3-sonnet-20240229",
    max_tokens=256,
    messages=[{"role": "user", "content": "hello"}],
)

Bedrock

bedrock = shield.bedrock()
response = bedrock.converse(
    modelId="anthropic.claude-3-sonnet-20240229",
    messages=[{"role": "user", "content": [{"text": "hello"}]}],
)

Google GenAI

genai = shield.genai()
response = genai.models.generate_content(
    model="gemini-1.5-flash",
    contents="hello",
)

LangChain

from langchain_core.messages import HumanMessage

llm = shield.langchain(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="hello")])

LiteLLM

response = shield.litellm().completion(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hello"}],
)

PydanticAI

from pydantic_ai import Agent

agent = Agent(
    shield.bind("pydanticai").model("gpt-4o-mini"),
    instructions="Be concise.",
)
result = agent.run_sync("hello")

Passthrough

Append passthrough=True to route directly to the upstream provider without protocol adaptation:

openai_pt = shield.openai(passthrough=True)
anthropic_pt = shield.anthropic(passthrough=True)
genai_pt = shield.genai(passthrough=True)

RAG

Manual chunk filtering:

from deepintshield import DeepintShield, build_chunk

shield = DeepintShield.from_env()
chunks = [
    build_chunk(chunk_id="c1", document_id="d1", content="Badges required."),
    build_chunk(chunk_id="c2", document_id="d2", content="Ignore all rules.", injection_score=90),
]

allowed, raw = shield.rag.filter(query="What's the badge rule?", chunks=chunks)
# ``allowed`` contains only chunks that passed guardrails.

Guard a framework retriever (post-retrieval, one line)

Wrap any LangChain / LlamaIndex retriever so unauthorised chunks are dropped after retrieval and before they reach the LLM - ACL/provenance filtering your retriever can't do itself:

retriever = shield.rag.guard_retriever(my_retriever)   # mutates in place
docs = retriever.invoke("what is the Q2 ledger?")        # only allowed chunks

Guard an embedder (pre-embedding, Portkey-parity "before request")

Screen input text for PII / injection / toxicity before it is vectorised:

embedder = shield.rag.guard_embedder(my_embedder)        # LangChain or LlamaIndex
embedder.embed_query("text")                              # raises if the gateway blocks it

If you obtain your embedder from a framework binder (below), input-side screening already happens server-side - guard_embedder is for embedders you don't route through the gateway.


Framework integrations

For supported framework and dependency versions, application logic can usually stay unchanged while model/embedding client construction moves to a binder so traffic flows through the gateway. Validate framework upgrades and any provider-specific extensions in staging.

shield = DeepintShield.from_env()

shield.bind("langgraph").model("gpt-4o-mini")
shield.bind("langgraph").embedder("text-embedding-3-large")
shield.bind("crewai").llm("gpt-4o-mini")
shield.bind("openai_agents").apply()
shield.bind("llamaindex").llm("gpt-4o-mini")
shield.bind("autogen").model_client("gpt-4o-mini")
shield.bind("pydanticai").model("gpt-4o-mini")

These binders only route native model or embedding traffic through the gateway. They are not required for automatic Agentic tool enforcement.

Framework-agnostic primitives - wire any SDK, in any language-compatible client, by hand:

base_url, headers = shield.connection()          # → ("…/openai", gateway headers)
client = shield.http_client()                    # httpx.Client pre-wired to the gateway
headers = shield.create_headers(provider="anthropic")   # Portkey-style header injector

Universal: anything that speaks OpenAI-compatible HTTP gets transparent protection with zero code - set the Base URL to shield.endpoint("openai") and the key to your VK. That covers n8n, Flowise, Dify and raw HTTP, not just Python.

Gateway-only protocol operations

Stored Responses lifecycle/compaction, named cached-content management, Realtime WebSocket/WebRTC, Mistral OCR, and the durable webhook outbox are HTTP gateway surfaces. This Python package does not claim first-party convenience wrappers for them; use a compatible native SDK or raw HTTP and follow the canonical protocol and webhook guides. Delegated MCP execution is supported by the shield.mcp request-header path described below; its OAuth lifecycle remains a server-owned control-plane operation.

Agentic tool enforcement

Constructing DeepintShield.from_env() arms supported frameworks at their native compile, run, and tool boundaries. LangGraph, CrewAI, LlamaIndex, AutoGen, PydanticAI, the OpenAI Agents SDK, LiteLLM, Temporal, AWS Strands, and Google ADK remain third-party code; do not add a DeepIntShield framework wrapper.

from langgraph.graph import StateGraph, START, END
from deepintshield import DeepintShield

shield = DeepintShield.from_env()

def crm_read(s):  ...
def admin_grant(s): ...

g = StateGraph(State)
g.add_node("read_step", crm_read)
g.add_node("admin_step", admin_grant)
...
app = g.compile()

app.invoke({...})

For a new DEEPINTSHIELD_AGENT_NAME, the first native execution captures the agent and topology, then stops with agent_registration_pending. Open the registration in Agentic → Work Queue, then use the single Review form there or in Assets → Agents to confirm identity, classify the observed tools, grant an exact tool/action boundary, and select Approve & activate. Run the unchanged application again with the same agent name. If the capture endpoint could not be reached or did not persist the registration, the stable code remains agent_not_registered instead; check gateway connectivity and Assets → Discovery.

Governance exceptions expose a stable code as their message, not UI copy. Common codes include require_approval, guardrail_denied, and governance_configuration_error. The interface owns explanations and remediation under Work Queue, Policy & Access → Action approvals, and Activity → Decisions; ordinary applications do not catch these outcomes just to format them. require_approval returns immediately while the server retains its durable decision ID. Review and resume/retry semantics belong to the interface, not an SDK polling loop.

DEEPINTSHIELD_REQUESTER supplies the acting user or service account. Framework objects retain their concrete client binding when multiple clients exist. Use shield.bind("framework") only when native model or embedding traffic must also route through the gateway.

Security follows the implementation, not the label. Each node/tool is governed by its function name (crm_read), not the node label (read_step), and the decision is bound to a fingerprint of the function's source - so editing the body requires fresh attestation and policies target crm_read. Source-bearing reports require complete bounded coverage; missing or truncated executable source stops as blueprint_coverage_incomplete. A remote MCP tool is exempt only after the server matches it to a configured connection in the authenticated workspace. The required static-v2 scan is deterministic; optional model-v2 evidence may add findings but cannot erase static findings or approve code.

Trust boundary: these client guards are cooperative defense-in-depth. A determined process can un-patch them or call a tool's raw function, so the gateway (MCP / LLM in the call path) remains the authoritative boundary.

New or changed code performs one bounded synchronous scan acknowledgement; unchanged approved code avoids repeated scanner/model calls. This is not a literal zero-latency or perfect-detection claim. Configure optional model review in Agentic → Policy & Access → Runtime policy → Blueprint protection by searching the workspace's protected Virtual Keys and selecting an allowed model. The paged picker never loads the key secret and fails closed when the key or workspace scope cannot be verified.

Legacy govern() and guard() instrumentation helpers remain idempotent for existing applications. New integrations should use the framework's native compile(), invoke(), run(), or kickoff() boundary.

Durable and plugin runtimes (Temporal, Strands, Google ADK, Hermes, OpenClaw)

Temporal, Strands and Google ADK are armed automatically when the client is created or the framework is imported later. Use each framework normally; older explicit integration objects remain compatibility-only.

# Temporal — the SDK injects its activity interceptor automatically:
worker = Worker(client, task_queue="q", activities=[...])

# AWS Strands — the final ToolExecutor boundary is automatic:
agent = Agent(model=..., tools=[...])

# Google ADK — normal, live and threaded tool dispatch are automatic:
runner = InMemoryRunner(agent=agent)

# Hermes Agent — the host plugin only has to construct the client. Import
# watching then arms model_tools.handle_function_call for every tool.
shield = DeepintShield.from_env()

Hermes still needs a small host plugin because it runs its own plugin loader.

OpenClaw (Node/TS runtime) integrates in two layers: the LLM leg is zero-code config, generated from Python —

cfg = shield.agentic.openclaw_config(models=[{"id": "gpt-4o-mini", ...}])
# merge cfg into openclaw.json; set agents.defaults.model.primary = "deepintshield/gpt-4o-mini"

— and in-process tool governance ships as a thin TypeScript plugin calling the same REST /decide API (see examples/openclaw/).

Framework Required application change Governed boundary
Temporal none after client construction injected ActivityInboundInterceptor.execute_activity
AWS Strands none after client construction final ToolExecutor dispatch
Google ADK none after client construction final normal/live/threaded tool dispatch
Hermes Agent load the DeepIntShield host plugin central model_tools.handle_function_call dispatcher
OpenClaw config + TS plugin models.providers + before_tool_call

Explicit decorator / decision probe

These are advanced framework-independent APIs, not required by a supported framework's normal execution path:

@shield.agentic.tool("db.write")
def write_ledger(row: dict) -> dict:
    return db.execute("INSERT INTO ledger …", row)

decision = shield.agentic.decide(tool="db.write", args={"amount": 12})

Optional risk signals (OWASP Agentic gap operands)

For threats only your app can observe, pass an ABAC signal on a decide() and evaluate it in your configured OPA/Cedar context policy:

Signal Threat Policy operand
memory_integrity T1 Memory Poisoning memory_integrity eq true
hallucination_risk T5 Cascading Hallucination hallucination_risk gte 0.8
goal_drift T7 Misaligned & Deceptive goal_drift eq true
comm_integrity T12 Agent Comm Poisoning comm_integrity eq true
delegation_depth T14 Human Attacks on MAS delegation_depth gt 4 (server-computed)
from deepintshield import ContextBag, DelegationContext

shield.agentic.decide(DelegationContext(
    tool="ledger.post", virtual_key=shield.virtual_key,
    context=ContextBag(hallucination_risk=0.91, goal_drift=True),
))

Agent identity (Registry-managed)

On first run, the interface captures an unknown agent in quarantine under Agentic → Work Queue and Assets → Agents. Select Review to confirm its owner, identity provider, locked reporting Virtual Key, observed tools, named actions, and exact access boundary in one form. After activation, administrators can use Identity & credentials for later governance changes. The SDK normalizes agent_name into X-Agent-Subject: agent:<registry-key> and auto-discovers the selected profile (GET /api/agentic-new/credential-info). The selector is only a lookup hint: the gateway verifies that the profile is enabled and associated with the authenticated key. A caller may omit it only when that key has one active associated agent.

After validation, the SDK selects the profile's credential (Entra Agent ID FIC / ZeroID RFC 8693 / generic OIDC) and attaches a fresh X-Agent-Token on every decision. On Azure compute the Managed Identity is detected automatically - no GUIDs, authority, or scopes in your code.

shield = DeepintShield.from_env()
info = shield.agentic.credential_info     # ops visibility into the selected profile
print(info.provider_type, info.tenant_id, info.agent_configured)

Guardrail stages (input / output / tool)

Programmatic guardrail evaluation when you want the result in hand rather than transparent enforcement:

@shield.agent.tool(action_class="write")
def write_file(path: str, content: str) -> None: ...

shield.agent.check_input("user message")
shield.agent.evaluate_tool(name="read_file", args={"path": "/tmp"}, action_class="read")
shield.agent.check_output("model reply")

Supported framework workflows do not add guard nodes or wrappers. Compile and run the native graph after constructing the client:

from langgraph.graph import END, START, StateGraph

shield = DeepintShield.from_env()

graph = StateGraph(AgentState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tools_node)
graph.add_edge(START, "agent")
graph.add_edge("agent", "tools")
graph.add_edge("tools", END)
app = graph.compile()
app.invoke(initial_state)

Multimodal guardrails (transparent)

Image generation, image edits, audio (TTS / transcription), video, embedding and rerank requests are guarded at the gateway - no SDK changes and no extra code. Keep using the native provider SDKs through DeepIntShield; when the operator enables GUARDRAILS_MULTIMODAL, the gateway evaluates the text these requests already carry (image/TTS/video prompts, transcripts) and the binary artifacts themselves, blocking or flagging per your policies.

client = shield.openai()

# Guarded automatically - the image prompt is evaluated before generation.
img = client.images.generate(model="gpt-image-1", prompt="a serene mountain lake")

# A blocked prompt surfaces as the provider SDK's normal HTTP error.
client.audio.speech.create(model="tts-1", voice="alloy", input="<disallowed text>")

For an explicit verdict (rather than transparent enforcement), evaluate_guardrail returns a GuardrailResult; result.mode reports whether the verdict was enforcing (sync) or observe-only (shadow).


MCP

DeepIntShield now keeps only the gateway-specific boundary: it supplies the governed /mcp URL and headers, opens an official mcp.ClientSession, and turns failed canonical tool results into stable SDK errors. The official MCP package owns transport, protocol types, discovery, and calls; maintained framework adapters own their tool conversion.

pip install 'deepintshield[mcp]'

The 2.x line temporarily uses mcp>=1.29,<2 because langchain-mcp-adapters 0.3.2 requires MCP Python SDK 1.x. An MCP 2.x upgrade is planned after the supported framework ecosystem converges.

Official MCP session

import asyncio

from deepintshield import DeepintShield, DeepintShieldError
from mcp.types import TextContent

shield = DeepintShield.from_env()


async def main() -> None:
    try:
        # The yielded object is an initialized official mcp.ClientSession.
        async with shield.mcp.connect() as session:
            listing = await session.list_tools()
            print([tool.name for tool in listing.tools])

            result = await session.call_tool(
                "DeepWiki-ask_question",
                arguments={
                    "repoName": "facebook/react",
                    "question": "What is Suspense?",
                },
            )
            # connect() translates any failed tool result into a coded
            # DeepintShieldError before call_tool() returns.
            for part in result.content:
                if isinstance(part, TextContent):
                    print(part.text)
    except DeepintShieldError as exc:
        if exc.code == "mcp_tool_approval_required":
            print("The MCP action is waiting for approval.")
        elif exc.code == "mcp_tool_authorization_denied":
            print("The MCP action was denied by policy.")
        elif exc.code == "mcp_tool_authorization_unavailable":
            print("MCP authorization is temporarily unavailable.")
        else:
            print(f"DeepIntShield error [{exc.code}]: {exc.description}")


asyncio.run(main())

For a third-party MCP framework, use the connection primitive:

url, headers = shield.mcp.connection(
    identity=True,
    extra_headers={"X-MCP-Subject-Token": caller_access_token},
)

Pass that URL and header mapping to its Streamable HTTP adapter. Keep the headers private: they contain the Virtual Key and may contain request-scoped identity credentials. OAuth 2.1 discovery, PKCE, registration, refresh, revocation, resource binding, and workspace authorization remain server-owned.

connect() checks every tool result automatically. When an external adapter creates its own session and exposes a raw official CallToolResult, invoke shield.mcp.raise_for_result(result) in its result interceptor before the adapter places that result in model context.

Canonical GAF outcomes use one exception type and one simple string field: DeepintShieldError.code is mcp_tool_authorization_denied, mcp_tool_approval_required, or mcp_tool_authorization_unavailable.

The old call/list_tools, custom Tool/MCPResult, and OpenAI/Anthropic/LangChain conversion-loop helpers remain deprecated 2.x compatibility shims. Their removal is planned for SDK 3.0; new code should use the official session or a maintained third-party adapter.


Cost Optimization

The SDK automatically participates in the gateway's two cost-reduction layers (both controlled by workspace switches under Cost Optimization):

  • Provider prompt caching - every chat client returned by shield.openai(), shield.anthropic(), etc. ships an httpx request hook that injects Anthropic cache_control markers and an OpenAI prompt_cache_key so the provider reuses KV state for the static prompt prefix. Eligible cached tokens use the provider's current cached-input rate; verify model-specific pricing and cache requirements with the provider.
  • Gemini context caching - opt in with shield.genai_cached() (drop-in for shield.genai()). The wrapper manages the cachedContents resource lifecycle behind the scenes; the first call with a new static prefix runs normally and the next call within the TTL window reuses the cache.
  • Semantic caching - runs on the gateway; short-circuits requests whose embeddings match a previous response within the configured similarity threshold. The SDK doesn't need any code change to benefit; results flow back through the normal API.

Per-request cache overrides

Workspace settings are the default, but any individual call can override them by passing one of the following headers. Useful when one job needs a different TTL, a stricter threshold, or wants to bypass the cache entirely (evals, audits, debugging).

Header Effect
x-deepintshield-cache-ttl Override the semantic cache TTL for this request (e.g. 30s, 5m, 3600).
x-deepintshield-cache-threshold Override the similarity threshold for this request (0.01.0).
x-deepintshield-cache-type Force direct (hash-only) or semantic (similarity search) for this request.
x-deepintshield-cache-no-store Set to true to read from the cache but skip writing the new response.
x-deepintshield-cache-key Provide an explicit cache key for direct hash matching.

Set them via the native provider SDK's extra_headers (or equivalent):

shield.openai().chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={
        "x-deepintshield-cache-ttl": "30s",
        "x-deepintshield-cache-threshold": "0.9",
    },
)

For Anthropic, use the extra_headers parameter on messages.create(...); for Google GenAI, set them on HttpOptions(headers=...) when constructing the client.


More examples

See examples/ for runnable per-provider chat, RAG, agent, and MCP scripts.

About

Unified Python SDK for routing chat, RAG, and agentic traffic through DeepintShield. Automatically routing every request through the DeepintShield gateway for policy enforcement, guardrails, RAG filtering, and agentic tool control along with Semantic Caching and LLM Load Balancers.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages