refactor: clean up legacy code, rename hopper to entity_extractor, and add third-party notices
- Add NOTICE file and README license references for mobile-use and finalrun-agent - Add derivative attribution headers to 17 files derived from mobile-use - Rename hopper agent to entity_extractor across prompts, tools, and configs - Remove obsolete adb_tunnel, ApiBaseUrl, and legacy CLI utility scripts - Replace legacy examples with native Artemis diagnostic and workflow examples - Update unit and tool tests to validate entity_extractor and SDK builders
This commit is contained in:
1 parent
0860788192
commit
54fcef9887
83 files changed
+1996
-3660
No files matched your search
@@ -0,0 +1,25 @@
|
||||
# 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.
|
||||
|
||||
"""Entity extractor and package resolver agent for ARTEMIS."""
|
||||
|
||||
from artemis.agents.entity_extractor.entity_extractor import (
|
||||
ExtractionResult,
|
||||
extract_entity,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ExtractionResult",
|
||||
"extract_entity",
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
# Artemis Entity Extractor & Package Resolver
|
||||
|
||||
You are an autonomous, high-precision structured data extractor in the ARTEMIS mobile testing runtime.
|
||||
Your responsibility is to extract the exact requested identifier, package name, or configuration token from raw device dump outputs (such as `pm list packages`, `dumpsys`, logcat records, or accessibility tree dumps).
|
||||
|
||||
## Operating Directives
|
||||
|
||||
1. **Verbatim Extraction**:
|
||||
- Return the extracted entity exactly as it appears in the source data.
|
||||
- Do NOT modify capitalization, strip domain segments, translate, or normalize the matched identifier.
|
||||
|
||||
2. **Package Resolution Rules**:
|
||||
- Match target application titles, codenames, or brand identifiers against standard Android package formats (`com.<vendor>.<app>`, `org.<project>.<module>`, etc.).
|
||||
- Prioritize exact matches over partial or prefix matches.
|
||||
|
||||
3. **Strict Disambiguation & Null-Safety**:
|
||||
- If the requested entity is absent from the input dataset, return `found: false` with `output: null`.
|
||||
- If multiple candidates conflict and cannot be deterministically verified from the query, do NOT guess. Set `found: false` and `output: null`.
|
||||
|
||||
## Structured Deliverable Schema
|
||||
|
||||
You must produce your final answer conforming to the following JSON structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"found": true,
|
||||
"output": "<exact_matched_string_or_null>",
|
||||
"reason": "<one_sentence_rationale_confirming_criteria>"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
# 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.
|
||||
|
||||
"""Lightweight entity extraction and package resolver for ARTEMIS.
|
||||
|
||||
Dispatches bounded LLM extraction turns to identify application package identifiers
|
||||
and configuration attributes from raw device listings without manual parsing heuristics.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from jinja2 import Template
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.data_engine.trace import trace
|
||||
from artemis.services.llm import get_llm, invoke_llm_with_timeout_message, with_fallback
|
||||
from artemis.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ExtractionResult(BaseModel):
|
||||
"""Structured extraction outcome for entity resolution."""
|
||||
|
||||
found: bool = Field(description="True if the requested entity was definitively identified.")
|
||||
output: str | None = Field(default=None, description="Exact extracted identifier or string value.")
|
||||
reason: str = Field(description="Brief justification of the match criteria applied.")
|
||||
|
||||
|
||||
@trace(type="agent", name="entity_extractor")
|
||||
async def extract_entity(
|
||||
ctx: ArtemisContext,
|
||||
request: str,
|
||||
data: str,
|
||||
use_fallback: bool = True,
|
||||
) -> ExtractionResult:
|
||||
"""Extract a specific identifier or entity from raw batch device data."""
|
||||
logger.info(f"Invoking Artemis Entity Extractor (fallback={use_fallback})")
|
||||
template_path = Path(__file__).parent / "entity_extractor.md"
|
||||
system_prompt = Template(template_path.read_text(encoding="utf-8")).render()
|
||||
|
||||
messages = [
|
||||
SystemMessage(content=system_prompt),
|
||||
HumanMessage(content=f"Target Query:\n{request}\n\nCandidate Raw Dataset:\n{data}"),
|
||||
]
|
||||
|
||||
primary_llm = get_llm(ctx=ctx, name="entity_extractor", is_utils=True).with_structured_output(
|
||||
ExtractionResult
|
||||
)
|
||||
|
||||
try:
|
||||
if use_fallback:
|
||||
fallback_llm = get_llm(
|
||||
ctx=ctx, name="entity_extractor", is_utils=True, use_fallback=True
|
||||
).with_structured_output(ExtractionResult)
|
||||
raw = await with_fallback(
|
||||
main_call=lambda: invoke_llm_with_timeout_message(primary_llm.ainvoke(messages)),
|
||||
fallback_call=lambda: invoke_llm_with_timeout_message(fallback_llm.ainvoke(messages)),
|
||||
)
|
||||
else:
|
||||
raw = await invoke_llm_with_timeout_message(primary_llm.ainvoke(messages))
|
||||
|
||||
if isinstance(raw, ExtractionResult):
|
||||
return raw
|
||||
if isinstance(raw, dict):
|
||||
return ExtractionResult.model_validate(raw)
|
||||
return cast(ExtractionResult, raw)
|
||||
except Exception as exc:
|
||||
logger.error(f"Entity extraction turn failed: {exc}")
|
||||
return ExtractionResult(
|
||||
found=False,
|
||||
output=None,
|
||||
reason=f"LLM extraction error: {str(exc)[:400]}",
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
## Hopper
|
||||
|
||||
Extract relevant information from batch data. **Keep extracted data exactly as-is** - no reformatting.
|
||||
|
||||
## Output
|
||||
- **found**: `true` if data was found, `false` otherwise
|
||||
- **output**: Extracted information if found, `null` otherwise
|
||||
- **reason**: Brief explanation of search logic
|
||||
|
||||
## Rules
|
||||
1. **Search entire input** - may contain hundreds of entries
|
||||
2. **For app package lookup**: Match app name (or variations) in package identifier
|
||||
- Common patterns: lowercase app name, company+app, brand name, codenames
|
||||
3. **Prefer direct matches** over partial matches
|
||||
4. **Return `null`** if not found or if multiple ambiguous matches exist - don't guess
|
||||
@@ -1,93 +0,0 @@
|
||||
# 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 pathlib import Path
|
||||
|
||||
from jinja2 import Template
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.data_engine.trace import trace
|
||||
from artemis.services.llm import get_llm, invoke_llm_with_timeout_message, with_fallback
|
||||
from artemis.utils.logger import get_logger
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class _CyFunctionDetectorMeta(type):
|
||||
def __instancecheck__(self, instance):
|
||||
name = type(instance).__name__
|
||||
return (
|
||||
name
|
||||
in (
|
||||
"cyfunction",
|
||||
"cython_function_or_method",
|
||||
"builtin_function_or_method",
|
||||
)
|
||||
or "cyfunction" in name.lower()
|
||||
)
|
||||
|
||||
|
||||
class CyFunctionDetector(metaclass=_CyFunctionDetectorMeta):
|
||||
pass
|
||||
|
||||
|
||||
class HopperOutput(BaseModel):
|
||||
model_config = {"ignored_types": (CyFunctionDetector,)}
|
||||
found: bool = Field(description="True if the requested data was found, False otherwise.")
|
||||
output: str | None = Field(description="The extracted data if found, null otherwise.")
|
||||
reason: str = Field(
|
||||
description="A short explanation of what you looked for"
|
||||
+ " and how you decided what to extract."
|
||||
)
|
||||
|
||||
|
||||
@trace(type="agent", name="hopper")
|
||||
async def hopper(
|
||||
ctx: ArtemisContext,
|
||||
request: str,
|
||||
data: str,
|
||||
use_fallback: bool = True,
|
||||
) -> HopperOutput:
|
||||
logger.info(f"Starting Hopper Agent (use_fallback={use_fallback})")
|
||||
system_message = Template(
|
||||
Path(__file__).parent.joinpath("hopper.md").read_text(encoding="utf-8")
|
||||
).render()
|
||||
messages = [
|
||||
SystemMessage(content=system_message),
|
||||
HumanMessage(content=f"{request}\nHere is the data you must dig:\n{data}"),
|
||||
]
|
||||
|
||||
llm = get_llm(ctx=ctx, name="hopper", is_utils=True).with_structured_output(HopperOutput)
|
||||
try:
|
||||
if use_fallback:
|
||||
llm_fallback = get_llm(
|
||||
ctx=ctx, name="hopper", is_utils=True, use_fallback=True
|
||||
).with_structured_output(HopperOutput)
|
||||
response: HopperOutput = await with_fallback(
|
||||
main_call=lambda: invoke_llm_with_timeout_message(llm.ainvoke(messages)),
|
||||
fallback_call=lambda: invoke_llm_with_timeout_message(
|
||||
llm_fallback.ainvoke(messages)
|
||||
),
|
||||
) # type: ignore
|
||||
else:
|
||||
response: HopperOutput = await invoke_llm_with_timeout_message(llm.ainvoke(messages))
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Hopper LLM invocation failed: {e}")
|
||||
return HopperOutput(
|
||||
found=False,
|
||||
output=None,
|
||||
reason=f"Failed due to LLM error: {str(e)[:500]}",
|
||||
)
|
||||
@@ -21,26 +21,7 @@ from langchain_core.tools import BaseTool, StructuredTool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class _CyFunctionDetectorMeta(type):
|
||||
def __instancecheck__(self, instance):
|
||||
name = type(instance).__name__
|
||||
return (
|
||||
name
|
||||
in (
|
||||
"cyfunction",
|
||||
"cython_function_or_method",
|
||||
"builtin_function_or_method",
|
||||
)
|
||||
or "cyfunction" in name.lower()
|
||||
)
|
||||
|
||||
|
||||
class CyFunctionDetector(metaclass=_CyFunctionDetectorMeta):
|
||||
pass
|
||||
|
||||
|
||||
class SpawnLogReaderArgs(BaseModel):
|
||||
model_config = {"ignored_types": (CyFunctionDetector,)}
|
||||
specific_query: str = Field(
|
||||
...,
|
||||
description=(
|
||||
@@ -54,7 +35,6 @@ from langgraph.prebuilt import InjectedState
|
||||
from artemis.core.tool_failure import ToolFailure, is_tool_failure
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.data_engine.trace import (
|
||||
CURRENT_TRACE_ID,
|
||||
TraceSpan,
|
||||
trace,
|
||||
)
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
# 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.
|
||||
#
|
||||
# Portions of this file are derived from mobile-use (https://github.com/minitap-ai/mobile-use)
|
||||
# Copyright 2025-2026 Minitap, Inc. Licensed under the Apache License 2.0.
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
@@ -23,7 +23,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.controllers.unified_controller import UnifiedMobileController
|
||||
from artemis.data_engine.trace import CURRENT_TRACE_ID, TraceSpan, trace
|
||||
from artemis.data_engine.trace import TraceSpan, trace
|
||||
from artemis.graph.state import State
|
||||
from artemis.graph.visibility import strict_state
|
||||
from artemis.services.llm import (
|
||||
@@ -108,26 +108,7 @@ def build_planner_system_blocks(prompts_data: dict, mode: str, include_checks: b
|
||||
return blocks
|
||||
|
||||
|
||||
class _CyFunctionDetectorMeta(type):
|
||||
def __instancecheck__(self, instance):
|
||||
name = type(instance).__name__
|
||||
return (
|
||||
name
|
||||
in (
|
||||
"cyfunction",
|
||||
"cython_function_or_method",
|
||||
"builtin_function_or_method",
|
||||
)
|
||||
or "cyfunction" in name.lower()
|
||||
)
|
||||
|
||||
|
||||
class CyFunctionDetector(metaclass=_CyFunctionDetectorMeta):
|
||||
pass
|
||||
|
||||
|
||||
class ValidationResult(BaseModel):
|
||||
model_config = {"ignored_types": (CyFunctionDetector,)}
|
||||
is_approved: bool = Field(
|
||||
description=(
|
||||
"True if the new plan still serves the initial goal and is coherent."
|
||||
|
||||
Reference in new issue
Block a user