227 lines
6.6 KiB
Python
227 lines
6.6 KiB
Python
"""
|
|
Intent routing data models.
|
|
[AC-AISVC-111~AC-AISVC-125] Data models for hybrid routing.
|
|
"""
|
|
|
|
import uuid
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
|
|
@dataclass
|
|
class RuleMatchResult:
|
|
"""
|
|
[AC-AISVC-112] Result of rule matching.
|
|
Contains matched rule and score.
|
|
"""
|
|
rule_id: uuid.UUID | None
|
|
rule: Any | None
|
|
match_type: str | None
|
|
matched_text: str | None
|
|
score: float
|
|
duration_ms: int
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"rule_id": str(self.rule_id) if self.rule_id else None,
|
|
"rule_name": self.rule.name if self.rule else None,
|
|
"match_type": self.match_type,
|
|
"matched_text": self.matched_text,
|
|
"score": self.score,
|
|
"duration_ms": self.duration_ms,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class SemanticCandidate:
|
|
"""
|
|
[AC-AISVC-113] Semantic match candidate.
|
|
"""
|
|
rule: Any
|
|
score: float
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"rule_id": str(self.rule.id),
|
|
"rule_name": self.rule.name,
|
|
"score": self.score,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class SemanticMatchResult:
|
|
"""
|
|
[AC-AISVC-113] Result of semantic matching.
|
|
"""
|
|
candidates: list[SemanticCandidate]
|
|
top_score: float
|
|
duration_ms: int
|
|
skipped: bool
|
|
skip_reason: str | None
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"top_candidates": [c.to_dict() for c in self.candidates],
|
|
"top_score": self.top_score,
|
|
"duration_ms": self.duration_ms,
|
|
"skipped": self.skipped,
|
|
"skip_reason": self.skip_reason,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class LlmJudgeInput:
|
|
"""
|
|
[AC-AISVC-119] Input for LLM judge.
|
|
"""
|
|
message: str
|
|
candidates: list[dict[str, Any]]
|
|
conflict_type: str
|
|
|
|
|
|
@dataclass
|
|
class LlmJudgeResult:
|
|
"""
|
|
[AC-AISVC-119] Result of LLM judge.
|
|
"""
|
|
intent_id: str | None
|
|
intent_name: str | None
|
|
score: float
|
|
reasoning: str | None
|
|
duration_ms: int
|
|
tokens_used: int
|
|
triggered: bool
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"triggered": self.triggered,
|
|
"intent_id": self.intent_id,
|
|
"intent_name": self.intent_name,
|
|
"score": self.score,
|
|
"reasoning": self.reasoning,
|
|
"duration_ms": self.duration_ms,
|
|
"tokens_used": self.tokens_used,
|
|
}
|
|
|
|
@classmethod
|
|
def empty(cls) -> "LlmJudgeResult":
|
|
return cls(
|
|
intent_id=None,
|
|
intent_name=None,
|
|
score=0.0,
|
|
reasoning=None,
|
|
duration_ms=0,
|
|
tokens_used=0,
|
|
triggered=False,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class FusionConfig:
|
|
"""
|
|
[AC-AISVC-116] Fusion configuration.
|
|
"""
|
|
w_rule: float = 0.5
|
|
w_semantic: float = 0.3
|
|
w_llm: float = 0.2
|
|
semantic_threshold: float = 0.7
|
|
conflict_threshold: float = 0.2
|
|
gray_zone_threshold: float = 0.6
|
|
min_trigger_threshold: float = 0.3
|
|
clarify_threshold: float = 0.4
|
|
multi_intent_threshold: float = 0.15
|
|
llm_judge_enabled: bool = True
|
|
semantic_matcher_enabled: bool = True
|
|
semantic_matcher_timeout_ms: int = 100
|
|
llm_judge_timeout_ms: int = 2000
|
|
semantic_top_k: int = 3
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"w_rule": self.w_rule,
|
|
"w_semantic": self.w_semantic,
|
|
"w_llm": self.w_llm,
|
|
"semantic_threshold": self.semantic_threshold,
|
|
"conflict_threshold": self.conflict_threshold,
|
|
"gray_zone_threshold": self.gray_zone_threshold,
|
|
"min_trigger_threshold": self.min_trigger_threshold,
|
|
"clarify_threshold": self.clarify_threshold,
|
|
"multi_intent_threshold": self.multi_intent_threshold,
|
|
"llm_judge_enabled": self.llm_judge_enabled,
|
|
"semantic_matcher_enabled": self.semantic_matcher_enabled,
|
|
"semantic_matcher_timeout_ms": self.semantic_matcher_timeout_ms,
|
|
"llm_judge_timeout_ms": self.llm_judge_timeout_ms,
|
|
"semantic_top_k": self.semantic_top_k,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> "FusionConfig":
|
|
return cls(
|
|
w_rule=data.get("w_rule", 0.5),
|
|
w_semantic=data.get("w_semantic", 0.3),
|
|
w_llm=data.get("w_llm", 0.2),
|
|
semantic_threshold=data.get("semantic_threshold", 0.7),
|
|
conflict_threshold=data.get("conflict_threshold", 0.2),
|
|
gray_zone_threshold=data.get("gray_zone_threshold", 0.6),
|
|
min_trigger_threshold=data.get("min_trigger_threshold", 0.3),
|
|
clarify_threshold=data.get("clarify_threshold", 0.4),
|
|
multi_intent_threshold=data.get("multi_intent_threshold", 0.15),
|
|
llm_judge_enabled=data.get("llm_judge_enabled", True),
|
|
semantic_matcher_enabled=data.get("semantic_matcher_enabled", True),
|
|
semantic_matcher_timeout_ms=data.get("semantic_matcher_timeout_ms", 100),
|
|
llm_judge_timeout_ms=data.get("llm_judge_timeout_ms", 2000),
|
|
semantic_top_k=data.get("semantic_top_k", 3),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class RouteTrace:
|
|
"""
|
|
[AC-AISVC-122] Route trace log.
|
|
"""
|
|
rule_match: dict[str, Any] = field(default_factory=dict)
|
|
semantic_match: dict[str, Any] = field(default_factory=dict)
|
|
llm_judge: dict[str, Any] = field(default_factory=dict)
|
|
fusion: dict[str, Any] = field(default_factory=dict)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"rule_match": self.rule_match,
|
|
"semantic_match": self.semantic_match,
|
|
"llm_judge": self.llm_judge,
|
|
"fusion": self.fusion,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class FusionResult:
|
|
"""
|
|
[AC-AISVC-115] Fusion decision result.
|
|
"""
|
|
final_intent: Any | None
|
|
final_confidence: float
|
|
decision_reason: str
|
|
need_clarify: bool
|
|
clarify_candidates: list[Any] | None
|
|
trace: RouteTrace
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"final_intent": {
|
|
"id": str(self.final_intent.id),
|
|
"name": self.final_intent.name,
|
|
"response_type": self.final_intent.response_type,
|
|
} if self.final_intent else None,
|
|
"final_confidence": self.final_confidence,
|
|
"decision_reason": self.decision_reason,
|
|
"need_clarify": self.need_clarify,
|
|
"clarify_candidates": [
|
|
{"id": str(c.id), "name": c.name}
|
|
for c in (self.clarify_candidates or [])
|
|
],
|
|
"trace": self.trace.to_dict(),
|
|
}
|
|
|
|
|
|
DEFAULT_FUSION_CONFIG = FusionConfig()
|