diff --git a/litellm/policy_templates_backup.json b/litellm/policy_templates_backup.json index 84c701f727e..ef8b5e61060 100644 --- a/litellm/policy_templates_backup.json +++ b/litellm/policy_templates_backup.json @@ -1844,6 +1844,51 @@ "Security" ] }, + { + "id": "airline-off-topic-restriction", + "title": "Airline Off-Topic Restriction", + "description": "Restricts an airline chatbot to airline-related topics only. Blocks off-topic questions about news, sports, coding, politics, entertainment, finance, recipes, homework, and general knowledge using keyword-based detection with no additional LLM calls.", + "icon": "ShieldExclamationIcon", + "iconColor": "text-orange-500", + "iconBg": "bg-orange-50", + "guardrails": [ + "airline-off-topic-filter" + ], + "complexity": "Medium", + "guardrailDefinitions": [ + { + "guardrail_name": "airline-off-topic-filter", + "litellm_params": { + "guardrail": "litellm_content_filter", + "mode": "pre_call", + "categories": [ + { + "category": "airline_off_topic_restriction", + "category_file": "litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/policy_templates/airline_off_topic_restriction.yaml", + "enabled": true, + "action": "BLOCK", + "severity_threshold": "medium" + } + ] + }, + "guardrail_info": { + "description": "Blocks off-topic questions unrelated to airline services (news, sports, coding, politics, entertainment, finance, recipes, etc.)" + } + } + ], + "templateData": { + "policy_name": "airline-off-topic-restriction", + "description": "Restricts chatbot to airline-related topics. Blocks off-topic questions using keyword matching with no extra LLM calls.", + "guardrails_add": [ + "airline-off-topic-filter" + ], + "guardrails_remove": [] + }, + "tags": [ + "Aviation", + "Topic Restriction" + ] + }, { "id": "uae-regulatory-compliance", "title": "UAE Regulatory Compliance", diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/policy_templates/airline_off_topic_restriction.yaml b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/policy_templates/airline_off_topic_restriction.yaml new file mode 100644 index 00000000000..ec7cc2a0953 --- /dev/null +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/policy_templates/airline_off_topic_restriction.yaml @@ -0,0 +1,270 @@ +# Airline Off-Topic Restriction +# Blocks questions unrelated to airline services (news, sports, coding, politics, etc.) +# Uses conditional matching: identifier_word + block_word in same sentence = BLOCK +# Plus always_block_keywords for unambiguous off-topic phrases +category_name: "airline_off_topic_restriction" +description: "Blocks off-topic questions unrelated to airline services" +default_action: "BLOCK" + +# OFF-TOPIC DOMAIN SIGNALS +# These words indicate the user is asking about a non-airline topic. +# They only trigger a block when paired with a block_word in the same sentence. +identifier_words: + # News & current events + - "news" + - "headlines" + - "breaking" + - "journalism" + - "reporter" + # Sports + - "sports" + - "football" + - "soccer" + - "basketball" + - "baseball" + - "cricket" + - "tennis" + - "championship" + - "playoffs" + - "tournament" + - "league" + - "FIFA" + - "NBA" + - "NFL" + # Technology & coding + - "code" + - "coding" + - "programming" + - "python" + - "javascript" + - "software" + - "algorithm" + - "database" + - "API" + - "machine learning" + - "AI gateway" + - "blockchain" + - "cryptocurrency" + - "bitcoin" + - "ethereum" + # Entertainment + - "movie" + - "Netflix" + - "TV show" + - "series" + - "album" + - "song" + - "lyrics" + - "celebrity" + - "actor" + - "actress" + # Politics & government + - "election" + - "president" + - "prime minister" + - "congress" + - "parliament" + - "political party" + - "senator" + - "governor" + - "democrat" + - "republican" + # Finance & investing + - "stock market" + - "stock price" + - "invest" + - "trading" + - "forex" + - "mutual fund" + - "portfolio" + # Food & cooking + - "recipe" + - "cooking" + - "restaurant" + - "cuisine" + - "ingredient" + # Education & homework + - "homework" + - "equation" + - "calculus" + - "algebra" + - "physics" + - "chemistry" + - "biology" + - "history lesson" + # Health & medical (non-travel) + - "diagnosis" + - "symptom" + - "treatment" + - "prescription" + - "surgery" + # Real estate + - "real estate" + - "mortgage" + - "apartment" + - "house price" + # Dating & relationships + - "dating" + - "relationship advice" + - "break up" + - "tinder" + # Gaming + - "video game" + - "gaming" + - "playstation" + - "xbox" + - "fortnite" + - "minecraft" + +# CONTEXTUAL TRIGGERS +# When combined with an identifier_word in the same sentence, triggers a block. +additional_block_words: + # Action/query words that confirm off-topic intent + - "today" + - "latest" + - "score" + - "won" + - "winner" + - "lost" + - "write" + - "build" + - "create" + - "develop" + - "debug" + - "fix" + - "top" + - "favorite" + - "watch" + - "listen" + - "play" + - "download" + - "install" + - "price" + - "cost" + - "buy" + - "sell" + - "vote" + - "voted" + - "opinion" + - "who won" + - "make" + - "how to" + - "tutorial" + - "learn" + - "teach" + - "solve" + - "calculate" + - "convert" + - "translate" + +# ALWAYS BLOCK - Unambiguous off-topic phrases (blocked regardless of context) +always_block_keywords: + # News queries + - keyword: "what's in the news" + severity: "high" + - keyword: "what is in the news" + severity: "high" + - keyword: "latest headlines" + severity: "high" + - keyword: "what happened in the world" + severity: "high" + # Jokes & fun + - keyword: "tell me a joke" + severity: "high" + - keyword: "tell me a story" + severity: "high" + - keyword: "tell me a fun fact" + severity: "high" + - keyword: "tell me something interesting" + severity: "high" + # Coding requests + - keyword: "write me code" + severity: "high" + - keyword: "write a script" + severity: "high" + - keyword: "write a program" + severity: "high" + - keyword: "help me code" + severity: "high" + - keyword: "fix my code" + severity: "high" + - keyword: "debug my code" + severity: "high" + # General knowledge + - keyword: "capital of" + severity: "high" + - keyword: "who invented" + severity: "high" + - keyword: "how tall is" + severity: "high" + - keyword: "how old is" + severity: "high" + - keyword: "what year did" + severity: "high" + - keyword: "who is the president" + severity: "high" + # Math & homework + - keyword: "solve this equation" + severity: "high" + - keyword: "what is 2+2" + severity: "high" + - keyword: "help me with my homework" + severity: "high" + # Recipes + - keyword: "recipe for" + severity: "high" + - keyword: "how to cook" + severity: "high" + - keyword: "how to bake" + severity: "high" + # Relationship advice + - keyword: "relationship advice" + severity: "high" + - keyword: "should I break up" + severity: "high" + - keyword: "dating advice" + severity: "high" + # AI / tech queries + - keyword: "what is an AI gateway" + severity: "high" + - keyword: "explain machine learning" + severity: "high" + - keyword: "what is blockchain" + severity: "high" + - keyword: "what is cryptocurrency" + severity: "high" + +# EXCEPTIONS - Airline-adjacent contexts that should NOT be blocked +exceptions: + - "in-flight entertainment" + - "flight entertainment" + - "in-flight movie" + - "airport news" + - "travel news" + - "airline news" + - "flight news" + - "aviation news" + - "airport restaurant" + - "airport lounge" + - "travel recommend" + - "destination recommend" + - "flight price" + - "ticket price" + - "fare price" + - "baggage cost" + - "upgrade cost" + - "booking cost" + - "seat recommend" + - "recommend seat" + - "recommend flight" + - "suggest flight" + - "suggest seat" + - "suggest upgrade" + - "best seat" + - "best flight" + - "best fare" + - "flight movie" + - "explain my" + - "explain the" + - "explain flight" + - "explain booking" diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_airline_off_topic_restriction.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_airline_off_topic_restriction.py new file mode 100644 index 00000000000..83616b296df --- /dev/null +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_airline_off_topic_restriction.py @@ -0,0 +1,216 @@ +""" +Tests for the airline off-topic restriction policy template. + +Verifies that off-topic messages are blocked and on-topic/conversational messages pass. +""" + +import os +import sys + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../") +) # Adds the parent directory to the system path + +from fastapi import HTTPException + +from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.content_filter import ( + ContentFilterGuardrail, +) + +POLICY_TEMPLATE_PATH = os.path.join( + os.path.dirname(__file__), + "../../../../../../litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/policy_templates/airline_off_topic_restriction.yaml", +) + + +def _make_guardrail(): + """Create a ContentFilterGuardrail with the airline off-topic restriction loaded.""" + return ContentFilterGuardrail( + guardrail_name="test-airline-off-topic", + categories=[ + { + "category": "airline_off_topic_restriction", + "category_file": POLICY_TEMPLATE_PATH, + "enabled": True, + "action": "BLOCK", + } + ], + ) + + +class TestAirlineOffTopicRestriction: + """Test the airline off-topic restriction policy template.""" + + def test_on_topic_flight_booking(self): + """Airline booking questions should pass.""" + guardrail = _make_guardrail() + # Should not raise + result = guardrail._filter_single_text("I want to book a flight to Dubai") + assert result == "I want to book a flight to Dubai" + + def test_on_topic_baggage(self): + """Baggage questions should pass.""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("What is the baggage allowance for economy?") + assert result == "What is the baggage allowance for economy?" + + def test_on_topic_checkin(self): + """Check-in questions should pass.""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("How do I check in online?") + assert "check in" in result + + def test_on_topic_delay(self): + """Flight delay questions should pass.""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("My flight is delayed, what are my options?") + assert "delayed" in result + + def test_conversational_hello(self): + """Greetings should pass.""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("Hello") + assert result == "Hello" + + def test_conversational_thanks(self): + """Thank you should pass.""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("Thank you for your help") + assert "Thank you" in result + + def test_conversational_help(self): + """Help requests should pass.""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("Can you help me?") + assert "help" in result + + def test_conversational_yes_no(self): + """Simple yes/no should pass.""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("Yes") + assert result == "Yes" + + def test_off_topic_news_always_block(self): + """News questions should be blocked via always_block_keywords.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("What's in the news today?") + assert exc_info.value.status_code == 403 + + def test_off_topic_joke_always_block(self): + """Joke requests should be blocked via always_block_keywords.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("Tell me a joke") + assert exc_info.value.status_code == 403 + + def test_off_topic_coding_always_block(self): + """Coding requests should be blocked via always_block_keywords.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("Write me code in python") + assert exc_info.value.status_code == 403 + + def test_off_topic_ai_gateway_always_block(self): + """AI gateway questions should be blocked via always_block_keywords.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("What is an AI gateway?") + assert exc_info.value.status_code == 403 + + def test_off_topic_capital_always_block(self): + """General knowledge questions should be blocked via always_block_keywords.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("What is the capital of France?") + assert exc_info.value.status_code == 403 + + def test_off_topic_sports_conditional(self): + """Sports questions should be blocked via conditional matching.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("Who won the football game today?") + assert exc_info.value.status_code == 403 + + def test_off_topic_recipe_always_block(self): + """Recipe questions should be blocked via always_block_keywords.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("Give me a recipe for pasta") + assert exc_info.value.status_code == 403 + + def test_off_topic_movie_conditional(self): + """Movie questions with a block word should be blocked.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("What is the top movie to watch on Netflix?") + assert exc_info.value.status_code == 403 + + def test_on_topic_recommend_seat(self): + """Airline recommendation questions should pass (not false-positive).""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("Can you recommend the best seat?") + assert "recommend" in result.lower() + + def test_on_topic_explain_booking(self): + """Explain questions about airline topics should pass.""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("Can you explain my booking details?") + assert "explain" in result.lower() + + def test_off_topic_stock_conditional(self): + """Stock market questions should be blocked via conditional matching.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("What is the stock price of Apple today?") + assert exc_info.value.status_code == 403 + + def test_off_topic_homework_always_block(self): + """Homework requests should be blocked via always_block_keywords.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("Help me with my homework") + assert exc_info.value.status_code == 403 + + def test_off_topic_relationship_always_block(self): + """Relationship advice should be blocked via always_block_keywords.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("Can you give me relationship advice?") + assert exc_info.value.status_code == 403 + + def test_exception_inflight_entertainment(self): + """In-flight entertainment questions should pass (exception).""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text( + "What movies are available on the in-flight entertainment?" + ) + assert "in-flight entertainment" in result.lower() + + def test_exception_flight_price(self): + """Flight price questions should pass (exception).""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("What is the flight price to London?") + assert "flight price" in result.lower() + + def test_exception_travel_news(self): + """Travel news should pass (exception).""" + guardrail = _make_guardrail() + result = guardrail._filter_single_text("Any travel news I should know about?") + assert "travel news" in result.lower() + + def test_off_topic_president_always_block(self): + """Political questions should be blocked.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("Who is the president of the United States?") + assert exc_info.value.status_code == 403 + + def test_off_topic_blockchain_always_block(self): + """Blockchain questions should be blocked.""" + guardrail = _make_guardrail() + with pytest.raises(HTTPException) as exc_info: + guardrail._filter_single_text("What is blockchain technology?") + assert exc_info.value.status_code == 403