{"record":{"id":"94cb6da16abb5ac9","repo":"BerriAI/litellm","slug":"keyword-tier-rules-entries-must-contain-at-least-o","errorCode":null,"errorMessage":"keyword_tier_rules entries must contain at least one non-empty keyword","messagePattern":"keyword_tier_rules entries must contain at least one non-empty keyword","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/router_strategy/complexity_router/config.py","lineNumber":71,"sourceCode":"    \"\"\"A deterministic override: if any keyword matches, route to this tier.\"\"\"\n\n    keywords: list[str] = Field(\n        min_length=1,\n        description=\"Keywords/phrases that trigger this rule (lexical or semantic match)\",\n    )\n    tier: ComplexityTier = Field(\n        description=\"Tier to route to when this rule matches\",\n    )\n\n    @model_validator(mode=\"after\")\n    def _normalize_keywords(self) -> \"KeywordTierRule\":\n        # Strip and drop blank keywords. An empty/whitespace keyword is a routing foot-gun:\n        # _keyword_matches treats \"\" / \" \" as a substring that matches essentially every\n        # prompt, so a single stray blank would silently force this rule's tier for all\n        # traffic. Require at least one real keyword to remain.\n        cleaned: Final = [stripped for keyword in self.keywords if (stripped := keyword.strip())]\n        if not cleaned:\n            raise ValueError(\"keyword_tier_rules entries must contain at least one non-empty keyword\")\n        self.keywords = cleaned\n        return self\n\n\nclass ReminderMarkerPair(BaseModel):\n    \"\"\"One open/close delimiter pair a harness wraps injected context in.\n\n    Normalizing here rather than at the scan is what makes matching case-insensitive: markers reach\n    the scan already lowered, so it lowercases only the haystack and never the needles. Stripping\n    keeps YAML indentation whitespace from becoming part of the delimiter.\n    \"\"\"\n\n    open: str = Field(description=\"Opening delimiter, e.g. '<system-reminder>'\")\n    close: str = Field(description=\"Closing delimiter, e.g. '</system-reminder>'\")\n\n    @model_validator(mode=\"after\")\n    def _normalize(self) -> \"ReminderMarkerPair\":\n        open_marker: Final = self.open.strip().lower()","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/router_strategy/complexity_router/config.py#L53-L89","documentation":"Pydantic validator on KeywordTierRule: after stripping whitespace, the keywords list contains no non-empty keyword. An empty keyword would substring-match everything and hijack routing, so the rule is rejected.","triggerScenarios":"Thrown at litellm/router_strategy/complexity_router/config.py:71 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Give each keyword_tier_rules entry at least one non-empty keyword."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}