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
135 lines
4.7 KiB
Python
135 lines
4.7 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.
|
|
|
|
from typing import Annotated, Any
|
|
from langchain_core.messages import AnyMessage
|
|
from langgraph.graph import add_messages
|
|
from artemis.config import AgentNode
|
|
from artemis.context import ArtemisContext
|
|
from artemis.utils.logger import get_logger
|
|
from pydantic import BaseModel, ConfigDict
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def take_last(a, b):
|
|
"""Reducer function keeping the latest value."""
|
|
return b
|
|
|
|
|
|
class State(BaseModel):
|
|
model_config = ConfigDict(
|
|
arbitrary_types_allowed=True,
|
|
extra="allow",
|
|
)
|
|
|
|
messages: Annotated[list[AnyMessage], "Sequential messages", add_messages]
|
|
|
|
remaining_steps: Annotated[int | None, "Remaining steps before the task is completed"] = None
|
|
|
|
# planner related keys
|
|
initial_goal: Annotated[str, "Initial goal given by the user"]
|
|
injected_instruction: Annotated[
|
|
str | None,
|
|
"Injected instruction from user for micro-adjustment",
|
|
take_last,
|
|
] = None
|
|
|
|
# operator related keys (perception)
|
|
latest_ui_hierarchy: Annotated[
|
|
list[dict] | None, "Latest UI hierarchy of the device", take_last
|
|
]
|
|
latest_screenshot: Annotated[str | None, "Path to the latest screenshot", take_last]
|
|
focused_app_info: Annotated[str | None, "Focused app info", take_last]
|
|
device_date: Annotated[str | None, "Date of the device", take_last]
|
|
|
|
# operator related keys (decisions)
|
|
structured_decisions: Annotated[
|
|
str | None,
|
|
"Structured decisions made by the operator, for the validator to follow",
|
|
take_last,
|
|
]
|
|
operator_raw_thinking: Annotated[
|
|
str | None,
|
|
"Raw thinking process of the operator",
|
|
take_last,
|
|
] = None
|
|
operator_native_thinking: Annotated[
|
|
str | None,
|
|
"Native/implicit thinking process of the operator",
|
|
take_last,
|
|
] = None
|
|
short_term_memory: Annotated[
|
|
str | None,
|
|
"Short term memory / scratchpad for the operator",
|
|
take_last,
|
|
] = None
|
|
complete_subgoals_by_ids: Annotated[
|
|
list[str],
|
|
"List of subgoal IDs to complete",
|
|
take_last,
|
|
]
|
|
current_agent: Annotated[str | None, "Current active agent", take_last] = None
|
|
operator_replan_reason: Annotated[str | None, "Reason for operator replan", take_last] = None
|
|
subgoal_plan: Annotated[list[Any] | None, "Subgoal plan", take_last] = None
|
|
operator_tactical_plan: Annotated[list[Any] | None, "Operator tactical plan", take_last] = None
|
|
|
|
# validator related keys
|
|
validator_messages: Annotated[list[AnyMessage], "Sequential Validator messages", add_messages]
|
|
last_execution_result: Annotated[
|
|
dict | None, "Last execution result from validator", take_last
|
|
] = None
|
|
current_step_id: Annotated[str | None, "Current step ID from data engine", take_last] = None
|
|
operator_raw_data: Annotated[
|
|
dict | None,
|
|
"Raw perception data from operator (screenshot, xml, ocr)",
|
|
take_last,
|
|
] = None
|
|
checker_success: Annotated[
|
|
bool | None,
|
|
"True if checker succeeded or not triggered, False if failed",
|
|
take_last,
|
|
] = None
|
|
subagent_calls: Annotated[
|
|
list[str], "List of sub-agent calls in current session", take_last
|
|
] = []
|
|
operator_replied: Annotated[bool | None, "True if operator replied to checker", take_last] = (
|
|
None
|
|
)
|
|
operator_tool_limit_exceeded: Annotated[
|
|
bool | None,
|
|
"True if operator exceeded tool call limit in the previous turn",
|
|
take_last,
|
|
] = None
|
|
indexed_points: Annotated[
|
|
list[list[int]] | None,
|
|
"Active coordinate mappings matching indexed text elements",
|
|
take_last,
|
|
] = None
|
|
indexed_elements: Annotated[
|
|
list[dict] | None,
|
|
"Active interactable elements matching indexed text elements,"
|
|
" containing text, bounds, class, and center coordinates.",
|
|
take_last,
|
|
] = None
|
|
|
|
async def asanitize_update(
|
|
self,
|
|
ctx: ArtemisContext,
|
|
update: dict,
|
|
agent: AgentNode | None = None,
|
|
):
|
|
"""Sanitizes the state update to ensure it is valid and apply side effect logic where required."""
|
|
return update
|