Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-guard/src/lexigram/ai/guard/input/topic.py: 44%
27 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
1"""Topic restrictor input guard.
3Blocks or warns on inputs that touch topics declared off-limits for
4a deployment. Matching uses exact keyword/phrase lookup and optional
5word-boundary enforcement — no external API calls.
7For more accurate topic classification use an LLM-based classifier as
8a second guard in the pipeline after this fast pre-filter.
9"""
11from __future__ import annotations
13import re
14from typing import TYPE_CHECKING, Any
16from lexigram.ai.guard.input.base import AbstractInputGuard
17from lexigram.ai.guard.pipeline.result import GuardCheckResult
18from lexigram.contracts.ai.guards import GuardResultProtocol
19from lexigram.result import Ok, Result
21if TYPE_CHECKING:
22 from lexigram.contracts.ai.exceptions import GuardError
25def _build_pattern(topic: str, whole_word: bool) -> re.Pattern[str]:
26 """Compile a regex pattern for a topic phrase.
28 Args:
29 topic: The topic phrase to match.
30 whole_word: Whether to require word boundaries around the match.
32 Returns:
33 Compiled regex pattern.
34 """
35 escaped = re.escape(topic)
36 if whole_word:
37 return re.compile(r"\b" + escaped + r"\b", re.I)
38 return re.compile(escaped, re.I)
41class TopicRestrictor(AbstractInputGuard):
42 """Input guard that blocks prohibited topics.
44 Args:
45 restricted_topics: List of topic keywords/phrases to prohibit.
46 action: Action when a restricted topic is found —
47 ``"block"`` (default) or ``"warn"``.
48 whole_word: If ``True`` (default), only match whole words /
49 phrase boundaries to reduce false positives.
51 Example::
53 guard = TopicRestrictor(
54 restricted_topics=["weapons", "explosives", "self-harm"],
55 action="block",
56 )
57 result = await guard.check(user_message)
58 """
60 def __init__(
61 self,
62 restricted_topics: list[str],
63 action: str = "block",
64 whole_word: bool = True,
65 ) -> None:
66 """Initialise the topic restrictor.
68 Args:
69 restricted_topics: List of prohibited topic keywords/phrases.
70 action: ``"block"`` or ``"warn"``.
71 whole_word: Enforce word boundaries on matches.
72 """
73 super().__init__(action=action)
74 self._patterns: list[tuple[str, re.Pattern[str]]] = [
75 (topic, _build_pattern(topic, whole_word)) for topic in restricted_topics
76 ]
78 async def check(
79 self,
80 content: str,
81 *,
82 messages: list[Any] | None = None,
83 metadata: dict[str, Any] | None = None,
84 ) -> Result[GuardResultProtocol, GuardError]:
85 """Check content against restricted topics.
87 Args:
88 content: Input text to evaluate.
89 messages: Unused — present for protocol compatibility.
90 metadata: Optional metadata.
92 Returns:
93 PASS if no restricted topics found; BLOCK or WARN otherwise.
94 """
95 matched_topics: list[str] = []
96 for topic, pattern in self._patterns:
97 if pattern.search(content):
98 matched_topics.append(topic)
100 if not matched_topics:
101 return Ok(GuardCheckResult.allow(self.name))
103 if self._action == "warn":
104 return Ok(
105 GuardCheckResult.warn(
106 self.name,
107 reason=f"Restricted topics mentioned: {', '.join(matched_topics)}",
108 matched_topics=matched_topics,
109 )
110 )
112 return Ok(
113 GuardCheckResult.block(
114 self.name,
115 reason=f"Input mentions restricted topics: {', '.join(matched_topics)}",
116 matched_topics=matched_topics,
117 )
118 )
121__all__ = ["TopicRestrictor"]