Integrations

OpenAI SDK

Route the official OpenAI Python and Node SDKs, and the Python Agents SDK, through Sentisec by changing one argument — and what your code sees when a tool call is blocked.

The official OpenAI SDKs accept a workspace base URL and a Sentisec API key. Sentisec inspects supported model responses before your client receives their tool calls. Calls your application makes outside this model route are outside that boundary.

The swap

import os
from openai import OpenAI
client = OpenAI(base_url="https://api.sentisec.ch/ws/<your-workspace-id>/v1", api_key=os.environ["SENTISEC_API_KEY"])
import OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://api.sentisec.ch/ws/<your-workspace-id>/v1', apiKey: process.env.SENTISEC_API_KEY! });

The API key is your Sentisec key, not your OpenAI key — Sentisec holds the upstream credential, so your provider key never ships in your application config. The /v1 suffix belongs on this base URL, and the workspace id in it is what carries your policy; why it matters.

Both SDK entry points are inspected

The OpenAI SDK can reach two different endpoints. Tool calls are evaluated on both.

SDK callEndpointWhat Sentisec does
client.chat.completions.create(...)/v1/chat/completionsInspected. Every tool call is evaluated against your workspace policy before your code sees it, on both the streaming and non-streaming paths. A blocked call is withheld and explained — see below.
client.responses.create(...)/v1/responsesInspected. The same evaluation, on both paths. A blocked call is withheld and explained in an assistant message. From 0.8 it uses workspace provider selection, supports the measured Bedrock mantle Responses lane, and records available per-request usage — see Responses and Embeddings.

Choose the API shape your client and selected model support. Both routes record request metadata; token counts depend on the upstream usage data. For Bedrock, use the model/API pairs in the capability table.

What happens when a call is blocked

Your code gets no exception and no HTTP error: the request succeeds with 200. By default the blocked call is removed from tool_calls, and the message content carries the explanation instead:

[Sentisec blocked tool_use: <why the call was stopped>]

Other calls in the same message that only read are released. A call that changes something is not sent when another call in the message is held or refused, and it carries its own short explanation (see calls proposed together). finish_reason stays tool_calls while an allowed call remains and is stop otherwise. The same holds on a stream. On Responses, the blocked item becomes an assistant message carrying the same text. Your loop needs no special case: with no call left to run, it treats the message as the final answer.

If your workspace turns on the rename opt-in (how), the call instead stays in tool_calls, keeping its id, with its name and arguments replaced:

{"id":"call_...","type":"function","function":{"name":"sentisec_tool_call_blocked","arguments":"{\"error\":\"tool_call_blocked\",\"reason\":\"…\"}"}}

The OpenAI SDK has no tool loop of its own, so that call reaches your dispatch code. Return its reason as the tool result and let the model choose another action. Never run the original call.

OpenAI Agents SDK

The Python Agents SDK (openai-agents) takes the same client. Give its model a client pointed at your workspace:

import asyncio, os
from agents import Agent, AsyncOpenAI, OpenAIResponsesModel, RunConfig, Runner

client = AsyncOpenAI(base_url="https://api.sentisec.ch/ws/<your-workspace-id>/v1", api_key=os.environ["SENTISEC_API_KEY"])
agent = Agent(name="assistant", instructions="Help the user.", model=OpenAIResponsesModel(model="<model-id>", openai_client=client))

result = asyncio.run(Runner.run(agent, "Summarise my open tickets.", run_config=RunConfig(tool_not_found_behavior="return_error_to_model")))
print(result.final_output)

Add your tools to the Agent as usual. If your model is served on Chat Completions, use OpenAIChatCompletionsModel with the same openai_client.

What Runner does with a blocked call:

  • Default (withhold). The blocked call becomes assistant text. When no call is left, Runner takes the answer as the final output, so the run ends with the explanation.
  • With rename. The call comes back named sentisec_tool_call_blocked, a tool your agent does not have. By default the SDK raises ModelBehaviorError on it and the run ends. With RunConfig(tool_not_found_behavior="return_error_to_model"), as above, the SDK answers the unknown tool with an error and the run goes on. Sentisec replaces that answer with its ruling before the model reads it. The model learns why the call was stopped and can re-plan in the same run. For a refused destination, it can take the recipient from your own records and propose the call again.

The recipe uses openai-agents 0.23.1 with OpenAIResponsesModel, on Responses without streaming. Re-planning with rename on Chat Completions (OpenAIChatCompletionsModel) and on streamed runs is not covered yet. If your agent depends on it, tell us. Turn on rename as described in opt in: rename.

Streaming

stream = client.chat.completions.create(model=model, messages=messages, tools=tools, stream=True, stream_options={"include_usage": True})
for chunk in stream:
    handle_chunk(chunk)

Streaming is the same endpoint with stream=True — not a separate route — and it carries your workspace's policy exactly as the non-streaming lane does.

handle_chunk is your application's handler. Usage chunks can have an empty choices array; check it before reading a choice. Without stream_options.include_usage, streamed Chat records carry no token counts. An allowed Bedrock mantle Chat stream ends as mantle sends it, without [DONE]; clients must accept a clean EOF. Responses has its own event shape.

From 0.8 all streamed calls omit X-Sentisec-Verdict. The final verdict rides the trailing : sentisec-verdict comment; SDK chunk iterators may hide SSE comments. See streaming.

Verdicts

Evaluated requests produce verdicts you can see on the dashboard — NOMINAL when nothing was unusual, escalating through to HIJACK when a tool call is traced back to content the agent was never supposed to act on. The intervention (PROCEED, SANITIZE, HALT) is what actually happened to the call.

In shadow mode every call passes, including calls Sentisec could not read; the verdict records what enforcing would have done. Check the workspace protection mode before relying on enforcement.

See also

Protected docs