Files
Artemis-Eapil/artemis/interfaces/cli/commands/bench.py
T
somew1nd 206a9ce921 feat: initial release of ARTEMIS Android Agent & Automation Platform
ARTEMIS is an autonomous multimodal Android agent and mobile UI automation
platform engineered for physical devices and emulators. It combines state-of-the-art
multimodal Vision-Language Models (VLMs) with hierarchical closed-loop planning,
resilient self-healing, and deep Android subsystem integration.

Key Features & Architectural Highlights:
- Dual-Engine Orchestration:
  * Pro Engine: Multi-agent closed-loop (Planner -> Operator -> Validator/Failure Analyzer -> Summarizer) powered by LangGraph.
  * Flash Engine: Ultra-fast reactive loop for deterministic, low-latency UI execution (<35 steps).
- Multimodal Perception & Grounding:
  * Hybrid visual screenshot grounding, real-time OCR extraction, and XML accessibility hierarchy parsing.
  * Dynamic-First, Coordinate-Fallback locating strategy for maximum resilience against UI drift.
- Autonomous Self-Healing & Diagnostics:
  * Failure analyzer and safety net for popup dismissal, crash recovery, and auto-retry.
  * Diagnostic sub-agents for log analysis, video recording inspection, and object detection.
- Unified CLI & Python SDK:
  * Rich CLI with commands: init, doctor, run, trace, batch, bench, server, and mcp.
  * Fluent Python SDK with Pydantic structured output models and package locking.
- Multi-Provider LLM Architecture:
  * Support for Google Gemini, OpenAI, Anthropic Claude, Vertex AI, OpenRouter, xAI Grok, and local endpoints (Ollama/vLLM).
  * Centralized configuration in config/artemis.jsonc.
- Native MCP & Observability:
  * Model Context Protocol (MCP) server endpoints for AI IDEs.
  * Full trace recording with before/after screenshots, action overlays, and execution replays.

Change-Id: I430b93ee2b76b54cc5a3c6eeccd15d2cf29830a1
2026-08-13 11:46:47 -07:00

158 lines
5.0 KiB
Python

# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Benchmark and evaluation command (artemis bench)."""
import asyncio
from pathlib import Path
import subprocess
import sys
from typing import Annotated
from artemis.config import initialize_llm_config
from artemis.sdk import Agent
from artemis.sdk.builders import Builders
from artemis.sdk.types.task import AgentProfile
from artemis.utils.logger import get_logger
import typer
logger = get_logger(__name__)
def check_and_start_emulator(adb_path: str = "adb") -> bool:
"""Checks if Android emulator is running; optionally starts it."""
try:
res = subprocess.run([adb_path, "devices"], capture_output=True, text=True, timeout=5)
for line in res.stdout.splitlines():
if "emulator-" in line and "device" in line:
return True
except Exception:
pass
return False
async def run_benchmark(
task_name: str | None = None,
profile_name: str = "pro",
console_port: int = 5554,
adb_path: str = "adb",
) -> None:
"""Runs AndroidWorld benchmark task evaluations."""
# Add android_world-main if present
workspace_root = Path(__file__).resolve().parent.parent.parent.parent.parent
aw_path = workspace_root / "android_world-main"
if aw_path.exists() and str(aw_path) not in sys.path:
sys.path.insert(0, str(aw_path))
try:
from android_world import registry
from android_world.env import env_launcher
except ImportError:
logger.error("AndroidWorld benchmark suite is not installed or available in path.")
raise typer.Exit(1)
task_registry = registry.TaskRegistry()
aw_registry = task_registry.get_registry(task_registry.ANDROID_WORLD_FAMILY)
if task_name and task_name not in aw_registry:
logger.error(f"Task '{task_name}' not found in AndroidWorld registry.")
available = sorted(list(aw_registry.keys()))
logger.info(f"Available tasks ({len(available)}): {', '.join(available[:10])}...")
raise typer.Exit(1)
logger.info(f"Initializing AndroidWorld environment on console port {console_port}...")
env = env_launcher.load_and_setup_env(
console_port=console_port,
emulator_setup=False,
adb_path=adb_path,
)
llm_config = initialize_llm_config()
profile = AgentProfile(name="default", llm_config=llm_config)
config = Builders.AgentConfig.with_default_profile(profile).build()
agent = Agent(config=config)
await agent.init()
tasks_to_run = [task_name] if task_name else list(aw_registry.keys())
try:
for t_name in tasks_to_run:
task_type = aw_registry[t_name]
params = task_type.generate_random_params()
task_instance = task_type(params)
logger.info(f"\n🎯 [Benchmark] Starting: {t_name} | Goal: {task_instance.goal}")
task_instance.initialize_task(env)
result = await agent.run_task(
goal=task_instance.goal,
name=f"bench_{t_name}",
profile=profile_name,
)
success = task_instance.is_successful(env)
logger.info(f"🏆 Task '{t_name}' Result: {result} | Benchmark Verified: {success}")
finally:
await agent.clean()
env.close()
def bench_command(
task: Annotated[
str | None,
typer.Option(
"--task",
"-t",
help="Specific AndroidWorld task name to evaluate (e.g. Clock_1, Calculator_1).",
),
] = None,
profile: Annotated[
str,
typer.Option(
"--profile",
"-p",
help="Agent profile to evaluate ('flash' or 'pro').",
),
] = "pro",
adb_path: Annotated[
str,
typer.Option(
"--adb-path",
help="Path to ADB executable.",
),
] = "adb",
console_port: Annotated[
int,
typer.Option(
"--port",
help="Emulator console port.",
),
] = 5554,
) -> None:
"""Run AndroidWorld benchmark task evaluations against the agent."""
if not check_and_start_emulator(adb_path=adb_path):
typer.secho(
f"Warning: No active emulator detected on port {console_port}. Please start an emulator.",
fg=typer.colors.YELLOW,
)
asyncio.run(
run_benchmark(
task_name=task,
profile_name=profile,
console_port=console_port,
adb_path=adb_path,
)
)