fix(reindex): return a readable summary so Studio stops showing a placeholder (#565)
Some checks are pending
CI / Python packages / Build and verify distributions (push) Waiting to run
CI / Python quality / GitHub Actions (push) Waiting to run
CI / Python tests / Unit Tests - py3.11 (push) Waiting to run
CI / Python tests / Unit Tests - py3.12 (push) Waiting to run
CI / Windows / CLI smoke - py3.11 (push) Waiting to run
CI and Release / Docker / Build and test / amd64 (push) Waiting to run
CI and Release / Docker / Publish multi-platform tags (push) Blocked by required conditions
Security / CodeQL / Analyze javascript-typescript (push) Waiting to run
CI / Python quality / Pre-commit (push) Waiting to run
CI / Python tests / Unit Tests - py3.13 (push) Waiting to run
CI / Python tests / Unit Tests - py3.14 (push) Waiting to run
CI and Release / Docker / Build and test / arm64 (push) Waiting to run
Security / CodeQL / Analyze python (push) Waiting to run

* fix(reindex): return a readable summary so Studio stops showing a placeholder

`ReindexStep` assigned the store's report dict straight to
`response.answer`. ReMe Studio's settings panel already anticipates a string
answer:

    typeof result.answer === "string" && result.answer ? result.answer : t("indexRebuilt")

(`reme_studio/app/settings-center.tsx`), so with a dict answer that branch was
unreachable and every rebuild reported the generic "index rebuilt" text. The
operator never saw which scopes ran or how many entries each indexed.

Render the report as one summary line — `Reindexed scope=all: bm25=7,
embedding=5, tag=2` for the composite scope and `Reindexed scope=bm25: 3
indexed` for a single one. The report itself still lands in metadata, so
programmatic consumers are unaffected, and Studio needs no change because its
string branch now fires.

`reindex` is the only answer-carrying consumer of this step; no test asserted
the answer content, only metadata, so nothing else moved.

* chore(reindex): tighten the renderer and deduplicate the summary test

Review on #565: the helper's docstring carried the Studio background
that already lives in the PR description; the intermediate per_scope
dict collapses into the join generator; and the all-scope delegation
test now uses distinct counts (7, 5, 2) so it alone pins both the exact
summary answer and the full metadata report, replacing the overlapping
summary test.

* refactor(reindex): share summary return formatting

---------

Co-authored-by: jinli.yl <jinli.yl@alibaba-inc.com>
This commit is contained in:
Ziyang Guo 2026-10-14 11:25:48 +08:00 • committed by GitHub
parent 081f5df39a
commit a99459ac93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 17 additions and 5 deletions

View file

@ -4,6 +4,16 @@ from ..base_step import BaseStep
from ...components import R
def _render_reindex(details: dict) -> str:
"""Render the store's reindex report as one readable summary line."""
scope = str(details.get("scope") or "all")
counts = ", ".join(
f"{name}={entry.get('indexed', 0)}" for name, entry in details.items() if isinstance(entry, dict)
)
summary = counts or f"{details.get('indexed', 0)} indexed"
return f"Reindexed scope={scope}: {summary}"
@R.register("reindex_step")
class ReindexStep(BaseStep):
"""Rebuild BM25, embeddings, and/or tags without scanning workspace files."""
@ -13,7 +23,7 @@ class ReindexStep(BaseStep):
scope = str(self.context.get("scope", "all"))
details = await self.file_store.reindex(scope)
self.context.response.answer = details
self.context.response.answer = _render_reindex(details)
self.context.response.metadata.update(details)
self.context.response.metadata["scope"] = scope
return self.context.response

View file

@ -23,17 +23,18 @@ async def test_reindex_step_delegates_scope(scope):
store.reindex.assert_awaited_once_with(scope)
store.clear.assert_not_called()
assert response.metadata == {"indexed": 3, "scope": scope}
assert response.answer == f"Reindexed scope={scope}: 3 indexed"
@pytest.mark.asyncio
async def test_reindex_step_delegates_all_once():
"""The step delegates the composite scope exactly once."""
"""The step delegates the composite scope once; the answer summarizes it."""
store = LocalFileStore(name="test_reindex_all", embedding_store="")
details = {
"scope": "all",
"bm25": {"scope": "bm25", "indexed": 3},
"embedding": {"scope": "embedding", "indexed": 3},
"tag": {"scope": "tag", "indexed": 3},
"bm25": {"scope": "bm25", "indexed": 7},
"embedding": {"scope": "embedding", "indexed": 5},
"tag": {"scope": "tag", "indexed": 2},
}
store.reindex = AsyncMock(return_value=details)
@ -41,6 +42,7 @@ async def test_reindex_step_delegates_all_once():
store.reindex.assert_awaited_once_with("all")
assert response.metadata == details
assert response.answer == "Reindexed scope=all: bm25=7, embedding=5, tag=2"
def test_reindex_job_schema_exposes_tag_scope():