mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-09 22:31:05 +00:00
refactor: preserve auto resource compatibility
This commit is contained in:
parent
1c9101aa72
commit
b3b9dd7cdf
5 changed files with 112 additions and 11 deletions
|
|
@ -140,6 +140,14 @@ class BaseAutoResourceStep(BaseStep):
|
|||
return before_bytes is not None
|
||||
return after_path != before_path or before_bytes != after_bytes
|
||||
|
||||
async def _refresh_day_index(self, day: str) -> dict:
|
||||
"""Refresh and return the derived daily index for a resource-note change."""
|
||||
daily_dir = self.config_value("daily_dir")
|
||||
self.logger.info(f"[{self.name}] refresh index start date={day} daily_dir={daily_dir}")
|
||||
index_payload = await refresh_day_index(self.file_store, day, daily_dir)
|
||||
self.logger.info(f"[{self.name}] refresh index done date={day}")
|
||||
return index_payload
|
||||
|
||||
def _find_resource_note(self, notes: list[dict], file_path: str, fallback_path: str) -> dict | None:
|
||||
source = self._source_resource_link(file_path)
|
||||
for note in notes:
|
||||
|
|
@ -259,9 +267,7 @@ class BaseAutoResourceStep(BaseStep):
|
|||
|
||||
await self.file_store.delete([note_rel])
|
||||
self.logger.info(f"[{self.name}] catalog delete done note={note_rel}")
|
||||
self.logger.info(f"[{self.name}] refresh index start date={date_str} daily_dir={daily_dir}")
|
||||
index_payload = await refresh_day_index(self.file_store, date_str, daily_dir)
|
||||
self.logger.info(f"[{self.name}] refresh index done date={date_str}")
|
||||
index_payload = await self._refresh_day_index(date_str)
|
||||
|
||||
self.context.response.success = True
|
||||
self.context.response.answer = f"Deleted resource note: {note_rel}"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from agentscope.message import Base64Source, DataBlock, TextBlock, UserMsg
|
|||
from agentscope.model import ChatModelBase
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..file_io import is_image_file, refresh_day_index
|
||||
from ..file_io import is_image_file
|
||||
from ..file_io._path import IMAGE_MIME_BY_EXT, IMAGE_SUFFIXES
|
||||
from ._auto_resource import _SOURCE_RESOURCE_KEY, _sanitize_note_name, BaseAutoResourceStep
|
||||
from ...components import R
|
||||
|
|
@ -477,7 +477,7 @@ class AutoImageResourceStep(BaseAutoResourceStep):
|
|||
|
||||
modified = self._note_modified(before_note_path, before_note_bytes, note_path)
|
||||
self.context.response.metadata.update({"path": note_path, "created": note_created, "modified": modified})
|
||||
index_payload = await refresh_day_index(self.file_store, date_str, daily_dir)
|
||||
index_payload = await self._refresh_day_index(date_str)
|
||||
|
||||
self.context.response.success = True
|
||||
self.context.response.answer = f"Captioned image resource {file_path} -> {note_path}"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import copy
|
|||
import inspect
|
||||
|
||||
from ...components import R
|
||||
from ...enumeration import ComponentEnum
|
||||
from ..base_step import BaseStep
|
||||
from ._auto_resource import BaseAutoResourceStep, _results_answer
|
||||
|
||||
|
|
@ -35,7 +36,23 @@ class AutoResourceStep(BaseStep):
|
|||
"""Resolve configured processors and allocate their per-invocation batches."""
|
||||
raw_specs = self.dispatch_step_specs
|
||||
if not raw_specs:
|
||||
raise RuntimeError("AutoResourceStep requires resource processors in dispatch_steps")
|
||||
registry = self.app_context.registry if self.app_context is not None else R
|
||||
raw_specs = [
|
||||
backend
|
||||
for backend, step_cls in registry.get_all(ComponentEnum.STEP).items()
|
||||
if isinstance(step_cls, type)
|
||||
and issubclass(step_cls, BaseAutoResourceStep)
|
||||
and step_cls.resource_fallback
|
||||
]
|
||||
if len(raw_specs) != 1:
|
||||
candidates = ", ".join(sorted(raw_specs)) or "none"
|
||||
raise RuntimeError(
|
||||
"AutoResourceStep without dispatch_steps requires exactly one registered "
|
||||
f"fallback resource processor; found: {candidates}",
|
||||
)
|
||||
self.logger.warning(
|
||||
f"[{self.name}] dispatch_steps omitted; using registered fallback processor={raw_specs[0]}",
|
||||
)
|
||||
|
||||
routes: list[_ProcessorRoute] = []
|
||||
fallback_indexes: list[int] = []
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import uuid
|
|||
import aiofiles
|
||||
|
||||
from ...components import R
|
||||
from ..file_io import refresh_day_index
|
||||
from ._evolve import agent_reply_result_text
|
||||
from ._auto_resource import BaseAutoResourceStep
|
||||
|
||||
|
|
@ -176,9 +175,7 @@ class AutoTextResourceStep(BaseAutoResourceStep):
|
|||
return
|
||||
|
||||
modified = self._note_modified(before_note_path, before_note_bytes, note_path)
|
||||
self.logger.info(f"[{self.name}] refresh index start date={date_str} daily_dir={daily_dir}")
|
||||
index_payload = await refresh_day_index(self.file_store, date_str, daily_dir)
|
||||
self.logger.info(f"[{self.name}] refresh index done date={date_str}")
|
||||
index_payload = await self._refresh_day_index(date_str)
|
||||
|
||||
self.context.response.success = True
|
||||
self.context.response.answer = agent_reply_result_text(result)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ from PIL import Image
|
|||
|
||||
from reme.components import R
|
||||
from reme.components.agent_wrapper import BaseAgentWrapper
|
||||
from reme.components.component_registry import ComponentRegistry
|
||||
from reme.components.file_store import LocalFileStore
|
||||
from reme.components.job import BaseJob
|
||||
from reme.components.runtime_context import RuntimeContext
|
||||
from reme.enumeration import ComponentEnum
|
||||
from reme.steps.evolve._auto_resource import BaseAutoResourceStep
|
||||
|
|
@ -725,6 +727,85 @@ def test_auto_resource_router_requires_the_fallback_processor_to_be_last():
|
|||
step._processor_routes()
|
||||
|
||||
|
||||
def test_auto_resource_router_discovers_unique_fallback_for_legacy_config():
|
||||
"""An old Step spec without dispatch_steps keeps its text-resource behavior."""
|
||||
app_ctx = _make_app_context(Path.cwd())
|
||||
app_ctx.registry = R.copy()
|
||||
step = AutoResourceStep(app_context=app_ctx)
|
||||
|
||||
routes = step._processor_routes()
|
||||
|
||||
assert [(spec, step_cls) for spec, step_cls, _ in routes] == [
|
||||
({"backend": "auto_text_resource_step"}, AutoTextResourceStep),
|
||||
]
|
||||
|
||||
|
||||
def test_auto_resource_legacy_job_config_dispatches_text_resource():
|
||||
"""A real BaseJob accepts the pre-router auto_resource Step configuration."""
|
||||
|
||||
async def run():
|
||||
with tempfile.TemporaryDirectory() as tmpdir, temp_chdir(tmpdir):
|
||||
cwd = Path.cwd()
|
||||
app_ctx = _make_app_context(cwd)
|
||||
app_ctx.registry = R.copy()
|
||||
fs = LocalFileStore(name="test_store", embedding_store="")
|
||||
await fs.start()
|
||||
_install_file_jobs(app_ctx, fs)
|
||||
wrapper = _FakeAgentWrapper()
|
||||
job = BaseJob(
|
||||
name="legacy_auto_resource",
|
||||
app_context=app_ctx,
|
||||
steps=[
|
||||
{
|
||||
"backend": "auto_resource_step",
|
||||
"file_store": fs,
|
||||
"agent_wrapper": wrapper,
|
||||
},
|
||||
],
|
||||
)
|
||||
await job.start()
|
||||
try:
|
||||
source = cwd / "resource" / "2026-01-01" / "legacy.txt"
|
||||
source.parent.mkdir(parents=True, exist_ok=True)
|
||||
source.write_text("legacy text resource", encoding="utf-8")
|
||||
|
||||
response = await job(changes=[{"change": "added", "path": str(source)}])
|
||||
|
||||
assert response.success is True
|
||||
assert response.metadata["processed"] == 1
|
||||
assert response.metadata["results"][0]["success"] is True
|
||||
assert "legacy text resource" in wrapper.inputs
|
||||
finally:
|
||||
await job.close()
|
||||
await fs.close()
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_auto_resource_router_rejects_ambiguous_registered_fallbacks():
|
||||
"""Legacy discovery fails closed when plugins register multiple fallbacks."""
|
||||
app_ctx = _make_app_context(Path.cwd())
|
||||
app_ctx.registry = R.copy()
|
||||
app_ctx.registry.add("second_text_fallback", AutoTextResourceStep, owner=__name__)
|
||||
step = AutoResourceStep(app_context=app_ctx)
|
||||
|
||||
with pytest.raises(RuntimeError, match="exactly one registered fallback resource processor"):
|
||||
step._processor_routes()
|
||||
|
||||
explicit = AutoResourceStep(app_context=app_ctx, dispatch_steps=["auto_text_resource_step"])
|
||||
assert len(explicit._processor_routes()) == 1
|
||||
|
||||
|
||||
def test_auto_resource_router_rejects_missing_registered_fallback():
|
||||
"""Legacy discovery fails clearly when no fallback processor is installed."""
|
||||
app_ctx = _make_app_context(Path.cwd())
|
||||
app_ctx.registry = ComponentRegistry()
|
||||
step = AutoResourceStep(app_context=app_ctx)
|
||||
|
||||
with pytest.raises(RuntimeError, match=r"fallback resource processor; found: none"):
|
||||
step._processor_routes()
|
||||
|
||||
|
||||
def test_resource_processors_have_canonical_registrations_and_isolated_prompts():
|
||||
"""Each modality owns one backend and loads only its module-local prompts."""
|
||||
assert R.get(ComponentEnum.STEP, "auto_resource_step") is AutoResourceStep
|
||||
|
|
@ -875,7 +956,7 @@ def test_auto_image_reports_modified_when_index_refresh_fails_after_write():
|
|||
async def fail_refresh(*_args, **_kwargs):
|
||||
raise RuntimeError("index refresh failed")
|
||||
|
||||
with patch("reme.steps.evolve.auto_image_resource.refresh_day_index", new=fail_refresh):
|
||||
with patch("reme.steps.evolve._auto_resource.refresh_day_index", new=fail_refresh):
|
||||
resp = await _run_step(step, [{"change": "added", "path": str(source)}])
|
||||
|
||||
result = resp.metadata["results"][0]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue