Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
A comprehensive collection of Agent Skills for context engineering, multi-agent architectures, and production agent systems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
skills/context-degradation/scripts/degradation_detector.py
1"""2Context Degradation Detection — Public API3============================================45Detect, measure, and diagnose context degradation patterns in LLM agent systems.67Public API:8measure_attention_distribution — Map attention weight across context positions.9detect_lost_in_middle — Flag critical information in degraded-attention regions.10analyze_context_structure — Assess structural degradation risk factors.11PoisoningDetector — Detect context poisoning indicators (error accumulation,12contradictions, hallucination markers).13ContextHealthAnalyzer — Run composite health analysis combining attention,14poisoning, and utilization metrics.15analyze_agent_context — One-call convenience function for agent sessions.1617PRODUCTION NOTES:18- The attention estimation functions simulate U-shaped attention curves for demonstration19purposes. Production systems should extract actual attention weights from model internals20when available (e.g., via TransformerLens or model-specific APIs).21- Token estimation uses simplified heuristics (~1 token per whitespace-split word).22Production systems should use model-specific tokenizers for accurate counts.23- Poisoning and hallucination detection uses pattern matching as a proxy. Production24systems may benefit from fine-tuned classifiers or model-based detection.25"""2627import random28import re29from typing import Dict, List, Optional3031__all__ = [32"measure_attention_distribution",33"detect_lost_in_middle",34"analyze_context_structure",35"PoisoningDetector",36"ContextHealthAnalyzer",37"analyze_agent_context",38]394041# ---------------------------------------------------------------------------42# Attention Distribution Analysis43# ---------------------------------------------------------------------------4445def measure_attention_distribution(46context_tokens: List[str],47query: str,48) -> List[Dict[str, object]]:49"""Map simulated attention weight to each context position.5051Use when: diagnosing whether critical information sits in the52low-attention middle region of a long context.5354Args:55context_tokens: Whitespace-split tokens (or chunks) of the context.56query: The query or task description the context is meant to support.5758Returns:59List of dicts, one per position, each containing:60position (int), attention (float), region (str), tokens (str | None).61"""62n = len(context_tokens)63attention_by_position: List[Dict[str, object]] = []6465for position in range(n):66is_beginning = position < n * 0.167is_end = position > n * 0.96869attention = _estimate_attention(position, n, is_beginning, is_end)7071attention_by_position.append({72"position": position,73"attention": attention,74"region": "attention_favored" if (is_beginning or is_end) else "attention_degraded",75"tokens": context_tokens[position][:50] if position < 5 or position > n - 5 else None,76})7778return attention_by_position798081def _estimate_attention(82position: int,83total: int,84is_beginning: bool,85is_end: bool,86) -> float:87"""Estimate attention weight for a single position.8889Simulates the U-shaped attention curve documented in lost-in-middle research:90- Beginning tokens receive high attention (primacy / attention-sink effect).91- End tokens receive high attention (recency effect).92- Middle tokens receive degraded attention.9394IMPORTANT: This is a simulation for demonstration. Production systems should95extract actual attention weights from model forward passes or use96interpretability libraries (e.g., TransformerLens).97"""98if is_beginning:99return 0.8 + random.random() * 0.2100elif is_end:101return 0.7 + random.random() * 0.3102else:103middle_progress = (position - total * 0.1) / (total * 0.8)104base_attention = 0.3 * (1 - middle_progress) + 0.1 * middle_progress105return base_attention + random.random() * 0.1106107108# ---------------------------------------------------------------------------109# Lost-in-Middle Detection110# ---------------------------------------------------------------------------111112def detect_lost_in_middle(113critical_positions: List[int],114attention_distribution: List[Dict[str, object]],115) -> Dict[str, object]:116"""Check if critical information sits in attention-degraded positions.117118Use when: context has been assembled and you need to verify that119high-priority content is not buried in the low-attention middle zone.120121Args:122critical_positions: Indices into the context that hold critical info.123attention_distribution: Output of ``measure_attention_distribution``.124125Returns:126Dict with keys: at_risk (list[int]), safe (list[int]),127recommendations (list[str]), degradation_score (float 0-1).128"""129results: Dict[str, object] = {130"at_risk": [],131"safe": [],132"recommendations": [],133"degradation_score": 0.0,134}135136at_risk_count = 0137total_critical = len(critical_positions)138139for pos in critical_positions:140if pos < len(attention_distribution):141region = attention_distribution[pos]["region"]142if region == "attention_degraded":143results["at_risk"].append(pos)144at_risk_count += 1145else:146results["safe"].append(pos)147148if total_critical > 0:149results["degradation_score"] = at_risk_count / total_critical150151if results["at_risk"]:152results["recommendations"].extend([153"Move critical information to attention-favored positions",154"Use explicit markers to highlight critical information",155"Consider splitting context to reduce middle section",156f"{at_risk_count}/{total_critical} critical items are in degraded region",157])158159return results160161162# ---------------------------------------------------------------------------163# Context Structure Analysis164# ---------------------------------------------------------------------------165166def analyze_context_structure(context: str) -> Dict[str, object]:167"""Assess structural degradation risk factors in a context string.168169Use when: evaluating whether a context layout puts too much content170in the low-attention middle zone before sending it to a model.171172Args:173context: The full context string to analyze.174175Returns:176Dict with total_lines, sections list, middle_content_ratio,177and degradation_risk level (low / medium / high).178"""179lines = context.split("\n")180sections: List[Dict[str, object]] = []181182current_section: Dict[str, object] = {"start": 0, "type": "unknown", "length": 0}183184for i, line in enumerate(lines):185if line.startswith("#"):186if current_section["length"] > 0:187sections.append(current_section)188current_section = {189"start": i,190"type": "header",191"length": 1,192"header": line.lstrip("#").strip(),193}194else:195current_section["length"] += 1196197sections.append(current_section)198199n = len(lines)200middle_start = int(n * 0.3)201middle_end = int(n * 0.7)202203middle_content = sum(204s["length"] for s in sections205if s["start"] >= middle_start and s["start"] <= middle_end206)207208middle_ratio = middle_content / n if n > 0 else 0209return {210"total_lines": n,211"sections": sections,212"middle_content_ratio": middle_ratio,213"degradation_risk": (214"high" if middle_ratio > 0.5215else "medium" if middle_ratio > 0.3216else "low"217),218}219220221# ---------------------------------------------------------------------------222# Context Poisoning Detection223# ---------------------------------------------------------------------------224225class PoisoningDetector:226"""Detect context poisoning indicators via pattern matching.227228Use when: context quality is suspect — outputs degrade on previously229successful tasks, tool calls misalign, or hallucinations persist230despite corrections.231"""232233def __init__(self) -> None:234self.claims: List[Dict[str, object]] = []235self.error_patterns: List[str] = [236r"error",237r"failed",238r"exception",239r"cannot",240r"unable",241r"invalid",242r"not found",243]244245def extract_claims(self, text: str) -> List[Dict[str, object]]:246"""Extract claims from text for verification tracking.247248Use when: building a provenance chain to trace which claims249entered context and whether they have been verified.250251Args:252text: Raw text to extract claims from.253254Returns:255List of claim dicts with id, text, verified status, and256error indicator flag.257"""258sentences = text.split(".")259claims: List[Dict[str, object]] = []260261for i, sentence in enumerate(sentences):262sentence = sentence.strip()263if len(sentence) < 10:264continue265266claims.append({267"id": i,268"text": sentence,269"verified": None,270"has_error_indicator": any(271re.search(pattern, sentence, re.IGNORECASE)272for pattern in self.error_patterns273),274})275276self.claims.extend(claims)277return claims278279def detect_poisoning(self, context: str) -> Dict[str, object]:280"""Detect potential context poisoning indicators.281282Use when: agent output quality has degraded and context283contamination is suspected. Checks for error accumulation,284contradictions, and hallucination markers.285286Args:287context: The full context string to analyze.288289Returns:290Dict with poisoning_risk (bool), indicators (list),291and overall_risk level (low / medium / high).292"""293indicators: List[Dict[str, object]] = []294295# Check for error accumulation296error_count = sum(2971 for pattern in self.error_patterns298if re.search(pattern, context, re.IGNORECASE)299)300301if error_count > 3:302indicators.append({303"type": "error_accumulation",304"count": error_count,305"severity": "high" if error_count > 5 else "medium",306"message": f"Found {error_count} error indicators in context",307})308309# Check for contradiction patterns310contradictions = self._detect_contradictions(context)311if contradictions:312indicators.append({313"type": "contradictions",314"count": len(contradictions),315"examples": contradictions[:3],316"severity": "high",317"message": f"Found {len(contradictions)} potential contradictions",318})319320# Check for hallucination markers321hallucination_markers = self._detect_hallucination_markers(context)322if hallucination_markers:323indicators.append({324"type": "hallucination_markers",325"count": len(hallucination_markers),326"severity": "medium",327"message": f"Found {len(hallucination_markers)} phrases associated with uncertain claims",328})329330return {331"poisoning_risk": len(indicators) > 0,332"indicators": indicators,333"overall_risk": (334"high" if len(indicators) > 2335else "medium" if len(indicators) > 0336else "low"337),338}339340def _detect_contradictions(self, text: str) -> List[str]:341"""Detect potential contradictions in text."""342contradictions: List[str] = []343344conflict_patterns = [345(r"however", r"but"),346(r"on the other hand", r"instead"),347(r"although", r"yet"),348(r"despite", r"nevertheless"),349]350351for pattern1, pattern2 in conflict_patterns:352if re.search(pattern1, text, re.IGNORECASE) and re.search(pattern2, text, re.IGNORECASE):353sentences = text.split(".")354for sentence in sentences:355if (re.search(pattern1, sentence, re.IGNORECASE)356or re.search(pattern2, sentence, re.IGNORECASE)):357stripped = sentence.strip()358if stripped and len(stripped) < 200:359contradictions.append(stripped[:100])360361return contradictions[:5]362363def _detect_hallucination_markers(self, text: str) -> List[str]:364"""Detect phrases associated with uncertain or hallucinated claims."""365markers = [366"may have been",367"might have",368"could potentially",369"possibly",370"apparently",371"reportedly",372"it is said that",373"sources suggest",374"believed to be",375"thought to be",376]377378return [marker for marker in markers if marker in text.lower()]379380381# ---------------------------------------------------------------------------382# Context Health Analyzer383# ---------------------------------------------------------------------------384385class ContextHealthAnalyzer:386"""Run composite health analysis on a context string.387388Use when: performing routine health checks on agent context during389long-running sessions, or when setting up automated monitoring that390triggers compaction or isolation before degradation hits.391392Combines attention distribution, poisoning detection, and utilization393metrics into a single 0-1 health score with status interpretation.394"""395396def __init__(self, context_limit: int = 100_000) -> None:397self.context_limit: int = context_limit398self.metrics_history: List[Dict[str, object]] = []399400def analyze(401self,402context: str,403critical_positions: Optional[List[int]] = None,404) -> Dict[str, object]:405"""Perform comprehensive context health analysis.406407Use when: a single health-check call is needed that covers408attention, poisoning, and utilization in one pass.409410Args:411context: The full context string to analyze.412critical_positions: Indices of tokens holding critical info.413Defaults to the first 10 positions if not provided.414415Returns:416Dict with health_score (float 0-1), status (str),417metrics (dict), issues (dict), and recommendations (list[str]).418"""419tokens = context.split()420421token_count = len(tokens)422utilization = token_count / self.context_limit423424attention_dist = measure_attention_distribution(425tokens[:1000], # Sample for efficiency426"current_task",427)428429degradation = detect_lost_in_middle(430critical_positions or list(range(10)),431attention_dist,432)433434poisoning = PoisoningDetector().detect_poisoning(context)435436health_score = self._calculate_health_score(437utilization=utilization,438degradation=degradation["degradation_score"],439poisoning_risk=1.0 if poisoning["poisoning_risk"] else 0.0,440)441442result: Dict[str, object] = {443"health_score": health_score,444"status": self._interpret_score(health_score),445"metrics": {446"token_count": token_count,447"utilization": utilization,448"degradation_score": degradation["degradation_score"],449"poisoning_risk": poisoning["overall_risk"],450},451"issues": {452"lost_in_middle": degradation,453"poisoning": poisoning,454},455"recommendations": self._generate_recommendations(456utilization, degradation, poisoning457),458}459460self.metrics_history.append(result)461return result462463def _calculate_health_score(464self,465utilization: float,466degradation: float,467poisoning_risk: float,468) -> float:469"""Calculate composite health score (0-1, higher is healthier)."""470utilization_penalty = min(utilization * 0.5, 0.3)471degradation_penalty = degradation * 0.3472poisoning_penalty = poisoning_risk * 0.2473474score = 1.0 - utilization_penalty - degradation_penalty - poisoning_penalty475return max(0.0, min(1.0, score))476477def _interpret_score(self, score: float) -> str:478"""Map numeric score to human-readable status."""479if score > 0.8:480return "healthy"481elif score > 0.6:482return "warning"483elif score > 0.4:484return "degraded"485else:486return "critical"487488def _generate_recommendations(489self,490utilization: float,491degradation: Dict[str, object],492poisoning: Dict[str, object],493) -> List[str]:494"""Generate actionable recommendations based on analysis."""495recommendations: List[str] = []496497if utilization > 0.8:498recommendations.append("Context near limit - consider compaction")499recommendations.append("Implement observation masking for tool outputs")500501if degradation.get("at_risk"):502recommendations.append("Critical information in degraded attention region")503recommendations.append("Move key information to beginning or end of context")504505if poisoning["poisoning_risk"]:506recommendations.append("Context poisoning indicators detected")507recommendations.append("Review and remove potentially erroneous information")508509if not recommendations:510recommendations.append("Context appears healthy - continue monitoring")511512return recommendations513514515# ---------------------------------------------------------------------------516# Convenience Function517# ---------------------------------------------------------------------------518519def analyze_agent_context(520context: str,521context_limit: int = 80_000,522critical_positions: Optional[List[int]] = None,523) -> Dict[str, object]:524"""One-call health analysis for an agent session.525526Use when: a quick health check is needed without manually configuring527an analyzer instance. Prints a summary and returns the full result dict.528529Args:530context: The full context string to analyze.531context_limit: Maximum token budget for this agent's context window.532critical_positions: Indices of critical tokens. Defaults to [0..4].533534Returns:535Full health analysis dict from ``ContextHealthAnalyzer.analyze``.536"""537analyzer = ContextHealthAnalyzer(context_limit=context_limit)538539if critical_positions is None:540critical_positions = list(range(5))541542result = analyzer.analyze(context, critical_positions)543544print(f"Health Score: {result['health_score']:.2f}")545print(f"Status: {result['status']}")546print("Recommendations:")547for rec in result["recommendations"]:548print(f" - {rec}")549550return result551552553# ---------------------------------------------------------------------------554# CLI Demo555# ---------------------------------------------------------------------------556557if __name__ == "__main__":558# Demonstrate the public API with synthetic context559print("=" * 60)560print("Context Degradation Detector — Demo")561print("=" * 60)562563# Build a synthetic context with identifiable sections564intro = "System prompt: Analyze quarterly revenue data and produce a report. "565middle = "Background information. " * 200 # Filler to simulate long context566conclusion = "Key finding: Revenue increased 15% year-over-year. "567sample_context = intro + middle + conclusion568569print(f"\nSample context length: {len(sample_context.split())} tokens")570571# 1. Structure analysis572print("\n--- Structure Analysis ---")573structure = analyze_context_structure(sample_context)574print(f" Lines: {structure['total_lines']}")575print(f" Middle content ratio: {structure['middle_content_ratio']:.2f}")576print(f" Degradation risk: {structure['degradation_risk']}")577578# 2. Attention distribution (first 50 tokens for brevity)579print("\n--- Attention Distribution (first 50 tokens) ---")580tokens = sample_context.split()[:50]581attention = measure_attention_distribution(tokens, "quarterly revenue")582favored = sum(1 for a in attention if a["region"] == "attention_favored")583degraded = sum(1 for a in attention if a["region"] == "attention_degraded")584print(f" Favored positions: {favored}")585print(f" Degraded positions: {degraded}")586587# 3. Lost-in-middle detection588print("\n--- Lost-in-Middle Detection ---")589critical = [0, 1, 2, 25, 26, 48, 49] # Start, middle, end590lim_result = detect_lost_in_middle(critical, attention)591print(f" At risk: {lim_result['at_risk']}")592print(f" Safe: {lim_result['safe']}")593print(f" Degradation score: {lim_result['degradation_score']:.2f}")594595# 4. Poisoning detection596print("\n--- Poisoning Detection ---")597poisoned_context = (598"The API returned an error. However, the system reportedly "599"recovered. But the error persisted and the request failed. "600"Unable to parse the response. Sources suggest the endpoint "601"may have been deprecated. Although retries succeeded, yet "602"the invalid token caused an exception."603)604detector = PoisoningDetector()605poisoning = detector.detect_poisoning(poisoned_context)606print(f" Poisoning risk: {poisoning['poisoning_risk']}")607print(f" Overall risk: {poisoning['overall_risk']}")608for indicator in poisoning["indicators"]:609print(f" [{indicator['severity']}] {indicator['message']}")610611# 5. Full health analysis612print("\n--- Full Health Analysis ---")613result = analyze_agent_context(sample_context)614print(f"\n Full result keys: {list(result.keys())}")615