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 call | Endpoint | What Sentisec does |
|---|---|---|
client.chat.completions.create(...) | /v1/chat/completions | Inspected. 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/responses | Inspected. 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,Runnertakes the answer as the final output, so the run ends with the explanation. - With
rename. The call comes back namedsentisec_tool_call_blocked, a tool your agent does not have. By default the SDK raisesModelBehaviorErroron it and the run ends. WithRunConfig(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
Calls waiting for approval
What your application receives when a tool call waits for a person's approval, how to wait for the decision, and how to re-send so the approved call runs once.
Anthropic SDK
Route the official Anthropic Python and Node SDKs through Sentisec by changing one argument — and what your code sees when a tool call is blocked.