Moving from LLM Guard to Sunglasses
The LLM Guard repository is archived on GitHub. Its README says the project and its models are no longer maintained. Sunglasses is a different kind of tool. It matches known attack wording and shapes with rules that run on your machine. Text scanning needs no model download. Some LLM Guard scanners have a direct replacement here and some have none. This page says which, so you can plan the gap instead of finding it later.
What maps and what does not
LLM Guard is a set of scanners you pick one by one. Sunglasses is one engine with a rule set you can extend. Read the right column before you remove anything.
| PromptInjection | Replaced by the built in rules. LLM Guard asks a classifier model for a score. Sunglasses matches wording and shapes it has rules for and returns a decision with the rule ids that fired. A paraphrase no rule covers can pass, so keep your other defences. |
| Secrets | Replaced by the built in credential rules. The AWS documentation example key is refused with block at critical. Sunglasses refuses the text and does not redact it. |
| BanSubstrings, Regex, BanCompetitors | Replaced by your own rules passed as extra_patterns. See the section below. |
| TokenLimit | No token counting. max_scan_bytes caps how much text is scanned. A capped scan says so with inspection_complete set to False. |
| InvisibleText | No equivalent. Sunglasses never removes characters from your text. |
| Anonymize and Deanonymize | No equivalent. There is no personal data vault and nothing is rewritten. |
| Toxicity, Sentiment, Bias, BanTopics, Language, Gibberish, Code | No equivalent. These are classifier jobs and Sunglasses does not run classifier models on text. |
| Output scanners such as Relevance, FactualConsistency, NoRefusal and JSON | No equivalent. You can pass a model reply through the same scan to catch a credential in it, shown below. |
The call, before and after
This is the prompt check from the LLM Guard quickstart, shortened.
from llm_guard import scan_prompt
from llm_guard.input_scanners import PromptInjection, Secrets
scanners = [PromptInjection(), Secrets()]
sanitized_prompt, results_valid, results_score = scan_prompt(scanners, prompt)
if not all(results_valid.values()):
raise ValueError(f"refused, scores {results_score}")The same check in Sunglasses. Build the engine once and reuse it, because building it compiles the whole rule set.
from sunglasses.engine import SunglassesEngine
engine = SunglassesEngine()
def check(text, channel="message"):
result = engine.scan(text, channel=channel)
if not result.inspection_complete:
raise ValueError("not fully scanned, so not checked")
if result.decision in ("block", "quarantine"):
ids = [f["id"] for f in result.findings]
raise ValueError(f"refused ({result.severity}) by {ids}")
return textsanitized_promptNothing to replace it with. Sunglasses does not rewrite text, so you either pass the original on or refuse it.
results_validresult.decision is one of allow, allow_redacted, quarantine or block. High and critical findings block. Medium findings quarantine. Low findings return allow_redacted, which lets the text through unchanged.
results_scoreresult.severity and result.findings, where each finding carries the rule id, its name and the matched text. There is no probability, because a rule either matched or it did not.
Tell the engine where the text came from, for example message, file or web_content. A misspelled channel raises ValueError instead of scanning against no rules.
Your own banned words and patterns
BanSubstrings and Regex become rules you pass to the engine. Keep words and regexes in separate rules. In a rule that has both, the keyword only nominates the rule and the regex decides.
from sunglasses.engine import SunglassesEngine
banned_words = {
"id": "ACME-001",
"name": "Internal project names",
"category": "custom",
"severity": "medium",
"channel": ["message", "web_content"],
"keywords": ["project bluebird", "acme rival corp"],
"description": "Names we do not send to a model.",
}
ticket_ids = {
"id": "ACME-002",
"name": "Internal ticket ids",
"category": "custom",
"severity": "high",
"channel": ["message", "web_content"],
"regex": [r"\bACME-\d{4,}\b"],
"description": "Ticket ids from our tracker.",
}
engine = SunglassesEngine(extra_patterns=[banned_words, ticket_ids])
for text in ["Ask Acme Rival Corp for a quote", "See ACME-20931 for details", "hello"]:
result = engine.scan(text, channel="message")
print(result.decision, [f["id"] for f in result.findings])quarantine ['ACME-001'] block ['ACME-002'] allow []
Keywords ignore case. A keyword must not run on into a longer word, so project bluebird does not match project bluebirds, but the start of a match is not checked. Very common single words on the engine's own denylist, such as token, secret and config, never fire as a keyword on their own. Put a word like that in a regex rule instead. Regexes are compiled case insensitive.
Checking a model reply
There is no separate output API. Pass the reply through the same check and the credential rules still apply, which covers the most common output leak. Relevance, refusals and factual checks have no replacement here.
reply = "Sure. The key is AKIAIOSFODNN7EXAMPLE" check(reply) # ValueError, refused (critical)
Outside Python
If you ran the LLM Guard API as a service, the closest pieces here are the command line and the MCP server. There is no HTTP server.
pip install sunglasses sunglasses scan "text to check" --channel web_content --json # exit 1 on a finding, 0 when clean python -m sunglasses.mcp # MCP server over stdio
What this page does not claim
Nothing here measures how well either tool detects anything. This is a map of calls and features.
It is taken from the LLM Guard quickstart and was not run for this page. Every Sunglasses example was.
Rules catch what they describe. Read what Sunglasses catches and what it does not before you rely on it.