""" ReAct JSON Converter Converts generic ReAct-format traces (thought/action/observation steps) to Potato's canonical format. Expected input format: { "id": "trace_001", "task": "Book a flight...", "steps": [ {"thought": "I need to...", "action": "search(...)", "observation": "Found..."}, ... ], "metadata": {"agent": "GPT-4", "tokens": 2340} } """ from typing import Any, Dict, List, Optional from ..base import BaseTraceConverter, CanonicalTrace class ReActConverter(BaseTraceConverter): """Converter for generic ReAct JSON traces.""" format_name = "react" description = "Generic ReAct JSON format (thought/action/observation steps)" file_extensions = [".json", ".jsonl"] def convert(self, data: Any, options: Optional[Dict] = None) -> List[CanonicalTrace]: options = options or {} traces = data if isinstance(data, list) else [data] results = [] for item in traces: trace_id = item.get("id", f"trace_{len(results)}") task = item.get("task", item.get("task_description", "")) agent_name = item.get("agent", item.get("agent_name", "")) steps = item.get("steps", []) metadata = item.get("metadata", {}) # Build conversation turns conversation = [] for step in steps: if "thought" in step and step["thought"]: conversation.append({ "speaker": "Agent (Thought)", "text": step["thought"] }) if "action" in step and step["action"]: conversation.append({ "speaker": "Agent (Action)", "text": step["action"] }) if "observation" in step and step["observation"]: conversation.append({ "speaker": "Environment", "text": step["observation"] }) # Build metadata table metadata_table = [] metadata_table.append({"Property": "Steps", "Value": str(len(steps))}) for key, value in metadata.items(): metadata_table.append({"Property": key, "Value": str(value)}) trace = CanonicalTrace( id=trace_id, task_description=task, conversation=conversation, agent_name=agent_name, metadata_table=metadata_table, ) results.append(trace) return results def detect(self, data: Any) -> bool: items = data if isinstance(data, list) else [data] if not items: return False first = items[0] if not isinstance(first, dict): return False # ReAct format has "steps" with thought/action/observation if "steps" not in first: return False steps = first["steps"] if not isinstance(steps, list) or not steps: return False step = steps[0] if not isinstance(step, dict): return False # Reject if steps have "agent" field (that's CrewAI multi-agent format) if "agent" in step: return False return any(k in step for k in ("thought", "action", "observation"))