fix: correct task scheduling and simplify agent internals
Fix queue cancellation, concurrency limits and worker status updates. Use direct Explorer imports, split the Diagnoser loop into helpers and remove unused tool registration and prompt-builder code. Correct model caching, endpoint selection and retry handling.
This commit is contained in:
1 parent
67248c887e
commit
12d7902a82
92 files changed
+1085
-1763
No files matched your search
@@ -27,31 +27,6 @@
|
||||
"target"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "click",
|
||||
"description": "[ACTION] Tap/click on the target coordinate on the screen. The screen after click will be returned automatically. For buttons, checkboxes, tabs, icons, items in a list, tap ON the element. For text fields / search bars / input boxes, tap INSIDE the input box to focus it.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "Normalized coordinates [x, y] in 0-1000 scale."
|
||||
},
|
||||
"times": {
|
||||
"type": "integer",
|
||||
"description": "Number of taps to perform (default 1). Set to 2 for double click."
|
||||
},
|
||||
"delay_ms": {
|
||||
"type": "integer",
|
||||
"description": "Delay between taps in milliseconds (default 100)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"target"
|
||||
]
|
||||
}
|
||||
},
|
||||
"CLICK_SEQUENCE_TOOL": {
|
||||
@@ -82,31 +57,6 @@
|
||||
"sequence"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "click_sequence",
|
||||
"description": "[ACTION] Executes a sequence of taps one by one in order on the specified targets (e.g. [[500, 280], [885, 362]]). The screen will be returned ONLY after all clicks in the sequence have completed.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sequence": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "Normalized coordinates [x, y] in 0-1000 scale (e.g., [500, 280]), or a single integer element index."
|
||||
},
|
||||
"description": "List of targets to tap in sequence, e.g. [[500, 280], [885, 362]]."
|
||||
},
|
||||
"delay_ms": {
|
||||
"type": "integer",
|
||||
"description": "Delay between consecutive taps in milliseconds (default 50ms)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sequence"
|
||||
]
|
||||
}
|
||||
},
|
||||
"LONG_PRESS_TOOL": {
|
||||
@@ -133,27 +83,6 @@
|
||||
"target"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "long_press",
|
||||
"description": "[ACTION] Long press on a target coordinate on the screen. The screen after long pressing will be returned automatically.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "Normalized coordinates [x, y] in 0-1000 scale."
|
||||
},
|
||||
"duration_ms": {
|
||||
"type": "integer",
|
||||
"description": "Duration of press in milliseconds (default 1000ms)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"target"
|
||||
]
|
||||
}
|
||||
},
|
||||
"INPUT_TEXT_TOOL": {
|
||||
@@ -185,32 +114,6 @@
|
||||
"target"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "input_text",
|
||||
"description": "[ACTION] Type text into an input field on the screen. The screen after typing will be returned automatically. Automatically taps inside the input box at target [x, y] to focus before typing.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Text to type into the focused input field."
|
||||
},
|
||||
"target": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "Coordinates [x, y] of the input box in 0-1000 scale."
|
||||
},
|
||||
"clear_exist": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to clear existing text in the input box before typing (default True)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"text",
|
||||
"target"
|
||||
]
|
||||
}
|
||||
},
|
||||
"SWIPE_TOOL": {
|
||||
@@ -257,47 +160,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "swipe",
|
||||
"description": "[ACTION] Perform a swipe, drag, or slider-adjustment gesture on the screen. The screen and UI hierarchy after swipe will be returned automatically.\n\n• Directional Scrolling ('direction' or 'action'): Recommended for general browsing and standard page scrolling in most scenarios. Automatically computes safe swipe vectors and adaptive duration, retains a ~40% visual overlap anchor for zero-omission traversal, and prevents inertial flings. Supports scoping to a sub-container via 'target'. If it fails on certain custom layouts, fall back to specifying exact coordinates ('start' and 'end') directly.\n• Precise Coordinate Gestures ('start', 'end' or coordinates list): Best for local, fine-grained interactions such as adjusting sliders/SeekBars (e.g., volume, brightness, progress bars), drag-and-drop / list reordering, or as a reliable fallback when directional scrolling fails on specific containers. Always drag slightly PAST the target position to overcome touch slop and reliably trigger the update. When setting a slider to Maximum (100%) or Minimum (0%), swipe fully to the extreme boundary.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up",
|
||||
"down",
|
||||
"left",
|
||||
"right"
|
||||
],
|
||||
"description": "Direction for scrolling and swiping: 'up' (drags bottom-to-top, scrolling down to reveal content below), 'down' (drags top-to-bottom, scrolling up to reveal content above), 'left' (drags right-to-left, scrolling right), 'right' (drags left-to-right, scrolling left)."
|
||||
},
|
||||
"start": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "Start normalized coordinates [start_x, start_y] in 0-1000 scale."
|
||||
},
|
||||
"end": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "End normalized coordinates [end_x, end_y] in 0-1000 scale."
|
||||
},
|
||||
"target": {
|
||||
"description": "Optional target element index (e.g. 2) or container bounds [left, top, right, bottom] to scope the directional swipe within."
|
||||
},
|
||||
"action": {
|
||||
"description": "Backward-compatible swipe gesture: smart direction string ('up', 'down', 'left', 'right') OR precise custom coordinates [start_x, start_y, end_x, end_y] in 0-1000 scale."
|
||||
},
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "Optional swipe/drag duration in milliseconds (default 800). For drag-and-drop, list reordering, or sliding/adjusting sliders (e.g., volume, brightness, SeekBars), set duration >= 1000 (e.g. 1500). If omitted for directional swipe, duration is computed automatically."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PRESS_KEY_TOOL": {
|
||||
@@ -317,20 +179,6 @@
|
||||
"key"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "press_key",
|
||||
"description": "[ACTION] Press a physical or virtual system button. The screen after pressing will be returned automatically.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Standard Android system button name (ENTER, BACK, HOME, APP_SWITCH)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key"
|
||||
]
|
||||
}
|
||||
},
|
||||
"MANAGE_APP_TOOL": {
|
||||
@@ -355,25 +203,6 @@
|
||||
"app_name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "manage_app",
|
||||
"description": "[ACTION] Launch or force stop a specified application. The screen after launch/stop will be returned automatically.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "The action type ('launch' or 'stop')."
|
||||
},
|
||||
"app_name": {
|
||||
"type": "string",
|
||||
"description": "Display name or package name of the application."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"app_name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"WAIT_FOR_DELAY_TOOL": {
|
||||
@@ -393,20 +222,6 @@
|
||||
"time_in_ms"
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "wait_for_delay",
|
||||
"description": "[ACTION] Pause execution and wait for a specified duration in milliseconds. The screen after pause will be returned automatically.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"time_in_ms": {
|
||||
"type": "integer",
|
||||
"description": "The exact duration to pause in milliseconds (e.g., 2000 for 2s, 60000 for 1 min, 180000 for 3 mins, 300000 for 5 mins). Convert any required waiting duration into milliseconds."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"time_in_ms"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -87,7 +87,7 @@ async def test_explorer_all_tools_sequential_mocked():
|
||||
logger.addHandler(handler)
|
||||
|
||||
# Attach the main agent logger to capture internal step logs
|
||||
agent_logger = logging.getLogger("artemis.agents.explorer.explorer")
|
||||
agent_logger = logging.getLogger("artemis.agents.explorer")
|
||||
agent_logger.setLevel(logging.INFO)
|
||||
agent_logger.addHandler(handler)
|
||||
|
||||
@@ -238,12 +238,13 @@ async def test_explorer_all_tools_sequential_mocked():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
# get_ocr_list is only exposed when an OCR backend is configured.
|
||||
patch("artemis.agents.explorer.explorer.is_ocr_configured", return_value=True),
|
||||
patch("artemis.agents.explorer.explorer._run_object_detection", mock_detect),
|
||||
patch("artemis.agents.explorer.run_setup.is_ocr_configured", return_value=True),
|
||||
patch("artemis.agents.explorer.perception_tools._run_object_detection", mock_detect),
|
||||
):
|
||||
explorer = Explorer(mock_ctx)
|
||||
result = await explorer.run(
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ async def test_explorer_ask_image_processor_tool():
|
||||
logger.addHandler(handler)
|
||||
|
||||
# Attach the main agent logger to capture internal step logs
|
||||
agent_logger = logging.getLogger("artemis.agents.explorer.explorer")
|
||||
agent_logger = logging.getLogger("artemis.agents.explorer")
|
||||
agent_logger.setLevel(logging.INFO)
|
||||
agent_logger.addHandler(handler)
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ def test_diagnoser_submit_answer_tool(artemis_context):
|
||||
assert "error" not in result.lower()
|
||||
|
||||
|
||||
def test_submit_answer_tool_subclass_and_registry():
|
||||
"""Verify SubmitAnswerTool is an ArtemisTool subclass and registered in ToolRegistry."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_submit_answer_tool_subclass():
|
||||
"""Verify SubmitAnswerTool is an ArtemisTool subclass."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.diagnoser_submit_answer_tool import (
|
||||
DiagnoserSubmitAnswerTool,
|
||||
SubmitAnswer,
|
||||
@@ -52,11 +52,6 @@ def test_submit_answer_tool_subclass_and_registry():
|
||||
assert submit_answer.category == "custom"
|
||||
assert submit_answer.args_schema == SubmitAnswerArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("submit_answer")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, SubmitAnswerTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = submit_answer.to_genai_declaration()
|
||||
assert declaration.name == "submit_answer"
|
||||
@@ -64,9 +59,9 @@ def test_submit_answer_tool_subclass_and_registry():
|
||||
assert "actionable_steps" in declaration.parameters.properties
|
||||
|
||||
|
||||
def test_ask_diagnoser_tool_subclass_and_registry():
|
||||
"""Verify AskDiagnoserTool is an ArtemisTool subclass and registered in ToolRegistry."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_ask_diagnoser_tool_subclass():
|
||||
"""Verify AskDiagnoserTool is an ArtemisTool subclass."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.diagnostic_tool import (
|
||||
AskDiagnoser,
|
||||
AskDiagnoserArgs,
|
||||
@@ -85,20 +80,15 @@ def test_ask_diagnoser_tool_subclass_and_registry():
|
||||
assert ask_diagnoser.category == "custom"
|
||||
assert ask_diagnoser.args_schema == AskDiagnoserArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("ask_diagnoser")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, AskDiagnoserTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = ask_diagnoser.to_genai_declaration()
|
||||
assert declaration.name == "ask_diagnoser"
|
||||
assert "query" in declaration.parameters.properties
|
||||
|
||||
|
||||
def test_analyze_logs_tool_subclass_and_registry():
|
||||
"""Verify AnalyzeLogsTool is an ArtemisTool subclass and registered in ToolRegistry."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_analyze_logs_tool_subclass():
|
||||
"""Verify AnalyzeLogsTool is an ArtemisTool subclass."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.log_tool import (
|
||||
AnalyzeLogs,
|
||||
AnalyzeLogsArgs,
|
||||
@@ -117,11 +107,6 @@ def test_analyze_logs_tool_subclass_and_registry():
|
||||
assert analyze_logs.category == "custom"
|
||||
assert analyze_logs.args_schema == AnalyzeLogsArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("analyze_logs")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, AnalyzeLogsTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = analyze_logs.to_genai_declaration()
|
||||
assert declaration.name == "analyze_logs"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
"""Tests for object_detector universal tools."""
|
||||
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.object_detection_tool import (
|
||||
ObjectDetection,
|
||||
ObjectDetectionArgs,
|
||||
@@ -29,8 +29,8 @@ from artemis.tools.object_detection_tool import (
|
||||
)
|
||||
|
||||
|
||||
def test_object_detector_tool_subclass_and_registry():
|
||||
"""Verify ObjectDetectionTool is an ArtemisTool subclass and registered in ToolRegistry."""
|
||||
def test_object_detector_tool_subclass():
|
||||
"""Verify ObjectDetectionTool is an ArtemisTool subclass."""
|
||||
assert issubclass(ObjectDetectionTool, ArtemisTool)
|
||||
assert issubclass(ObjectDetection, ArtemisTool)
|
||||
assert issubclass(ObjectDetectorTool, ArtemisTool)
|
||||
@@ -51,8 +51,3 @@ def test_object_detector_tool_subclass_and_registry():
|
||||
assert operator_object_detection.name == "object_detection"
|
||||
assert operator_object_detection.category == "perception"
|
||||
assert operator_object_detection.args_schema == OperatorObjectDetectionArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("object_detection")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, ObjectDetectionTool)
|
||||
@@ -112,7 +112,7 @@ async def test_enqueued_task_keeps_its_adb_endpoint_snapshot():
|
||||
return_value=original,
|
||||
),
|
||||
patch(
|
||||
"artemis.runtime.device_pool.device_pool.select_device",
|
||||
"artemis.runtime.device_pool.device_pool.select_device_async",
|
||||
return_value="emulator-5554",
|
||||
),
|
||||
):
|
||||
@@ -230,7 +230,7 @@ async def test_queue_worker_execution_lifecycle():
|
||||
patch("apps.admin_console.services.task_queue_service.session_repo") as mock_repo,
|
||||
patch("apps.admin_console.services.task_queue_service.media_service"),
|
||||
patch(
|
||||
"artemis.runtime.device_pool.device_pool.select_device",
|
||||
"artemis.runtime.device_pool.device_pool.select_device_async",
|
||||
return_value="emulator-5554",
|
||||
),
|
||||
):
|
||||
@@ -276,7 +276,7 @@ async def test_queue_worker_cmd_construction():
|
||||
patch("apps.admin_console.services.task_queue_service.session_repo") as mock_repo,
|
||||
patch("apps.admin_console.services.task_queue_service.media_service"),
|
||||
patch(
|
||||
"artemis.runtime.device_pool.device_pool.select_device",
|
||||
"artemis.runtime.device_pool.device_pool.select_device_async",
|
||||
return_value="emulator-5554",
|
||||
),
|
||||
):
|
||||
@@ -594,7 +594,7 @@ async def test_cancel_task_triggers_next_pending_task():
|
||||
patch("apps.admin_console.services.task_queue_service.session_repo") as mock_repo,
|
||||
patch("apps.admin_console.services.task_queue_service.media_service"),
|
||||
patch(
|
||||
"artemis.runtime.device_pool.device_pool.select_device",
|
||||
"artemis.runtime.device_pool.device_pool.select_device_async",
|
||||
return_value="emulator-5554",
|
||||
),
|
||||
):
|
||||
@@ -645,7 +645,7 @@ async def test_immediate_cancel_ignores_stale_ipc_and_runs_next_task():
|
||||
patch("apps.admin_console.services.task_queue_service.session_repo") as mock_repo,
|
||||
patch("apps.admin_console.services.task_queue_service.media_service"),
|
||||
patch(
|
||||
"artemis.runtime.device_pool.device_pool.select_device",
|
||||
"artemis.runtime.device_pool.device_pool.select_device_async",
|
||||
return_value="emulator-5554",
|
||||
),
|
||||
):
|
||||
|
||||
@@ -106,7 +106,7 @@ async def test_explorer_run():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
@@ -193,7 +193,7 @@ async def test_explorer_submit_answer():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
@@ -339,7 +339,7 @@ async def test_explorer_submit_answer_self_correction():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
@@ -427,10 +427,10 @@ async def test_explorer_initial_visual_marking():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch("artemis.agents.explorer.explorer.draw_dots") as mock_draw_dots,
|
||||
patch("artemis.agents.explorer.run_setup.draw_dots") as mock_draw_dots,
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
patch("pathlib.Path.read_text", return_value=mock_prompt),
|
||||
patch("builtins.open", custom_open),
|
||||
@@ -518,10 +518,10 @@ async def test_explorer_initial_visual_marking_previous_screenshot():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch("artemis.agents.explorer.explorer.draw_dots") as mock_draw_dots,
|
||||
patch("artemis.agents.explorer.run_setup.draw_dots") as mock_draw_dots,
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
patch("pathlib.Path.read_text", return_value=mock_prompt),
|
||||
patch("builtins.open", custom_open),
|
||||
@@ -606,10 +606,10 @@ async def test_explorer_initial_visual_marking_previous_screenshot_no_ui_tree():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch("artemis.agents.explorer.explorer.draw_dots") as mock_draw_dots,
|
||||
patch("artemis.agents.explorer.run_setup.draw_dots") as mock_draw_dots,
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
patch("pathlib.Path.read_text", return_value=mock_prompt),
|
||||
patch("builtins.open", custom_open),
|
||||
@@ -704,10 +704,10 @@ async def test_explorer_initial_visual_marking_previous_screenshot_ocr_fusion():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch("artemis.agents.explorer.explorer.draw_dots") as mock_draw_dots,
|
||||
patch("artemis.agents.explorer.run_setup.draw_dots") as mock_draw_dots,
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
patch("pathlib.Path.read_text", return_value=mock_prompt),
|
||||
patch("builtins.open", custom_open),
|
||||
@@ -808,19 +808,19 @@ async def test_explorer_initial_visual_marking_previous_screenshot_on_the_fly_oc
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.perform_ocr",
|
||||
"artemis.agents.explorer.run_setup.perform_ocr",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_ocr_result,
|
||||
) as mock_perform_ocr,
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.is_ocr_configured",
|
||||
"artemis.agents.explorer.run_setup.is_ocr_configured",
|
||||
return_value=True,
|
||||
),
|
||||
patch("artemis.agents.explorer.explorer.draw_dots") as mock_draw_dots,
|
||||
patch("artemis.agents.explorer.run_setup.draw_dots") as mock_draw_dots,
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
patch("pathlib.Path.read_text", return_value=mock_prompt),
|
||||
patch("builtins.open", custom_open),
|
||||
@@ -933,7 +933,7 @@ async def test_explorer_denylisted_tool():
|
||||
return_value=mock_client,
|
||||
),
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer.StorageManager",
|
||||
"artemis.agents.explorer.run_setup.StorageManager",
|
||||
return_value=mock_storage,
|
||||
),
|
||||
patch("pathlib.Path.exists", return_value=True),
|
||||
@@ -1014,7 +1014,7 @@ async def test_explorer_inspect_region():
|
||||
patch("cv2.imwrite", return_value=True) as mock_imwrite,
|
||||
patch("pathlib.Path.mkdir") as mock_mkdir,
|
||||
patch("glob.glob", return_value=[]),
|
||||
patch("artemis.agents.explorer.explorer.settings") as mock_settings,
|
||||
patch("artemis.agents.explorer.perception_tools.settings") as mock_settings,
|
||||
):
|
||||
mock_settings.TRACES_PATH = pathlib.Path("/tmp/traces")
|
||||
res = await explorer.exec_inspect_region(
|
||||
@@ -1181,12 +1181,12 @@ async def test_explorer_ask_perception_tool():
|
||||
mock_draw_dots = MagicMock()
|
||||
|
||||
with (
|
||||
patch("artemis.agents.explorer.explorer.StorageManager") as storage_cls,
|
||||
patch("artemis.agents.explorer.run_setup.StorageManager") as storage_cls,
|
||||
patch(
|
||||
"artemis.agents.explorer.explorer._run_object_detection",
|
||||
"artemis.agents.explorer.perception_tools._run_object_detection",
|
||||
return_value=mock_detector_res,
|
||||
),
|
||||
patch("artemis.agents.explorer.explorer.draw_dots", mock_draw_dots),
|
||||
patch("artemis.agents.explorer.perception_tools.draw_dots", mock_draw_dots),
|
||||
patch("artemis.utils.visualization.draw_dots", mock_draw_dots),
|
||||
patch("pathlib.Path.mkdir"),
|
||||
patch("glob.glob", return_value=[]),
|
||||
@@ -1277,7 +1277,7 @@ class _FakeGenaiError(Exception):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_content_reliability_permanent_error_is_not_retried():
|
||||
from artemis.agents.explorer.explorer import _generate_content_with_reliability
|
||||
from artemis.agents.explorer.native_runner import _generate_content_with_reliability
|
||||
from artemis.llm.reliability import LLMPermanentError
|
||||
|
||||
calls = 0
|
||||
@@ -1296,7 +1296,7 @@ async def test_generate_content_reliability_permanent_error_is_not_retried():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_content_reliability_bad_request_is_not_retried():
|
||||
from artemis.agents.explorer.explorer import _generate_content_with_reliability
|
||||
from artemis.agents.explorer.native_runner import _generate_content_with_reliability
|
||||
from artemis.llm.reliability import LLMPermanentError
|
||||
|
||||
calls = 0
|
||||
@@ -1314,7 +1314,7 @@ async def test_generate_content_reliability_bad_request_is_not_retried():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_content_reliability_retries_transient_then_succeeds():
|
||||
from artemis.agents.explorer import explorer as explorer_module
|
||||
from artemis.agents.explorer import native_runner as native_runner_module
|
||||
|
||||
calls = 0
|
||||
sentinel = object()
|
||||
@@ -1326,8 +1326,8 @@ async def test_generate_content_reliability_retries_transient_then_succeeds():
|
||||
raise _FakeGenaiError("429 rate limit exceeded", code=429)
|
||||
return sentinel
|
||||
|
||||
with patch.object(explorer_module.asyncio, "sleep", new=AsyncMock()) as sleep_mock:
|
||||
result = await explorer_module._generate_content_with_reliability(op)
|
||||
with patch.object(native_runner_module.asyncio, "sleep", new=AsyncMock()) as sleep_mock:
|
||||
result = await native_runner_module._generate_content_with_reliability(op)
|
||||
assert result is sentinel
|
||||
assert calls == 2
|
||||
assert sleep_mock.await_count == 1
|
||||
@@ -1335,7 +1335,7 @@ async def test_generate_content_reliability_retries_transient_then_succeeds():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_content_reliability_exhausts_to_typed_error():
|
||||
from artemis.agents.explorer import explorer as explorer_module
|
||||
from artemis.agents.explorer import native_runner as native_runner_module
|
||||
from artemis.llm.reliability import (
|
||||
FailureCategory,
|
||||
LLMExhaustedError,
|
||||
@@ -1349,9 +1349,9 @@ async def test_generate_content_reliability_exhausts_to_typed_error():
|
||||
calls += 1
|
||||
raise _FakeGenaiError("503 service unavailable", code=503)
|
||||
|
||||
with patch.object(explorer_module.asyncio, "sleep", new=AsyncMock()):
|
||||
with patch.object(native_runner_module.asyncio, "sleep", new=AsyncMock()):
|
||||
with pytest.raises(LLMExhaustedError) as exc_info:
|
||||
await explorer_module._generate_content_with_reliability(op)
|
||||
await native_runner_module._generate_content_with_reliability(op)
|
||||
assert calls == retry_policy_for(FailureCategory.PROVIDER_UNAVAILABLE).max_attempts
|
||||
assert exc_info.value.failure.category.value == "provider_unavailable"
|
||||
assert isinstance(exc_info.value.__cause__, _FakeGenaiError)
|
||||
@@ -37,7 +37,7 @@ def _perception_host() -> PerceptionToolsMixin:
|
||||
async def test_get_ocr_list_reads_the_screen_index_without_storage() -> None:
|
||||
host = _perception_host()
|
||||
|
||||
with patch("artemis.agents.explorer.explorer.StorageManager") as storage_cls:
|
||||
with patch("artemis.agents.explorer.run_setup.StorageManager") as storage_cls:
|
||||
result = await host.exec_get_ocr_list()
|
||||
|
||||
assert result == {
|
||||
|
||||
@@ -25,7 +25,8 @@ from artemis.agents.explorer.screen_index import ScreenElement, ScreenIndex
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
|
||||
FACADE = "artemis.agents.explorer.explorer"
|
||||
RUN_SETUP = "artemis.agents.explorer.run_setup"
|
||||
PERCEPTION_TOOLS = "artemis.agents.explorer.perception_tools"
|
||||
W, H = 1080, 2400
|
||||
|
||||
#: One "Settings" button, two "Send" buttons and two OCR-only texts.
|
||||
@@ -108,8 +109,10 @@ def screenshot(tmp_path):
|
||||
@pytest.fixture
|
||||
def no_drawing():
|
||||
"""Keeps the annotation helpers away from the file system."""
|
||||
draw_dots = MagicMock()
|
||||
with (
|
||||
patch(f"{FACADE}.draw_dots") as draw_dots,
|
||||
patch(f"{RUN_SETUP}.draw_dots", draw_dots),
|
||||
patch(f"{PERCEPTION_TOOLS}.draw_dots", draw_dots),
|
||||
patch("pathlib.Path.mkdir"),
|
||||
patch("glob.glob", return_value=[]),
|
||||
):
|
||||
@@ -127,8 +130,8 @@ async def test_index_is_built_from_state_hierarchy_when_record_is_missing(screen
|
||||
explorer._run_flash = AsyncMock(return_value=_outcome(fallback="nothing"))
|
||||
|
||||
with (
|
||||
patch(f"{FACADE}.StorageManager", return_value=_storage(None)),
|
||||
patch(f"{FACADE}.perform_ocr", new_callable=AsyncMock) as perform_ocr,
|
||||
patch(f"{RUN_SETUP}.StorageManager", return_value=_storage(None)),
|
||||
patch(f"{RUN_SETUP}.perform_ocr", new_callable=AsyncMock) as perform_ocr,
|
||||
):
|
||||
await explorer.run("gear icon", "", screenshot, _state(screenshot), version="flash")
|
||||
text = (await explorer._search_ui_helper("Settings", prefix="X"))["text"]
|
||||
@@ -145,7 +148,7 @@ async def test_index_ignores_state_hierarchy_for_another_screenshot(screenshot):
|
||||
explorer._run_flash = AsyncMock(return_value=_outcome())
|
||||
state = _state("/tmp/other.jpg")
|
||||
|
||||
with patch(f"{FACADE}.StorageManager", return_value=_storage(None)):
|
||||
with patch(f"{RUN_SETUP}.StorageManager", return_value=_storage(None)):
|
||||
await explorer.run("Settings", "", screenshot, state, version="flash")
|
||||
|
||||
assert len(explorer.screen_index) == 0
|
||||
@@ -224,7 +227,7 @@ async def test_search_by_coords_helper_reports_innermost_first(no_drawing):
|
||||
async def test_get_ocr_list_reads_the_index(no_drawing):
|
||||
explorer = _indexed_explorer()
|
||||
|
||||
with patch(f"{FACADE}.StorageManager") as storage_cls:
|
||||
with patch(f"{RUN_SETUP}.StorageManager") as storage_cls:
|
||||
result = await explorer.exec_get_ocr_list()
|
||||
|
||||
storage_cls.assert_not_called()
|
||||
@@ -369,7 +372,7 @@ async def test_prepass_answers_unique_exact_match_without_the_engine(screenshot,
|
||||
explorer._run_flash = AsyncMock()
|
||||
explorer._run_loop = AsyncMock()
|
||||
|
||||
with patch(f"{FACADE}.StorageManager", return_value=_storage(None)):
|
||||
with patch(f"{RUN_SETUP}.StorageManager", return_value=_storage(None)):
|
||||
raw = await explorer.run(" settings ", "", screenshot, _state(screenshot), version=version)
|
||||
|
||||
explorer._run_flash.assert_not_called()
|
||||
@@ -395,7 +398,7 @@ async def test_prepass_leaves_ambiguous_matches_to_the_engine(screenshot):
|
||||
engine = _outcome([{"label": "D1", "coords": [185, 854], "description": "Send"}])
|
||||
explorer._run_flash = AsyncMock(return_value=engine)
|
||||
|
||||
with patch(f"{FACADE}.StorageManager", return_value=_storage(None)):
|
||||
with patch(f"{RUN_SETUP}.StorageManager", return_value=_storage(None)):
|
||||
raw = await explorer.run("Send", "", screenshot, _state(screenshot), version="flash")
|
||||
|
||||
explorer._run_flash.assert_awaited_once_with("Send", screenshot)
|
||||
@@ -408,7 +411,7 @@ async def test_prepass_without_pipe_or_match_passes_the_query_through(screenshot
|
||||
engine = _outcome(fallback="Failed to detect: gear icon")
|
||||
explorer._run_flash = AsyncMock(return_value=engine)
|
||||
|
||||
with patch(f"{FACADE}.StorageManager", return_value=_storage(None)):
|
||||
with patch(f"{RUN_SETUP}.StorageManager", return_value=_storage(None)):
|
||||
raw = await explorer.run("gear icon", "", screenshot, _state(screenshot), version="flash")
|
||||
|
||||
explorer._run_flash.assert_awaited_once_with("gear icon", screenshot)
|
||||
@@ -421,7 +424,7 @@ async def test_prepass_merges_partial_results_with_the_engine_outcome(screenshot
|
||||
detected = {"label": "D1", "coords": [185, 854], "description": "Send"}
|
||||
explorer._run_loop = AsyncMock(return_value=_outcome([detected], fallback="two Send buttons"))
|
||||
|
||||
with patch(f"{FACADE}.StorageManager", return_value=_storage(None)):
|
||||
with patch(f"{RUN_SETUP}.StorageManager", return_value=_storage(None)):
|
||||
raw = await explorer.run(
|
||||
"Settings | Send | Welcome", "", screenshot, _state(screenshot), version="pro"
|
||||
)
|
||||
@@ -451,9 +454,9 @@ async def test_prepass_never_runs_on_the_fly_ocr_for_flash(screenshot):
|
||||
explorer._run_loop = AsyncMock(return_value=_outcome())
|
||||
|
||||
with (
|
||||
patch(f"{FACADE}.StorageManager", return_value=_storage(record)),
|
||||
patch(f"{FACADE}.is_ocr_configured", return_value=True),
|
||||
patch(f"{FACADE}.perform_ocr", new_callable=AsyncMock, return_value=[]) as perform_ocr,
|
||||
patch(f"{RUN_SETUP}.StorageManager", return_value=_storage(record)),
|
||||
patch(f"{RUN_SETUP}.is_ocr_configured", return_value=True),
|
||||
patch(f"{RUN_SETUP}.perform_ocr", new_callable=AsyncMock, return_value=[]) as perform_ocr,
|
||||
):
|
||||
await explorer.run("gear icon", "", screenshot, _state(screenshot, []), version="flash")
|
||||
perform_ocr.assert_not_awaited()
|
||||
|
||||
@@ -28,7 +28,9 @@ from artemis.config import settings
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
|
||||
FACADE = "artemis.agents.explorer.explorer"
|
||||
EXPLORER = "artemis.agents.explorer.explorer"
|
||||
RUN_SETUP = "artemis.agents.explorer.run_setup"
|
||||
UNIVERSAL_RUNNER = "artemis.agents.explorer.universal_runner"
|
||||
|
||||
|
||||
def _context(model: str = "gemini-3.8-flash", denylist: list[str] | None = None) -> MagicMock:
|
||||
@@ -108,7 +110,7 @@ def _isolated_env(monkeypatch):
|
||||
|
||||
|
||||
def test_construction_creates_no_genai_client():
|
||||
with patch(f"{FACADE}.genai.Client") as client_cls:
|
||||
with patch(f"{EXPLORER}.genai.Client") as client_cls:
|
||||
explorer = Explorer(_context())
|
||||
client_cls.assert_not_called()
|
||||
assert explorer.client is None
|
||||
@@ -124,7 +126,7 @@ def test_construction_creates_no_genai_client():
|
||||
)
|
||||
def test_tier_sets_turn_budget_and_exposed_tools(version, max_turns, expected_tools):
|
||||
explorer = Explorer(_context())
|
||||
with patch(f"{FACADE}.is_ocr_configured", return_value=True):
|
||||
with patch(f"{EXPLORER}.is_ocr_configured", return_value=True):
|
||||
explorer._apply_tier(get_tier(version))
|
||||
native_names = {t.name for t in explorer.get_exposed_tools()}
|
||||
universal_names = {t["function"]["name"] for t in explorer._universal_exposed_tools()}
|
||||
@@ -137,7 +139,7 @@ def test_tier_sets_turn_budget_and_exposed_tools(version, max_turns, expected_to
|
||||
|
||||
def test_unconfigured_ocr_and_user_denylist_hide_tools_on_ultra():
|
||||
explorer = Explorer(_context(denylist=["inspect_region"]))
|
||||
with patch(f"{FACADE}.is_ocr_configured", return_value=False):
|
||||
with patch(f"{EXPLORER}.is_ocr_configured", return_value=False):
|
||||
explorer._apply_tier(get_tier("ultra"))
|
||||
names = {t.name for t in explorer.get_exposed_tools()}
|
||||
assert "get_ocr_list" not in names
|
||||
@@ -190,9 +192,9 @@ async def test_universal_run_without_google_key_never_touches_genai(screenshot):
|
||||
|
||||
with (
|
||||
patch.object(settings, "GOOGLE_API_KEY", None),
|
||||
patch(f"{FACADE}.genai.Client") as client_cls,
|
||||
patch(f"{FACADE}.get_llm", return_value=llm),
|
||||
patch(f"{FACADE}.StorageManager", return_value=storage),
|
||||
patch(f"{EXPLORER}.genai.Client") as client_cls,
|
||||
patch(f"{UNIVERSAL_RUNNER}.get_llm", return_value=llm),
|
||||
patch(f"{RUN_SETUP}.StorageManager", return_value=storage),
|
||||
):
|
||||
explorer = Explorer(ctx)
|
||||
raw = await explorer.run("find it", "", screenshot, _state(screenshot), version="pro")
|
||||
@@ -342,7 +344,7 @@ def test_pro_prompt_only_describes_exposed_tools():
|
||||
|
||||
def test_ultra_prompt_describes_all_tools():
|
||||
explorer = Explorer(_context())
|
||||
with patch(f"{FACADE}.is_ocr_configured", return_value=True):
|
||||
with patch(f"{EXPLORER}.is_ocr_configured", return_value=True):
|
||||
explorer._apply_tier(get_tier("ultra"))
|
||||
prompt, error = explorer._build_prompt_template(get_tier("ultra"))
|
||||
assert error is None
|
||||
@@ -355,7 +357,7 @@ def test_ultra_prompt_describes_all_tools():
|
||||
|
||||
def test_prompt_denylist_section_only_for_user_denied_tier_tools():
|
||||
explorer = Explorer(_context(denylist=["inspect_region", "search_xml_ocr"]))
|
||||
with patch(f"{FACADE}.is_ocr_configured", return_value=True):
|
||||
with patch(f"{EXPLORER}.is_ocr_configured", return_value=True):
|
||||
explorer._apply_tier(get_tier("ultra"))
|
||||
ultra_prompt, _ = explorer._build_prompt_template(get_tier("ultra"))
|
||||
explorer._apply_tier(get_tier("pro"))
|
||||
@@ -399,8 +401,8 @@ async def test_flash_run_returns_detected_candidates(screenshot):
|
||||
}
|
||||
)
|
||||
with (
|
||||
patch(f"{FACADE}._run_object_detection", detection),
|
||||
patch(f"{FACADE}.genai.Client") as client_cls,
|
||||
patch(f"{RUN_SETUP}._run_object_detection", detection),
|
||||
patch(f"{EXPLORER}.genai.Client") as client_cls,
|
||||
):
|
||||
explorer = Explorer(_context())
|
||||
raw = await explorer.run(
|
||||
@@ -421,7 +423,7 @@ async def test_flash_run_returns_detected_candidates(screenshot):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_flash_run_reports_missing_detection(screenshot):
|
||||
with patch(f"{FACADE}._run_object_detection", AsyncMock(return_value={"detected": []})):
|
||||
with patch(f"{RUN_SETUP}._run_object_detection", AsyncMock(return_value={"detected": []})):
|
||||
raw = await Explorer(_context()).run(
|
||||
"ghost", "", screenshot, _state(screenshot), version="flash"
|
||||
)
|
||||
@@ -442,8 +444,8 @@ async def test_native_ultra_run_uses_async_cache_api(screenshot):
|
||||
storage.get_image.return_value = None
|
||||
|
||||
with (
|
||||
patch(f"{FACADE}.genai.Client") as client_cls,
|
||||
patch(f"{FACADE}.StorageManager", return_value=storage),
|
||||
patch(f"{EXPLORER}.genai.Client") as client_cls,
|
||||
patch(f"{RUN_SETUP}.StorageManager", return_value=storage),
|
||||
patch.object(settings, "EXPLORER_CACHING", None),
|
||||
):
|
||||
explorer = Explorer(ctx)
|
||||
@@ -466,7 +468,7 @@ async def test_native_pro_run_skips_cache_by_default(screenshot):
|
||||
storage.get_image.return_value = None
|
||||
|
||||
with (
|
||||
patch(f"{FACADE}.StorageManager", return_value=storage),
|
||||
patch(f"{RUN_SETUP}.StorageManager", return_value=storage),
|
||||
patch.object(settings, "EXPLORER_CACHING", None),
|
||||
):
|
||||
await Explorer(ctx).run("target", "", screenshot, _state(screenshot), version="pro")
|
||||
@@ -489,7 +491,7 @@ async def test_native_file_api_uploads_and_deletes_asynchronously(screenshot, mo
|
||||
storage = MagicMock()
|
||||
storage.get_image.return_value = None
|
||||
|
||||
with patch(f"{FACADE}.StorageManager", return_value=storage):
|
||||
with patch(f"{RUN_SETUP}.StorageManager", return_value=storage):
|
||||
await Explorer(ctx).run("target", "", screenshot, _state(screenshot), version="pro")
|
||||
|
||||
client.aio.files.upload.assert_awaited_once_with(file=screenshot)
|
||||
@@ -507,7 +509,7 @@ async def test_run_closes_http_client_on_both_engines(screenshot):
|
||||
ctx._genai_client = client
|
||||
native = Explorer(ctx)
|
||||
native.http_client = SimpleNamespace(aclose=AsyncMock())
|
||||
with patch(f"{FACADE}.StorageManager", return_value=storage):
|
||||
with patch(f"{RUN_SETUP}.StorageManager", return_value=storage):
|
||||
await native.run("t", "", screenshot, _state(screenshot), version="pro")
|
||||
native_close = native.http_client # already reset
|
||||
assert native_close is None
|
||||
@@ -519,8 +521,8 @@ async def test_run_closes_http_client_on_both_engines(screenshot):
|
||||
http_client = SimpleNamespace(aclose=AsyncMock())
|
||||
universal.http_client = http_client
|
||||
with (
|
||||
patch(f"{FACADE}.StorageManager", return_value=storage),
|
||||
patch(f"{FACADE}.get_llm", return_value=llm),
|
||||
patch(f"{RUN_SETUP}.StorageManager", return_value=storage),
|
||||
patch(f"{UNIVERSAL_RUNNER}.get_llm", return_value=llm),
|
||||
pytest.raises(RuntimeError),
|
||||
):
|
||||
await universal.run("t", "", screenshot, _state(screenshot), version="pro")
|
||||
|
||||
@@ -20,10 +20,8 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
import pytest
|
||||
from langchain_core.messages import AIMessage
|
||||
|
||||
from artemis.agents.explorer.explorer import (
|
||||
UNIVERSAL_EXPLORER_TOOLS,
|
||||
Explorer,
|
||||
)
|
||||
from artemis.agents.explorer.explorer import Explorer
|
||||
from artemis.agents.explorer.tool_declarations import UNIVERSAL_EXPLORER_TOOLS
|
||||
from artemis.config import settings
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
@@ -106,7 +104,7 @@ async def test_run_universal_explorer_success(mock_context, tmp_path):
|
||||
mock_llm.bind_tools.return_value = mock_bound
|
||||
|
||||
with patch(
|
||||
"artemis.agents.explorer.explorer.get_llm",
|
||||
"artemis.agents.explorer.universal_runner.get_llm",
|
||||
return_value=mock_llm,
|
||||
):
|
||||
mock_state = Mock(spec=State)
|
||||
|
||||
@@ -12,11 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from artemis.agents.operator.prompt_builder import (
|
||||
OperatorPromptBuilder,
|
||||
from artemis.agents.operator.prompts import (
|
||||
apply_operator_prompt_contract,
|
||||
load_operator_prompts,
|
||||
)
|
||||
from artemis.agents.operator.prompts import apply_operator_prompt_contract
|
||||
|
||||
|
||||
def test_operator_prompt_contract_matches_note_runtime_semantics():
|
||||
@@ -68,9 +67,8 @@ def test_operator_prompt_keeps_large_list_traversal_single_pass_until_boundary()
|
||||
assert "Terminate exploration when a definitive boundary is reached" not in prompt
|
||||
|
||||
|
||||
def test_compatibility_builder_uses_canonical_operator_template():
|
||||
def test_contract_strips_legacy_tool_literals():
|
||||
expected = apply_operator_prompt_contract(load_operator_prompts()["main_template"])
|
||||
|
||||
assert OperatorPromptBuilder.build_system_message() == expected
|
||||
assert "wait_for_delay(seconds=" not in expected
|
||||
assert 'press_key(key="home"' not in expected
|
||||
@@ -33,7 +33,7 @@ import pytest
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
|
||||
from artemis.agents.operator.operator import OperatorNode
|
||||
from artemis.agents.operator.prompt_builder import load_operator_prompts
|
||||
from artemis.agents.operator.prompts import load_operator_prompts
|
||||
from artemis.agents.operator.prompts import (
|
||||
PLAN_HISTORY_STATIC_POINTER,
|
||||
PLAN_HISTORY_TEMPLATE_SECTION,
|
||||
|
||||
@@ -20,7 +20,7 @@ import re
|
||||
from jinja2 import Template
|
||||
import pytest
|
||||
|
||||
from artemis.agents.operator.prompt_builder import load_operator_prompts
|
||||
from artemis.agents.operator.prompts import load_operator_prompts
|
||||
from artemis.agents.operator.prompts import (
|
||||
OPERATOR_PROMPT_TOOLSET,
|
||||
apply_operator_prompt_contract,
|
||||
|
||||
@@ -208,20 +208,14 @@ def test_operator_verbs_lower_onto_manifest_actions():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_wait_for_delay_seconds_drift_stays_dead():
|
||||
"""Guards against re-growing a shadow device-action registration.
|
||||
def test_manifest_wait_for_delay_uses_milliseconds():
|
||||
"""The manifest's ``wait_for_delay`` takes ``time_in_ms``, never *seconds*.
|
||||
|
||||
``artemis/tools/actions/device_actions.py`` used to shadow-register a second
|
||||
``wait_for_delay`` taking *seconds* while every prompt teaches milliseconds
|
||||
(the last such surface, ``artemis/tools/mobile/exec_tools.py``, is deleted).
|
||||
The ToolRegistry must never again carry a ``wait_for_delay`` whose unit
|
||||
disagrees with the manifest's ``time_in_ms``.
|
||||
``wait_for_delay`` taking *seconds* while every prompt teaches milliseconds.
|
||||
The shadow ToolRegistry channel is deleted; the manifest is the only surface
|
||||
left, and its unit must stay milliseconds.
|
||||
"""
|
||||
from artemis.tools.base import ToolRegistry
|
||||
|
||||
tool = ToolRegistry.get("wait_for_delay")
|
||||
if tool is None:
|
||||
return # Not registered at all: nothing to drift.
|
||||
fields = set(tool.args_schema.model_json_schema().get("properties", {}))
|
||||
fields = set(tool_declaration("wait_for_delay").parameters["properties"])
|
||||
assert "seconds" not in fields
|
||||
assert "time_in_ms" in fields
|
||||
@@ -29,7 +29,10 @@ def test_usb_policy_is_primary_when_android_reports_it_active(mock_run, lease_ty
|
||||
lease_type.assert_called_once_with("device-123")
|
||||
lease_type.return_value.cleanup_unowned_references.assert_called_once_with()
|
||||
commands = [call.args[0] for call in mock_run.call_args_list]
|
||||
assert ["shell", "svc", "power", "stayon", "usb"] in [command[3:] for command in commands]
|
||||
# Commands are endpoint-qualified (adb -H host -P port -s serial ...);
|
||||
# assert on the payload after the serial rather than a fixed prefix width.
|
||||
payloads = [command[command.index("-s") + 2 :] for command in commands if "-s" in command]
|
||||
assert ["shell", "svc", "power", "stayon", "usb"] in payloads
|
||||
assert not any("SCREEN_BRIGHT_WAKE_LOCK" in command for command in commands)
|
||||
assert not any("KEYCODE_UNKNOWN" in command for command in commands)
|
||||
|
||||
|
||||
@@ -1,34 +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.
|
||||
|
||||
"""Unit tests for OperatorPromptBuilder."""
|
||||
|
||||
from artemis.agents.operator.prompt_builder import OperatorPromptBuilder
|
||||
|
||||
|
||||
def test_operator_prompt_builder():
|
||||
"""Verify prompt rendering formats variables correctly."""
|
||||
system_msg = OperatorPromptBuilder.build_system_message()
|
||||
assert "Android Device Operator" in system_msg
|
||||
assert "click" in system_msg
|
||||
|
||||
human_msg = OperatorPromptBuilder.build_human_message(
|
||||
goal="Open Settings",
|
||||
sub_goal="Tap Network & internet",
|
||||
current_turn=2,
|
||||
history="1. Tapped Settings icon",
|
||||
)
|
||||
assert "Open Settings" in human_msg
|
||||
assert "Tap Network & internet" in human_msg
|
||||
assert "Current Turn: 2" in human_msg
|
||||
@@ -16,7 +16,7 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
AppendNoteArgs,
|
||||
AppendNotePure,
|
||||
@@ -35,8 +35,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_append_note_pure_tool_subclass_and_registry():
|
||||
"""Verify AppendNotePureTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_append_note_pure_tool_subclass():
|
||||
"""Verify AppendNotePureTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(AppendNotePureTool, ArtemisTool)
|
||||
assert issubclass(AppendNotePure, ArtemisTool)
|
||||
assert isinstance(append_note_pure, ArtemisTool)
|
||||
@@ -46,11 +46,6 @@ def test_append_note_pure_tool_subclass_and_registry():
|
||||
assert append_note_pure.category == "memory"
|
||||
assert append_note_pure.args_schema == AppendNoteArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("append_note_pure")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, AppendNotePureTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = append_note_pure.to_genai_declaration()
|
||||
assert declaration.name == "append_note_pure"
|
||||
|
||||
@@ -17,7 +17,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
AppendNote,
|
||||
AppendNoteArgs,
|
||||
@@ -39,8 +39,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_append_note_tool_subclass_and_registry():
|
||||
"""Verify AppendNoteTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_append_note_tool_subclass():
|
||||
"""Verify AppendNoteTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(AppendNoteTool, ArtemisTool)
|
||||
assert issubclass(AppendNote, ArtemisTool)
|
||||
assert isinstance(append_note, ArtemisTool)
|
||||
@@ -50,11 +50,6 @@ def test_append_note_tool_subclass_and_registry():
|
||||
assert append_note.category == "memory"
|
||||
assert append_note.args_schema == AppendNoteArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("append_note")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, AppendNoteTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = append_note.to_genai_declaration()
|
||||
assert declaration.name == "append_note"
|
||||
|
||||
@@ -373,9 +373,9 @@ async def test_analyze_task_output_tool(mock_get_llm, mock_ctx):
|
||||
del _FINISHED_TASKS_LOGS[task_id]
|
||||
|
||||
|
||||
def test_run_adb_command_tool_subclass_and_registry():
|
||||
"""Verify RunAdbCommandTool is a subclass of ArtemisTool and properly registered."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_run_adb_command_tool_subclass():
|
||||
"""Verify RunAdbCommandTool is a subclass of ArtemisTool."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.command_tool import (
|
||||
RunAdbCommand,
|
||||
RunAdbCommandArgs,
|
||||
@@ -392,11 +392,6 @@ def test_run_adb_command_tool_subclass_and_registry():
|
||||
assert run_adb_command.category == "system"
|
||||
assert run_adb_command.args_schema == RunAdbCommandArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("run_adb_command")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, RunAdbCommandTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = run_adb_command.to_genai_declaration()
|
||||
assert declaration.name == "run_adb_command"
|
||||
@@ -421,9 +416,9 @@ async def test_run_adb_command_tool_mock_driver_execute():
|
||||
assert "pm list packages" in driver.action_history[-1]["command"]
|
||||
|
||||
|
||||
def test_manage_task_tool_subclass_and_registry():
|
||||
"""Verify ManageTaskTool is a subclass of ArtemisTool and properly registered."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_manage_task_tool_subclass():
|
||||
"""Verify ManageTaskTool is a subclass of ArtemisTool."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.command_tool import (
|
||||
ManageTask,
|
||||
ManageTaskArgs,
|
||||
@@ -440,11 +435,6 @@ def test_manage_task_tool_subclass_and_registry():
|
||||
assert manage_task.category == "system"
|
||||
assert manage_task.args_schema == ManageTaskArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("manage_task")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, ManageTaskTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = manage_task.to_genai_declaration()
|
||||
assert declaration.name == "manage_task"
|
||||
@@ -488,9 +478,9 @@ async def test_manage_task_tool_direct_execute(mock_ctx):
|
||||
registry.background.clear()
|
||||
|
||||
|
||||
def test_analyze_task_output_tool_subclass_and_registry():
|
||||
"""Verify AnalyzeTaskOutputTool is a subclass of ArtemisTool and properly registered."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_analyze_task_output_tool_subclass():
|
||||
"""Verify AnalyzeTaskOutputTool is a subclass of ArtemisTool."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.command_tool import (
|
||||
AnalyzeTaskOutput,
|
||||
AnalyzeTaskOutputArgs,
|
||||
@@ -507,11 +497,6 @@ def test_analyze_task_output_tool_subclass_and_registry():
|
||||
assert analyze_task_output.category == "system"
|
||||
assert analyze_task_output.args_schema == AnalyzeTaskOutputArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("analyze_task_output")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, AnalyzeTaskOutputTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = analyze_task_output.to_genai_declaration()
|
||||
assert declaration.name == "analyze_task_output"
|
||||
@@ -558,9 +543,9 @@ async def test_analyze_task_output_tool_direct_execute(mock_get_llm, mock_ctx):
|
||||
del _FINISHED_TASKS_LOGS[task_id]
|
||||
|
||||
|
||||
def test_run_short_adb_command_tool_subclass_and_registry():
|
||||
"""Verify RunShortAdbCommandTool is a subclass of ArtemisTool and properly registered."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_run_short_adb_command_tool_subclass():
|
||||
"""Verify RunShortAdbCommandTool is a subclass of ArtemisTool."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.command_tool import (
|
||||
RunShortAdbCommand,
|
||||
RunShortAdbCommandArgs,
|
||||
@@ -577,11 +562,6 @@ def test_run_short_adb_command_tool_subclass_and_registry():
|
||||
assert run_short_adb_command.category == "system"
|
||||
assert run_short_adb_command.args_schema == RunShortAdbCommandArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("run_short_adb_command")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, RunShortAdbCommandTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = run_short_adb_command.to_genai_declaration()
|
||||
assert declaration.name == "run_short_adb_command"
|
||||
|
||||
@@ -168,9 +168,9 @@ class TestCommitteeTool(unittest.IsolatedAsyncioTestCase):
|
||||
# Clean up temp directory
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
def test_ask_committee_tool_subclass_and_registry(self):
|
||||
"""Verify AskCommitteeTool is a subclass of ArtemisTool and properly registered."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_ask_committee_tool_subclass(self):
|
||||
"""Verify AskCommitteeTool is a subclass of ArtemisTool."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.committee_tool import (
|
||||
AskCommittee,
|
||||
AskCommitteeArgs,
|
||||
@@ -191,11 +191,6 @@ class TestCommitteeTool(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(ask_committee.category, "custom")
|
||||
self.assertEqual(ask_committee.args_schema, AskCommitteeArgs)
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("ask_committee")
|
||||
self.assertIsNotNone(reg_tool)
|
||||
self.assertIsInstance(reg_tool, AskCommitteeTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = ask_committee.to_genai_declaration()
|
||||
self.assertEqual(declaration.name, "ask_committee")
|
||||
|
||||
@@ -27,7 +27,7 @@ from artemis.agents.explorer.constants import (
|
||||
)
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.explorer_tool import (
|
||||
AskExplorerArgs,
|
||||
AskExplorerTool,
|
||||
@@ -122,9 +122,7 @@ def test_description_does_not_depend_on_the_tier(version):
|
||||
assert tool.version == version
|
||||
|
||||
|
||||
def test_registry_and_wrapper_export():
|
||||
reg_tool = ToolRegistry.get("ask_explorer")
|
||||
assert isinstance(reg_tool, AskExplorerTool)
|
||||
def test_wrapper_export():
|
||||
assert ask_explorer_wrapper.tool_fn_getter is get_ask_explorer_tool
|
||||
assert ask_explorer_wrapper.on_success_fn("x") == "x"
|
||||
assert "boom" in ask_explorer_wrapper.on_failure_fn("boom")
|
||||
|
||||
@@ -64,9 +64,9 @@ async def test_ask_vision_coder_success():
|
||||
assert result == vision_coder_outcome
|
||||
|
||||
|
||||
def test_ask_image_processor_tool_subclass_and_registry():
|
||||
"""Verify AskImageProcessorTool is an ArtemisTool subclass and registered in ToolRegistry."""
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
def test_ask_image_processor_tool_subclass():
|
||||
"""Verify AskImageProcessorTool is an ArtemisTool subclass."""
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.image_processor_tool import (
|
||||
AskImageProcessor,
|
||||
AskImageProcessorTool,
|
||||
@@ -87,11 +87,6 @@ def test_ask_image_processor_tool_subclass_and_registry():
|
||||
assert ask_image_processor.category == "custom"
|
||||
assert ask_image_processor.args_schema == AskVisionCoderArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("ask_image_processor")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, AskImageProcessorTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = ask_image_processor.to_genai_declaration()
|
||||
assert declaration.name == "ask_image_processor"
|
||||
|
||||
@@ -17,7 +17,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.drivers.base import BaseDeviceDriver
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.mobile.launch_app import (
|
||||
LaunchApp,
|
||||
LaunchAppArgs,
|
||||
@@ -49,8 +49,8 @@ def mock_state():
|
||||
return MagicMock(spec=State)
|
||||
|
||||
|
||||
def test_launch_app_tool_subclass_and_registry():
|
||||
"""Verify LaunchAppTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_launch_app_tool_subclass():
|
||||
"""Verify LaunchAppTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(LaunchAppTool, ArtemisTool)
|
||||
assert issubclass(LaunchApp, ArtemisTool)
|
||||
assert isinstance(launch_app, ArtemisTool)
|
||||
@@ -60,11 +60,6 @@ def test_launch_app_tool_subclass_and_registry():
|
||||
assert launch_app.category == "action"
|
||||
assert launch_app.args_schema == LaunchAppArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("launch_app")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, LaunchAppTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = launch_app.to_genai_declaration()
|
||||
assert declaration.name == "launch_app"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
ListNotesArgs,
|
||||
ListNotesPure,
|
||||
@@ -35,8 +35,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_list_notes_pure_tool_subclass_and_registry():
|
||||
"""Verify ListNotesPureTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_list_notes_pure_tool_subclass():
|
||||
"""Verify ListNotesPureTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(ListNotesPureTool, ArtemisTool)
|
||||
assert issubclass(ListNotesPure, ArtemisTool)
|
||||
assert isinstance(list_notes_pure, ArtemisTool)
|
||||
@@ -46,11 +46,6 @@ def test_list_notes_pure_tool_subclass_and_registry():
|
||||
assert list_notes_pure.category == "memory"
|
||||
assert list_notes_pure.args_schema == ListNotesArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("list_notes_pure")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, ListNotesPureTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = list_notes_pure.to_genai_declaration()
|
||||
assert declaration.name == "list_notes_pure"
|
||||
|
||||
@@ -16,7 +16,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
ListNotes,
|
||||
ListNotesArgs,
|
||||
@@ -39,8 +39,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_list_notes_tool_subclass_and_registry():
|
||||
"""Verify ListNotesTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_list_notes_tool_subclass():
|
||||
"""Verify ListNotesTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(ListNotesTool, ArtemisTool)
|
||||
assert issubclass(ListNotes, ArtemisTool)
|
||||
assert isinstance(list_notes, ArtemisTool)
|
||||
@@ -50,11 +50,6 @@ def test_list_notes_tool_subclass_and_registry():
|
||||
assert list_notes.category == "memory"
|
||||
assert list_notes.args_schema == ListNotesArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("list_notes")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, ListNotesTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = list_notes.to_genai_declaration()
|
||||
assert declaration.name == "list_notes"
|
||||
|
||||
@@ -16,7 +16,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.log_tool import (
|
||||
AnalyzeLogs,
|
||||
AnalyzeLogsArgs,
|
||||
@@ -88,8 +88,8 @@ async def test_analyze_logs_tool_no_context():
|
||||
assert result == "Error: ArtemisContext is required for LogAnalyzer."
|
||||
|
||||
|
||||
def test_analyze_logs_tool_subclass_and_registry():
|
||||
"""Verify AnalyzeLogsTool is an ArtemisTool subclass and registered in ToolRegistry."""
|
||||
def test_analyze_logs_tool_subclass():
|
||||
"""Verify AnalyzeLogsTool is an ArtemisTool subclass."""
|
||||
assert issubclass(AnalyzeLogsTool, ArtemisTool)
|
||||
assert issubclass(AnalyzeLogs, ArtemisTool)
|
||||
assert issubclass(LogTool, ArtemisTool)
|
||||
@@ -100,11 +100,6 @@ def test_analyze_logs_tool_subclass_and_registry():
|
||||
assert analyze_logs.category == "custom"
|
||||
assert analyze_logs.args_schema == AnalyzeLogsArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("analyze_logs")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, AnalyzeLogsTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = analyze_logs.to_genai_declaration()
|
||||
assert declaration.name == "analyze_logs"
|
||||
|
||||
@@ -21,7 +21,7 @@ from langchain_core.messages import ToolMessage
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.object_detection_tool import (
|
||||
ObjectDetection,
|
||||
ObjectDetectionArgs,
|
||||
@@ -41,7 +41,7 @@ from artemis.tools.object_detection_tool import (
|
||||
import pytest
|
||||
|
||||
|
||||
def test_object_detection_tool_subclass_and_registry():
|
||||
def test_object_detection_tool_subclass():
|
||||
"""Verify ObjectDetectionTool and OperatorObjectDetectionTool subclass ArtemisTool and register."""
|
||||
assert issubclass(ObjectDetectionTool, ArtemisTool)
|
||||
assert issubclass(ObjectDetection, ArtemisTool)
|
||||
@@ -65,11 +65,6 @@ def test_object_detection_tool_subclass_and_registry():
|
||||
assert operator_object_detection.category == "perception"
|
||||
assert operator_object_detection.args_schema == OperatorObjectDetectionArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("object_detection")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, ObjectDetectionTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = object_detection.to_genai_declaration()
|
||||
assert declaration.name == "object_detection"
|
||||
|
||||
@@ -18,7 +18,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.drivers.base import BaseDeviceDriver
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.mobile.ocr import (
|
||||
OCRRecognition,
|
||||
OcrArgs,
|
||||
@@ -63,8 +63,8 @@ def mock_state(tmp_path):
|
||||
return state
|
||||
|
||||
|
||||
def test_ocr_tool_subclass_and_registry():
|
||||
"""Verify OcrRecognitionTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_ocr_tool_subclass():
|
||||
"""Verify OcrRecognitionTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(OcrRecognitionTool, ArtemisTool)
|
||||
assert issubclass(OcrRecognition, ArtemisTool)
|
||||
assert issubclass(OCRRecognition, ArtemisTool)
|
||||
@@ -76,11 +76,6 @@ def test_ocr_tool_subclass_and_registry():
|
||||
assert ocr_recognition.category == "explorer"
|
||||
assert ocr_recognition.args_schema == OcrArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("ocr_recognition")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, OcrRecognitionTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = ocr_recognition.to_genai_declaration()
|
||||
assert declaration.name == "ocr_recognition"
|
||||
|
||||
@@ -16,7 +16,7 @@ from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.drivers.base import BaseDeviceDriver
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.mobile.read_hierarchy import (
|
||||
GetUIHierarchy,
|
||||
GetUiHierarchy,
|
||||
@@ -46,8 +46,8 @@ def mock_driver():
|
||||
return driver
|
||||
|
||||
|
||||
def test_get_ui_hierarchy_tool_subclass_and_registry():
|
||||
"""Verify GetUiHierarchyTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_get_ui_hierarchy_tool_subclass():
|
||||
"""Verify GetUiHierarchyTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(GetUiHierarchyTool, ArtemisTool)
|
||||
assert issubclass(GetUiHierarchy, ArtemisTool)
|
||||
assert issubclass(GetUIHierarchy, ArtemisTool)
|
||||
@@ -58,11 +58,6 @@ def test_get_ui_hierarchy_tool_subclass_and_registry():
|
||||
assert get_ui_hierarchy.category == "diagnostic"
|
||||
assert get_ui_hierarchy.args_schema == GetUiHierarchyArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("get_ui_hierarchy")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, GetUiHierarchyTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = get_ui_hierarchy.to_genai_declaration()
|
||||
assert declaration.name == "get_ui_hierarchy"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.mobile.read_logs import (
|
||||
ReadLogs,
|
||||
ReadLogsArgs,
|
||||
@@ -33,8 +33,8 @@ def mock_ctx():
|
||||
return ctx
|
||||
|
||||
|
||||
def test_read_logs_tool_subclass_and_registry():
|
||||
"""Verify ReadLogsTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_read_logs_tool_subclass():
|
||||
"""Verify ReadLogsTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(ReadLogsTool, ArtemisTool)
|
||||
assert issubclass(ReadLogs, ArtemisTool)
|
||||
assert issubclass(ReadLogsToolAlias, ArtemisTool)
|
||||
@@ -45,11 +45,6 @@ def test_read_logs_tool_subclass_and_registry():
|
||||
assert read_logs.category == "diagnostic"
|
||||
assert read_logs.args_schema == ReadLogsArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("read_logs")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, ReadLogsTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = read_logs.to_genai_declaration()
|
||||
assert declaration.name == "read_logs"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
ReadNoteArgs,
|
||||
ReadNotePure,
|
||||
@@ -35,8 +35,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_read_note_pure_tool_subclass_and_registry():
|
||||
"""Verify ReadNotePureTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_read_note_pure_tool_subclass():
|
||||
"""Verify ReadNotePureTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(ReadNotePureTool, ArtemisTool)
|
||||
assert issubclass(ReadNotePure, ArtemisTool)
|
||||
assert isinstance(read_note_pure, ArtemisTool)
|
||||
@@ -46,11 +46,6 @@ def test_read_note_pure_tool_subclass_and_registry():
|
||||
assert read_note_pure.category == "memory"
|
||||
assert read_note_pure.args_schema == ReadNoteArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("read_note_pure")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, ReadNotePureTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = read_note_pure.to_genai_declaration()
|
||||
assert declaration.name == "read_note_pure"
|
||||
|
||||
@@ -16,7 +16,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
ReadNote,
|
||||
ReadNoteArgs,
|
||||
@@ -39,8 +39,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_read_note_tool_subclass_and_registry():
|
||||
"""Verify ReadNoteTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_read_note_tool_subclass():
|
||||
"""Verify ReadNoteTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(ReadNoteTool, ArtemisTool)
|
||||
assert issubclass(ReadNote, ArtemisTool)
|
||||
assert isinstance(read_note, ArtemisTool)
|
||||
@@ -50,11 +50,6 @@ def test_read_note_tool_subclass_and_registry():
|
||||
assert read_note.category == "memory"
|
||||
assert read_note.args_schema == ReadNoteArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("read_note")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, ReadNoteTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = read_note.to_genai_declaration()
|
||||
assert declaration.name == "read_note"
|
||||
|
||||
@@ -16,7 +16,7 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
SaveNoteArgs,
|
||||
SaveNotePure,
|
||||
@@ -35,8 +35,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_save_note_pure_tool_subclass_and_registry():
|
||||
"""Verify SaveNotePureTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_save_note_pure_tool_subclass():
|
||||
"""Verify SaveNotePureTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(SaveNotePureTool, ArtemisTool)
|
||||
assert issubclass(SaveNotePure, ArtemisTool)
|
||||
assert isinstance(save_note_pure, ArtemisTool)
|
||||
@@ -46,11 +46,6 @@ def test_save_note_pure_tool_subclass_and_registry():
|
||||
assert save_note_pure.category == "memory"
|
||||
assert save_note_pure.args_schema == SaveNoteArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("save_note_pure")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, SaveNotePureTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = save_note_pure.to_genai_declaration()
|
||||
assert declaration.name == "save_note_pure"
|
||||
|
||||
@@ -17,7 +17,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
SaveNote,
|
||||
SaveNoteArgs,
|
||||
@@ -39,8 +39,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_save_note_tool_subclass_and_registry():
|
||||
"""Verify SaveNoteTool is a subclass of ArtemisTool and properly registered in ToolRegistry."""
|
||||
def test_save_note_tool_subclass():
|
||||
"""Verify SaveNoteTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(SaveNoteTool, ArtemisTool)
|
||||
assert issubclass(SaveNote, ArtemisTool)
|
||||
assert isinstance(save_note, ArtemisTool)
|
||||
@@ -50,11 +50,6 @@ def test_save_note_tool_subclass_and_registry():
|
||||
assert save_note.category == "memory"
|
||||
assert save_note.args_schema == SaveNoteArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("save_note")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, SaveNoteTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = save_note.to_genai_declaration()
|
||||
assert declaration.name == "save_note"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.mobile.search_logs import (
|
||||
SearchLogs,
|
||||
SearchLogsArgs,
|
||||
@@ -147,8 +147,8 @@ def mock_ctx():
|
||||
return MagicMock(spec=ArtemisContext)
|
||||
|
||||
|
||||
def test_search_logs_tool_subclass_and_registry():
|
||||
"""Verify SearchLogsTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_search_logs_tool_subclass():
|
||||
"""Verify SearchLogsTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(SearchLogsTool, ArtemisTool)
|
||||
assert issubclass(SearchLogs, ArtemisTool)
|
||||
assert issubclass(SearchLogsToolAlias, ArtemisTool)
|
||||
@@ -159,11 +159,6 @@ def test_search_logs_tool_subclass_and_registry():
|
||||
assert search_logs.category == "diagnostic"
|
||||
assert search_logs.args_schema == SearchLogsArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("search_logs")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, SearchLogsTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = search_logs.to_genai_declaration()
|
||||
assert declaration.name == "search_logs"
|
||||
|
||||
@@ -16,7 +16,7 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
UpdateNoteArgs,
|
||||
UpdateNotePure,
|
||||
@@ -36,8 +36,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_update_note_pure_tool_subclass_and_registry():
|
||||
"""Verify UpdateNotePureTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_update_note_pure_tool_subclass():
|
||||
"""Verify UpdateNotePureTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(UpdateNotePureTool, ArtemisTool)
|
||||
assert issubclass(UpdateNotePure, ArtemisTool)
|
||||
assert isinstance(update_note_pure, ArtemisTool)
|
||||
@@ -47,11 +47,6 @@ def test_update_note_pure_tool_subclass_and_registry():
|
||||
assert update_note_pure.category == "memory"
|
||||
assert update_note_pure.args_schema == UpdateNoteArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("update_note_pure")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, UpdateNotePureTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = update_note_pure.to_genai_declaration()
|
||||
assert declaration.name == "update_note_pure"
|
||||
|
||||
@@ -17,7 +17,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.scratchpad import (
|
||||
UpdateNote,
|
||||
UpdateNoteArgs,
|
||||
@@ -40,8 +40,8 @@ def mock_ctx(tmp_path):
|
||||
return ctx
|
||||
|
||||
|
||||
def test_update_note_tool_subclass_and_registry():
|
||||
"""Verify UpdateNoteTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_update_note_tool_subclass():
|
||||
"""Verify UpdateNoteTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(UpdateNoteTool, ArtemisTool)
|
||||
assert issubclass(UpdateNote, ArtemisTool)
|
||||
assert isinstance(update_note, ArtemisTool)
|
||||
@@ -51,11 +51,6 @@ def test_update_note_tool_subclass_and_registry():
|
||||
assert update_note.category == "memory"
|
||||
assert update_note.args_schema == UpdateNoteArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("update_note")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, UpdateNoteTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = update_note.to_genai_declaration()
|
||||
assert declaration.name == "update_note"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.video_tool import (
|
||||
VideoAnalyzerArgs,
|
||||
VideoAnalyzerPure,
|
||||
@@ -32,8 +32,8 @@ def mock_ctx():
|
||||
return ctx
|
||||
|
||||
|
||||
def test_video_analyzer_pure_tool_subclass_and_registry():
|
||||
"""Verify VideoAnalyzerPureTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_video_analyzer_pure_tool_subclass():
|
||||
"""Verify VideoAnalyzerPureTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(VideoAnalyzerPureTool, ArtemisTool)
|
||||
assert issubclass(VideoAnalyzerPure, ArtemisTool)
|
||||
assert isinstance(video_analyzer_pure, ArtemisTool)
|
||||
@@ -43,11 +43,6 @@ def test_video_analyzer_pure_tool_subclass_and_registry():
|
||||
assert video_analyzer_pure.category == "perception"
|
||||
assert video_analyzer_pure.args_schema == VideoAnalyzerArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("video_analyzer_pure")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, VideoAnalyzerPureTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = video_analyzer_pure.to_genai_declaration()
|
||||
assert declaration.name == "video_analyzer_pure"
|
||||
|
||||
@@ -16,7 +16,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.video_tool import (
|
||||
DIAGNOSER_VIDEO_ANALYZER_DOCSTRING,
|
||||
OPERATOR_VIDEO_ANALYZER_DOCSTRING,
|
||||
@@ -41,8 +41,8 @@ def mock_state():
|
||||
return MagicMock(spec=State)
|
||||
|
||||
|
||||
def test_video_analyzer_tool_subclass_and_registry():
|
||||
"""Verify VideoAnalyzerTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_video_analyzer_tool_subclass():
|
||||
"""Verify VideoAnalyzerTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(VideoAnalyzerTool, ArtemisTool)
|
||||
assert isinstance(video_analyzer, ArtemisTool)
|
||||
assert isinstance(video_analyzer, VideoAnalyzerTool)
|
||||
@@ -51,11 +51,6 @@ def test_video_analyzer_tool_subclass_and_registry():
|
||||
assert video_analyzer.category == "perception"
|
||||
assert video_analyzer.args_schema == VideoAnalyzerArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("video_analyzer")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, VideoAnalyzerTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = video_analyzer.to_genai_declaration()
|
||||
assert declaration.name == "video_analyzer"
|
||||
|
||||
@@ -17,7 +17,7 @@ import time
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from artemis.context import ArtemisContext
|
||||
from artemis.tools.base import ArtemisTool, ToolRegistry
|
||||
from artemis.tools.base import ArtemisTool
|
||||
from artemis.tools.wait_tool import (
|
||||
Wait,
|
||||
WaitArgs,
|
||||
@@ -35,8 +35,8 @@ def mock_ctx():
|
||||
return ctx
|
||||
|
||||
|
||||
def test_wait_tool_subclass_and_registry():
|
||||
"""Verify WaitTool is a subclass of ArtemisTool and properly registered."""
|
||||
def test_wait_tool_subclass():
|
||||
"""Verify WaitTool is a subclass of ArtemisTool."""
|
||||
assert issubclass(WaitTool, ArtemisTool)
|
||||
assert issubclass(Wait, ArtemisTool)
|
||||
assert isinstance(wait, ArtemisTool)
|
||||
@@ -46,11 +46,6 @@ def test_wait_tool_subclass_and_registry():
|
||||
assert wait.category == "system"
|
||||
assert wait.args_schema == WaitArgs
|
||||
|
||||
# Registry lookup
|
||||
reg_tool = ToolRegistry.get("wait")
|
||||
assert reg_tool is not None
|
||||
assert isinstance(reg_tool, WaitTool)
|
||||
|
||||
# GenAI FunctionDeclaration export
|
||||
declaration = wait.to_genai_declaration()
|
||||
assert declaration.name == "wait"
|
||||
|
||||
@@ -21,7 +21,6 @@ from artemis.agents.explorer.explorer import Explorer
|
||||
from artemis.config.settings import Settings
|
||||
from artemis.graph.perception import perception_node
|
||||
from artemis.graph.state import State
|
||||
from artemis.tools.base import ToolRegistry
|
||||
from artemis.tools.index import get_tools_from_wrappers
|
||||
from artemis.tools.mobile.ocr import (
|
||||
ocr_recognition,
|
||||
@@ -126,20 +125,8 @@ def test_ocr_tool_not_exposed_when_unconfigured():
|
||||
with patch("artemis.tools.mobile.ocr.is_ocr_configured", return_value=False):
|
||||
assert not ocr_recognition.is_available()
|
||||
|
||||
# ToolRegistry.list_tools filters out unavailable tools
|
||||
available_tools = ToolRegistry.list_tools(available_only=True)
|
||||
assert "ocr_recognition" not in [t.name for t in available_tools]
|
||||
|
||||
# ToolRegistry.get_langchain_tools filters out unavailable tools
|
||||
mock_ctx = MagicMock()
|
||||
lc_tools = ToolRegistry.get_langchain_tools(mock_ctx, available_only=True)
|
||||
assert "ocr_recognition" not in [t.name for t in lc_tools]
|
||||
|
||||
# ToolRegistry.get_genai_declarations filters out unavailable tools
|
||||
genai_tools = ToolRegistry.get_genai_declarations(available_only=True)
|
||||
assert "ocr_recognition" not in [t.name for t in genai_tools]
|
||||
|
||||
# get_tools_from_wrappers excludes ocr_recognition_wrapper
|
||||
mock_ctx = MagicMock()
|
||||
wrapped_tools = get_tools_from_wrappers(mock_ctx, [ocr_recognition_wrapper])
|
||||
assert len(wrapped_tools) == 0
|
||||
|
||||
|
||||
Reference in new issue
Block a user