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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

llm_siren-0.2.0.tar.gz (18.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

llm_siren-0.2.0-py3-none-any.whl (20.8 kB view details)

Uploaded Python 3

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

Hashes for llm_siren-0.2.0.tar.gz
Algorithm Hash digest
SHA256 f6ba4c4f9766387aa4a1af7123c588774dd986b3f96a10b8bcc10d811e2cfa34
MD5 0e7a74d4189f967fda4eba54b7e3e53c
BLAKE2b-256 349da75c968a0122e66a345cbeac1ee49aed26af894216fc4ef720e4caaac8e9

See more details on using hashes here.

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

Hashes for llm_siren-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7f7361a30518913cce985476285e537c14de715503fadf21cd01bed39a9eaf67
MD5 b15a098a7800418d3bdc31ac4aca8873
BLAKE2b-256 a345ff502f31de8f83e0199ba24b21f94ffebc324d445bb1058ddd5b317513f2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page