diff --git a/cascadeflow/integrations/hermes/classifier.py b/cascadeflow/integrations/hermes/classifier.py index eb5f15ac..5852f216 100644 --- a/cascadeflow/integrations/hermes/classifier.py +++ b/cascadeflow/integrations/hermes/classifier.py @@ -2,6 +2,7 @@ from __future__ import annotations +import re from dataclasses import dataclass from typing import Optional @@ -122,10 +123,17 @@ def classify(self, request: HermesDelegationRequest) -> HermesTaskClassification reason=domain_reason, ) + @staticmethod + def _keyword_in_text(keyword: str, lowered: str) -> bool: + # Match on word boundaries so short keywords (e.g. "ci", "api", "law") + # don't match inside unrelated words like "financial" ("ci") or "always" + # ("law"). Handles multi-word keywords ("stack trace") correctly too. + return re.search(rf"\b{re.escape(keyword)}\b", lowered) is not None + def _detect_domain(self, lowered: str, toolsets: tuple[str, ...]) -> tuple[str, float, str]: scores: dict[str, int] = {} for domain, keywords in DOMAIN_KEYWORDS.items(): - hits = sum(1 for keyword in keywords if keyword in lowered) + hits = sum(1 for keyword in keywords if self._keyword_in_text(keyword, lowered)) if hits: scores[domain] = hits diff --git a/tests/test_provider_tool_retry.py b/tests/test_provider_tool_retry.py index 23b1107d..449307d7 100644 --- a/tests/test_provider_tool_retry.py +++ b/tests/test_provider_tool_retry.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio from cascadeflow.providers.base import BaseProvider, ModelResponse, RetryConfig