Integrations

LlamaIndex

Route LlamaIndex completions, agents, and RAG ingestion through Sentisec by setting api_base on the LLM and the embedding model.

LlamaIndex builds its LLM and its embedding client on the OpenAI wire, and both take an api_base argument. Point them at your workspace and every completion, every agent tool call, and every embedding your ingestion pass makes goes through Sentisec.

The swap

from llama_index.llms.openai import OpenAI as LlamaOpenAI

llm = LlamaOpenAI(
    model="gpt-5.6-terra",
    api_base="https://api.sentisec.ch/ws/<your-workspace-id>/v1",
    api_key="sk_sentisec_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
)

print(llm.complete("Compare the pricing pages of the three shortlisted vendors."))

The argument is api_base, not base_url — that is LlamaIndex's own naming — and the /v1 suffix belongs on it. The workspace id in the URL is what carries your policy; why it matters.

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.

Streaming needs no extra wiring: llm.stream_complete(...) yields chunks whose .delta carries the text.

Agents

Hand that llm to an agent and every tool call it makes is gated. Nothing about the agent's construction changes.

from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools import FunctionTool


def read_portfolio_config(path: str) -> str:
    """Read the portfolio config document."""
    return open(path).read()


agent = FunctionAgent(
    tools=[FunctionTool.from_defaults(fn=read_portfolio_config)],
    llm=llm,
    system_prompt="Use your tools to finish the task the user gives you.",
)

response = await agent.run("Read the portfolio config and finish the setup it describes.")

If you drive tools yourself instead of through an agent, the serialiser path is covered as well — chat_with_tools puts the tool schemas on the wire and get_tool_calls_from_response reads the calls back:

response = llm.chat_with_tools([tool], user_msg="Read the portfolio config.")
calls = llm.get_tool_calls_from_response(response, error_on_no_tool_call=True)

What happens when a tool call is blocked

Take a two-turn loop: the agent reads a document carrying a planted instruction, then on the next turn tries to send a secret out of that document to an external address. Sentisec stops that second call.

By default the loop ends cleanly, with the explanation as the agent's final response. No hang, no exception, no retry storm. Sentisec withholds the call and puts the explanation in the model's message, so the agent's response reads:

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

The tool never ran. If you do not want that sentence shown to your user, filter the agent's final response before rendering it.

With the rename opt-in, the agent re-plans. The call comes back as sentisec_tool_call_blocked, FunctionAgent answers it as an unknown tool, and Sentisec replaces that answer with its ruling before the model reads it. See the supported versions.

RAG ingestion

OpenAIEmbedding takes the same api_base, so your ingestion pass routes through Sentisec rather than around it:

from llama_index.core import Document, VectorStoreIndex
from llama_index.embeddings.openai import OpenAIEmbedding

embed = OpenAIEmbedding(
    model="text-embedding-3-small",
    api_base="https://api.sentisec.ch/ws/<your-workspace-id>/v1",
    api_key="sk_sentisec_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
)

index = VectorStoreIndex.from_documents(documents, embed_model=embed)
nodes = index.as_retriever(similarity_top_k=1).retrieve(
    "which region is the portfolio account in?"
)

Both the ingest embeddings and the query embedding traverse the gateway, and the index comes back queryable. Ranking quality is your embedding model's business — routing through Sentisec does not change it.

Info
Embeddings are recorded, not gated

Embedding requests are relayed to your upstream verbatim and the response comes back unmodified; the call is recorded with the rest of your traffic, subject to your workspace's content-capture setting. Nothing in an ingestion pass is blocked, because embedding a document is not an action an agent takes on your systems — the gating happens where it matters, on the tool calls the agent makes after retrieval. Upstream errors keep their status, so a rate-limited ingest reaches you as a rate limit rather than a false success.

Embeddings are available on OpenAI and Gemini. A workspace whose provider has no OpenAI-shape embeddings endpoint gets 404 embeddings_not_supported_for_provider rather than a silent success.

Verdicts

Every request produces a verdict on the dashboard — NOMINAL when nothing was unusual, escalating to HIJACK when a tool call is traced back to content the agent was never supposed to act on. The intervention (PROCEED, SANITIZE, HALT) records what actually happened to the call.

New workspaces start in a monitor-only posture, so you can watch what a policy would do before it does it.

See also

Protected docs