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

81 lines
2.8 KiB
Python

"""世谱匹配引擎:关键词 / 正则 / selective / constant / 递归扫描。
纯逻辑,无 IO。扫描文本由调用方(集成层)从对话历史截取后传入。
"""
from __future__ import annotations
import re
from app.modules.lorebook.types import LorebookBook, LorebookEntry, MatchedEntry
_RECURSION_MAX_ITERATIONS = 5
def match_entries(book: LorebookBook, scan_text: str) -> list[MatchedEntry]:
"""返回命中的条目(含 constant 与关键词命中);递归扫描按 book 配置。"""
matched: list[MatchedEntry] = []
for entry in book.entries:
if not entry.enabled:
continue
if entry.constant:
matched.append(MatchedEntry(entry=entry, reason="constant"))
continue
if _entry_matches(entry, scan_text):
matched.append(MatchedEntry(entry=entry, reason="key"))
if book.recursive_scanning:
matched = _expand_recursive(matched, book, scan_text)
return matched
def _entry_matches(entry: LorebookEntry, scan_text: str) -> bool:
if entry.selective and entry.secondary_keys:
return _any_key_hit(entry.keys, entry, scan_text) and _any_key_hit(
entry.secondary_keys, entry, scan_text
)
return _any_key_hit(entry.keys, entry, scan_text)
def _any_key_hit(keys: list[str], entry: LorebookEntry, scan_text: str) -> bool:
for key in keys:
if not key:
continue
if entry.use_regex:
flags = 0 if entry.case_sensitive else re.IGNORECASE
try:
if re.search(key, scan_text, flags):
return True
except re.error:
# 非法正则降级为字面量包含
pass
else:
hay = scan_text if entry.case_sensitive else scan_text.casefold()
needle = key if entry.case_sensitive else key.casefold()
if needle in hay:
return True
return False
def _expand_recursive(
matched: list[MatchedEntry], book: LorebookBook, original_text: str
) -> list[MatchedEntry]:
"""已命中条目的 content 追加进扫描文本,再扫未命中条目;限迭代防振荡。"""
hit_ids = {m.entry.id for m in matched}
pool = list(matched)
extra_text = original_text + "\n" + "\n".join(m.entry.content for m in matched)
for _ in range(_RECURSION_MAX_ITERATIONS):
grew = False
for entry in book.entries:
if entry.id in hit_ids or not entry.enabled or entry.constant:
continue
if _entry_matches(entry, extra_text):
pool.append(MatchedEntry(entry=entry, reason="recursive"))
hit_ids.add(entry.id)
extra_text += "\n" + entry.content
grew = True
if not grew:
break
return pool