Files
Hy-Memory/tests/test_additive_scoring_ner.py
T
2026-10-16 16:26:27 +08:00

138 lines
4.2 KiB
Python

# -*- coding: utf-8 -*-
"""Additive VDB scoring + month lemmatize + NER tag helpers."""
import pytest
from hy_memory.pipelines._retrieval.scoring import (
score_vdb_node,
score_vdb_node_additive,
)
from hy_memory.pipelines._retrieval.lemmatize import lemmatize_for_bm25
from hy_memory.pipelines._retrieval.ner import (
BOOST_ENTITY_LABELS,
_get_nlp,
classify_tag,
annotate_tag_ners,
entity_tag_pairs,
extract_entities,
max_entity_tag_cos,
same_entity_group,
)
requires_spacy_model = pytest.mark.skipif(
_get_nlp() is None,
reason="spaCy English NER model is not installed",
)
class TestScoreVdbNodeAdditive:
def test_no_bm25_keeps_full_sem(self):
# 旧公式会把无 BM25 的 sem 乘 0.6;新公式应保留满权重
assert abs(score_vdb_node_additive(0.3578) - 0.3578) < 1e-6
assert abs(score_vdb_node(0.3578, 0.0, 0.6, 0.4) - 0.21468) < 1e-4
def test_bm25_is_bonus_not_dilution(self):
s = score_vdb_node_additive(0.5, bm25_score=0.4, w_bm25=0.2)
assert abs(s - (0.5 + 0.2 * 0.4)) < 1e-6
def test_tag_cos_bonus(self):
s = score_vdb_node_additive(
0.3578, bm25_score=0.0, tag_cos=0.665, w_tag=0.3,
)
assert abs(s - (0.3578 + 0.3 * 0.665)) < 1e-4
def test_clamp01(self):
s = score_vdb_node_additive(
0.9, bm25_score=1.0, tag_cos=0.9, w_bm25=0.2, w_tag=0.3,
)
assert s == 1.0
class TestMonthLemmatize:
def test_may_preserved(self):
out = lemmatize_for_bm25("Which country was Evan visiting in May 2023?")
tokens = set(out.split())
assert "may" in tokens
assert "2023" in tokens
class TestNerTag:
def test_boost_excludes_person(self):
assert "PERSON" not in BOOST_ENTITY_LABELS
assert "LAW" not in BOOST_ENTITY_LABELS
assert "LANGUAGE" not in BOOST_ENTITY_LABELS
assert "GPE" in BOOST_ENTITY_LABELS
assert "PRODUCT" in BOOST_ENTITY_LABELS
def test_query_person_not_for_boost(self):
ents = extract_entities(
"What kind of car does Evan drive?", for_boost=True,
)
assert all(lab != "PERSON" for _, lab in ents)
@requires_spacy_model
def test_classify_jasper(self):
lab = classify_tag("Jasper")
assert lab in ("GPE", "LOC", "FAC")
def test_classify_prius_not_gpe_heuristic(self):
# spaCy 常不认 Prius;旧 TitleCase→GPE 启发式会误标,现应为空或 PRODUCT
lab = classify_tag("Prius")
assert lab in ("", "PRODUCT")
def test_classify_topic_empty(self):
assert classify_tag("travel") == ""
assert classify_tag("health") == ""
@requires_spacy_model
def test_annotate_parallel(self):
tags = ["travel", "Jasper", "family"]
ners = annotate_tag_ners(tags)
assert len(ners) == 3
assert ners[0] == ""
assert ners[1] != ""
assert ners[2] == ""
def test_entity_tag_pairs_skips_person(self):
pairs = entity_tag_pairs(
["Evan", "Jasper", "fitness"],
["PERSON", "GPE", ""],
for_boost=True,
)
assert pairs == [("Jasper", "GPE")]
def test_same_group_geo(self):
assert same_entity_group("GPE", "LOC")
assert not same_entity_group("PERSON", "GPE")
def test_max_entity_tag_cos_threshold(self):
import math
def n(v):
s = math.sqrt(sum(x * x for x in v)) or 1
return [x / s for x in v]
q = [1.0, 0.0, 0.0]
t_hi = [0.9, 0.43589, 0.0] # cos≈0.9
t_lo = [0.3, 0.9539, 0.0] # cos≈0.3
emb = {"Canada": n(q), "Jasper": n(t_hi), "Banff": n(t_lo)}
cos, pair = max_entity_tag_cos(
[("Canada", "GPE")],
[("Jasper", "GPE"), ("Banff", "GPE")],
emb,
min_cos=0.55,
)
assert cos >= 0.55
assert pair[2] == "Jasper"
def test_max_entity_tag_cos_ignores_person(self):
emb = {"Evan": [1.0, 0.0], "Sam": [1.0, 0.0]}
cos, pair = max_entity_tag_cos(
[("Evan", "PERSON")],
[("Sam", "PERSON")],
emb,
min_cos=0.5,
)
assert cos == 0.0
assert pair is None