Getting Started¶
Installation¶
With uv:
Prerequisites¶
You need a Strahl API key. Set it as an environment variable:
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¶
- Core concepts — understand labels, tool flow policies, and tag sets before writing your own
- Registering tools — decorator, imperative, and provider schema forms
- Reading results — the full typed response model
- Guides — common patterns and setups