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/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/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 f6183825..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") @@ -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/app_config.yml b/tueri_api/config/app_config.yml new file mode 100644 index 00000000..82de2eb7 --- /dev/null +++ b/tueri_api/config/app_config.yml @@ -0,0 +1,343 @@ +app: + name: ${APP_NAME:Tueri API} + log_level: ${LOG_LEVEL:DEBUG} + log_json: ${LOG_JSON:true} + scan_fail_fast: ${SCAN_FAIL_FAST:false} + scan_prompt_timeout: ${SCAN_PROMPT_TIMEOUT:30} + scan_output_timeout: ${SCAN_OUTPUT_TIMEOUT:30} + lazy_load: ${LAZY_LOAD:false} + +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: +# 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 +# # - 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/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 deleted file mode 100644 index c938113c..00000000 --- a/tueri_api/config/scanners.yml +++ /dev/null @@ -1,343 +0,0 @@ -app: - name: ${APP_NAME:Tueri API} - log_level: ${LOG_LEVEL:INFO} - log_json: ${LOG_JSON:true} - scan_fail_fast: ${SCAN_FAIL_FAST:false} - scan_prompt_timeout: ${SCAN_PROMPT_TIMEOUT:30} - scan_output_timeout: ${SCAN_OUTPUT_TIMEOUT:30} - lazy_load: ${LAZY_LOAD:false} - -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: - 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 -# - 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..38b7d53b 100644 --- a/tueri_api/docker-compose.yml +++ b/tueri_api/docker-compose.yml @@ -4,14 +4,18 @@ 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 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 5b6623bd..116999ca 100755 --- a/tueri_api/entrypoint.sh +++ b/tueri_api/entrypoint.sh @@ -1,7 +1,21 @@ #!/bin/bash APP_WORKERS=${APP_WORKERS:-1} -CONFIG_FILE=${CONFIG_FILE:-./config/scanners.yml} +CONFIG_FILE=${CONFIG_FILE:-./config/app_config.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]