feat(bot): LLM gateway with GigaChat and offline mock providers
CI / ruff · pytest (pull_request) Successful in 23s
CI / ruff · pytest (pull_request) Successful in 23s
Single entry point app/llm/gateway.py: extract_item, extract_checkin, risk_classify, phrase, summarize_for_doctor. Two-level design: LLMProvider (transport, GigaChat) and LLMTasks (PromptedTasks over a provider, or rule-based MockTasks). Shared policy in the gateway: assert_scrubbed backstop, confidence threshold 0.5, risk failure -> NONE, forbidden-output filter with config/texts/phrases.yaml fallbacks. Refs #7 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
"""Defence in depth for guardrail #1 ("only scrubbed text goes to the LLM").
|
||||
|
||||
`ScrubbedText` is a `NewType`, i.e. a plain `str` at runtime, so the type checker is the primary guard.
|
||||
`assert_scrubbed` is a cheap runtime backstop right before text leaves the process: if an obvious
|
||||
identifier (phone, e-mail, SNILS) is still present, the call to the provider is refused. It is NOT a
|
||||
replacement for `app.core.privacy.scrub()` — names, addresses etc. are not detected here.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from app.core.types import ScrubbedText
|
||||
|
||||
_LEAK_PATTERNS: tuple[re.Pattern[str], ...] = (
|
||||
# Russian phone numbers: +7 / 8 followed by 10 digits with optional separators.
|
||||
re.compile(r"(?:\+7|(?<!\d)8)[\s\-()]*\d{3}[\s\-()]*\d{3}[\s\-]*\d{2}[\s\-]*\d{2}(?!\d)"),
|
||||
re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+"),
|
||||
# SNILS: 123-456-789 01
|
||||
re.compile(r"(?<!\d)\d{3}-\d{3}-\d{3}[\s-]\d{2}(?!\d)"),
|
||||
)
|
||||
|
||||
|
||||
class UnscrubbedTextError(ValueError):
|
||||
"""Raised when text that looks unscrubbed is about to be sent to the LLM."""
|
||||
|
||||
|
||||
def assert_scrubbed(text: str) -> ScrubbedText:
|
||||
for pattern in _LEAK_PATTERNS:
|
||||
if pattern.search(text):
|
||||
# Never include the text itself in the message: it would end up in logs.
|
||||
raise UnscrubbedTextError("identifier pattern found in text bound for LLM")
|
||||
return ScrubbedText(text)
|
||||
Reference in New Issue
Block a user