From 94de5da0d3b5a8c6361bb4c37e45589ecfb7f462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=85=B1=E7=89=9B=E8=82=89?= Date: Thu, 10 Sep 2026 15:59:24 +0800 Subject: [PATCH] Require underscore-separated tags in auto-tag prompts - Forbid spaces in tags and require underscores (e.g. sam_altman) in both English and Chinese auto_tag prompts, with English examples switched to English entities (OpenAI, gold) - Drop prompt-string assertions superseded by the new tagging rule - Merge construction/runtime tag_key validation tests into one parametrized case --- reme/steps/evolve/auto_tag.yaml | 11 ++++--- tests/unit/test_auto_tag.py | 11 ------- tests/unit/test_tag_index.py | 54 +++++++++++++++------------------ 3 files changed, 30 insertions(+), 46 deletions(-) diff --git a/reme/steps/evolve/auto_tag.yaml b/reme/steps/evolve/auto_tag.yaml index c1f0b299..3c45aa60 100644 --- a/reme/steps/evolve/auto_tag.yaml +++ b/reme/steps/evolve/auto_tag.yaml @@ -8,13 +8,13 @@ system_prompt: | 2. Use `list_tags` to inspect tags already used in the workspace. Check additional pages when useful. 3. Prefer one primary entity that best answers the question. Use two or three tags only when the memory is genuinely about multiple independent central entities, and never use more than three. Prefer an existing tag whenever it - represents the same entity. Examples include a person (`Sam Altman`), an organization (`宁德时代`), or an - asset/entity (`黄金`). + represents the same entity. Tags must not contain spaces; replace spaces with underscores, for example + `sam_altman`. Examples include a person (`sam_altman`), an organization (`OpenAI`), or an asset/entity (`gold`). 4. Use `frontmatter_update` to write the complete `{tag_key}` list, including `[]` when no entity is central. Choose entities, not broad topics, activities, attributes, dates, or generic keywords. Keep the canonical real-world name and use one concise string per entity. Store multiple entities as separate YAML list items, for example - `[宁德时代, 黄金]`; never combine them into one comma-delimited string. Do not create aliases or near-duplicate + `[OpenAI, gold]`; never combine them into one comma-delimited string. Do not create aliases or near-duplicate spellings for an existing tag. Update only `{tag_key}`; never change the document body or another frontmatter field. system_prompt_zh: | 你负责为每一份 Markdown 记忆维护以实体为中心的记忆标签。 @@ -25,8 +25,9 @@ system_prompt_zh: | 1. 使用 `read` 和 `frontmatter_read` 阅读目标文档的完整内容及当前 frontmatter。 2. 使用 `list_tags` 查看工作区已经使用的标签;必要时继续查看后续分页。 3. 默认只选择一个最能回答上述问题的主实体。只有当记忆确实同时围绕多个相互独立的核心实体时,才选择 - 两个或三个标签,并且绝不能超过三个。同一实体已有标签时必须优先复用。实体可以是人物(如 - `Sam Altman`)、组织或公司(如 `宁德时代`),也可以是资产等实体(如 `黄金`)。 + 两个或三个标签,并且绝不能超过三个。同一实体已有标签时必须优先复用。标签中不能包含空格;如有空格, + 必须替换为下划线,例如将 `Sam Altman` 写成 `sam_altman`。实体可以是人物(如 `sam_altman`)、组织或 + 公司(如 `宁德时代`),也可以是资产等实体(如 `黄金`)。 4. 使用 `frontmatter_update` 写入完整的 `{tag_key}` 列表;没有核心实体时也要写入 `[]`。 只选择实体,不要使用宽泛主题、行为、属性、日期或普通关键词。每个实体使用一个简洁、规范的现实名称。 diff --git a/tests/unit/test_auto_tag.py b/tests/unit/test_auto_tag.py index 021b6244..f49a4e75 100644 --- a/tests/unit/test_auto_tag.py +++ b/tests/unit/test_auto_tag.py @@ -225,14 +225,3 @@ def test_normalize_memory_tags_enforces_entity_storage_contract(): "黄金", ], ) == ["OpenAI", "Sam Altman", "宁德时代"] - - -def test_auto_tag_prompt_prefers_one_existing_entity_and_yaml_list_storage(): - step = AutoTagStep() - prompt = step.prompt_format("system_prompt", tag_key="memory_tags") - - assert "Prefer one primary entity" in prompt - assert "never use more than three" in prompt - assert "list_tags" in prompt - assert "[宁德时代, 黄金]" in prompt - assert "never combine them into one comma-delimited string" in prompt diff --git a/tests/unit/test_tag_index.py b/tests/unit/test_tag_index.py index 18aeba15..f73099ef 100644 --- a/tests/unit/test_tag_index.py +++ b/tests/unit/test_tag_index.py @@ -251,18 +251,32 @@ def test_tag_index_reads_configured_frontmatter_key() -> None: asyncio.run(run()) -@pytest.mark.parametrize("tag_key", ["", " ", None, 123]) -def test_tag_index_rejects_invalid_frontmatter_key(tag_key) -> None: - """Reject keys that cannot identify a frontmatter field.""" - with pytest.raises(ValueError, match="tag_key must be a non-empty string"): - LocalTagIndex(tag_key=tag_key) +@pytest.mark.parametrize( + ("tag_key", "message", "at_runtime"), + [ + ("", "tag_key must be a non-empty string", False), + (" ", "tag_key must be a non-empty string", False), + (None, "tag_key must be a non-empty string", False), + (123, "tag_key must be a non-empty string", False), + ("name", "tag_key must not be a reserved frontmatter key", False), + ("description", "tag_key must not be a reserved frontmatter key", False), + ("", "tag_key must be a non-empty string", True), + ("name", "tag_key must not be a reserved frontmatter key", True), + ("description", "tag_key must not be a reserved frontmatter key", True), + ], +) +def test_tag_index_rejects_invalid_frontmatter_key(tag_key, message: str, at_runtime: bool) -> None: + """Apply the same frontmatter-key validation during construction and runtime updates.""" + index = LocalTagIndex() + with pytest.raises(ValueError, match=message): + if at_runtime: + index.tag_key = tag_key + else: + LocalTagIndex(tag_key=tag_key) -@pytest.mark.parametrize("tag_key", ["name", "description"]) -def test_tag_index_rejects_reserved_frontmatter_key(tag_key) -> None: - """Reject fields declared by FileFrontMatter as reserved metadata.""" - with pytest.raises(ValueError, match="tag_key must not be a reserved frontmatter key"): - LocalTagIndex(tag_key=tag_key) + if at_runtime: + assert index.tag_key == "memory_tags" def test_tag_index_allows_model_config_as_frontmatter_key() -> None: @@ -279,26 +293,6 @@ def test_changing_tag_key_invalidates_the_derived_index() -> None: assert not index.is_healthy -def test_tag_index_rejects_invalid_runtime_frontmatter_key() -> None: - """Apply the same validation when a live component key is updated.""" - index = LocalTagIndex() - - with pytest.raises(ValueError, match="tag_key must be a non-empty string"): - index.tag_key = "" - assert index.tag_key == "memory_tags" - - -@pytest.mark.parametrize("tag_key", ["name", "description"]) -def test_tag_index_rejects_reserved_runtime_frontmatter_key(tag_key: str) -> None: - """Runtime updates preserve the same reserved-field invariant as construction.""" - index = LocalTagIndex() - - with pytest.raises(ValueError, match="tag_key must not be a reserved frontmatter key"): - index.tag_key = tag_key - - assert index.tag_key == "memory_tags" - - def test_file_store_updates_tag_index_from_file_nodes(monkeypatch, tmp_path: Path) -> None: """Keep daily and digest tags aligned through file-store mutations."""