From 53c9c707f1b5d14e0a5c1f533c1599f91fddda7b Mon Sep 17 00:00:00 2001 From: Akshith Ambekar Date: Sat, 13 Sep 2025 13:16:01 -0400 Subject: [PATCH 1/2] mongodb connection --- tueri/input_scanners/anonymize.py | 2 +- tueri_api/app/app.py | 16 +- tueri_api/app/config.py | 4 +- tueri_api/app/scanner.py | 54 ++- tueri_api/config/scanners.json | 31 ++ tueri_api/config/scanners.yml | 534 +++++++++++++++--------------- tueri_api/docker-compose.yml | 6 +- tueri_api/entrypoint.sh | 16 +- tueri_api/pyproject.toml | 3 +- 9 files changed, 372 insertions(+), 294 deletions(-) create mode 100644 tueri_api/config/scanners.json diff --git a/tueri/input_scanners/anonymize.py b/tueri/input_scanners/anonymize.py index b007032d..84be73cb 100644 --- a/tueri/input_scanners/anonymize.py +++ b/tueri/input_scanners/anonymize.py @@ -186,7 +186,7 @@ def scan(self, prompt: str) -> tuple[str, bool, float]: self._vault.append((placeholder, original_value)) return ( self._preamble + sanitized_prompt, - False, + True, calculate_risk_score(risk_score, self._threshold), ) diff --git a/tueri_api/app/app.py b/tueri_api/app/app.py index f6183825..accfa9b7 100644 --- a/tueri_api/app/app.py +++ b/tueri_api/app/app.py @@ -124,15 +124,15 @@ async def check_auth(credentials: credentials_type) -> bool: def _get_input_scanners_function(config: Config, vault: Vault) -> Callable: scanners = [] if not config.app.lazy_load: - LOGGER.debug("Loading input scanners") - scanners = get_input_scanners(config.input_scanners, vault) + LOGGER.debug("Loading input scanners from MongoDB") + scanners = get_input_scanners([], vault) def get_cached_scanners() -> List[InputScanner]: nonlocal scanners if not scanners and config.app.lazy_load: - LOGGER.debug("Lazy loading input scanners") - scanners = get_input_scanners(config.input_scanners, vault) + LOGGER.debug("Lazy loading input scanners from MongoDB") + scanners = get_input_scanners([], vault) return scanners @@ -142,15 +142,15 @@ def get_cached_scanners() -> List[InputScanner]: def _get_output_scanners_function(config: Config, vault: Vault) -> Callable: scanners = [] if not config.app.lazy_load: - LOGGER.debug("Loading output scanners") - scanners = get_output_scanners(config.output_scanners, vault) + LOGGER.debug("Loading output scanners from MongoDB") + scanners = get_output_scanners([], vault) def get_cached_scanners() -> List[OutputScanner]: nonlocal scanners if not scanners and config.app.lazy_load: - LOGGER.debug("Lazy loading output scanners") - scanners = get_output_scanners(config.output_scanners, vault) + LOGGER.debug("Lazy loading output scanners from MongoDB") + scanners = get_output_scanners([], vault) return scanners diff --git a/tueri_api/app/config.py b/tueri_api/app/config.py index 1cef7a52..876de101 100644 --- a/tueri_api/app/config.py +++ b/tueri_api/app/config.py @@ -50,8 +50,8 @@ class ScannerConfig(BaseModel): class Config(BaseModel): - input_scanners: List[ScannerConfig] = Field() - output_scanners: List[ScannerConfig] = Field() + input_scanners: List[ScannerConfig] = Field(default_factory=list) + output_scanners: List[ScannerConfig] = Field(default_factory=list) rate_limit: RateLimitConfig = Field(default_factory=RateLimitConfig) auth: Optional[AuthConfig] = Field(default=None) app: AppConfig = Field(default_factory=AppConfig) diff --git a/tueri_api/app/scanner.py b/tueri_api/app/scanner.py index 50bc592e..6d3e3472 100644 --- a/tueri_api/app/scanner.py +++ b/tueri_api/app/scanner.py @@ -1,10 +1,13 @@ import asyncio import time +import os +import logging from typing import Dict, List, Optional import structlog import torch from opentelemetry import metrics +from pymongo import MongoClient from tueri import input_scanners, output_scanners from tueri.input_scanners.ban_competitors import MODEL_V1 as BAN_COMPETITORS_MODEL @@ -27,6 +30,22 @@ LOGGER = structlog.getLogger(__name__) +MONGO_URI = os.getenv("MONGO_URI", "mongodb://root:example@localhost:27017/") +MONGO_DB, MONGO_COLLECTION = os.getenv("MONGO_DB", "ChatApp"), os.getenv("MONGO_COLLECTION", "TueriScanners") + +# Suppress MongoDB heartbeat logs +logging.getLogger("pymongo.topology").setLevel(logging.WARNING) +logging.getLogger("pymongo.serverSelection").setLevel(logging.WARNING) + +try: + mongo_client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000, heartbeatFrequencyMS=60000) + db = mongo_client[MONGO_DB] + scanners_collection = db[MONGO_COLLECTION] + mongo_client.admin.command("ping") +except Exception as e: + LOGGER.error("Error connecting to MongoDB", error=str(e)) + raise + meter = metrics.get_meter_provider().get_meter(__name__) scanners_valid_counter = meter.create_counter( name="scanners.valid", @@ -34,16 +53,24 @@ description="measures the number of valid scanners", ) +def _fetch_scanners_from_mongo(scanner_type: str) -> List[ScannerConfig]: + coll = scanners_collection.find({"type": scanner_type}) + scanners: List[ScannerConfig] = [] + for scanner in coll: + scanners.append(ScannerConfig( + type=scanner.get("id"), + params=scanner.get("params", {}))) + return scanners -def get_input_scanners(scanners: List[ScannerConfig], vault: Vault) -> List[InputScanner]: +def get_input_scanners(scanners: List[ScannerConfig], vault: Vault) -> List[InputScanner]: """ - Load input scanners from the configuration file. + Load input scanners from MongoDB. """ - - input_scanners_loaded = [] - for scanner in scanners: + input_scanners_config = _fetch_scanners_from_mongo("input") + loaded_input_scanners: List[InputScanner] = [] + for scanner in input_scanners_config: LOGGER.debug("Loading input scanner", scanner=scanner.type, **get_resource_utilization()) - input_scanners_loaded.append( + loaded_input_scanners.append( _get_input_scanner( scanner.type, scanner.params, @@ -51,17 +78,18 @@ def get_input_scanners(scanners: List[ScannerConfig], vault: Vault) -> List[Inpu ) ) - return input_scanners_loaded + return loaded_input_scanners def get_output_scanners(scanners: List[ScannerConfig], vault: Vault) -> List[OutputScanner]: """ - Load output scanners from the configuration file. + Load output scanners from MongoDB. """ - output_scanners_loaded = [] - for scanner in scanners: + output_scanners_config = _fetch_scanners_from_mongo("output") + loaded_output_scanners: List[OutputScanner] = [] + for scanner in output_scanners_config: LOGGER.debug("Loading output scanner", scanner=scanner.type, **get_resource_utilization()) - output_scanners_loaded.append( + loaded_output_scanners.append( _get_output_scanner( scanner.type, scanner.params, @@ -69,10 +97,10 @@ def get_output_scanners(scanners: List[ScannerConfig], vault: Vault) -> List[Out ) ) - return output_scanners_loaded + return loaded_output_scanners -def _configure_model(model: Model, scanner_config: Optional[Dict]): +def _configure_model(model: Model, scanner_config: Optional[Dict]): if scanner_config is None: scanner_config = {} diff --git a/tueri_api/config/scanners.json b/tueri_api/config/scanners.json new file mode 100644 index 00000000..c528926d --- /dev/null +++ b/tueri_api/config/scanners.json @@ -0,0 +1,31 @@ +[ + { "type": "input", "id": "Anonymize", "params": { "threshold": 0.50, "language": "en", "preamble": "" }, "order": 1 }, + { "type": "input", "id": "BanTopics", "params": { "topics": ["violence and physical harm to individuals", "hate speech and discriminatory content", "illegal activities and criminal activity", "pornography, erotica, adult and sexual content", "terrorism and indiscriminate Weapons", "self-harm and suicide", "medical diagnosis without professional oversight", "legal advice without professional qualification", "tax evasion and financial fraud methods", "insurance fraud and illegal practices", "cybersecurity exploits and hacking techniques", "software piracy and copyright infringement", "political opinions and partisan commentary", "religious discussions and theological debates", "personal relationship advice and dating guidance", "substance abuse and drug-related discussions", "gambling advice and betting strategies", "harassment and workplace bullying", "conspiracy theories and misinformation spreading", "defamatory statements about individuals or organizations", "spam and deceptive content generation", "gender identity and sexual orientation"], "threshold": 0.6 }, "order": 2 }, + { "type": "input", "id": "MaskCode", "params": { "threshold": 0.97, "mask_token": "[CODE]", "language_threshold": 0.5 }, "order": 3 }, + { "type": "input", "id": "InvisibleText", "params": {}, "order": 4 }, + { "type": "input", "id": "PromptInjection", "params": { "threshold": 0.95, "match_type": "truncate_head_tail" }, "order": 5 }, + { "type": "input", "id": "Regex", "params": { "patterns": ["Bearer [A-Za-z0-9-._~+/]+"], "is_blocked": true, "match_type": "search", "redact": true }, "order": 6 }, + { "type": "input", "id": "Secrets", "params": { "redact_mode": "all" }, "order": 7 }, + { "type": "input", "id": "BanCompetitors", "params": { "competitors": ["OpenAI", "Google", "Microsoft"], "threshold": 0.5, "redact": true }, "order": 8 }, + { "type": "input", "id": "BanSubstrings", "params": { "substrings": ["bypass", "jailbreak", "ignore instructions"], "match_type": "str", "case_sensitive": false, "redact": false, "contains_all": false }, "order": 9 }, + { "type": "input", "id": "Language", "params": { "valid_languages": ["en"], "threshold": 0.6, "match_type": "full" }, "order": 10 }, + { "type": "input", "id": "Sentiment", "params": { "threshold": -0.3, "lexicon": "vader_lexicon" }, "order": 11 }, + { "type": "input", "id": "TokenLimit", "params": { "limit": 4096, "encoding_name": "cl100k_base" }, "order": 12 }, + { "type": "output", "id": "BanTopics", "params": { "topics": ["violence and physical harm to individuals", "hate speech and discriminatory content", "illegal activities and criminal activity", "pornography, erotica, adult and sexual content", "terrorism and indiscriminate Weapons", "self-harm and suicide", "medical diagnosis without professional oversight", "legal advice without professional qualification", "tax evasion and financial fraud methods", "insurance fraud and illegal practices", "cybersecurity exploits and hacking techniques", "software piracy and copyright infringement", "political opinions and partisan commentary", "religious discussions and theological debates", "personal relationship advice and dating guidance", "substance abuse and drug-related discussions", "gambling advice and betting strategies", "harassment and workplace bullying", "conspiracy theories and misinformation spreading", "defamatory statements about individuals or organizations", "spam and deceptive content generation", "gender identity and sexual orientation"], "threshold": 0.6 }, "order": 13 }, + { "type": "output", "id": "Bias", "params": { "threshold": 0.97, "match_type": "full" }, "order": 14 }, + { "type": "output", "id": "Deanonymize", "params": { "matching_strategy": "exact" }, "order": 15 }, + { "type": "output", "id": "FactualConsistency", "params": { "minimum_score": 0.5 }, "order": 16 }, + { "type": "output", "id": "BadURL", "params": { "threshold": 0.95 }, "order": 17 }, + { "type": "output", "id": "Regex", "params": { "patterns": ["Bearer [A-Za-z0-9-._~+/]+"], "is_blocked": true, "match_type": "search", "redact": true }, "order": 18 }, + { "type": "output", "id": "Sensitive", "params": { "redact": false, "threshold": 0.50, "language": "en" }, "order": 19 }, + { "type": "output", "id": "BanCompetitors", "params": { "competitors": ["OpenAI", "Google", "Microsoft"], "threshold": 0.5, "redact": true }, "order": 20 }, + { "type": "output", "id": "BanSubstrings", "params": { "substrings": ["confidential", "internal"], "match_type": "str", "case_sensitive": false, "redact": true, "contains_all": false }, "order": 21 }, + { "type": "output", "id": "JSON", "params": { "required_elements": 0, "repair": true }, "order": 22 }, + { "type": "output", "id": "Language", "params": { "valid_languages": ["en"], "threshold": 0.6, "match_type": "full" }, "order": 23 }, + { "type": "output", "id": "LanguageSame", "params": { "threshold": 0.6 }, "order": 24 }, + { "type": "output", "id": "MaskCode", "params": { "threshold": 0.5, "mask_token": "[CODE]", "language_threshold": 0.5 }, "order": 25 }, + { "type": "output", "id": "NoRefusal", "params": { "threshold": 0.75, "match_type": "full" }, "order": 26 }, + { "type": "output", "id": "NoRefusalLight", "params": {}, "order": 27 }, + { "type": "output", "id": "Relevance", "params": { "threshold": 0.5 }, "order": 28 }, + { "type": "output", "id": "Sentiment", "params": { "threshold": -0.3, "lexicon": "vader_lexicon" }, "order": 29 } +] \ No newline at end of file diff --git a/tueri_api/config/scanners.yml b/tueri_api/config/scanners.yml index c938113c..82de2eb7 100644 --- a/tueri_api/config/scanners.yml +++ b/tueri_api/config/scanners.yml @@ -1,6 +1,6 @@ app: name: ${APP_NAME:Tueri API} - log_level: ${LOG_LEVEL:INFO} + log_level: ${LOG_LEVEL:DEBUG} log_json: ${LOG_JSON:true} scan_fail_fast: ${SCAN_FAIL_FAST:false} scan_prompt_timeout: ${SCAN_PROMPT_TIMEOUT:30} @@ -24,210 +24,54 @@ metrics: endpoint: ${METRICS_ENDPOINT:} # Example: "/v1/metrics" # Scanners are applied in the order they are listed here. -input_scanners: - - type: Anonymize - params: - threshold: 0.50 # Higher value = less aggressive PII removal - # Removed use_faker and model_path, no longer needed - - type: BanTopics - params: - topics: [ - # Core Safety & Legal Compliance - "violence and physical harm to individuals", - "hate speech and discriminatory content", - "illegal activities and criminal activity", - "pornography, erotica, adult and sexual content", - "terrorism and indiscriminate Weapons", - "self-harm and suicide", - - # Professional Liability - "medical diagnosis without professional oversight", - "legal advice without professional qualification", - "tax evasion and financial fraud methods", - "insurance fraud and illegal practices", - - # Information Security - "cybersecurity exploits and hacking techniques", - "software piracy and copyright infringement", - - # Workplace Appropriateness - "political opinions and partisan commentary", - "religious discussions and theological debates", - "personal relationship advice and dating guidance", - "substance abuse and drug-related discussions", - "gambling advice and betting strategies", - "harassment and workplace bullying", - - # Content Quality & Misinformation - "conspiracy theories and misinformation spreading", - "defamatory statements about individuals or organizations", - "spam and deceptive content generation", - "gender identity and sexual orientation" - ] - threshold: 0.6 - model_max_length: 256 - - type: MaskCode - params: - threshold: 0.97 - model_max_length: 256 - - type: InvisibleText - params: { } - - type: PromptInjection - params: - threshold: 0.95 - match_type: truncate_head_tail - model_max_length: 256 - - type: Regex - params: - patterns: ["Bearer [A-Za-z0-9-._~+/]+"] - is_blocked: true - match_type: search - redact: true - - type: Secrets - params: - redact_mode: "all" -# - type: Toxicity -# params: -# threshold: 0.95 -# model_max_length: 256 - -#------------ - -output_scanners: - - type: BanTopics - params: - topics: [ - # Core Safety & Legal Compliance - "violence and physical harm to individuals", - "hate speech and discriminatory content", - "illegal activities and criminal activity", - "pornography, erotica, adult and sexual content", - "terrorism and indiscriminate Weapons", - "self-harm and suicide", - - # Professional Liability - "medical diagnosis without professional oversight", - "legal advice without professional qualification", - "tax evasion and financial fraud methods", - "insurance fraud and illegal practices", - - # Information Security - "cybersecurity exploits and hacking techniques", - "software piracy and copyright infringement", - - # Workplace Appropriateness - "political opinions and partisan commentary", - "religious discussions and theological debates", - "personal relationship advice and dating guidance", - "substance abuse and drug-related discussions", - "gambling advice and betting strategies", - "harassment and workplace bullying", - - # Content Quality & Misinformation - "conspiracy theories and misinformation spreading", - "defamatory statements about individuals or organizations", - "spam and deceptive content generation", - "gender identity and sexual orientation" - ] - threshold: 0.6 - - type: Bias - params: - threshold: 0.97 - model_max_length: 256 - - type: Deanonymize - params: - matching_strategy: "exact" - - type: FactualConsistency - params: - minimum_score: 0.5 -# - type: JSON -# params: -# required_elements: 0 -# repair: true - - type: BadURL - params: - threshold: 0.95 - - type: Regex - params: - patterns: ["Bearer [A-Za-z0-9-._~+/]+"] - is_blocked: true - match_type: search - redact: true - - type: Sensitive - params: - redact: false - threshold: 0.50 # Higher value = less aggressive PII removal -# - type: Toxicity -# params: -# threshold: 0.6 -# model_max_length: 256 - -# app: -# name: ${APP_NAME:Tueri API} -# log_level: ${LOG_LEVEL:INFO} -# log_json: ${LOG_JSON:true} -# scan_fail_fast: ${SCAN_FAIL_FAST:true} -# scan_prompt_timeout: ${SCAN_PROMPT_TIMEOUT:30} -# scan_output_timeout: ${SCAN_OUTPUT_TIMEOUT:30} -# lazy_load: ${LAZY_LOAD:true} - -# rate_limit: -# enabled: ${RATE_LIMIT_ENABLED:false} -# limit: ${RATE_LIMIT_LIMIT:100/minute} - -# auth: -# type: http_bearer -# token: ${AUTH_TOKEN:} - -# tracing: -# exporter: ${TRACING_EXPORTER:console} -# endpoint: ${TRACING_OTEL_ENDPOINT:} # Example: "/v1/traces" - -# metrics: -# exporter: ${METRICS_TYPE:prometheus} -# endpoint: ${METRICS_ENDPOINT:} # Example: "/v1/metrics" - -# # Scanners are applied in the order they are listed here. # input_scanners: # - type: Anonymize # params: -# use_faker: false -# threshold: 0.9 -# # model_path: "./distilbert_finetuned_ai4privacy_v2" -# # - type: BanCode -# # params: -# # threshold: 0.97 -# # model_max_length: 256 -# # - type: BanCompetitors -# # params: -# # competitors: ["facebook"] -# # threshold: 0.75 -# # - type: BanSubstrings -# # params: -# # substrings: ["test"] -# # match_type: "word" -# # case_sensitive: false -# # redact: false -# # contains_all: false +# threshold: 0.50 # Higher value = less aggressive PII removal +# # Removed use_faker and model_path, no longer needed # - type: BanTopics # params: -# topics: ["violence"] -# threshold: 0.95 +# topics: [ +# # Core Safety & Legal Compliance +# "violence and physical harm to individuals", +# "hate speech and discriminatory content", +# "illegal activities and criminal activity", +# "pornography, erotica, adult and sexual content", +# "terrorism and indiscriminate Weapons", +# "self-harm and suicide", + +# # Professional Liability +# "medical diagnosis without professional oversight", +# "legal advice without professional qualification", +# "tax evasion and financial fraud methods", +# "insurance fraud and illegal practices", + +# # Information Security +# "cybersecurity exploits and hacking techniques", +# "software piracy and copyright infringement", + +# # Workplace Appropriateness +# "political opinions and partisan commentary", +# "religious discussions and theological debates", +# "personal relationship advice and dating guidance", +# "substance abuse and drug-related discussions", +# "gambling advice and betting strategies", +# "harassment and workplace bullying", + +# # Content Quality & Misinformation +# "conspiracy theories and misinformation spreading", +# "defamatory statements about individuals or organizations", +# "spam and deceptive content generation", +# "gender identity and sexual orientation" +# ] +# threshold: 0.6 +# model_max_length: 256 +# - type: MaskCode +# params: +# threshold: 0.97 # model_max_length: 256 -# # - type: Code -# # params: -# # languages: ["Python"] -# # is_blocked: true -# # - type: Gibberish -# # params: -# # threshold: 0.97 -# # model_max_length: 256 # - type: InvisibleText # params: { } -# # - type: Language -# # params: -# # valid_languages: [ "en" ] -# # model_max_length: 256 # - type: PromptInjection # params: # threshold: 0.95 @@ -242,61 +86,50 @@ output_scanners: # - type: Secrets # params: # redact_mode: "all" -# # - type: Sentiment -# # params: -# # threshold: 0.0 -# # - type: TokenLimit -# # params: -# # limit: 4096 -# # encoding_name: "cl100k_base" -# - type: Toxicity -# params: -# threshold: 0.95 -# model_max_length: 256 +# # - type: Toxicity +# # params: +# # threshold: 0.95 +# # model_max_length: 256 +# #------------ # output_scanners: -# # - type: BanCompetitors -# # params: -# # competitors: ["facebook"] -# # threshold: 0.75 -# # - type: BanSubstrings -# # params: -# # substrings: ["test"] -# # match_type: "word" -# # case_sensitive: false -# # redact: false -# # contains_all: false # - type: BanTopics # params: # topics: [ -# # Core Safety Categories (Universal across all major providers) -# "violence and physical harm", -# "hate speech and discrimination", -# "harassment and bullying", -# "adult and sexual content", -# "child safety and exploitation", -# "self-harm and suicide", - -# # Illegal Activities (Standard across enterprise products) -# "illegal activities and substances", -# "terrorism and extremism", -# "weapons and explosives", -# "fraud and financial crimes", - -# # Privacy and Security (Enterprise requirements) -# "cybersecurity threats and hacking", - -# # Professional Liability (Common in enterprise) -# "medical advice and diagnosis", -# "legal advice and representation", -# "financial investment advice", - -# # Platform Risk Management -# "misinformation and conspiracy theories", -# "spam and deceptive content" -# ] -# threshold: 0.95 +# # Core Safety & Legal Compliance +# "violence and physical harm to individuals", +# "hate speech and discriminatory content", +# "illegal activities and criminal activity", +# "pornography, erotica, adult and sexual content", +# "terrorism and indiscriminate Weapons", +# "self-harm and suicide", + +# # Professional Liability +# "medical diagnosis without professional oversight", +# "legal advice without professional qualification", +# "tax evasion and financial fraud methods", +# "insurance fraud and illegal practices", + +# # Information Security +# "cybersecurity exploits and hacking techniques", +# "software piracy and copyright infringement", + +# # Workplace Appropriateness +# "political opinions and partisan commentary", +# "religious discussions and theological debates", +# "personal relationship advice and dating guidance", +# "substance abuse and drug-related discussions", +# "gambling advice and betting strategies", +# "harassment and workplace bullying", + +# # Content Quality & Misinformation +# "conspiracy theories and misinformation spreading", +# "defamatory statements about individuals or organizations", +# "spam and deceptive content generation", +# "gender identity and sexual orientation" +# ] +# threshold: 0.6 # - type: Bias # params: # threshold: 0.97 @@ -311,33 +144,200 @@ output_scanners: # # params: # # required_elements: 0 # # repair: true -# # - type: Language -# # params: -# # valid_languages: [ "en" ] -# # model_max_length: 256 -# # - type: LanguageSame -# # params: -# # model_max_length: 256 -# # - type: NoRefusal -# # params: -# # threshold: 0.9 +# - type: BadURL +# params: +# threshold: 0.95 # - type: Regex # params: # patterns: ["Bearer [A-Za-z0-9-._~+/]+"] # is_blocked: true # match_type: search # redact: true -# # - type: Relevance -# # params: -# # threshold: 0.2 # - type: Sensitive # params: # redact: false -# threshold: 0.95 -# # - type: Sentiment -# # params: -# # threshold: 0.0 -# - type: Toxicity -# params: -# threshold: 0.95 -# model_max_length: 256 +# threshold: 0.50 # Higher value = less aggressive PII removal +# # - type: Toxicity +# # params: +# # threshold: 0.6 +# # model_max_length: 256 + +# # app: +# # name: ${APP_NAME:Tueri API} +# # log_level: ${LOG_LEVEL:INFO} +# # log_json: ${LOG_JSON:true} +# # scan_fail_fast: ${SCAN_FAIL_FAST:true} +# # scan_prompt_timeout: ${SCAN_PROMPT_TIMEOUT:30} +# # scan_output_timeout: ${SCAN_OUTPUT_TIMEOUT:30} +# # lazy_load: ${LAZY_LOAD:true} + +# # rate_limit: +# # enabled: ${RATE_LIMIT_ENABLED:false} +# # limit: ${RATE_LIMIT_LIMIT:100/minute} + +# # auth: +# # type: http_bearer +# # token: ${AUTH_TOKEN:} + +# # tracing: +# # exporter: ${TRACING_EXPORTER:console} +# # endpoint: ${TRACING_OTEL_ENDPOINT:} # Example: "/v1/traces" + +# # metrics: +# # exporter: ${METRICS_TYPE:prometheus} +# # endpoint: ${METRICS_ENDPOINT:} # Example: "/v1/metrics" + +# # # Scanners are applied in the order they are listed here. +# # input_scanners: +# # - type: Anonymize +# # params: +# # use_faker: false +# # threshold: 0.9 +# # # model_path: "./distilbert_finetuned_ai4privacy_v2" +# # # - type: BanCode +# # # params: +# # # threshold: 0.97 +# # # model_max_length: 256 +# # # - type: BanCompetitors +# # # params: +# # # competitors: ["facebook"] +# # # threshold: 0.75 +# # # - type: BanSubstrings +# # # params: +# # # substrings: ["test"] +# # # match_type: "word" +# # # case_sensitive: false +# # # redact: false +# # # contains_all: false +# # - type: BanTopics +# # params: +# # topics: ["violence"] +# # threshold: 0.95 +# # model_max_length: 256 +# # # - type: Code +# # # params: +# # # languages: ["Python"] +# # # is_blocked: true +# # # - type: Gibberish +# # # params: +# # # threshold: 0.97 +# # # model_max_length: 256 +# # - type: InvisibleText +# # params: { } +# # # - type: Language +# # # params: +# # # valid_languages: [ "en" ] +# # # model_max_length: 256 +# # - type: PromptInjection +# # params: +# # threshold: 0.95 +# # match_type: truncate_head_tail +# # model_max_length: 256 +# # - type: Regex +# # params: +# # patterns: ["Bearer [A-Za-z0-9-._~+/]+"] +# # is_blocked: true +# # match_type: search +# # redact: true +# # - type: Secrets +# # params: +# # redact_mode: "all" +# # # - type: Sentiment +# # # params: +# # # threshold: 0.0 +# # # - type: TokenLimit +# # # params: +# # # limit: 4096 +# # # encoding_name: "cl100k_base" +# # - type: Toxicity +# # params: +# # threshold: 0.95 +# # model_max_length: 256 + + +# # output_scanners: +# # # - type: BanCompetitors +# # # params: +# # # competitors: ["facebook"] +# # # threshold: 0.75 +# # # - type: BanSubstrings +# # # params: +# # # substrings: ["test"] +# # # match_type: "word" +# # # case_sensitive: false +# # # redact: false +# # # contains_all: false +# # - type: BanTopics +# # params: +# # topics: [ +# # # Core Safety Categories (Universal across all major providers) +# # "violence and physical harm", +# # "hate speech and discrimination", +# # "harassment and bullying", +# # "adult and sexual content", +# # "child safety and exploitation", +# # "self-harm and suicide", + +# # # Illegal Activities (Standard across enterprise products) +# # "illegal activities and substances", +# # "terrorism and extremism", +# # "weapons and explosives", +# # "fraud and financial crimes", + +# # # Privacy and Security (Enterprise requirements) +# # "cybersecurity threats and hacking", + +# # # Professional Liability (Common in enterprise) +# # "medical advice and diagnosis", +# # "legal advice and representation", +# # "financial investment advice", + +# # # Platform Risk Management +# # "misinformation and conspiracy theories", +# # "spam and deceptive content" +# # ] +# # threshold: 0.95 +# # - type: Bias +# # params: +# # threshold: 0.97 +# # model_max_length: 256 +# # - type: Deanonymize +# # params: +# # matching_strategy: "exact" +# # - type: FactualConsistency +# # params: +# # minimum_score: 0.5 +# # # - type: JSON +# # # params: +# # # required_elements: 0 +# # # repair: true +# # # - type: Language +# # # params: +# # # valid_languages: [ "en" ] +# # # model_max_length: 256 +# # # - type: LanguageSame +# # # params: +# # # model_max_length: 256 +# # # - type: NoRefusal +# # # params: +# # # threshold: 0.9 +# # - type: Regex +# # params: +# # patterns: ["Bearer [A-Za-z0-9-._~+/]+"] +# # is_blocked: true +# # match_type: search +# # redact: true +# # # - type: Relevance +# # # params: +# # # threshold: 0.2 +# # - type: Sensitive +# # params: +# # redact: false +# # threshold: 0.95 +# # # - type: Sentiment +# # # params: +# # # threshold: 0.0 +# # - type: Toxicity +# # params: +# # threshold: 0.95 +# # model_max_length: 256 diff --git a/tueri_api/docker-compose.yml b/tueri_api/docker-compose.yml index 7a1b040a..99e8dad6 100644 --- a/tueri_api/docker-compose.yml +++ b/tueri_api/docker-compose.yml @@ -4,12 +4,16 @@ services: context: .. dockerfile: tueri_api/Dockerfile ports: - - "8000:8000" + - "8001:8000" environment: - AUTH_TOKEN=example-auth-token - LOG_LEVEL=DEBUG - APP_WORKERS=1 - SCAN_FAIL_FAST=true + - LAZY_LOAD=true + - MONGO_URI=mongodb://root:example@host.docker.internal:27017/ + - MONGO_DB=ChatApp + - MONGO_COLLECTION=TueriScanners # - CACHE_MAX_SIZE=1000 # - CACHE_TTL=3600 restart: unless-stopped diff --git a/tueri_api/entrypoint.sh b/tueri_api/entrypoint.sh index 5b6623bd..74235bd7 100755 --- a/tueri_api/entrypoint.sh +++ b/tueri_api/entrypoint.sh @@ -3,5 +3,19 @@ APP_WORKERS=${APP_WORKERS:-1} CONFIG_FILE=${CONFIG_FILE:-./config/scanners.yml} -# Uvicorn with workers +# Start the API in the background uvicorn app.app:create_app --host=0.0.0.0 --port=8000 --factory --workers="$APP_WORKERS" --forwarded-allow-ips="*" --proxy-headers --timeout-keep-alive="2" + +# Wait for API to be ready +# echo "Waiting for API to be ready..." +# until python3 -c "import urllib.request; urllib.request.urlopen('http://0.0.0.0:8001/healthz')" 2>/dev/null; do +# sleep 2 +# done +# echo "API is ready!" + +# # Download models +# echo "Downloading models..." +# python3 -c "import urllib.request; urllib.request.urlopen('http://l0.0.0.0:8001/download/models', data=b'')" + +# # Keep the API running in foreground +# wait diff --git a/tueri_api/pyproject.toml b/tueri_api/pyproject.toml index cc67b29a..363af1f6 100644 --- a/tueri_api/pyproject.toml +++ b/tueri_api/pyproject.toml @@ -33,7 +33,8 @@ dependencies = [ "opentelemetry-exporter-prometheus==0.54b1", "opentelemetry-sdk-extension-aws==2.1.0", "opentelemetry-propagator-aws-xray==1.0.2", - "psutil>=5.9" + "psutil>=5.9", + "pymongo==4.14.1" ] [project.optional-dependencies] From b9d06b78d566d9dd4ffb5e7920adbab5f021c7b9 Mon Sep 17 00:00:00 2001 From: Akshith Ambekar Date: Mon, 15 Sep 2025 11:01:04 -0400 Subject: [PATCH 2/2] mongodb connection, changed scanners.yml to app_config.yml --- docs/api/deployment.md | 6 +++--- tueri_api/Dockerfile | 2 +- tueri_api/Dockerfile-cuda | 2 +- tueri_api/app/app.py | 2 +- tueri_api/config/{scanners.yml => app_config.yml} | 0 tueri_api/docker-compose.yml | 2 +- tueri_api/entrypoint.sh | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename tueri_api/config/{scanners.yml => app_config.yml} (100%) diff --git a/docs/api/deployment.md b/docs/api/deployment.md index d5b4305d..6bc4975b 100644 --- a/docs/api/deployment.md +++ b/docs/api/deployment.md @@ -26,7 +26,7 @@ make run Or using CLI: ```bash -llm_guard_api ./config/scanners.yml +llm_guard_api ./config/app_config.yml ``` ### Using gunicorn @@ -34,7 +34,7 @@ llm_guard_api ./config/scanners.yml In case you want to use `gunicorn` to run the API, you can use the following command: ```bash -gunicorn --workers 1 --preload --worker-class uvicorn.workers.UvicornWorker 'app.app:create_app(config_file="./config/scanners.yml")' +gunicorn --workers 1 --preload --worker-class uvicorn.workers.UvicornWorker 'app.app:create_app(config_file="./config/app_config.yml")' ``` It will preload models in the shared memory among workers, which can be useful for performance. @@ -67,7 +67,7 @@ This will start the API on port 8000. You can now access the API at `http://loca If you want to use a custom configuration, you can mount a volume to `/home/user/app/config`: ```bash -docker run -d -p 8000:8000 -e APP_WORKERS=1 -e AUTH_TOKEN='my-token' -e LOG_LEVEL='DEBUG' -v ./entrypoint.sh:/home/user/app/entrypoint.sh -v ./config/scanners.yml:/home/user/app/config/scanners.yml laiyer/llm-guard-api:latest +docker run -d -p 8000:8000 -e APP_WORKERS=1 -e AUTH_TOKEN='my-token' -e LOG_LEVEL='DEBUG' -v ./entrypoint.sh:/home/user/app/entrypoint.sh -v ./config/app_config.yml:/home/user/app/config/app_config.yml laiyer/llm-guard-api:latest ``` !!! warning diff --git a/tueri_api/Dockerfile b/tueri_api/Dockerfile index 537e9683..35842451 100644 --- a/tueri_api/Dockerfile +++ b/tueri_api/Dockerfile @@ -39,7 +39,7 @@ RUN pip install --no-cache-dir --upgrade pip && \ RUN python -m spacy download en_core_web_sm -COPY --chown=user:user tueri_api/config/scanners.yml ./config/scanners.yml +COPY --chown=user:user tueri_api/config/app_config.yml ./config/app_config.yml COPY --chown=user:user tueri_api/entrypoint.sh ./entrypoint.sh RUN chmod +x ./entrypoint.sh diff --git a/tueri_api/Dockerfile-cuda b/tueri_api/Dockerfile-cuda index f5419c23..3011b3b7 100644 --- a/tueri_api/Dockerfile-cuda +++ b/tueri_api/Dockerfile-cuda @@ -42,7 +42,7 @@ RUN pip3 install --no-cache-dir --upgrade pip && \ RUN python -m spacy download en_core_web_sm -COPY --chown=user:user ./config/scanners.yml ./config/scanners.yml +COPY --chown=user:user ./config/app_config.yml ./config/app_config.yml COPY --chown=user:user entrypoint.sh ./entrypoint.sh RUN chmod +x ./entrypoint.sh diff --git a/tueri_api/app/app.py b/tueri_api/app/app.py index accfa9b7..ac678421 100644 --- a/tueri_api/app/app.py +++ b/tueri_api/app/app.py @@ -55,7 +55,7 @@ def create_app() -> FastAPI: - config_file = os.getenv("CONFIG_FILE", "./config/scanners.yml") + config_file = os.getenv("CONFIG_FILE", "./config/app_config.yml") if not config_file: raise ValueError("Config file is required") diff --git a/tueri_api/config/scanners.yml b/tueri_api/config/app_config.yml similarity index 100% rename from tueri_api/config/scanners.yml rename to tueri_api/config/app_config.yml diff --git a/tueri_api/docker-compose.yml b/tueri_api/docker-compose.yml index 99e8dad6..38b7d53b 100644 --- a/tueri_api/docker-compose.yml +++ b/tueri_api/docker-compose.yml @@ -18,4 +18,4 @@ services: # - CACHE_TTL=3600 restart: unless-stopped volumes: - - ./config/scanners.yml:/home/user/app/config/scanners.yml + - ./config/app_config.yml:/home/user/app/config/app_config.yml diff --git a/tueri_api/entrypoint.sh b/tueri_api/entrypoint.sh index 74235bd7..116999ca 100755 --- a/tueri_api/entrypoint.sh +++ b/tueri_api/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/bash APP_WORKERS=${APP_WORKERS:-1} -CONFIG_FILE=${CONFIG_FILE:-./config/scanners.yml} +CONFIG_FILE=${CONFIG_FILE:-./config/app_config.yml} # Start the API in the background uvicorn app.app:create_app --host=0.0.0.0 --port=8000 --factory --workers="$APP_WORKERS" --forwarded-allow-ips="*" --proxy-headers --timeout-keep-alive="2"