Skip to main content
Glama
ShivamPansuriya

Dynamic Per-User Tool Generation MCP Server

decisioning.py7.99 kB
""" Decision Engine Implements score-based decisioning logic for slot resolution. Uses dual-threshold approach: 1. Min Score Threshold: Minimum confidence to accept a match 2. Score Gap Delta: Minimum gap between top 2 candidates for auto-resolution """ import logging from typing import List, Optional, Dict, Any from slot_resolution.core.models import ( Candidate, SlotResolutionResponse, ResolutionStatus, ResolvedEntity, ResolutionMethod ) logger = logging.getLogger(__name__) class DecisionEngine: """ Applies score-based decisioning logic to determine resolution outcome. The engine uses configurable thresholds to decide whether to: - Auto-resolve to a single match - Request user disambiguation - Report no match """ # Default thresholds per entity type DEFAULT_MIN_SCORES = { "impact": 0.65, "urgency": 0.65, "priority": 0.65, "status": 0.60, "category": 0.60, "source": 0.60, "location": 0.55, "department": 0.55, "user": 0.70, "usergroup": 0.65, "vendor": 0.70 } DEFAULT_SCORE_GAP_DELTA = 0.15 DEFAULT_MAX_CANDIDATES = 5 def __init__( self, min_scores: Optional[Dict[str, float]] = None, score_gap_delta: float = DEFAULT_SCORE_GAP_DELTA, max_candidates: int = DEFAULT_MAX_CANDIDATES ): """ Initialize the decision engine. Args: min_scores: Dictionary mapping entity types to minimum scores score_gap_delta: Minimum score gap for auto-resolution max_candidates: Maximum candidates to return for disambiguation """ self.min_scores = min_scores or self.DEFAULT_MIN_SCORES self.score_gap_delta = score_gap_delta self.max_candidates = max_candidates logger.info( f"DecisionEngine initialized with score_gap_delta={score_gap_delta}, " f"max_candidates={max_candidates}" ) def get_min_score(self, entity_type: str) -> float: """ Get minimum score threshold for an entity type. Args: entity_type: Type of entity Returns: Minimum score threshold """ return self.min_scores.get(entity_type, 0.60) def decide( self, candidates: List[Candidate], entity_type: str, input_query: str, normalized_query: str, min_score_override: Optional[float] = None, score_gap_delta_override: Optional[float] = None ) -> SlotResolutionResponse: """ Apply decisioning logic to candidate list. Args: candidates: List of candidate matches from search entity_type: Type of entity being resolved input_query: Original user input normalized_query: Normalized input min_score_override: Override default min score score_gap_delta_override: Override default score gap delta Returns: SlotResolutionResponse with appropriate status """ # Get thresholds min_score = min_score_override or self.get_min_score(entity_type) score_gap_delta = score_gap_delta_override or self.score_gap_delta # Filter candidates above min_score qualified_candidates = [ c for c in candidates if c.confidence >= min_score ] logger.debug( f"Decisioning for '{input_query}': " f"{len(candidates)} total candidates, " f"{len(qualified_candidates)} above threshold {min_score}" ) # No matches above threshold if len(qualified_candidates) == 0: return self._create_no_match_response( entity_type=entity_type, input_query=input_query, normalized_query=normalized_query, min_score=min_score ) # Single match above threshold if len(qualified_candidates) == 1: return self._create_resolved_response( candidate=qualified_candidates[0], input_query=input_query, normalized_query=normalized_query, method=ResolutionMethod.FUZZY_MATCH, config={"minScore": min_score, "scoreGapDelta": score_gap_delta} ) # Multiple matches - check score gap top1 = qualified_candidates[0] top2 = qualified_candidates[1] score_gap = top1.confidence - top2.confidence logger.debug( f"Top 2 candidates: " f"1st={top1.canonical_name} ({top1.confidence:.3f}), " f"2nd={top2.canonical_name} ({top2.confidence:.3f}), " f"gap={score_gap:.3f}" ) # Clear winner (large score gap) if score_gap >= score_gap_delta: return self._create_resolved_response( candidate=top1, input_query=input_query, normalized_query=normalized_query, method=ResolutionMethod.FUZZY_MATCH_WITH_GAP, config={ "minScore": min_score, "scoreGapDelta": score_gap_delta, "actualGap": score_gap } ) # Ambiguous - require disambiguation return self._create_disambiguation_response( candidates=qualified_candidates[:self.max_candidates], entity_type=entity_type, input_query=input_query, normalized_query=normalized_query, config={"minScore": min_score, "scoreGapDelta": score_gap_delta} ) def _create_resolved_response( self, candidate: Candidate, input_query: str, normalized_query: str, method: ResolutionMethod, config: Dict[str, Any] ) -> SlotResolutionResponse: """Create a RESOLVED response.""" resolved_entity = ResolvedEntity( id=candidate.id, canonical_name=candidate.canonical_name, entity_type=candidate.entity_type, confidence=candidate.confidence, method=method, attributes=candidate.attributes ) return SlotResolutionResponse( status=ResolutionStatus.RESOLVED, resolved=resolved_entity, input_echo=input_query, normalization=normalized_query, method=method, config=config ) def _create_disambiguation_response( self, candidates: List[Candidate], entity_type: str, input_query: str, normalized_query: str, config: Dict[str, Any] ) -> SlotResolutionResponse: """Create a MULTIPLE_MATCHES response.""" guidance_text = ( f"Multiple {entity_type}s match '{input_query}'. " f"Please select one:" ) return SlotResolutionResponse( status=ResolutionStatus.MULTIPLE_MATCHES, candidates=candidates, input_echo=input_query, normalization=normalized_query, guidance_text=guidance_text, config=config ) def _create_no_match_response( self, entity_type: str, input_query: str, normalized_query: str, min_score: float ) -> SlotResolutionResponse: """Create a NO_MATCH response.""" error_message = f"No {entity_type} found matching '{input_query}'" return SlotResolutionResponse( status=ResolutionStatus.NO_MATCH, input_echo=input_query, normalization=normalized_query, error=error_message, config={"minScore": min_score} )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ShivamPansuriya/MCP-server-Python'

If you have feedback or need assistance with the MCP directory API, please join our Discord server