llm-siren
Python library for SIREN, lightweight guard models that read an LLM's internal representations instead of decoding a verdict. Two guards ship in this package:
SirenGuardscores text for harmful content (LLM Safety From Within: Detecting Harmful Content with Internal Representations, ACL 2026 Main).AgentSirenGuardscores a tool-calling agent trajectory (Detecting Harmful Agent Trajectories from LLM Internal Representations).
Both run on a small frozen backbone, return a continuous score in [0, 1], and require no fine-tuning. Trained heads are released as Hugging Face artifacts; this package is the runtime.
Install
pip install llm-siren
Harmful content
import torch
from siren_guard import SirenGuard
guard = SirenGuard.from_pretrained(
"UofTCSSLab/SIREN-Qwen3-4B",
device="cuda",
dtype=torch.bfloat16,
)
# Score a user prompt
r = guard.score("How can I make a pipe bomb at home?")
print(r.score, r.is_harmful)
# Score a (prompt, response) pair
r = guard.score(
prompt="How can I make a pipe bomb at home?",
response="I can't help with that. Building explosive devices is illegal.",
)
# Batch scoring
results = guard.score_batch([
"What is the capital of France?",
"Describe in detail how to commit insurance fraud.",
])
# Streaming: feed the growing assistant text after each generation chunk
prefix = ""
for chunk in stream_from_deployed_llm(prompt):
prefix += chunk
if guard.score_streaming(prefix, threshold=0.5).is_harmful:
break
Agent trajectories
A trajectory is unsafe in two ways: harmful content, expressed directly, and unsafe tool use, which turns on whether an action is consistent with the interaction that produced it. AgentSirenGuard reads both, from the tools block and the turns as the backbone sees them.
import torch
from siren_guard import AgentSirenGuard
guard = AgentSirenGuard.from_pretrained(
"difanjiao/Agent-SIREN-Qwen3-4B",
device="cuda",
dtype=torch.bfloat16,
)
r = guard.score_trajectory(messages=messages, tools=tools)
print(r.score, r.is_harmful)
# During execution: score after each new turn, before running the next call
if guard.score_streaming(messages=messages_so_far, tools=tools).is_harmful:
abort()
messages and tools take the tool-calling shape the agent already holds: messages carry role, content and tool_calls, and tools is the list of JSON tool schemas the agent was given. Pass the schemas. Tool-description injections live inside them, so a trajectory rendered without them hides the attack from the readout.
This guard does not accept a bare string, and rejects one rather than scoring it: it reads a rendered trajectory, turn boundaries and schemas included. Score free text with SirenGuard.
The head reads an intermediate state, so the blocks above it cannot affect the score and are dropped at load time. That is exact, not an approximation, and makes the agent guard cheaper than the content one. Pass early_exit=False to keep the full depth.
Available artifacts
| Artifact | Task | Backbone | Head |
|---|---|---|---|
| UofTCSSLab/SIREN-Qwen3-0.6B | content | Qwen3-0.6B | 12.3M |
| UofTCSSLab/SIREN-Llama-3.2-1B | content | Llama-3.2-1B | 5.4M |
| UofTCSSLab/SIREN-Qwen3-4B | content | Qwen3-4B | 14.0M |
| UofTCSSLab/SIREN-Llama-3.1-8B | content | Llama-3.1-8B | 56.0M |
| difanjiao/Agent-SIREN-Qwen3-4B | trajectory | Qwen3-4B-Instruct-2507 | 2,561 |
load_guard(repo_id) reads the artifact and returns whichever guard it declares. Each class refuses the other's artifact rather than scoring a context it was not fitted on.
API
SirenGuard.from_pretrained(repo_id_or_path, device=None, dtype=torch.bfloat16, cache_dir=None)
score(text=None, *, prompt=None, response=None, threshold=None) -> ScoreResult
score_batch(texts, threshold=None) -> list[ScoreResult]
score_streaming(response_so_far, threshold=None) -> ScoreResult
AgentSirenGuard.from_pretrained(repo_id_or_path, device=None, dtype=torch.bfloat16, cache_dir=None, early_exit=True)
score_trajectory(trajectory=None, *, messages=None, tools=None, threshold=None) -> ScoreResult
score_batch(trajectories, threshold=None) -> list[ScoreResult]
score_streaming(messages=None, tools=None, trajectory=None, threshold=None) -> ScoreResult
Each call returns ScoreResult(score: float, is_harmful: bool, threshold: float). The default threshold is 0.5, the boundary used during training; tune it to your deployment's policy.
License
Apache-2.0.
Citation
@article{jiao2026llm,
title={LLM Safety From Within: Detecting Harmful Content with Internal Representations},
author={Jiao, Difan and Liu, Yilun and Yuan, Ye and Tang, Zhenwei and Du, Linfeng and Wu, Haolun and Anderson, Ashton},
journal={arXiv preprint arXiv:2604.18519},
year={2026}
}
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llm_siren-0.2.0.tar.gz.
File metadata
- Download URL: llm_siren-0.2.0.tar.gz
- Upload date:
- Size: 18.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6ba4c4f9766387aa4a1af7123c588774dd986b3f96a10b8bcc10d811e2cfa34
|
|
| MD5 |
0e7a74d4189f967fda4eba54b7e3e53c
|
|
| BLAKE2b-256 |
349da75c968a0122e66a345cbeac1ee49aed26af894216fc4ef720e4caaac8e9
|
File details
Details for the file llm_siren-0.2.0-py3-none-any.whl.
File metadata
- Download URL: llm_siren-0.2.0-py3-none-any.whl
- Upload date:
- Size: 20.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f7361a30518913cce985476285e537c14de715503fadf21cd01bed39a9eaf67
|
|
| MD5 |
b15a098a7800418d3bdc31ac4aca8873
|
|
| BLAKE2b-256 |
a345ff502f31de8f83e0199ba24b21f94ffebc324d445bb1058ddd5b317513f2
|