uhhjj / app /config.py
Speedofmastery's picture
Deploy OpenManus Complete AI Platform - 200+ Models + Mobile Auth + Cloudflare Services
d94d354
raw
history blame
13 kB
import json
import threading
import tomllib
from pathlib import Path
from typing import Dict, List, Optional
from pydantic import BaseModel, Field
def get_project_root() -> Path:
"""Get the project root directory"""
return Path(__file__).resolve().parent.parent
PROJECT_ROOT = get_project_root()
WORKSPACE_ROOT = PROJECT_ROOT / "workspace"
class LLMSettings(BaseModel):
model: str = Field(..., description="Model name")
base_url: str = Field(..., description="API base URL")
api_key: str = Field(..., description="API key")
max_tokens: int = Field(4096, description="Maximum number of tokens per request")
max_input_tokens: Optional[int] = Field(
None,
description="Maximum input tokens to use across all requests (None for unlimited)",
)
temperature: float = Field(1.0, description="Sampling temperature")
api_type: str = Field(..., description="Azure, Openai, or Ollama")
api_version: str = Field(..., description="Azure Openai version if AzureOpenai")
class ProxySettings(BaseModel):
server: str = Field(None, description="Proxy server address")
username: Optional[str] = Field(None, description="Proxy username")
password: Optional[str] = Field(None, description="Proxy password")
class SearchSettings(BaseModel):
engine: str = Field(default="Google", description="Search engine the llm to use")
fallback_engines: List[str] = Field(
default_factory=lambda: ["DuckDuckGo", "Baidu", "Bing"],
description="Fallback search engines to try if the primary engine fails",
)
retry_delay: int = Field(
default=60,
description="Seconds to wait before retrying all engines again after they all fail",
)
max_retries: int = Field(
default=3,
description="Maximum number of times to retry all engines when all fail",
)
lang: str = Field(
default="en",
description="Language code for search results (e.g., en, zh, fr)",
)
country: str = Field(
default="us",
description="Country code for search results (e.g., us, cn, uk)",
)
class RunflowSettings(BaseModel):
use_data_analysis_agent: bool = Field(
default=False, description="Enable data analysis agent in run flow"
)
class BrowserSettings(BaseModel):
headless: bool = Field(False, description="Whether to run browser in headless mode")
disable_security: bool = Field(
True, description="Disable browser security features"
)
extra_chromium_args: List[str] = Field(
default_factory=list, description="Extra arguments to pass to the browser"
)
chrome_instance_path: Optional[str] = Field(
None, description="Path to a Chrome instance to use"
)
wss_url: Optional[str] = Field(
None, description="Connect to a browser instance via WebSocket"
)
cdp_url: Optional[str] = Field(
None, description="Connect to a browser instance via CDP"
)
proxy: Optional[ProxySettings] = Field(
None, description="Proxy settings for the browser"
)
max_content_length: int = Field(
2000, description="Maximum length for content retrieval operations"
)
class SandboxSettings(BaseModel):
"""Configuration for the execution sandbox"""
use_sandbox: bool = Field(False, description="Whether to use the sandbox")
image: str = Field("python:3.12-slim", description="Base image")
work_dir: str = Field("/workspace", description="Container working directory")
memory_limit: str = Field("512m", description="Memory limit")
cpu_limit: float = Field(1.0, description="CPU limit")
timeout: int = Field(300, description="Default command timeout (seconds)")
network_enabled: bool = Field(
False, description="Whether network access is allowed"
)
class DaytonaSettings(BaseModel):
daytona_api_key: str
daytona_server_url: Optional[str] = Field(
"https://app.daytona.io/api", description=""
)
daytona_target: Optional[str] = Field("us", description="enum ['eu', 'us']")
sandbox_image_name: Optional[str] = Field("whitezxj/sandbox:0.1.0", description="")
sandbox_entrypoint: Optional[str] = Field(
"/usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf",
description="",
)
# sandbox_id: Optional[str] = Field(
# None, description="ID of the daytona sandbox to use, if any"
# )
VNC_password: Optional[str] = Field(
"123456", description="VNC password for the vnc service in sandbox"
)
class MCPServerConfig(BaseModel):
"""Configuration for a single MCP server"""
type: str = Field(..., description="Server connection type (sse or stdio)")
url: Optional[str] = Field(None, description="Server URL for SSE connections")
command: Optional[str] = Field(None, description="Command for stdio connections")
args: List[str] = Field(
default_factory=list, description="Arguments for stdio command"
)
class MCPSettings(BaseModel):
"""Configuration for MCP (Model Context Protocol)"""
server_reference: str = Field(
"app.mcp.server", description="Module reference for the MCP server"
)
servers: Dict[str, MCPServerConfig] = Field(
default_factory=dict, description="MCP server configurations"
)
@classmethod
def load_server_config(cls) -> Dict[str, MCPServerConfig]:
"""Load MCP server configuration from JSON file"""
config_path = PROJECT_ROOT / "config" / "mcp.json"
try:
config_file = config_path if config_path.exists() else None
if not config_file:
return {}
with config_file.open() as f:
data = json.load(f)
servers = {}
for server_id, server_config in data.get("mcpServers", {}).items():
servers[server_id] = MCPServerConfig(
type=server_config["type"],
url=server_config.get("url"),
command=server_config.get("command"),
args=server_config.get("args", []),
)
return servers
except Exception as e:
raise ValueError(f"Failed to load MCP server config: {e}")
class AppConfig(BaseModel):
llm: Dict[str, LLMSettings]
sandbox: Optional[SandboxSettings] = Field(
None, description="Sandbox configuration"
)
browser_config: Optional[BrowserSettings] = Field(
None, description="Browser configuration"
)
search_config: Optional[SearchSettings] = Field(
None, description="Search configuration"
)
mcp_config: Optional[MCPSettings] = Field(None, description="MCP configuration")
run_flow_config: Optional[RunflowSettings] = Field(
None, description="Run flow configuration"
)
daytona_config: Optional[DaytonaSettings] = Field(
None, description="Daytona configuration"
)
class Config:
arbitrary_types_allowed = True
class Config:
_instance = None
_lock = threading.Lock()
_initialized = False
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not self._initialized:
with self._lock:
if not self._initialized:
self._config = None
self._load_initial_config()
self._initialized = True
@staticmethod
def _get_config_path() -> Path:
root = PROJECT_ROOT
config_path = root / "config" / "config.toml"
if config_path.exists():
return config_path
example_path = root / "config" / "config.example.toml"
if example_path.exists():
return example_path
raise FileNotFoundError("No configuration file found in config directory")
def _load_config(self) -> dict:
config_path = self._get_config_path()
with config_path.open("rb") as f:
return tomllib.load(f)
def _load_initial_config(self):
raw_config = self._load_config()
base_llm = raw_config.get("llm", {})
llm_overrides = {
k: v for k, v in raw_config.get("llm", {}).items() if isinstance(v, dict)
}
default_settings = {
"model": base_llm.get("model"),
"base_url": base_llm.get("base_url"),
"api_key": base_llm.get("api_key"),
"max_tokens": base_llm.get("max_tokens", 4096),
"max_input_tokens": base_llm.get("max_input_tokens"),
"temperature": base_llm.get("temperature", 1.0),
"api_type": base_llm.get("api_type", ""),
"api_version": base_llm.get("api_version", ""),
}
# handle browser config.
browser_config = raw_config.get("browser", {})
browser_settings = None
if browser_config:
# handle proxy settings.
proxy_config = browser_config.get("proxy", {})
proxy_settings = None
if proxy_config and proxy_config.get("server"):
proxy_settings = ProxySettings(
**{
k: v
for k, v in proxy_config.items()
if k in ["server", "username", "password"] and v
}
)
# filter valid browser config parameters.
valid_browser_params = {
k: v
for k, v in browser_config.items()
if k in BrowserSettings.__annotations__ and v is not None
}
# if there is proxy settings, add it to the parameters.
if proxy_settings:
valid_browser_params["proxy"] = proxy_settings
# only create BrowserSettings when there are valid parameters.
if valid_browser_params:
browser_settings = BrowserSettings(**valid_browser_params)
search_config = raw_config.get("search", {})
search_settings = None
if search_config:
search_settings = SearchSettings(**search_config)
sandbox_config = raw_config.get("sandbox", {})
if sandbox_config:
sandbox_settings = SandboxSettings(**sandbox_config)
else:
sandbox_settings = SandboxSettings()
daytona_config = raw_config.get("daytona", {})
if daytona_config:
daytona_settings = DaytonaSettings(**daytona_config)
else:
daytona_settings = DaytonaSettings()
mcp_config = raw_config.get("mcp", {})
mcp_settings = None
if mcp_config:
# Load server configurations from JSON
mcp_config["servers"] = MCPSettings.load_server_config()
mcp_settings = MCPSettings(**mcp_config)
else:
mcp_settings = MCPSettings(servers=MCPSettings.load_server_config())
run_flow_config = raw_config.get("runflow")
if run_flow_config:
run_flow_settings = RunflowSettings(**run_flow_config)
else:
run_flow_settings = RunflowSettings()
config_dict = {
"llm": {
"default": default_settings,
**{
name: {**default_settings, **override_config}
for name, override_config in llm_overrides.items()
},
},
"sandbox": sandbox_settings,
"browser_config": browser_settings,
"search_config": search_settings,
"mcp_config": mcp_settings,
"run_flow_config": run_flow_settings,
"daytona_config": daytona_settings,
}
self._config = AppConfig(**config_dict)
@property
def llm(self) -> Dict[str, LLMSettings]:
return self._config.llm
@property
def sandbox(self) -> SandboxSettings:
return self._config.sandbox
@property
def daytona(self) -> DaytonaSettings:
return self._config.daytona_config
@property
def browser_config(self) -> Optional[BrowserSettings]:
return self._config.browser_config
@property
def search_config(self) -> Optional[SearchSettings]:
return self._config.search_config
@property
def mcp_config(self) -> MCPSettings:
"""Get the MCP configuration"""
return self._config.mcp_config
@property
def run_flow_config(self) -> RunflowSettings:
"""Get the Run Flow configuration"""
return self._config.run_flow_config
@property
def workspace_root(self) -> Path:
"""Get the workspace root directory"""
return WORKSPACE_ROOT
@property
def root_path(self) -> Path:
"""Get the root path of the application"""
return PROJECT_ROOT
config = Config()