ReMe/tests/unit/test_reme_cli.py
jinliyl 2612d25959
feat(lme): add cli execution and agentic search tooling (#334)
* feat(service): add CLI service for local job execution

- Introduce CliService to execute single jobs locally without serving ports
- Add prepare_start_config and should_precheck_start functions for CLI job setup
- Update reme start command to use CLI service when job argument is provided
- Change default service backend from http to cli in jinli_lme config
- Modify SearchStep to use constants and rename configuration parameters
- Add unit tests for CLI service functionality and configuration handling
- Update file extension support to include json format in addition to md and jsonl

* feat(search): add BM25 and vector search steps with configuration updates

- Add Bm25SearchStep and VectorSearchStep classes with tool context deduplication
- Register new search step components in index module
- Update configuration to use separate vector_search and bm25_search endpoints
- Modify LLM models from qwen3.7-plus/glm-5.1 to glm-5.2 variants
- Adjust search parameters and remove hybrid search implementation
- Configure embedding store as default in storage settings
- Remove auto-memory and file catalog configurations
- Update watch directories from multiple paths to session_dir only

* feat(agent): add tool result offloading and workspace management

- Add tool_results_dir configuration option for offloaded tool results storage
- Implement ToolResultOffloadMiddleware to persist large tool results to files
- Create WorkspaceBackend to standardize file operations across tools
- Add configurable builtin tools selection with sequential execution option
- Integrate middleware support for agent wrapper with offloading capability
- Update application initialization to create tool results directory
- Add safety mechanisms for filesystem operations with sanitized filenames
- Enhance agent wrapper with configurable working directory handling
- Upgrade agentscope dependency to version 2.0.4 for improved features

# Conflicts:
#	reme/application.py

* feat(benchmark): add LongMemEval agentic search and result management

- Introduce AgenticAnswerStep for agent-based history search
- Add LmePrepareJudgeStep and LmeSaveResultStep for evaluation pipeline
- Implement AddDraftStep and ReadAllDraftStep for evidence accumulation
- Update configuration with new agent wrapper and search parameters
- Add comparison script for analyzing agent run differences
- Include documentation for LongMemEval failure analysis
- Enhance tool result offloading with skip options
- Modify search defaults and indexing behavior

* feat(agent): implement tool result offloading with system reminders

- Added tool_result_offload_message parameter to agent wrapper reply method
- Implemented configurable reminder template for offloaded tool results
- Created system reminder messages when tool results are offloaded to files
- Added Chinese user message template for agentic answer step
- Updated tool result offloading middleware to use custom reminder templates
- Enhanced agentic answer instructions to handle long tool results via draft storage

* feat(scripts): add LongMemEval results summarization tool

- Create summarize_lme_results.py script to analyze result JSON files
- Implement command line interface with answer id and dataset root options
- Add support for specifying index range with start and end parameters
- Include option to show failure details and non-successful completions
- Calculate completion statistics and accuracy metrics
- Display detailed breakdown of yes/no/other judgements
- Handle missing and unreadable result files gracefully
- Format output with percentages and comprehensive summary statistics

* feat(summarize_lme_results): add question type breakdown to result summary

- Import defaultdict from collections module
- Add by_type dictionary to track statistics by question type
- Count completed, yes, no, and other responses for each question type
- Display detailed breakdown table showing accuracy by question type
- Include question type column when processing judgements
- Print comprehensive summary with question type distribution
- Calculate and display accuracy percentage for each question type category

* feat(lme): switch to qwen3.7-max model and add shuffle functionality

- Changed default LLM model from glm-5.1 to qwen3.7-max in jinli_lme.yaml
- Added random module import for shuffle functionality
- Implemented --shuffle argument with BooleanOptionalAction for dataset shuffling
- Added --seed argument to control random seed for reproducible shuffling
- Applied random shuffle to dataset indices when shuffle is enabled
- Added console output showing shuffle operation and seed information

* fix(cli): set default random seed for shuffle functionality

- Changed default seed value from None to 42 for consistent shuffling behavior
- Ensures reproducible results when using shuffle option without explicit seed
- Maintains backward compatibility while providing deterministic defaults

* refactor(benchmark): update agentic answer guidelines for grounding

- Updated English instruction to emphasize strict grounding in retrieved context
- Modified Chinese instruction to stress evidence-based responses without inference
- Removed redundant conciseness requirement in both language versions
- Enhanced clarity on proper use of draft saving and retrieval mechanisms
- Strengthened emphasis against hallucination of unsupported facts

* refactor(benchmark): update agentic search instructions and configuration

- Replace separate vector_search and bm25_search with unified search tool
- Update agent instructions to use single search tool with multiple strategies
- Simplify Chinese instructions for search methodology
- Add comprehensive search tool configuration with hybrid vector/BM25 capabilities
- Increase model retry attempts from 1 to 3 for better reliability
- Remove redundant tool references from job_tools list

* feat(search): add configurable search limit with environment variable support

- Remove hardcoded limit and min_score parameters from config schema
- Increase LLM context size from 200000 to 1000000
- Add REME_SEARCH_LIMIT environment variable support for search configuration
- Implement command line argument --search-limit to override default search limit
- Add input validation to ensure search limit is positive
- Modify subprocess execution to pass environment variables
- Update search step to use dynamic default limit from environment or fallback to 5

* refactor(benchmark): remove agentic answer step and related configurations

- Removed AgenticAnswerStep class and its registration
- Deleted agentic_answer.yaml prompt configuration file
- Removed agentic answer related job definitions from jinli_lme.yaml
- Cleaned up tool result offloading middleware implementation
- Removed tool_results_dir configuration field from application config
- Deleted comparison and analysis scripts for agent runs
- Removed agentic answer step from LME init module exports
- Updated agent wrapper to remove tool result offloading functionality
- Removed unused imports and dependencies in agent wrapper module

* refactor(benchmark): remove unused LME result processing components

- Removed LmePrepareJudgeStep and LmeSaveResultStep classes from benchmark module
- Cleaned up imports and exports in lme module initialization
- Removed unused middleware configuration from agent wrapper
- Deleted obsolete result.py file containing deprecated result processing logic
- Simplified agent instantiation by removing middleware parameter
- Updated import statements to reflect removed dependencies

* refactor(index): remove unused search steps and update imports

- Remove Bm25SearchStep and VectorSearchStep from index steps module
- Remove unused prepare_start_config and should_precheck_start exports
- Move import statements to proper location in reme.py
- Update test module to use direct import path for CliService
- Remove vector_search and bm25_search configurations from jinli_lme.yaml
- Add workspace directory environment variable configuration
- Add docstring to getcwd method in agent wrapper
- Remove empty middleware list from agent wrapper initialization

* feat(index): add BM25 and vector search steps with tool context deduplication

- Add Bm25SearchStep for plain BM25 keyword search with tool_context deduplication
- Add VectorSearchStep for plain vector search with tool_context deduplication
- Implement tool context state management with TTL-based deduplication
- Add support for chunk deduplication across tool contexts within TTL window
- Update index steps module to include new search step classes
- Add test coverage for CLI metadata output functionality
- Refactor CLI service to remove unused show_status parameter
- Update documentation comments to reflect internal service configuration

* feat(steps): add Python code execution capability

- Introduce PythonExecuteStep to run Python code in subprocess
- Add configuration for python_execute step in jinli_lme.yaml
- Register python_execute in available tools list
- Implement timeout handling with default 60 second limit
- Capture stdout/stderr output and return code metadata
- Add comprehensive unit tests for execution scenarios
- Support workspace directory context for code execution
- Handle timeout errors and runtime exceptions gracefully

* refactor(python_execute): replace subprocess with asyncio for Python code execution

- Replace subprocess.run with asyncio.create_subprocess_exec for non-blocking execution
- Add _PythonResult dataclass to encapsulate execution results and timeout status
- Implement proper timeout handling with asyncio.wait_for and process.kill()
- Update metadata to include returncode and stderr when timeout occurs
- Convert synchronous _run_python method to asynchronous implementation
- Maintain backward compatibility while improving execution reliability

* refactor(python_execute): replace subprocess with asyncio for Python code execution

- Replace subprocess.run with asyncio.create_subprocess_exec for non-blocking execution
- Add _PythonResult dataclass to encapsulate execution results and timeout status
- Implement proper timeout handling with asyncio.wait_for and process.kill()
- Update metadata to include returncode and stderr when timeout occurs
- Convert synchronous _run_python method to asynchronous implementation
- Maintain backward compatibility while improving execution reliability
2026-07-08 17:43:09 +09:00

218 lines
6.6 KiB
Python

"""Tests for the ReMe CLI entry helpers."""
import asyncio
from types import SimpleNamespace
import pytest
from reme.components.service import cli_service
from reme.components.service.cli_service import CliService
from reme import reme as reme_module
def test_prepare_start_config_moves_unknown_start_args_to_job_args(monkeypatch):
"""``reme start job=...`` is translated into a one-shot cli service config."""
monkeypatch.setattr(
cli_service,
"resolve_app_config",
lambda **kwargs: {
**kwargs,
"service": {"backend": "http", "host": "127.0.0.1"},
},
)
cfg = cli_service.prepare_start_config(
{
"config": "jinli_lme",
"workspace_dir": "/tmp/reme",
"job": "search",
"query": "hello",
"limit": 3,
},
)
assert cfg["config"] == "jinli_lme"
assert cfg["workspace_dir"] == "/tmp/reme"
assert cfg["enable_logo"] is False
assert cfg["log_to_console"] is False
assert cfg["service"] == {
"backend": "cli",
"host": "127.0.0.1",
"job": "search",
"job_args": {"query": "hello", "limit": 3},
}
def test_should_precheck_start_skips_cli_service():
"""CLI service is local execution and should not run port prechecks."""
assert cli_service.should_precheck_start({"service": {"backend": "cli"}}) is False
assert cli_service.should_precheck_start({"service": {"backend": "http"}}) is True
def test_cli_service_runs_configured_job_and_closes_app(capsys):
"""CLI service runs one local job through app lifecycle and prints its answer."""
events = []
class FakeApp:
"""Minimal app stub for exercising CliService lifecycle."""
async def start(self):
"""Record app startup."""
events.append("start")
async def close(self):
"""Record app shutdown."""
events.append("close")
async def run_job(self, name, **kwargs):
"""Record job execution and return a successful response."""
events.append(("run_job", name, kwargs))
return SimpleNamespace(answer="found it", success=True, metadata={"hits": 1})
service = CliService(job="search", job_args={"query": "hello"})
service.start_service(FakeApp())
assert events == [
"start",
("run_job", "search", {"query": "hello"}),
"close",
]
assert capsys.readouterr().out == "found it\n"
def test_cli_service_can_print_metadata_from_service_config(capsys):
"""service.show_metadata controls optional CLI metadata output."""
class FakeApp:
"""Minimal app stub for exercising metadata output."""
async def start(self):
"""No-op app startup."""
async def close(self):
"""No-op app shutdown."""
async def run_job(self, _name, **_kwargs):
"""Return a successful response with metadata."""
return SimpleNamespace(answer="found it", success=True, metadata={"hits": 1})
service = CliService(job="search", show_metadata=True)
service.start_service(FakeApp())
assert capsys.readouterr().out == 'found it\n{"hits": 1}\n'
def test_cli_service_exits_nonzero_on_failed_response(capsys):
"""Failed local CLI jobs write to stderr and produce a failing process status."""
events = []
class FakeApp:
"""Minimal app stub for exercising failure handling."""
async def start(self):
"""Record app startup."""
events.append("start")
async def close(self):
"""Record app shutdown."""
events.append("close")
async def run_job(self, name, **kwargs):
"""Record job execution and return a failed response."""
events.append(("run_job", name, kwargs))
return SimpleNamespace(answer="boom", success=False, metadata={})
service = CliService(job="search", job_args={"query": "hello"})
with pytest.raises(SystemExit) as exc_info:
service.start_service(FakeApp())
assert exc_info.value.code == 1
assert events == [
"start",
("run_job", "search", {"query": "hello"}),
"close",
]
captured = capsys.readouterr()
assert captured.out == ""
assert captured.err == "boom\n"
def test_call_server_passes_client_kwargs_to_client(monkeypatch, capsys):
"""CLI helper forwards connection options to the selected client."""
seen = {}
class FakeClient:
"""Async client stub that records call arguments."""
def __init__(self, **kwargs):
seen["client_kwargs"] = kwargs
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
return None
async def __call__(self, action: str, **kwargs):
seen["action"] = action
seen["payload"] = kwargs
yield "ok"
monkeypatch.setattr(reme_module.R, "get", lambda component_type, backend: FakeClient)
monkeypatch.setattr(reme_module, "running_service_config", lambda: None)
async def run():
await reme_module.call_server(
"search",
backend="http",
host="127.0.0.2",
port=2444,
timeout=1.5,
query="hello",
)
asyncio.run(run())
assert seen["client_kwargs"] == {"host": "127.0.0.2", "port": 2444, "timeout": 1.5}
assert seen["action"] == "search"
assert seen["payload"] == {"query": "hello"}
assert capsys.readouterr().out == "ok\n"
def test_call_server_treats_show_metadata_as_client_kwarg(monkeypatch, capsys):
"""show_metadata controls client display and is not sent as a tool argument."""
seen = {}
class FakeClient:
"""Async client stub that records call arguments."""
def __init__(self, **kwargs):
seen["client_kwargs"] = kwargs
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
return None
async def __call__(self, action: str, **kwargs):
seen["action"] = action
seen["payload"] = kwargs
yield "ok"
monkeypatch.setattr(reme_module.R, "get", lambda component_type, backend: FakeClient)
monkeypatch.setattr(reme_module, "running_service_config", lambda: None)
async def run():
await reme_module.call_server("version", backend="http", show_metadata=True)
asyncio.run(run())
assert seen["client_kwargs"] == {"show_metadata": True}
assert seen["action"] == "version"
assert seen["payload"] == {}
assert capsys.readouterr().out == "ok\n"