Skip to content

Getting Started

Installation

# pip installation
pip install strahl

With uv:

# uv installation
uv add strahl

Prerequisites

You need a Strahl API key. Set it as an environment variable:

# set env var
export STRAHL_API_KEY=your_api_key_here

Your first analysis

This example shows the minimal setup needed to guard a sensitive tool against prompt injection.

import os
import strahl
from strahl import Label

# 1. Configure the SDK
strahl.set_api_key(os.environ["STRAHL_API_KEY"])

# 2. Label each message role
strahl.set_role_labels({
    "user": Label(source={"user"}, visibility={"user"}),
    "assistant": Label(source={"assistant"}, visibility={"user"}),
})

# 3. Declare a flow policy for each sensitive tool
@strahl.tool(
    requires=Label(source={"user"}, visibility={"user"}),
    produces=Label(source={"email-tool"}, visibility={"user"}),
)
def send_email(to: str, subject: str, body: str) -> str:
    ...

# 4. After the assistant responds, analyze before executing
messages = [
    {"role": "user", "content": "Send a note to alice@example.com."},
    {
        "role": "assistant",
        "tool_calls": [{
            "id": "call_1",
            "function": {
                "name": "send_email",
                "arguments": '{"to": "alice@example.com", "subject": "Note", "body": "Hello"}',
            },
        }],
    },
]

analysis = strahl.analyze(messages)
analysis.raise_if_denied()  # raises StrahlDenied if any tool call is blocked

# Safe to execute
send_email(to="alice@example.com", subject="Note", body="Hello")

Per-parameter requirements

The example above uses one requires label for the whole tool call, so every argument inherits the same requirement. Use params when arguments need explicit labels instead of inheriting the tool-level requirement:

@strahl.tool(
    requires=Label(source={"assistant"}, visibility={"user"}),
    params=dict(
        to=Label(source={"user"}, visibility={"user"}),
        subject=Label(source={"user"}, visibility={"user"}),
        body=Label(source={"user"}, visibility={"user"}),
    ),
    produces=Label(source={"email-tool"}, visibility={"user"}),
)
def send_email(to: str, subject: str, body: str) -> str:
    ...

When params is provided, every declared top-level parameter must be listed. If params is omitted, each argument inherits requires.

Where to call analyze()

Call analyze() after every assistant response that contains tool calls, before executing those tools.

user message
    → model response (may contain tool calls)
        → strahl.analyze()   ← guard point
            → execute tools (only if permitted)
                → tool results back to model

If the final assistant message contains no tool calls, analyze() returns a permitted result immediately without calling the Strahl API.

Next steps