Files
2026-08-21 16:00:13 +08:00

131 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""世谱匹配 / 预算 / 注入分组单元测试。"""
from __future__ import annotations
from app.modules.lorebook import (
LorebookBook,
LorebookEntry,
MatchedEntry,
build_injections,
match_entries,
select_within_budget,
)
def _entry(eid: str, **kw) -> LorebookEntry:
return LorebookEntry(id=eid, **kw)
def _book(entries: list[LorebookEntry], **kw) -> LorebookBook:
return LorebookBook(id="lorebook_x", entries=entries, **kw)
def test_keyword_match() -> None:
book = _book([_entry("1", keys=["猫"], content="她喜欢猫。")])
assert [m.entry.id for m in match_entries(book, "今天看到一只猫")] == ["1"]
def test_no_match_when_keyword_absent() -> None:
book = _book([_entry("1", keys=["猫"], content="她喜欢猫。")])
assert match_entries(book, "今天天气不错") == []
def test_case_insensitive_by_default() -> None:
book = _book([_entry("1", keys=["cat"], content="cat info")])
assert match_entries(book, "I have a CAT") != []
def test_case_sensitive() -> None:
book = _book([_entry("1", keys=["Cat"], content="x", case_sensitive=True)])
assert match_entries(book, "a cat here") == []
assert [m.entry.id for m in match_entries(book, "a Cat here")] == ["1"]
def test_constant_always_matched() -> None:
book = _book([_entry("1", keys=["不会命中"], content="恒定设定", constant=True)])
matched = match_entries(book, "无关文本")
assert [m.entry.id for m in matched] == ["1"]
assert matched[0].reason == "constant"
def test_disabled_entry_skipped() -> None:
book = _book([_entry("1", keys=["猫"], content="x", enabled=False)])
assert match_entries(book, "一只猫") == []
def test_selective_requires_both_keys() -> None:
book = _book(
[
_entry(
"1",
keys=["猫"],
secondary_keys=["黑"],
content="黑猫设定",
selective=True,
)
]
)
assert match_entries(book, "看到一只猫") == []
assert [m.entry.id for m in match_entries(book, "看到一只黑猫")] == ["1"]
def test_regex_match() -> None:
book = _book([_entry("1", keys=[r"\b\d{4}\b"], content="年份", use_regex=True)])
assert [m.entry.id for m in match_entries(book, "生于 1999 年")] == ["1"]
assert match_entries(book, "生于 99 年") == []
def test_recursive_scanning() -> None:
# 条目 1 命中关键词,其 content 含触发条目 2 的关键词
book = _book(
[
_entry("1", keys=["学校"], content="她在学校认识小明。"),
_entry("2", keys=["小明"], content="小明是同班同学。"),
],
recursive_scanning=True,
)
matched = match_entries(book, "今天去了学校")
ids = {m.entry.id for m in matched}
assert ids == {"1", "2"}
assert any(m.reason == "recursive" for m in matched if m.entry.id == "2")
def test_budget_keeps_lower_priority() -> None:
book = _book(
[
_entry("a", keys=["k"], content="aaaa", priority=10),
_entry("b", keys=["k"], content="bbbb", priority=1),
],
token_budget=5,
)
matched = match_entries(book, "k")
kept = select_within_budget(matched, token_budget=5)
# 仅 b(cost=4)能进预算;a(cost=4,但 priority 低)在 b 之后超预算
assert [m.entry.id for m in kept] == ["b"]
def test_budget_none_keeps_all_sorted_by_order() -> None:
book = _book(
[
_entry("a", keys=["k"], content="x", priority=5, insertion_order=20),
_entry("b", keys=["k"], content="y", priority=1, insertion_order=10),
]
)
kept = select_within_budget(match_entries(book, "k"))
assert [m.entry.id for m in kept] == ["b", "a"]
def test_build_injections_groups_by_position() -> None:
matched = [
MatchedEntry(entry=_entry("a", keys=["k"], content="前文", position="before_char")),
MatchedEntry(entry=_entry("b", keys=["k"], content="后文1", position="after_char")),
MatchedEntry(entry=_entry("c", keys=["k"], content="后文2", position="after_char")),
]
inj = build_injections(matched)
assert inj == {"before_char": "前文", "after_char": "后文1\n后文2"}
def test_unknown_position_falls_back_to_after_char() -> None:
matched = [MatchedEntry(entry=_entry("a", keys=["k"], content="x", position="weird"))]
assert build_injections(matched) == {"after_char": "x"}