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/tool-design/scripts/description_generator.py
1"""2Tool Description Engineering -- Generation and Evaluation Utilities.34Use when: building, auditing, or iterating on tool descriptions for agent5systems. Provides templates for structured descriptions, a scoring evaluator6that flags vague or incomplete descriptions, error-message generators that7produce agent-recoverable responses, and a builder that assembles complete8tool schemas.910Typical workflow:111. Define a tool spec with ``ToolSchemaBuilder``.122. Generate a rendered description with ``generate_tool_description``.133. Score the description with ``ToolDescriptionEvaluator.evaluate``.144. Generate error templates with ``ErrorMessageGenerator.generate``.1516Example::1718builder = ToolSchemaBuilder("get_customer")19builder.set_description("Retrieve customer record", "Full details...")20builder.add_parameter("customer_id", "string", "CUST-######", required=True)21schema = builder.build()2223desc = generate_tool_description(schema)24scores = ToolDescriptionEvaluator().evaluate(desc, schema)25"""2627from __future__ import annotations2829from dataclasses import dataclass, field30from typing import Any, Dict, List, Optional, Protocol, Sequence31import json32import re333435__all__ = [36"generate_tool_description",37"generate_usage_context",38"ToolDescriptionEvaluator",39"ErrorMessageGenerator",40"ToolSchemaBuilder",41]424344# ---------------------------------------------------------------------------45# Protocols -- lightweight structural typing for tool specs46# ---------------------------------------------------------------------------4748class ToolSpec(Protocol):49"""Structural interface expected by generation helpers.5051Use when: passing tool metadata objects that were not built with52``ToolSchemaBuilder`` (e.g., third-party specs).53"""5455name: str56description: str57triggers: Sequence[str]58examples: Sequence[Any]59parameters: Sequence[Dict[str, Any]]60returns: Dict[str, Any]61errors: Sequence[Dict[str, Any]]626364@dataclass65class _BuiltToolSpec:66"""Concrete implementation of ToolSpec returned by ToolSchemaBuilder.build()."""6768name: str69description: str70triggers: List[str]71examples: List[Dict[str, str]]72parameters: List[Dict[str, Any]]73returns: Dict[str, Any]74errors: List[Dict[str, Any]]757677# ---------------------------------------------------------------------------78# Description Templates79# ---------------------------------------------------------------------------8081TOOL_DESCRIPTION_TEMPLATE: str = """82## {tool_name}8384{detailed_description}8586### When to Use87{usage_context}8889### Parameters90{parameters_description}9192### Returns93{returns_description}9495### Errors96{errors_description}97"""9899PARAM_TEMPLATE: str = """100- **{param_name}** ({param_type}{required_label})101102{param_description}103{default_label}104"""105106107# ---------------------------------------------------------------------------108# Generation helpers109# ---------------------------------------------------------------------------110111def generate_tool_description(tool_spec: ToolSpec) -> str:112"""Render a complete markdown tool description from *tool_spec*.113114Use when: producing human-readable or agent-injectable documentation115from a structured spec object.116"""117description: str = TOOL_DESCRIPTION_TEMPLATE.format(118tool_name=tool_spec.name,119detailed_description=tool_spec.description,120usage_context=generate_usage_context(tool_spec),121parameters_description=_generate_parameters(tool_spec.parameters),122returns_description=_generate_returns(tool_spec.returns),123errors_description=_generate_errors(tool_spec.errors),124)125return description126127128def generate_usage_context(tool_spec: ToolSpec) -> str:129"""Build the 'When to Use' section from triggers and examples.130131Use when: the caller needs only the usage-context fragment rather132than the full rendered description.133"""134contexts: list[str] = []135136for trigger in tool_spec.triggers:137contexts.append(f"- When {trigger}")138139if tool_spec.examples:140contexts.append("\n**Examples**:\n")141for example in tool_spec.examples:142if isinstance(example, dict):143contexts.append(f"- Input: {example.get('input', '')}")144contexts.append(f" Output: {example.get('tool_call', '')}")145else:146contexts.append(f"- {example}")147148return "\n".join(contexts)149150151def _generate_parameters(parameters: Sequence[Dict[str, Any]]) -> str:152"""Render parameter list to markdown."""153parts: list[str] = []154for p in parameters:155required_label = " | required" if p.get("required") else " | optional"156default = p.get("default")157default_label = f"Default: {default}" if default is not None else ""158parts.append(159f"- **{p['name']}** ({p['type']}{required_label})\n"160f" {p['description']}\n"161f" {default_label}".rstrip()162)163return "\n".join(parts)164165166def _generate_returns(returns: Optional[Dict[str, Any]]) -> str:167"""Render the returns section to markdown."""168if not returns:169return "No return value documented."170desc = returns.get("description", "")171rtype = returns.get("type", "object")172return f"{rtype} -- {desc}"173174175def _generate_errors(errors: Sequence[Dict[str, Any]]) -> str:176"""Render error definitions to markdown."""177if not errors:178return "No error conditions documented."179parts: list[str] = []180for err in errors:181parts.append(f"- **{err['code']}**: {err['description']} -- {err.get('resolution', '')}")182return "\n".join(parts)183184185# ---------------------------------------------------------------------------186# Evaluator187# ---------------------------------------------------------------------------188189class ToolDescriptionEvaluator:190"""Score a rendered description against quality criteria.191192Use when: auditing existing tool descriptions for clarity,193completeness, accuracy, actionability, and consistency.194"""195196CRITERIA: List[str] = [197"clarity",198"completeness",199"accuracy",200"actionability",201"consistency",202]203204def evaluate(self, description: str, tool_spec: ToolSpec) -> Dict[str, float]:205"""Return per-criterion scores (0.0 -- 1.0) for *description*.206207Use when: running automated quality checks on tool descriptions208before deploying them into an agent system.209"""210results: Dict[str, float] = {211"clarity": self._check_clarity(description),212"completeness": self._check_completeness(description, tool_spec),213"accuracy": self._check_accuracy(description, tool_spec),214"actionability": self._check_actionability(description),215"consistency": self._check_consistency(description, tool_spec),216}217return results218219# -- private scoring helpers ------------------------------------------220221def _check_clarity(self, description: str) -> float:222"""Score description clarity (0-1).223224Use when: detecting vague or ambiguous language that would225confuse an agent during tool selection.226"""227vague_terms: list[str] = ["help", "assist", "thing", "stuff", "handle"]228vague_count: int = sum(1 for term in vague_terms if term in description.lower())229230ambiguous: list[str] = ["it", "this", "that"]231ambiguous_count: int = sum(1 for term in ambiguous if f" {term} " in description)232233clarity: float = 1.0 - (vague_count * 0.1) - (ambiguous_count * 0.05)234return max(0.0, clarity)235236def _check_completeness(self, description: str, tool_spec: ToolSpec) -> float:237"""Score presence of required sections (0-1).238239Use when: verifying a description has all mandatory sections240before publishing.241"""242required_patterns: list[tuple[str, str]] = [243("description", r"## " + re.escape(str(getattr(tool_spec, "name", "")))),244("parameters", r"### Parameters"),245("returns", r"### Returns"),246("errors", r"### Errors"),247]248present: int = sum(2491 for _, pattern in required_patterns if re.search(pattern, description)250)251return present / len(required_patterns)252253def _check_accuracy(self, description: str, tool_spec: ToolSpec) -> float:254"""Score alignment between description text and spec metadata.255256Use when: detecting description rot where the text no longer257matches the current tool spec.258"""259score = 1.0260# Check that tool name appears in description261if hasattr(tool_spec, "name") and tool_spec.name not in description:262score -= 0.3263# Check parameter names appear264if hasattr(tool_spec, "parameters"):265for param in tool_spec.parameters:266pname = param.get("name", "") if isinstance(param, dict) else ""267if pname and pname not in description:268score -= 0.15269return max(0.0, score)270271def _check_actionability(self, description: str) -> float:272"""Score whether the description contains actionable cues.273274Use when: confirming agents can determine correct usage from275the description alone.276"""277signals: list[str] = ["Use when", "Returns", "Errors", "Args", "Parameters"]278found: int = sum(1 for s in signals if s in description)279return min(1.0, found / max(1, len(signals)))280281def _check_consistency(self, description: str, tool_spec: ToolSpec) -> float:282"""Score naming and formatting consistency.283284Use when: checking that parameter and section naming follows285conventions across the tool collection.286"""287# Penalise mixed naming styles (camelCase vs snake_case)288camel = len(re.findall(r"[a-z][A-Z]", description))289snake = len(re.findall(r"[a-z]_[a-z]", description))290if camel > 0 and snake > 0:291return 0.5292return 1.0293294295# ---------------------------------------------------------------------------296# Error Message Generator297# ---------------------------------------------------------------------------298299class ErrorMessageGenerator:300"""Produce structured, agent-recoverable error messages.301302Use when: building error responses that tell agents what went wrong,303why, and how to correct the call.304"""305306TEMPLATES: Dict[str, str] = {307"NOT_FOUND": json.dumps({308"error": "{error_code}",309"message": "{specific_message}",310"resolution": "{how_to_resolve}",311"example": "{correct_format}",312}, indent=2),313314"INVALID_INPUT": json.dumps({315"error": "{error_code}",316"message": "Invalid {field}: {received_value}",317"expected_format": "{expected_format}",318"resolution": "Provide value matching {expected_format}",319}, indent=2),320321"RATE_LIMITED": json.dumps({322"error": "{error_code}",323"message": "Rate limit exceeded",324"retry_after": "{seconds}",325"resolution": "Wait {seconds} seconds before retrying",326}, indent=2),327}328329def generate(self, error_type: str, context: Dict[str, str]) -> str:330"""Render an error message for *error_type* using *context* values.331332Use when: a tool needs to return a structured error that an agent333can parse and act on.334"""335template: str = self.TEMPLATES.get(error_type, self.TEMPLATES["INVALID_INPUT"])336return template.format(**context)337338339# ---------------------------------------------------------------------------340# Schema Builder341# ---------------------------------------------------------------------------342343class ToolSchemaBuilder:344"""Fluent builder for complete tool schemas.345346Use when: defining a new tool's schema programmatically and want347compile-time structure rather than hand-written dictionaries.348"""349350def __init__(self, name: str) -> None:351self.name: str = name352self.description: str = ""353self.detailed_description: str = ""354self.parameters: List[Dict[str, Any]] = []355self.returns: Optional[Dict[str, Any]] = None356self.errors: List[Dict[str, str]] = []357self._triggers: List[str] = []358self._examples: List[Dict[str, str]] = []359360def set_description(self, short: str, detailed: str) -> "ToolSchemaBuilder":361"""Set short and detailed description sections.362363Use when: providing both a one-line summary and a full364multi-paragraph description for the tool.365"""366self.description = short367self.detailed_description = detailed368return self369370def add_parameter(371self,372name: str,373param_type: str,374description: str,375required: bool = False,376default: Optional[Any] = None,377enum: Optional[List[str]] = None,378) -> "ToolSchemaBuilder":379"""Append a parameter definition.380381Use when: declaring each accepted input for the tool.382"""383self.parameters.append({384"name": name,385"type": param_type,386"description": description,387"required": required,388"default": default,389"enum": enum,390})391return self392393def set_returns(394self,395return_type: str,396description: str,397properties: Dict[str, Any],398) -> "ToolSchemaBuilder":399"""Define the return value schema.400401Use when: documenting what the tool sends back on success.402"""403self.returns = {404"type": return_type,405"description": description,406"properties": properties,407}408return self409410def add_error(411self,412code: str,413description: str,414resolution: str,415) -> "ToolSchemaBuilder":416"""Register an error condition with recovery guidance.417418Use when: enumerating known failure modes so agents can419handle them gracefully.420"""421self.errors.append({422"code": code,423"description": description,424"resolution": resolution,425})426return self427428def build(self) -> "_BuiltToolSpec":429"""Assemble and return the complete tool spec.430431Use when: the builder is fully configured and the schema is432ready for registration, serialization, or passing to433``generate_tool_description``.434435Returns a ``_BuiltToolSpec`` object that satisfies the ``ToolSpec``436protocol, so it can be used directly with ``generate_tool_description``437and ``ToolDescriptionEvaluator``.438"""439return _BuiltToolSpec(440name=self.name,441description=self.detailed_description or self.description,442triggers=self._triggers,443examples=self._examples,444parameters=list(self.parameters),445returns=self.returns or {},446errors=list(self.errors),447)448449def add_trigger(self, trigger: str) -> "ToolSchemaBuilder":450"""Add an activation trigger for the tool.451452Use when: documenting when agents should select this tool.453"""454self._triggers.append(trigger)455return self456457def add_example(458self, input_text: str, tool_call: str459) -> "ToolSchemaBuilder":460"""Add a usage example.461462Use when: providing concrete input/output pairs that help agents463understand expected usage.464"""465self._examples.append({"input": input_text, "tool_call": tool_call})466return self467468469# ---------------------------------------------------------------------------470# CLI entry point471# ---------------------------------------------------------------------------472473if __name__ == "__main__":474# Quick demo: build a schema, render it, and evaluate it.475builder = ToolSchemaBuilder("get_customer")476builder.set_description(477"Retrieve customer record by ID",478"Fetches a customer object from the primary datastore. "479"Supports concise and detailed response formats.",480)481builder.add_parameter(482"customer_id", "string",483'Customer identifier in CUST-###### format (e.g., "CUST-000001")',484required=True,485)486builder.add_parameter(487"format", "string",488'"concise" for key fields, "detailed" for complete record',489required=False,490default="concise",491enum=["concise", "detailed"],492)493builder.set_returns(494"object",495"Customer object with requested fields",496{"id": {"type": "string"}, "name": {"type": "string"}},497)498builder.add_error("NOT_FOUND", "Customer ID not in datastore", "Verify ID format and retry")499builder.add_error("INVALID_FORMAT", "ID does not match CUST-######", "Use CUST-###### pattern")500501spec = builder.build()502503print("=== Built Spec ===")504print(f"Name: {spec.name}")505print(f"Parameters: {[p['name'] for p in spec.parameters]}")506print(f"Errors: {[e['code'] for e in spec.errors]}")507508# Generate and evaluate description509description = generate_tool_description(spec)510print("\n=== Generated Description ===")511print(description)512513evaluator = ToolDescriptionEvaluator()514scores = evaluator.evaluate(description, spec)515print("\n=== Evaluation Scores ===")516for criterion, score in scores.items():517print(f" {criterion}: {score:.2f}")518519# Generate an error message example520err_gen = ErrorMessageGenerator()521err_msg = err_gen.generate("NOT_FOUND", {522"error_code": "NOT_FOUND",523"specific_message": "No customer with ID CUST-999999",524"how_to_resolve": "Check ID and retry",525"correct_format": "CUST-######",526})527print("\n=== Sample Error Message ===")528print(err_msg)529