Localize the web console and persist custom model and capability settings. Use OpenAI Responses exclusively and move runtime limits into environment configuration. Bootstrap uv and the virtual environment on Windows, support optional China mirrors, and package source distributions without environments, build output, or credentials. Update regression tests and repository quality checks for the new behavior.
146 lines
4.8 KiB
Python
146 lines
4.8 KiB
Python
# Copyright 2026 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from artemis.context import ArtemisContext
|
|
from artemis.graph.state import State
|
|
from artemis.tools.base import ArtemisTool
|
|
from artemis.tools.scratchpad import (
|
|
ListNotes,
|
|
ListNotesArgs,
|
|
ListNotesTool,
|
|
get_list_notes_tool,
|
|
get_list_notes_tool_pure,
|
|
list_notes,
|
|
list_notes_wrapper,
|
|
)
|
|
from artemis.utils.notes import save_note_content
|
|
from langchain_core.messages import ToolMessage
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_ctx(tmp_path):
|
|
ctx = MagicMock(spec=ArtemisContext)
|
|
ctx.data_engine = MagicMock()
|
|
ctx.data_engine.base_dir = str(tmp_path)
|
|
return ctx
|
|
|
|
|
|
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)
|
|
assert isinstance(list_notes, ListNotesTool)
|
|
|
|
assert list_notes.name == "list_notes"
|
|
assert list_notes.category == "memory"
|
|
assert list_notes.args_schema == ListNotesArgs
|
|
|
|
# GenAI FunctionDeclaration export
|
|
declaration = list_notes.to_tool_declaration()
|
|
assert declaration.name == "list_notes"
|
|
|
|
# Wrapper check
|
|
assert list_notes_wrapper is not None
|
|
assert list_notes_wrapper.tool_fn_getter == get_list_notes_tool
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_notes_direct_execution_empty(mock_ctx):
|
|
"""Verify listing notes when no notes are stored."""
|
|
result = await list_notes.execute(ctx=mock_ctx)
|
|
assert "No notes saved yet." in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_notes_direct_execution_success(mock_ctx, tmp_path):
|
|
"""Verify direct execution of ListNotesTool.execute lists stored notes."""
|
|
save_note_content(tmp_path, "note_alpha", "line 1\nline 2")
|
|
save_note_content(tmp_path, "note_beta", "single line")
|
|
|
|
result = await list_notes.execute(ctx=mock_ctx)
|
|
assert "Here are all the notes:" in result
|
|
assert "- note_alpha (2 lines)" in result
|
|
assert "- note_beta (1 lines)" in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_notes_callable_execution(mock_ctx, tmp_path):
|
|
"""Verify invoking list_notes directly as a callable."""
|
|
save_note_content(tmp_path, "note_gamma", "line A\nline B\nline C")
|
|
|
|
result = await list_notes(ctx=mock_ctx)
|
|
assert "Here are all the notes:" in result
|
|
assert "- note_gamma (3 lines)" in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_notes_with_state_tool_message(mock_ctx, tmp_path):
|
|
"""Verify ListNotesTool.execute with state returns a ToolMessage directly."""
|
|
save_note_content(tmp_path, "state_note", "content")
|
|
|
|
mock_state = MagicMock(spec=State)
|
|
|
|
result = await list_notes.execute(
|
|
ctx=mock_ctx,
|
|
state=mock_state,
|
|
tool_call_id="call_list_999",
|
|
)
|
|
|
|
assert isinstance(result, ToolMessage)
|
|
assert result.tool_call_id == "call_list_999"
|
|
assert "Here are all the notes:" in result.content
|
|
assert "- state_note (1 lines)" in result.content
|
|
assert result.status == "success"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_notes_execution_failure(mock_ctx):
|
|
"""Verify error handling when list_notes fails."""
|
|
with patch(
|
|
"artemis.tools.scratchpad.list_notes_info",
|
|
side_effect=RuntimeError("Disk failure"),
|
|
):
|
|
result = await list_notes.execute(ctx=mock_ctx)
|
|
assert "Failed to list notes: Disk failure" in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_list_notes_tool_langchain_ainvoke(mock_ctx, tmp_path):
|
|
"""Verify get_list_notes_tool exports a LangChain tool that works with ainvoke."""
|
|
save_note_content(tmp_path, "lc_note", "hello")
|
|
|
|
lc_tool = get_list_notes_tool(mock_ctx)
|
|
assert lc_tool.name == "list_notes"
|
|
|
|
result = await lc_tool.ainvoke({})
|
|
assert "Here are all the notes:" in result
|
|
assert "- lc_note (1 lines)" in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_list_notes_tool_pure(mock_ctx, tmp_path):
|
|
"""Verify get_list_notes_tool_pure returns a pure LangChain tool."""
|
|
save_note_content(tmp_path, "pure_note", "world")
|
|
|
|
pure_tool = get_list_notes_tool_pure(mock_ctx)
|
|
assert pure_tool.name == "list_notes"
|
|
|
|
result = await pure_tool.ainvoke({})
|
|
assert "Here are all the notes:" in result
|
|
assert "- pure_note (1 lines)" in result
|