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
This commit is contained in:
酱牛肉 2026-09-10 15:59:24 +08:00
parent 481c98a42c
commit 94de5da0d3
3 changed files with 30 additions and 46 deletions

View file

@ -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}` 列表;没有核心实体时也要写入 `[]`。
只选择实体,不要使用宽泛主题、行为、属性、日期或普通关键词。每个实体使用一个简洁、规范的现实名称。

View file

@ -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

View file

@ -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."""