* Added a **pyproject.toml** file to define project metadata and dependencies. * Added **run\_maestro.py** and **osworld\_run\_maestro.py** to provide the main execution logic. * Introduced multiple new modules, including **Evaluator**, **Controller**, **Manager**, and **Sub-Worker**, supporting task planning, state management, and data analysis. * Added a **tools module** containing utility functions and tool configurations to improve code reusability. * Updated the **README** and documentation with usage examples and module descriptions. These changes lay the foundation for expanding the Maestro project’s functionality and improving the user experience. Co-authored-by: Hiroid <guoliangxuan@deepmatrix.com>
282 lines
8.5 KiB
Python
282 lines
8.5 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
Display Viewer - Used to display operation records in display.json file in chronological order
|
|
|
|
Usage:
|
|
python -m lybicguiagents.gui_agents.utils.display_viewer --file /path/to/display.json [--output text|json] [--filter module1,module2]
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, List, Any, Optional, Tuple
|
|
|
|
|
|
def load_display_json(file_path: str) -> Dict:
|
|
"""
|
|
Load display.json file
|
|
|
|
Args:
|
|
file_path: Path to display.json file
|
|
|
|
Returns:
|
|
Parsed JSON data
|
|
"""
|
|
try:
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except UnicodeDecodeError:
|
|
print(
|
|
f"Warning: Failed to decode '{file_path}' with utf-8, retrying with GB2312..."
|
|
)
|
|
with open(file_path, 'r', encoding='gb2312') as f:
|
|
return json.load(f)
|
|
except FileNotFoundError:
|
|
print(f"Error: File '{file_path}' does not exist")
|
|
sys.exit(1)
|
|
except json.JSONDecodeError:
|
|
print(f"Error: File '{file_path}' is not a valid JSON format")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"Error: An error occurred while reading file '{file_path}': {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
def flatten_operations(data: Dict) -> List[Dict]:
|
|
"""
|
|
Flatten all module operation records into a time-sorted list
|
|
|
|
Args:
|
|
data: display.json data
|
|
|
|
Returns:
|
|
List of operation records sorted by time
|
|
"""
|
|
all_operations = []
|
|
|
|
if "operations" not in data:
|
|
return all_operations
|
|
|
|
for module, operations in data["operations"].items():
|
|
for op in operations:
|
|
# Add module information
|
|
op["module"] = module
|
|
all_operations.append(op)
|
|
|
|
# Sort by timestamp
|
|
all_operations.sort(key=lambda x: x.get("timestamp", 0))
|
|
|
|
return all_operations
|
|
|
|
|
|
def format_timestamp(timestamp: float) -> str:
|
|
"""
|
|
Format timestamp into readable datetime
|
|
|
|
Args:
|
|
timestamp: UNIX timestamp
|
|
|
|
Returns:
|
|
Formatted datetime string
|
|
"""
|
|
dt = datetime.datetime.fromtimestamp(timestamp)
|
|
return dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
|
|
|
|
def format_duration(duration: float) -> str:
|
|
"""
|
|
Format duration
|
|
|
|
Args:
|
|
duration: Duration (seconds)
|
|
|
|
Returns:
|
|
Formatted duration string
|
|
"""
|
|
if duration < 0.001:
|
|
return f"{duration * 1000000:.2f}μs"
|
|
elif duration < 1:
|
|
return f"{duration * 1000:.2f}ms"
|
|
else:
|
|
return f"{duration:.2f}s"
|
|
|
|
|
|
def format_tokens(tokens: List[int]) -> str:
|
|
"""
|
|
Format tokens information
|
|
|
|
Args:
|
|
tokens: [input tokens, output tokens, total tokens]
|
|
|
|
Returns:
|
|
Formatted tokens string
|
|
"""
|
|
if not tokens or len(tokens) < 3:
|
|
return "N/A"
|
|
|
|
return f"in:{tokens[0]} out:{tokens[1]} total:{tokens[2]}"
|
|
|
|
|
|
def truncate_text(text: str, max_length: int = 100) -> str:
|
|
"""
|
|
Truncate text, add ellipsis when exceeding maximum length
|
|
|
|
Args:
|
|
text: Original text
|
|
max_length: Maximum length
|
|
|
|
Returns:
|
|
Truncated text
|
|
"""
|
|
if not text:
|
|
return ""
|
|
|
|
if isinstance(text, (dict, list)):
|
|
text = str(text)
|
|
|
|
if len(text) <= max_length:
|
|
return text
|
|
|
|
return text[:max_length - 3] + "..."
|
|
|
|
|
|
def find_latest_display_json() -> Optional[str]:
|
|
"""
|
|
Find the latest display.json file
|
|
|
|
Returns:
|
|
Path to the latest display.json file, or None if not found
|
|
"""
|
|
# Look for the runtime folder in the current directory
|
|
runtime_dir = Path("runtime")
|
|
if not runtime_dir.exists() or not runtime_dir.is_dir():
|
|
# Try looking in the parent directory
|
|
parent_runtime = Path("..") / "runtime"
|
|
if parent_runtime.exists() and parent_runtime.is_dir():
|
|
runtime_dir = parent_runtime
|
|
else:
|
|
return None
|
|
|
|
# Find all timestamp folders
|
|
timestamp_dirs = [d for d in runtime_dir.iterdir() if d.is_dir()]
|
|
if not timestamp_dirs:
|
|
return None
|
|
|
|
# Sort by folder name (timestamp) and take the latest
|
|
latest_dir = sorted(timestamp_dirs)[-1]
|
|
display_file = latest_dir / "display.json"
|
|
|
|
if display_file.exists():
|
|
return str(display_file)
|
|
|
|
return None
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description=
|
|
"Display operation records in display.json file in chronological order")
|
|
parser.add_argument("--file", help="Path to display.json file")
|
|
parser.add_argument("--dir", help="Path to directory containing display.json files (recursive)")
|
|
parser.add_argument("--output",
|
|
choices=["text", "json"],
|
|
default="text",
|
|
help="Output format (default: text)")
|
|
parser.add_argument(
|
|
"--filter",
|
|
help="Modules to filter, separated by commas (e.g., manager,worker)")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.file and args.dir:
|
|
print("Error: --file and --dir cannot be used together")
|
|
sys.exit(1)
|
|
|
|
def process_one_file(file_path: str):
|
|
# Load data
|
|
data = load_display_json(file_path)
|
|
# Flatten and sort operations
|
|
operations = flatten_operations(data)
|
|
# Handle module filtering
|
|
filter_modules = None
|
|
if args.filter:
|
|
filter_modules = [module.strip() for module in args.filter.split(",")]
|
|
# Generate output content
|
|
output_content = ""
|
|
if args.output == "json":
|
|
# Filter operations if modules are specified
|
|
if filter_modules:
|
|
filtered_ops = [op for op in operations if op["module"] in filter_modules]
|
|
else:
|
|
filtered_ops = operations
|
|
output_content = json.dumps(filtered_ops, indent=2, ensure_ascii=False)
|
|
else:
|
|
# Generate text format output
|
|
output_lines = []
|
|
for i, op in enumerate(operations):
|
|
# Skip modules that don't match the filter if a filter is specified
|
|
if filter_modules and op["module"] not in filter_modules:
|
|
continue
|
|
module = op["module"]
|
|
operation = op.get("operation", "unknown")
|
|
timestamp = format_timestamp(op.get("timestamp", 0))
|
|
# Output basic information
|
|
output_lines.append(f"{i+1:3d} | {timestamp} | {module:10} | {operation}")
|
|
# Output detailed information
|
|
if "duration" in op:
|
|
output_lines.append(f" └─ Duration: {format_duration(op['duration'])}")
|
|
if "tokens" in op:
|
|
output_lines.append(f" └─ Tokens: {format_tokens(op['tokens'])}")
|
|
if "cost" in op:
|
|
output_lines.append(f" └─ Cost: {op['cost']}")
|
|
if "content" in op:
|
|
content = op["content"]
|
|
output_lines.append(f" └─ Content: {content}")
|
|
if "status" in op:
|
|
output_lines.append(f" └─ Status: {op['status']}")
|
|
output_lines.append("")
|
|
output_content = "\n".join(output_lines)
|
|
# Write output to file
|
|
input_path = Path(file_path)
|
|
output_filename = f"display_viewer_output_{args.output}.txt"
|
|
output_path = input_path.parent / output_filename
|
|
try:
|
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|
f.write(output_content)
|
|
print(f"Output written to: {output_path}")
|
|
except Exception as e:
|
|
print(f"Error writing output file: {e}")
|
|
sys.exit(1)
|
|
|
|
if args.dir:
|
|
for root, dirs, files in os.walk(args.dir):
|
|
for file in files:
|
|
if file == "display.json":
|
|
file_path = os.path.join(root, file)
|
|
print(f"Processing: {file_path}")
|
|
process_one_file(file_path)
|
|
return
|
|
|
|
file_path = args.file
|
|
if not file_path:
|
|
file_path = find_latest_display_json()
|
|
if not file_path:
|
|
print(
|
|
"Error: Cannot find display.json file, please specify file path using --file parameter"
|
|
)
|
|
sys.exit(1)
|
|
print(f"Using the latest display.json file: {file_path}")
|
|
process_one_file(file_path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
"""
|
|
python display_viewer.py --file
|
|
python display_viewer.py --dir
|
|
"""
|
|
main()
|