* 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>
187 lines
6.9 KiB
Python
187 lines
6.9 KiB
Python
import os
|
|
import threading
|
|
from typing import Dict, List, Set
|
|
|
|
|
|
class PromptRegistry:
|
|
"""Central registry for system and module prompts.
|
|
|
|
- Loads .txt prompt files from the local `system` and `module` directories on initialization
|
|
- Allows programmatic registration of prompts (module-level or dynamic)
|
|
- Provides a simple, thread-safe API for getting/setting/listing prompts
|
|
- Names for module prompts are based on relative path inside `module`, e.g. `manager/task_planner`
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._lock = threading.Lock()
|
|
self._prompts: Dict[str, str] = {}
|
|
self._base_dir = os.path.dirname(__file__)
|
|
self._system_dir = os.path.join(self._base_dir, "system")
|
|
self._module_dir = os.path.join(self._base_dir, "module")
|
|
# Track namespaces
|
|
self._module_keys: Set[str] = set()
|
|
self._system_keys: Set[str] = set()
|
|
self._load_system_prompts()
|
|
self._load_module_prompts()
|
|
|
|
def _load_system_prompts(self) -> None:
|
|
system_prompts: Dict[str, str] = {}
|
|
system_keys: Set[str] = set()
|
|
try:
|
|
if os.path.isdir(self._system_dir):
|
|
for fname in os.listdir(self._system_dir):
|
|
if not fname.lower().endswith(".txt"):
|
|
continue
|
|
key = os.path.splitext(fname)[0]
|
|
fpath = os.path.join(self._system_dir, fname)
|
|
try:
|
|
with open(fpath, "r", encoding="utf-8") as f:
|
|
system_prompts[key] = f.read()
|
|
system_keys.add(key)
|
|
except Exception:
|
|
# Skip unreadable files but continue loading others
|
|
continue
|
|
finally:
|
|
with self._lock:
|
|
# System prompts become the baseline; module/dynamic can override
|
|
self._prompts.update(system_prompts)
|
|
self._system_keys = system_keys
|
|
|
|
def _load_module_prompts(self) -> None:
|
|
module_prompts: Dict[str, str] = {}
|
|
module_keys: Set[str] = set()
|
|
try:
|
|
if os.path.isdir(self._module_dir):
|
|
for root, _dirs, files in os.walk(self._module_dir):
|
|
for fname in files:
|
|
if not fname.lower().endswith(".txt"):
|
|
continue
|
|
fpath = os.path.join(root, fname)
|
|
rel = os.path.relpath(fpath, self._module_dir)
|
|
key = os.path.splitext(rel)[0].replace(os.sep, "/")
|
|
try:
|
|
with open(fpath, "r", encoding="utf-8") as f:
|
|
module_prompts[key] = f.read()
|
|
module_keys.add(key)
|
|
except Exception:
|
|
continue
|
|
finally:
|
|
with self._lock:
|
|
# Module prompts can override system prompts of the same key
|
|
self._prompts.update(module_prompts)
|
|
self._module_keys = module_keys
|
|
|
|
def refresh(self) -> None:
|
|
"""Reload system and module prompts from disk. Keeps any programmatically registered prompts unless overwritten by disk files."""
|
|
with self._lock:
|
|
old_prompts = dict(self._prompts)
|
|
# Reload from disk
|
|
with self._lock:
|
|
self._prompts = {}
|
|
self._module_keys = set()
|
|
self._system_keys = set()
|
|
self._load_system_prompts()
|
|
self._load_module_prompts()
|
|
# Reapply dynamic prompts that are not present on disk
|
|
with self._lock:
|
|
for name, content in old_prompts.items():
|
|
if name not in self._prompts:
|
|
self._prompts[name] = content
|
|
|
|
def _names_from_dir(self, directory: str) -> List[str]:
|
|
if not os.path.isdir(directory):
|
|
return []
|
|
return [os.path.splitext(f)[0] for f in os.listdir(directory) if f.lower().endswith(".txt")]
|
|
|
|
def get(self, name: str, default: str = "") -> str:
|
|
with self._lock:
|
|
return self._prompts.get(name, default)
|
|
|
|
def set(self, name: str, content: str) -> None:
|
|
"""Register or override a prompt by name."""
|
|
with self._lock:
|
|
self._prompts[name] = content
|
|
|
|
def exists(self, name: str) -> bool:
|
|
with self._lock:
|
|
return name in self._prompts
|
|
|
|
def exists_in_module(self, name: str) -> bool:
|
|
with self._lock:
|
|
return name in self._module_keys
|
|
|
|
def module_children_exist(self, prefix: str) -> bool:
|
|
with self._lock:
|
|
prefix_slash = prefix + "/"
|
|
return any(k.startswith(prefix_slash) for k in self._module_keys)
|
|
|
|
def all_names(self) -> List[str]:
|
|
with self._lock:
|
|
return sorted(self._prompts.keys())
|
|
|
|
def list_by_prefix(self, prefix: str) -> List[str]:
|
|
with self._lock:
|
|
return sorted([n for n in self._prompts.keys() if n.startswith(prefix)])
|
|
|
|
def as_dict(self) -> Dict[str, str]:
|
|
with self._lock:
|
|
return dict(self._prompts)
|
|
|
|
|
|
class PromptNamespace:
|
|
"""Hierarchical attribute-style access to module prompts.
|
|
|
|
Usage:
|
|
from gui_agents.prompts import module
|
|
text = module.evaluator.final_check_role
|
|
text2 = module.manager.planner_role
|
|
|
|
Resolution rules:
|
|
- Resolve only against module prompts (under `module/`), ignoring system prompts
|
|
- If an exact module key exists (e.g., "evaluator/final_check_role"), return its string content
|
|
- Else, if there are module children under that path, return a deeper namespace object
|
|
- Else, raise AttributeError
|
|
"""
|
|
|
|
def __init__(self, registry: PromptRegistry, parts: List[str] = None): #type: ignore
|
|
self._registry = registry
|
|
self._parts = parts or []
|
|
|
|
def __getattr__(self, name: str):
|
|
prefix = "/".join(self._parts + [name]) if self._parts else name
|
|
# Exact leaf in module space
|
|
if self._registry.exists_in_module(prefix):
|
|
return self._registry.get(prefix, "")
|
|
# Nested namespace in module space?
|
|
if self._registry.module_children_exist(prefix):
|
|
return PromptNamespace(self._registry, self._parts + [name])
|
|
raise AttributeError(f"No module prompt or namespace '{prefix}'")
|
|
|
|
|
|
# Singleton registry instance for convenient imports
|
|
prompt_registry = PromptRegistry()
|
|
|
|
# Convenience top-level helpers
|
|
|
|
def get_prompt(name: str, default: str = "") -> str:
|
|
return prompt_registry.get(name, default)
|
|
|
|
|
|
def register_prompt(name: str, content: str) -> None:
|
|
prompt_registry.set(name, content)
|
|
|
|
|
|
def list_prompts() -> List[str]:
|
|
return prompt_registry.all_names()
|
|
|
|
|
|
def list_prompts_by_prefix(prefix: str) -> List[str]:
|
|
return prompt_registry.list_by_prefix(prefix)
|
|
|
|
|
|
def refresh_prompts() -> None:
|
|
prompt_registry.refresh()
|
|
|
|
|
|
# Hierarchical accessor for module prompts
|
|
module = PromptNamespace(prompt_registry, []) |