Add comprehensive scoring system with three strategies: - RelevanceScorer: Semantic similarity with keyword fallback - RecencyScorer: Exponential decay with type-specific half-lives - PriorityScorer: Priority-based scoring with type bonuses Implement CompositeScorer combining all strategies with configurable weights (default: 50% relevance, 30% recency, 20% priority). Add ContextRanker for budget-aware context selection with: - Greedy selection algorithm respecting token budgets - CRITICAL priority contexts always included - Diversity reranking to prevent source dominance - Comprehensive selection statistics 68 tests covering all scoring and ranking functionality. Part of #61 - Context Management Engine 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
100 lines
2.1 KiB
Python
100 lines
2.1 KiB
Python
"""
|
|
Base Scorer Protocol and Types.
|
|
|
|
Defines the interface for context scoring implementations.
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
|
|
from ..types import BaseContext
|
|
|
|
if TYPE_CHECKING:
|
|
from app.services.mcp.client_manager import MCPClientManager
|
|
|
|
|
|
@runtime_checkable
|
|
class ScorerProtocol(Protocol):
|
|
"""Protocol for context scorers."""
|
|
|
|
async def score(
|
|
self,
|
|
context: BaseContext,
|
|
query: str,
|
|
**kwargs: Any,
|
|
) -> float:
|
|
"""
|
|
Score a context item.
|
|
|
|
Args:
|
|
context: Context to score
|
|
query: Query to score against
|
|
**kwargs: Additional scoring parameters
|
|
|
|
Returns:
|
|
Score between 0.0 and 1.0
|
|
"""
|
|
...
|
|
|
|
|
|
class BaseScorer(ABC):
|
|
"""
|
|
Abstract base class for context scorers.
|
|
|
|
Provides common functionality and interface for
|
|
different scoring strategies.
|
|
"""
|
|
|
|
def __init__(self, weight: float = 1.0) -> None:
|
|
"""
|
|
Initialize scorer.
|
|
|
|
Args:
|
|
weight: Weight for this scorer in composite scoring
|
|
"""
|
|
self._weight = weight
|
|
|
|
@property
|
|
def weight(self) -> float:
|
|
"""Get scorer weight."""
|
|
return self._weight
|
|
|
|
@weight.setter
|
|
def weight(self, value: float) -> None:
|
|
"""Set scorer weight."""
|
|
if not 0.0 <= value <= 1.0:
|
|
raise ValueError("Weight must be between 0.0 and 1.0")
|
|
self._weight = value
|
|
|
|
@abstractmethod
|
|
async def score(
|
|
self,
|
|
context: BaseContext,
|
|
query: str,
|
|
**kwargs: Any,
|
|
) -> float:
|
|
"""
|
|
Score a context item.
|
|
|
|
Args:
|
|
context: Context to score
|
|
query: Query to score against
|
|
**kwargs: Additional scoring parameters
|
|
|
|
Returns:
|
|
Score between 0.0 and 1.0
|
|
"""
|
|
...
|
|
|
|
def normalize_score(self, score: float) -> float:
|
|
"""
|
|
Normalize score to [0.0, 1.0] range.
|
|
|
|
Args:
|
|
score: Raw score
|
|
|
|
Returns:
|
|
Normalized score
|
|
"""
|
|
return max(0.0, min(1.0, score))
|