davidjurgens's picture
Deploy: Potato — Codebook Annotation
aceb1b2 verified
Raw
History Blame Contribute Delete
6.16 kB
"""
Trace Converter Registry
Centralized registry for trace format converters, following
the same pattern as SchemaRegistry, DisplayRegistry, and ExportRegistry.
Usage:
from potato.trace_converter.registry import converter_registry
# Convert traces
traces = converter_registry.convert("langchain", data)
# Auto-detect format
format_name = converter_registry.detect_format(data)
# List formats
formats = converter_registry.get_supported_formats()
"""
import logging
from typing import Dict, List, Optional, Any
from .base import BaseTraceConverter, CanonicalTrace
logger = logging.getLogger(__name__)
class TraceConverterRegistry:
"""
Centralized registry for trace format converters.
"""
def __init__(self):
self._converters: Dict[str, BaseTraceConverter] = {}
logger.debug("TraceConverterRegistry initialized")
def register(self, converter: BaseTraceConverter) -> None:
"""Register a converter instance."""
name = converter.format_name
if not name:
raise ValueError("Converter must have a non-empty format_name")
if name in self._converters:
raise ValueError(f"Converter '{name}' is already registered")
self._converters[name] = converter
logger.debug(f"Registered trace converter: {name}")
def get(self, name: str) -> Optional[BaseTraceConverter]:
"""Get a converter by format name."""
return self._converters.get(name)
def convert(self, format_name: str, data: Any,
options: Optional[Dict] = None) -> List[CanonicalTrace]:
"""
Convert traces using the named format converter.
Args:
format_name: Converter name (e.g., "langchain", "react")
data: Input data to convert
options: Format-specific options
Returns:
List of CanonicalTrace objects
Raises:
ValueError: If format is not registered
"""
converter = self.get(format_name)
if not converter:
supported = ", ".join(sorted(self._converters.keys()))
raise ValueError(
f"Unknown trace format: '{format_name}'. "
f"Supported formats: {supported}"
)
return converter.convert(data, options)
def detect_format(self, data: Any) -> Optional[str]:
"""
Auto-detect the format of input data.
Args:
data: Parsed input data
Returns:
Format name if detected, None otherwise
"""
for name, converter in self._converters.items():
try:
if converter.detect(data):
logger.info(f"Auto-detected trace format: {name}")
return name
except Exception:
continue
return None
def get_supported_formats(self) -> List[str]:
"""Get sorted list of supported format names."""
return sorted(self._converters.keys())
def list_converters(self) -> List[Dict[str, str]]:
"""List all registered converters with metadata."""
return [
converter.get_format_info()
for converter in sorted(self._converters.values(),
key=lambda c: c.format_name)
]
def is_registered(self, name: str) -> bool:
"""Check if a format is registered."""
return name in self._converters
# Global registry instance
converter_registry = TraceConverterRegistry()
def _register_builtin_converters():
"""Register all built-in converters. Called on import.
Registration order matters for auto-detection (first match wins).
More specific formats are registered before generic ones to avoid
false positives:
- ReAct before MultiAgent (both have "steps" but ReAct steps have thought/action/observation)
- SWE-bench before ReAct (instance_id is highly specific)
- OTEL before others (trace_id+span_id is unique)
- MCP before others (MCP methods are distinctive)
- Anthropic before OpenAI (content blocks vs string content)
- MultiAgent last among message-based (checks for sender/receiver/agents)
"""
from .converters.react_converter import ReActConverter
from .converters.langchain_converter import LangChainConverter
from .converters.langfuse_converter import LangfuseConverter
from .converters.atif_converter import ATIFConverter
from .converters.webarena_converter import WebArenaConverter
from .converters.swebench_converter import SWEBenchConverter
from .converters.otel_converter import OTELConverter
from .converters.mcp_converter import MCPConverter
from .converters.anthropic_converter import AnthropicConverter
from .converters.openai_converter import OpenAIConverter
from .converters.multi_agent_converter import MultiAgentConverter
from .converters.web_agent_converter import WebAgentConverter
from .converters.claude_code_converter import ClaudeCodeConverter
from .converters.aider_converter import AiderConverter
from .converters.swe_agent_trajectory_converter import SWEAgentTrajectoryConverter
converters = [
# Existing converters (specific formats first)
ReActConverter(),
LangChainConverter(),
LangfuseConverter(),
ATIFConverter(),
# Web agent (with coordinates/mouse_path) before plain WebArena
WebAgentConverter(),
WebArenaConverter(),
# New converters (specific-to-generic order)
SWEBenchConverter(),
SWEAgentTrajectoryConverter(), # SWE-Agent trajectories before generic
AiderConverter(), # Aider edit blocks
OTELConverter(),
MCPConverter(),
ClaudeCodeConverter(), # Before Anthropic (more specific: coding tool names)
AnthropicConverter(),
OpenAIConverter(),
MultiAgentConverter(),
]
for converter in converters:
converter_registry.register(converter)
logger.debug(f"Registered {len(converters)} built-in trace converters")
_register_builtin_converters()