Skip to main content

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:

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}
}

Metadata

Release files for llm-siren 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for llm-siren 0.2.0
File Size Uploaded Provenance Actions
llm_siren-0.2.0.tar.gz 18.2 kB Without provenance Details for llm_siren-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for llm-siren 0.2.0
File Interpreter ABI Platform Actions
llm_siren-0.2.0-py3-none-any.whl Python 3 none any Details for llm_siren-0.2.0-py3-none-any.whl

Total release size: 39.0 kB

Release files / llm_siren-0.2.0.tar.gz

Download URL llm_siren-0.2.0.tar.gz
Size 18.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f6ba4c4f9766387aa4a1af7123c588774dd986b3f96a10b8bcc10d811e2cfa34
BLAKE2b-256 checksum
How to use checksums
349da75c968a0122e66a345cbeac1ee49aed26af894216fc4ef720e4caaac8e9
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / llm_siren-0.2.0-py3-none-any.whl

Download URL llm_siren-0.2.0-py3-none-any.whl
Size 20.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7f7361a30518913cce985476285e537c14de715503fadf21cd01bed39a9eaf67
BLAKE2b-256 checksum
How to use checksums
a345ff502f31de8f83e0199ba24b21f94ffebc324d445bb1058ddd5b317513f2
Upload date
Uploaded using Trusted Publishing?
What is 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}

Security for llm-siren 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page