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-fundamentals/scripts/context_manager.py
1"""2Context Management Utilities for Agent Systems.34Public API5----------6Functions:7estimate_token_count — Rough token estimate from text (demo only).8estimate_message_tokens — Token estimate for a message list.9count_tokens_by_type — Break down token usage by context component.10truncate_context — Trim a context string to a token budget.11truncate_messages — Trim message history while preserving structure.12validate_context_structure — Detect empty, oversized, or duplicate sections.13build_agent_context — Assemble an optimized context dict from parts.1415Classes:16ContextBuilder — Priority-aware context assembly with budgets.17ProgressiveDisclosureManager — Lazy file loading with caching.1819Usage20-----21Import individual utilities or use `build_agent_context` as the high-level22entry point:2324from context_manager import build_agent_context25result = build_agent_context(26task="Refactor auth module",27system_prompt="You are a senior Python engineer.",28documents=["# Auth module docs ..."],29)30print(result["usage_report"])3132Run this module directly (`python context_manager.py`) for an interactive demo33that builds a sample context and prints the usage report.3435Note: Token estimation in this module uses a character-ratio heuristic. For36production systems, replace `estimate_token_count` with a real tokenizer37(tiktoken for OpenAI, Anthropic's token-counting API, etc.).38"""3940from __future__ import annotations4142import hashlib43from typing import Any, Dict, List, Optional4445__all__ = [46"estimate_token_count",47"estimate_message_tokens",48"count_tokens_by_type",49"truncate_context",50"truncate_messages",51"validate_context_structure",52"build_agent_context",53"ContextBuilder",54"ProgressiveDisclosureManager",55]565758# ---------------------------------------------------------------------------59# Token estimation60# ---------------------------------------------------------------------------6162def estimate_token_count(text: str) -> int:63"""Return a rough token estimate for *text*.6465Uses the ~4 characters-per-token heuristic for English prose.6667Use when: quick budget checks during development or logging. Do NOT rely68on this for hard budget enforcement — code, URLs, and non-English text69tokenize at very different ratios (see module docstring).7071WARNING: Production systems must use a real tokenizer:72- OpenAI models → ``tiktoken``73- Anthropic → Anthropic token-counting API74- Others → provider-specific tokenizer75"""76return len(text) // 4777879def estimate_message_tokens(messages: List[Dict[str, Any]]) -> int:80"""Estimate total tokens across a list of chat messages.8182Use when: deciding whether to trigger compaction on message history.83Each message adds ~10 tokens of role/formatting overhead on top of84its content tokens.85"""86total = 087for msg in messages:88content = msg.get("content", "")89total += estimate_token_count(content)90total += 10 # Overhead for role/formatting91return total929394def count_tokens_by_type(context: Dict[str, Any]) -> Dict[str, int]:95"""Break down token usage by context component type.9697Use when: profiling where tokens are spent so the highest-cost98component can be targeted for compression first.99100Recognized keys in *context*: ``system``, ``tools`` (list),101``documents`` (list), ``messages`` (list).102"""103breakdown: Dict[str, int] = {104"system_prompt": 0,105"tool_definitions": 0,106"retrieved_documents": 0,107"message_history": 0,108"tool_outputs": 0,109"other": 0,110}111112if "system" in context:113breakdown["system_prompt"] = estimate_token_count(context["system"])114115if "tools" in context:116for tool in context["tools"]:117breakdown["tool_definitions"] += estimate_token_count(str(tool))118119if "documents" in context:120for doc in context["documents"]:121breakdown["retrieved_documents"] += estimate_token_count(doc)122123if "messages" in context:124breakdown["message_history"] = estimate_message_tokens(context["messages"])125126return breakdown127128129# ---------------------------------------------------------------------------130# Context Builder131# ---------------------------------------------------------------------------132133class ContextBuilder:134"""Build context with priority-aware budget management.135136Use when: assembling context from multiple sources (system prompt,137retrieved documents, task description) and enforcing a hard token138ceiling. Higher-priority sections are kept first when the budget is139tight.140141Example::142143builder = ContextBuilder(context_limit=80_000)144builder.add_section("system", prompt, priority=10)145builder.add_section("task", task_text, priority=9)146built = builder.build()147"""148149def __init__(self, context_limit: int = 100_000) -> None:150self.context_limit: int = context_limit151self.sections: Dict[str, Dict[str, Any]] = {}152self.order: List[str] = []153154def add_section(155self,156name: str,157content: str,158priority: int = 0,159category: str = "other",160) -> None:161"""Add or replace a named section.162163Higher *priority* values are kept first when the budget is tight.164"""165if name not in self.sections:166self.order.append(name)167168self.sections[name] = {169"content": content,170"priority": priority,171"category": category,172"tokens": estimate_token_count(content),173}174175def build(self, max_tokens: Optional[int] = None) -> str:176"""Assemble context string within the token budget.177178Sections are included in descending priority order until the179budget is exhausted. Returns the concatenated text of all180included sections.181"""182limit = max_tokens or self.context_limit183184sorted_sections = sorted(185self.order,186key=lambda n: self.sections[n]["priority"],187reverse=True,188)189190context_parts: List[str] = []191current_tokens = 0192193for name in sorted_sections:194section = self.sections[name]195section_tokens = section["tokens"]196197if current_tokens + section_tokens <= limit:198context_parts.append(section["content"])199current_tokens += section_tokens200201return "\n\n".join(context_parts)202203def get_usage_report(self) -> Dict[str, Any]:204"""Return a summary of current context utilization.205206Use when: logging context composition during development or207deciding whether to trigger compaction.208"""209total = sum(s["tokens"] for s in self.sections.values())210return {211"total_tokens": total,212"limit": self.context_limit,213"utilization": total / self.context_limit if self.context_limit else 0,214"by_section": {215name: s["tokens"] for name, s in self.sections.items()216},217"status": self._get_status(total),218}219220def _get_status(self, total: int) -> str:221"""Return 'critical', 'warning', or 'healthy' based on utilization."""222ratio = total / self.context_limit if self.context_limit else 0223if ratio > 0.9:224return "critical"225elif ratio > 0.7:226return "warning"227else:228return "healthy"229230231# ---------------------------------------------------------------------------232# Context Truncation233# ---------------------------------------------------------------------------234235def truncate_context(236context: str,237max_tokens: int,238preserve_start: bool = True,239) -> str:240"""Truncate *context* to approximately *max_tokens*.241242Use when: a single large text block must fit a hard budget and243semantic chunking is not available.244245Set *preserve_start* to ``True`` (default) to keep the beginning246(system prompts, top-of-file content) or ``False`` to keep the end247(most recent information).248"""249tokens = context.split()250if len(tokens) <= max_tokens:251return context252253if preserve_start:254kept = tokens[:max_tokens]255else:256kept = tokens[-max_tokens:]257258return " ".join(kept)259260261def truncate_messages(262messages: List[Dict[str, Any]],263max_tokens: int,264) -> List[Dict[str, Any]]:265"""Truncate message history while preserving structural integrity.266267Use when: message history exceeds budget and compaction has not yet268been implemented. Keeps: (1) the system prompt, (2) any existing269summary message, and (3) the most recent messages that fit.270271Strategy:2721. Always keep the system prompt.2732. Keep any existing summary message.2743. Fill remaining budget with the most recent messages.275"""276system_prompt: Optional[Dict[str, Any]] = None277recent_messages: List[Dict[str, Any]] = []278summary: Optional[Dict[str, Any]] = None279280for msg in messages:281if msg.get("role") == "system":282system_prompt = msg283elif msg.get("is_summary"):284summary = msg285else:286recent_messages.append(msg)287288tokens_for_system = (289estimate_token_count(system_prompt["content"]) if system_prompt else 0290)291tokens_for_summary = (292estimate_token_count(summary["content"]) if summary else 0293)294available = max_tokens - tokens_for_system - tokens_for_summary295296tokens_for_recent = estimate_message_tokens(recent_messages)297if tokens_for_recent > available:298truncated_recent: List[Dict[str, Any]] = []299current_tokens = 0300for msg in reversed(recent_messages):301msg_tokens = estimate_token_count(msg.get("content", ""))302if current_tokens + msg_tokens <= available:303truncated_recent.insert(0, msg)304current_tokens += msg_tokens305recent_messages = truncated_recent306307result: List[Dict[str, Any]] = []308if system_prompt:309result.append(system_prompt)310if summary:311result.append(summary)312result.extend(recent_messages)313return result314315316# ---------------------------------------------------------------------------317# Context Validation318# ---------------------------------------------------------------------------319320def validate_context_structure(context: Dict[str, Any]) -> Dict[str, Any]:321"""Validate a context dict for common structural issues.322323Use when: testing context assembly before sending to the model.324Checks for empty sections, excessive length, missing recommended325sections, and potential duplicate content.326327Returns a dict with ``valid`` (bool), ``issues`` (list), and328``recommendations`` (list).329"""330issues: List[str] = []331recommendations: List[str] = []332333# Check for empty sections (skip list-type values like documents334# which are legitimately empty when no documents are retrieved)335for section, content in context.items():336if content is None or (isinstance(content, str) and not content):337issues.append(f"Empty {section} section")338recommendations.append(f"Remove or populate {section}")339340# Check for excessive length341total_tokens = sum(estimate_token_count(str(c)) for c in context.values())342if total_tokens > 80_000:343issues.append(344f"Context length ({total_tokens} tokens) exceeds recommended limit"345)346recommendations.append("Consider context compaction or partitioning")347348# Check for missing sections349recommended_sections = ["system", "task"]350for section in recommended_sections:351if section not in context:352issues.append(f"Missing recommended section: {section}")353recommendations.append(354f"Add {section} section with relevant information"355)356357# Check for duplicate content (first 1000 chars, hashed for consistency)358seen_content: set[str] = set()359for section, content in context.items():360content_str = str(content)[:1000]361content_hash = hashlib.md5(content_str.encode()).hexdigest()362if content_hash in seen_content:363issues.append(f"Potential duplicate content in {section}")364seen_content.add(content_hash)365366return {367"valid": len(issues) == 0,368"issues": issues,369"recommendations": recommendations,370}371372373# ---------------------------------------------------------------------------374# Progressive Disclosure375# ---------------------------------------------------------------------------376377class ProgressiveDisclosureManager:378"""Lazy loader for progressive disclosure of file-based context.379380Use when: an agent has access to many reference files but should381only pay the token cost for files that the current task actually382needs. Summaries are loaded first; detail files are loaded on demand383and cached for the session.384385Example::386387pdm = ProgressiveDisclosureManager(base_dir="docs")388overview = pdm.load_summary("docs/api_summary.md")389# ... later, when detail is needed ...390detail = pdm.load_detail("docs/api/endpoints.md")391"""392393def __init__(self, base_dir: str = ".") -> None:394self.base_dir: str = base_dir395self.loaded_files: Dict[str, str] = {}396397def load_summary(self, summary_path: str) -> str:398"""Load a summary file, returning cached content if available."""399if summary_path in self.loaded_files:400return self.loaded_files[summary_path]401try:402with open(summary_path, "r") as f:403content = f.read()404self.loaded_files[summary_path] = content405return content406except FileNotFoundError:407return ""408409def load_detail(self, detail_path: str, force: bool = False) -> str:410"""Load a detail file on demand.411412Set *force* to ``True`` to bypass the cache and re-read the file413(useful when the underlying file may have changed).414"""415if not force and detail_path in self.loaded_files:416return self.loaded_files[detail_path]417try:418with open(detail_path, "r") as f:419content = f.read()420self.loaded_files[detail_path] = content421return content422except FileNotFoundError:423return ""424425def get_contextual_info(self, reference: Dict[str, Any]) -> str:426"""Return summary or detail based on the reference's flags.427428Use when: a reference dict carries both ``summary_path`` and429``detail_path`` and the caller sets ``need_detail=True`` only430when full content is required.431"""432summary_path = reference.get("summary_path")433detail_path = reference.get("detail_path")434need_detail = reference.get("need_detail", False)435436if need_detail and detail_path:437return self.load_detail(detail_path)438elif summary_path:439return self.load_summary(summary_path)440else:441return ""442443444# ---------------------------------------------------------------------------445# High-level entry point446# ---------------------------------------------------------------------------447448def build_agent_context(449task: str,450system_prompt: str,451documents: Optional[List[str]] = None,452context_limit: int = 80_000,453) -> Dict[str, Any]:454"""Build an optimized, validated context dict for an agent task.455456Use when: assembling context for a single inference call. Combines457system prompt, task description, and optional retrieved documents458into a priority-ordered context string, then validates the result.459460Returns a dict with keys ``context`` (str), ``usage_report`` (dict),461and ``validation`` (dict).462"""463builder = ContextBuilder(context_limit=context_limit)464465# System prompt — highest priority, persists across turns466builder.add_section("system", system_prompt, priority=10, category="system")467468# Task description — second priority469builder.add_section("task", task, priority=9, category="task")470471# Retrieved documents — loaded just-in-time472if documents:473for i, doc in enumerate(documents):474builder.add_section(475f"document_{i}",476doc,477priority=5,478category="retrieved",479)480481context_dict: Dict[str, Any] = {482"system": system_prompt,483"task": task,484"documents": documents or [],485}486487validation = validate_context_structure(context_dict)488489return {490"context": builder.build(),491"usage_report": builder.get_usage_report(),492"validation": validation,493}494495496# ---------------------------------------------------------------------------497# Demo498# ---------------------------------------------------------------------------499500if __name__ == "__main__":501print("=== Context Manager Demo ===\n")502503sample_prompt = (504"You are a senior Python engineer. Follow PEP 8, use type hints, "505"and write docstrings for all public functions."506)507sample_task = "Refactor the authentication module to use OAuth 2.0."508sample_docs = [509"# OAuth 2.0 Reference\nThe OAuth 2.0 authorization framework...",510"# Current Auth Module\ndef login(user, password): ...",511]512513result = build_agent_context(514task=sample_task,515system_prompt=sample_prompt,516documents=sample_docs,517)518519report = result["usage_report"]520print(f"Total tokens : {report['total_tokens']}")521print(f"Utilization : {report['utilization']:.1%}")522print(f"Status : {report['status']}")523print(f"\nBreakdown by section:")524for section, tokens in report["by_section"].items():525print(f" {section:20s} : {tokens:,} tokens")526527validation = result["validation"]528if validation["valid"]:529print("\nValidation : PASSED")530else:531print(f"\nValidation : FAILED")532for issue in validation["issues"]:533print(f" - {issue}")534