Source code for lrs.core.registry
"""
Tool registry with natural transformation discovery.
Manages tools and their fallback chains. Automatically discovers
alternative tools based on schema compatibility.
"""
from typing import Dict, List, Optional, Any
from lrs.core.lens import ToolLens
[docs]
class ToolRegistry:
"""
Registry for managing tools and their alternatives.
Features:
- Register tools with explicit fallback chains
- Discover compatible alternatives via schema matching
- Track tool statistics for Free Energy calculation
Attributes:
tools: Dict mapping tool names to ToolLens objects
alternatives: Dict mapping tool names to lists of alternative names
statistics: Dict tracking execution history per tool
Examples:
>>> registry = ToolRegistry()
>>>
>>> # Register primary tool with alternatives
>>> registry.register(
... api_tool,
... alternatives=["cache_tool", "fallback_tool"]
... )
>>>
>>> # Register alternatives
>>> registry.register(cache_tool)
>>> registry.register(fallback_tool)
>>>
>>> # Find alternatives when primary fails
>>> alts = registry.find_alternatives("api_tool")
>>> print(alts)
['cache_tool', 'fallback_tool']
"""
[docs]
def __init__(self):
"""Initialize empty registry"""
self.tools: Dict[str, ToolLens] = {}
self.alternatives: Dict[str, List[str]] = {}
self.statistics: Dict[str, Dict[str, Any]] = {}
[docs]
def register(self, tool: ToolLens, alternatives: Optional[List[str]] = None):
"""
Register a tool with optional alternatives.
Args:
tool: ToolLens to register
alternatives: List of alternative tool names (fallback chain)
Examples:
>>> registry.register(
... APITool(),
... alternatives=["CacheTool", "LocalTool"]
... )
"""
self.tools[tool.name] = tool
if alternatives:
self.alternatives[tool.name] = alternatives
# Initialize statistics
if tool.name not in self.statistics:
self.statistics[tool.name] = {
"success_rate": 0.5, # Neutral prior
"avg_prediction_error": 0.5,
"error_variance": 0.0,
"call_count": 0,
"failure_count": 0,
}
[docs]
def get_tool(self, name: str) -> Optional[ToolLens]:
"""
Retrieve tool by name.
Args:
name: Tool name
Returns:
ToolLens or None if not found
"""
return self.tools.get(name)
[docs]
def find_alternatives(self, tool_name: str) -> List[str]:
"""
Find registered alternatives for a tool.
Args:
tool_name: Name of primary tool
Returns:
List of alternative tool names (may be empty)
Examples:
>>> alts = registry.find_alternatives("api_tool")
>>> for alt_name in alts:
... alt_tool = registry.get_tool(alt_name)
... result = alt_tool.get(state)
"""
return self.alternatives.get(tool_name, [])
[docs]
def discover_compatible_tools(
self, input_schema: Dict[str, Any], output_schema: Dict[str, Any]
) -> List[str]:
"""
Discover tools compatible with given schemas.
Uses structural matching to find tools that could serve as
natural transformations (alternatives).
Args:
input_schema: Required input schema
output_schema: Required output schema
Returns:
List of compatible tool names
Examples:
>>> compatible = registry.discover_compatible_tools(
... input_schema={'type': 'object', 'required': ['url']},
... output_schema={'type': 'string'}
... )
"""
compatible = []
for name, tool in self.tools.items():
if self._schemas_compatible(tool.input_schema, input_schema):
if self._schemas_compatible(tool.output_schema, output_schema):
compatible.append(name)
return compatible
def _schemas_compatible(self, schema_a: Dict[str, Any], schema_b: Dict[str, Any]) -> bool:
"""
Check if two JSON schemas are compatible.
Simplified check: types must match.
Full implementation would use jsonschema library.
Args:
schema_a: First schema
schema_b: Second schema
Returns:
True if compatible
"""
# Simple type check
type_a = schema_a.get("type")
type_b = schema_b.get("type")
if type_a != type_b:
return False
# Check required fields for objects
if type_a == "object":
req_a = set(schema_a.get("required", []))
req_b = set(schema_b.get("required", []))
# schema_b must provide all fields required by schema_a
if not req_a.issubset(req_b):
return False
return True
[docs]
def update_statistics(self, tool_name: str, success: bool, prediction_error: float):
"""
Update execution statistics for a tool.
Used by Free Energy calculation to estimate success probabilities
and epistemic values.
Args:
tool_name: Name of executed tool
success: Whether execution succeeded
prediction_error: Observed prediction error
Examples:
>>> registry.update_statistics("api_tool", success=True, prediction_error=0.1)
"""
if tool_name not in self.statistics:
self.statistics[tool_name] = {
"success_rate": 0.5,
"avg_prediction_error": 0.5,
"error_variance": 0.0,
"call_count": 0,
"failure_count": 0,
}
stats = self.statistics[tool_name]
# Update counts
stats["call_count"] += 1
if not success:
stats["failure_count"] += 1
# Update success rate (running average)
stats["success_rate"] = 1.0 - (stats["failure_count"] / stats["call_count"])
# Update prediction error average
n = stats["call_count"]
old_avg = stats["avg_prediction_error"]
new_avg = old_avg + (prediction_error - old_avg) / n
stats["avg_prediction_error"] = new_avg
# Update variance (Welford's online algorithm)
if n > 1:
old_var = stats["error_variance"]
stats["error_variance"] = old_var + (prediction_error - old_avg) * (
prediction_error - new_avg
)
[docs]
def get_statistics(self, tool_name: str) -> Optional[Dict[str, Any]]:
"""
Retrieve statistics for a tool.
Args:
tool_name: Tool name
Returns:
Statistics dict or None
"""
return self.statistics.get(tool_name)
[docs]
def list_tools(self) -> List[str]:
"""List all registered tool names"""
return list(self.tools.keys())