fix(inclusive-language): replace blacklist terminology with denylist

- Replace blacklisted_tools configuration key and comments in config/artemis.jsonc and README.md with denylisted_tools
- Update AgentGlobalConfig and AgentConfig with denylisted_tools and validation aliases
- Update AgentConfigBuilder and Explorer agent to use denylist conventions
- Update Admin Console replay manager and data engine trace filters
- Update all associated test suites and mock configurations
- Add license headers and clean up test imports
This commit is contained in:
yaoyao-open-source committed 2026-08-13 14:03:49 -07:00
1 parent 5d2283deaa
commit 701a74b546
24 files changed
+201 -67

No files matched your search

+4 -4
View File
@@ -1,7 +1,7 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
@@ -187,16 +187,16 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 Google LLC.
Copyright [yyyy] [name of copyright owner]
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
https://www.apache.org/licenses/LICENSE-2.0
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.
limitations under the License.
+1 -1
View File
@@ -303,7 +303,7 @@ All model configurations, thinking budgets, agent capabilities, and tool filteri
// 4. Agent Capabilities & Tool Filtering
"agent": {
"explorer_versions": { "operator": "flash", "validator": "flash" },
"blacklisted_tools": {
"denylisted_tools": {
"explorer": ["ask_image_processor", "get_ocr_list", "inspect_region"]
}
}
+15
View File
@@ -1 +1,16 @@
# 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.
"""Artemis / Latte Applications Package."""
+14
View File
@@ -1,3 +1,17 @@
# 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.
"""Artemis Admin & Trace Console Package."""
from apps.admin_console.server import app
+12
View File
@@ -1 +1,13 @@
# 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.
+12
View File
@@ -1 +1,13 @@
# 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.
@@ -1 +1,13 @@
# 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.
+10 -10
View File
@@ -52,7 +52,7 @@ REPLAY_TOOLS_CONFIG = {
"module": "artemis.tools.explorer_tool",
"function": "_run_explorer_logic",
"agent_name": "explorer",
"blacklist_args": ["ctx", "state"],
"denylist_args": ["ctx", "state"],
"fallback_mappings": {},
"llm_span_name": "gemini_explorer_call",
},
@@ -65,7 +65,7 @@ REPLAY_TOOLS_CONFIG = {
"module": "artemis.tools.image_processor_tool",
"function": "_run_image_processor_logic",
"agent_name": "image_processor",
"blacklist_args": ["ctx", "state"],
"denylist_args": ["ctx", "state"],
"fallback_mappings": {},
"llm_span_name": "gemini_image_processor_call",
},
@@ -1996,14 +1996,14 @@ class ReplayManager:
user_submits = user_submits or {}
sig = inspect.signature(tool_fn)
tool_args = {}
blacklist = tool_cfg.get("blacklist_args", [])
denylist = tool_cfg.get("denylist_args", [])
fallback_mappings = tool_cfg.get("fallback_mappings", {})
user_submits = user_submits or {}
sig = inspect.signature(tool_fn)
tool_args = {}
for name, param in sig.parameters.items():
if name in blacklist:
if name in denylist:
if name == "ctx":
tool_args["ctx"] = ctx
elif name == "state":
@@ -2012,7 +2012,7 @@ class ReplayManager:
if name in user_submits:
val = user_submits[name]
if param.annotation == bool and isinstance(val, str):
if param.annotation is bool and isinstance(val, str):
val = val.lower() == "true"
tool_args[name] = val
else:
@@ -2086,10 +2086,10 @@ class ReplayManager:
tool_fn = getattr(mod, tool_cfg["function"])
sig = inspect.signature(tool_fn)
blacklist = tool_cfg.get("blacklist_args", [])
denylist = tool_cfg.get("denylist_args", [])
explorer_params = []
for name, param in sig.parameters.items():
if name in blacklist:
if name in denylist:
continue
default_val = ""
if param.default is not inspect.Parameter.empty:
@@ -2106,7 +2106,7 @@ class ReplayManager:
)
):
input_type = "textarea"
elif param.annotation == bool:
elif param.annotation is bool:
input_type = "checkbox"
elif param.annotation in (int, float):
input_type = "number"
@@ -2181,12 +2181,12 @@ class ReplayManager:
all_tool_defaults = {}
for t_name, sig in tool_signatures.items():
t_cfg = REPLAY_TOOLS_CONFIG[t_name]
blacklist = t_cfg.get("blacklist_args", [])
denylist = t_cfg.get("denylist_args", [])
fallback_mappings = t_cfg.get("fallback_mappings", {})
defaults = {}
for name, param in sig.parameters.items():
if name in blacklist:
if name in denylist:
continue
val = ""
+12
View File
@@ -1 +1,13 @@
# 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.
+12
View File
@@ -1 +1,13 @@
# 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.
+12
View File
@@ -1 +1,13 @@
# 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.
+29 -29
View File
@@ -223,8 +223,8 @@ UNIVERSAL_EXPLORER_TOOLS = [
class Explorer:
BLACKLIST_TEMPLATE = (
"\n# TOOL BLACKLIST\n- The following tools are blacklisted and cannot be used: {tools}\n"
DENYLIST_TEMPLATE = (
"\n# TOOL DENYLIST\n- The following tools are denylisted and cannot be used: {tools}\n"
)
TOOLS = [
types.FunctionDeclaration(
@@ -445,12 +445,12 @@ class Explorer:
self.next_img_id = 1
try:
agent_cfg = getattr(self.ctx, "agent_config", None)
blacklisted_config = (
getattr(agent_cfg, "blacklisted_tools", {}).get("explorer", []) if agent_cfg else []
denylisted_config = (
getattr(agent_cfg, "denylisted_tools", {}).get("explorer", []) if agent_cfg else []
)
self.blacklisted_tools = set(blacklisted_config)
self.denylisted_tools = set(denylisted_config)
except (TypeError, AttributeError):
self.blacklisted_tools = set()
self.denylisted_tools = set()
self.http_client = None
self.turn_latencies = []
self.turn_cached_tokens = []
@@ -514,8 +514,8 @@ class Explorer:
]
def get_exposed_tools(self, only_submit: bool = False) -> list[types.FunctionDeclaration]:
"""Returns the list of tools, filtering out blacklisted ones."""
tools = [tool for tool in self.TOOLS if tool.name not in self.blacklisted_tools]
"""Returns the list of tools, filtering out denylisted ones."""
tools = [tool for tool in self.TOOLS if tool.name not in self.denylisted_tools]
if only_submit:
tools = [tool for tool in tools if tool.name == "submit_answer"]
return tools
@@ -1143,11 +1143,11 @@ class Explorer:
"""Executes Explorer reasoning loop via Universal LangChain ChatModel."""
llm = get_llm(self.ctx, name="explorer")
# Filter universal tools based on blacklist
# Filter universal tools based on denylist
exposed_tools = [
t
for t in UNIVERSAL_EXPLORER_TOOLS
if t["function"]["name"] not in self.blacklisted_tools
if t["function"]["name"] not in self.denylisted_tools
]
bound_llm = llm.bind_tools(exposed_tools)
@@ -1251,12 +1251,12 @@ class Explorer:
args = tc.get("args", {})
call_id = tc.get("id", f"call_{iterations}_{name}")
if name in self.blacklisted_tools:
if name in self.denylisted_tools:
messages.append(
ToolMessage(
tool_call_id=call_id,
name=name,
content=f"Tool '{name}' is blacklisted and unavailable.",
content=f"Tool '{name}' is denylisted and unavailable.",
)
)
continue
@@ -1428,12 +1428,12 @@ class Explorer:
# Resolve parameters
mode = version.capitalize()
version_blacklist = set()
version_denylist = set()
if version == "ultra":
max_iterations = 8
elif version == "pro":
max_iterations = 3
version_blacklist = {
version_denylist = {
"ask_image_processor",
"get_ocr_list",
"inspect_region",
@@ -1441,21 +1441,21 @@ class Explorer:
}
else:
max_iterations = 3
version_blacklist = {
version_denylist = {
"ask_image_processor",
"get_ocr_list",
"inspect_region",
}
try:
blacklisted_config = (
self.ctx.agent_config.blacklisted_tools.get("explorer", [])
denylisted_config = (
self.ctx.agent_config.denylisted_tools.get("explorer", [])
if self.ctx.agent_config
else []
)
self.blacklisted_tools = version_blacklist.union(set(blacklisted_config))
self.denylisted_tools = version_denylist.union(set(denylisted_config))
except (TypeError, AttributeError):
self.blacklisted_tools = version_blacklist
self.denylisted_tools = version_denylist
# 2. Prepare Native Tools declarations
self.width = 1080
@@ -1544,14 +1544,14 @@ class Explorer:
prompt_parts.append("")
prompt_template = "\n".join(prompt_parts)
prompt_template += "\n{blakclist}"
if self.blacklisted_tools:
blacklist_section = self.BLACKLIST_TEMPLATE.format(
tools=", ".join(sorted(self.blacklisted_tools))
prompt_template += "\n{denylist}"
if self.denylisted_tools:
denylist_section = self.DENYLIST_TEMPLATE.format(
tools=", ".join(sorted(self.denylisted_tools))
)
else:
blacklist_section = ""
prompt_template = prompt_template.replace("{blakclist}", blacklist_section)
denylist_section = ""
prompt_template = prompt_template.replace("{denylist}", denylist_section)
explore_info = EXPLORE_DESCRIPTIONS.get(version, EXPLORE_DESCRIPTIONS["pro"])
version_prompt = explore_info.get("version_prompt")
@@ -2039,20 +2039,20 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima
"name": name,
"args": args,
}
if name in self.blacklisted_tools:
if name in self.denylisted_tools:
logger.warning(
"Explorer attempted to call blacklisted tool"
"Explorer attempted to call denylisted tool"
f" '{name}'. Blocking execution."
)
tool_call_trace["response"] = {
"error": (f"Tool '{name}' is blacklisted and unavailable.")
"error": (f"Tool '{name}' is denylisted and unavailable.")
}
turn_record["tool_calls"].append(tool_call_trace)
tool_response_parts.append(
types.Part.from_function_response(
name=fc.name,
response={
"error": (f"Tool '{name}' is blacklisted and unavailable.")
"error": (f"Tool '{name}' is denylisted and unavailable.")
},
)
)
+4 -3
View File
@@ -18,7 +18,7 @@ import os
from pathlib import Path
from typing import Any
from pydantic import BaseModel, Field, model_validator
from pydantic import AliasChoices, BaseModel, Field, model_validator
from artemis.config.constants import (
AGENT_CONFIG_FILENAME,
@@ -286,9 +286,10 @@ class AgentGlobalConfig(BaseModel):
default_factory=OutputterConfig,
description="Outputter synthesis agent runtime options.",
)
blacklisted_tools: dict[str, list[str]] = Field(
denylisted_tools: dict[str, list[str]] = Field(
default_factory=dict,
description="List of blacklisted tools per agent.",
description="List of denylisted tools per agent.",
validation_alias=AliasChoices("denylisted_tools", "blocklisted_tools"),
)
video_analyzer: VideoAnalyzerConfig = Field(
default_factory=VideoAnalyzerConfig,
+2 -2
View File
@@ -851,7 +851,7 @@ class DataEngine:
interleaved_events = []
try:
blacklist = (
denylist = (
"operator",
"perception",
"planner",
@@ -891,7 +891,7 @@ class DataEngine:
if is_relevant_llm:
relevant_traces.append(t)
elif t.type in ("tool", "agent") and t.name not in blacklist:
elif t.type in ("tool", "agent") and t.name not in denylist:
relevant_traces.append(t)
# Sort by timestamp to ensure exact chronological order
+5 -5
View File
@@ -73,7 +73,7 @@ class AgentConfigBuilder:
agent_cfg = load_agent_config()
self._explorer = agent_cfg.explorer
self._explorer_versions = agent_cfg.explorer_versions
self._blacklisted_tools = agent_cfg.blacklisted_tools
self._denylisted_tools = agent_cfg.denylisted_tools
self._enable_video_ledger = agent_cfg.video_analyzer.enable_ledger
if agent_cfg.video_analyzer.enabled is not None:
self._video_recording_tools_enabled = agent_cfg.video_analyzer.enabled
@@ -431,9 +431,9 @@ class AgentConfigBuilder:
self._explorer_versions = versions
return self
def with_blacklisted_tools(self, tools: dict[str, list[str]]) -> "AgentConfigBuilder":
"""Configure blacklisted tools."""
self._blacklisted_tools = tools
def with_denylisted_tools(self, tools: dict[str, list[str]]) -> "AgentConfigBuilder":
"""Configure denylisted tools."""
self._denylisted_tools = tools
return self
def build(self, validate_profiles: bool = True) -> AgentConfig:
@@ -505,7 +505,7 @@ class AgentConfigBuilder:
pro=self._pro,
explorer=self._explorer,
explorer_versions=self._explorer_versions,
blacklisted_tools=self._blacklisted_tools,
denylisted_tools=self._denylisted_tools,
enable_video_ledger=self._enable_video_ledger,
)
+5 -2
View File
@@ -25,7 +25,7 @@ from artemis.config import (
from artemis.context import DevicePlatform
from artemis.sdk.types.task import AgentProfile, TaskRequestCommon
from artemis.utils.video import detect_video_tools_enabled
from pydantic import BaseModel, Field
from pydantic import AliasChoices, BaseModel, Field
class _CyFunctionDetectorMeta(type):
@@ -125,7 +125,10 @@ class AgentConfig(BaseModel):
explorer: ExplorerConfig = Field(default_factory=ExplorerConfig)
explorer_versions: dict[str, Literal["flash", "pro", "ultra"]]
blacklisted_tools: dict[str, list[str]]
denylisted_tools: dict[str, list[str]] = Field(
default_factory=dict,
validation_alias=AliasChoices("denylisted_tools", "blocklisted_tools"),
)
enable_video_ledger: bool = True
model_config = {"arbitrary_types_allowed": True}
+2 -2
View File
@@ -204,8 +204,8 @@
"operator": "flash",
"validator": "flash"
},
// 🚫 Per-agent Tool Blacklists
"blacklisted_tools": {
// 🚫 Per-agent Tool Denylists
"denylisted_tools": {
"explorer": [
"ask_image_processor",
"get_ocr_list",
@@ -29,7 +29,7 @@ def create_mock_context() -> MagicMock:
ctx.device.device_height = 2400
ctx.llm_config = MagicMock()
ctx.agent_config = MagicMock()
ctx.agent_config.blacklisted_tools = {"explorer": []}
ctx.agent_config.denylisted_tools = {"explorer": []}
ctx.data_engine = None
ctx.adb_client = None
return ctx
+14
View File
@@ -1,3 +1,17 @@
# 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.
import time
import uiautomator2 as u2
+1 -1
View File
@@ -77,7 +77,7 @@ def artemis_context(inputs_dir, mock_adb_client):
ctx.trace_dir = trace_out
ctx.agent_config = MagicMock()
ctx.agent_config.blacklisted_tools = {}
ctx.agent_config.denylisted_tools = {}
return ctx
+5 -5
View File
@@ -843,7 +843,7 @@ async def test_explorer_initial_visual_marking_previous_screenshot_on_the_fly_oc
@pytest.mark.asyncio
async def test_explorer_blacklisted_tool():
async def test_explorer_denylisted_tool():
# Mock context and state
mock_ctx = MagicMock(spec=ArtemisContext)
mock_ctx.device = MagicMock()
@@ -855,12 +855,12 @@ async def test_explorer_blacklisted_tool():
mock_ctx.llm_config.explorer = MagicMock()
mock_ctx.llm_config.explorer.model = "gemini-3.6-flash"
mock_ctx.agent_config = MagicMock()
mock_ctx.agent_config.blacklisted_tools = {"explorer": ["search_xml_ocr"]}
mock_ctx.agent_config.denylisted_tools = {"explorer": ["search_xml_ocr"]}
mock_state = MagicMock(spec=State)
mock_state.latest_screenshot = "/tmp/test_screenshot.jpg"
# Response 1: Model calls search_xml_ocr (which is blacklisted)
# Response 1: Model calls search_xml_ocr (which is denylisted)
mock_response1 = MagicMock()
mock_response1.text = ""
mock_func_call1 = MagicMock()
@@ -958,7 +958,7 @@ async def test_explorer_blacklisted_tool():
assert mock_client.aio.models.generate_content.call_count == 2
# Verify the contents passed to the second call contain the error response
# indicating that the tool is blacklisted and unavailable.
# indicating that the tool is denylisted and unavailable.
call_args = mock_client.aio.models.generate_content.call_args_list[1]
contents = call_args[1]["contents"]
@@ -978,7 +978,7 @@ async def test_explorer_blacklisted_tool():
for part in tool_content.parts:
func_resp = part.function_response
if func_resp and func_resp.name == "search_xml_ocr":
assert "blacklisted and unavailable" in func_resp.response["error"]
assert "denylisted and unavailable" in func_resp.response["error"]
found_error = True
assert found_error
+1 -1
View File
@@ -43,7 +43,7 @@ def mock_context(tmp_path):
ctx.device.device_height = 2400
ctx.agent_config = Mock()
ctx.agent_config.blacklisted_tools = {}
ctx.agent_config.denylisted_tools = {}
ctx.data_engine = Mock()
ctx.data_engine.base_dir = tmp_path
+1 -1
View File
@@ -136,7 +136,7 @@ def test_agent_config_loading():
assert agent_cfg.explorer.flash_mode == "flash"
assert agent_cfg.explorer.pro_mode == "flash"
assert agent_cfg.explorer.caching is True
assert "explorer" in agent_cfg.blacklisted_tools
assert "explorer" in agent_cfg.denylisted_tools
assert agent_cfg.video_analyzer.enable_ledger is True
assert agent_cfg.planner_validation.enabled is False
assert agent_cfg.planner_validation.similarity_threshold == 0.85
+15
View File
@@ -1,3 +1,18 @@
# 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.
import asyncio
import pytest
from artemis.context import ArtemisContext, DeviceContext, DevicePlatform