| import importlib.util |
| import json |
| import os |
| import sys |
| import tempfile |
| import types |
| import unittest |
| from pathlib import Path |
| from unittest import mock |
|
|
| import numpy |
|
|
|
|
| MODULE_PATH = ( |
| Path(__file__).resolve().parents[1] |
| / "evaluation" |
| / "pycocotools_ovod" |
| / "semantic_matching.py" |
| ) |
|
|
|
|
| class _FakeEmbeddings: |
| def __init__(self): |
| self.calls = [] |
|
|
| def create(self, input, model): |
| self.calls.append((list(input), model)) |
|
|
| class Response: |
| def model_dump(self_inner): |
| return { |
| "data": [ |
| {"embedding": [1.0, 0.0]}, |
| {"embedding": [1.0, 0.0]}, |
| ] |
| } |
|
|
| return Response() |
|
|
|
|
| def load_semantic_matching(cache_path, *, offline=False, readonly=False, api_key=None, fake_openai=None): |
| fake_openai = fake_openai or types.SimpleNamespace(embeddings=_FakeEmbeddings()) |
|
|
| with mock.patch.dict( |
| os.environ, |
| { |
| "ORIENTER_EMBEDDING_CACHE": str(cache_path), |
| "ORIENTER_EMBEDDING_OFFLINE": "1" if offline else "", |
| "ORIENTER_EMBEDDING_READONLY": "1" if readonly else "", |
| "ZHIPU_API_KEY": api_key or "", |
| }, |
| clear=False, |
| ), mock.patch.dict(sys.modules, {"openai": fake_openai}): |
| name = f"semantic_matching_under_test_{id(cache_path)}_{offline}_{readonly}" |
| spec = importlib.util.spec_from_file_location(name, MODULE_PATH) |
| module = importlib.util.module_from_spec(spec) |
| spec.loader.exec_module(module) |
| return module, fake_openai |
|
|
|
|
| class SemanticMatchingCacheTests(unittest.TestCase): |
| def test_offline_cache_hit_does_not_call_api(self): |
| with tempfile.TemporaryDirectory() as tmpdir: |
| cache_path = Path(tmpdir) / "embedding_dict.json" |
| cache_path.write_text(json.dumps({"Button": [1.0, 0.0]})) |
|
|
| module, fake_openai = load_semantic_matching(cache_path, offline=True) |
|
|
| self.assertTrue(module.is_semantic_match("button", "button", eval_dimension="s")) |
| self.assertEqual(fake_openai.embeddings.calls, []) |
|
|
| def test_offline_cache_miss_fails_before_api_call(self): |
| with tempfile.TemporaryDirectory() as tmpdir: |
| cache_path = Path(tmpdir) / "embedding_dict.json" |
| cache_path.write_text(json.dumps({"Known": [1.0, 0.0]})) |
|
|
| module, fake_openai = load_semantic_matching(cache_path, offline=True) |
|
|
| with self.assertRaisesRegex(RuntimeError, "ORIENTER_EMBEDDING_OFFLINE=1"): |
| module.is_semantic_match("known", "missing", eval_dimension="s") |
|
|
| self.assertEqual(fake_openai.embeddings.calls, []) |
| self.assertFalse((Path(tmpdir) / "embedding_dict.json.save").exists()) |
|
|
| def test_invalid_cache_is_rejected_at_load_time(self): |
| with tempfile.TemporaryDirectory() as tmpdir: |
| cache_path = Path(tmpdir) / "embedding_dict.json" |
| cache_path.write_text(json.dumps({"A": [1.0, 0.0], "B": [1.0]})) |
|
|
| with self.assertRaisesRegex(ValueError, "dimension"): |
| load_semantic_matching(cache_path, offline=True) |
|
|
| def test_non_finite_cache_value_is_rejected_at_load_time(self): |
| with tempfile.TemporaryDirectory() as tmpdir: |
| cache_path = Path(tmpdir) / "embedding_dict.json" |
| cache_path.write_text(json.dumps({"A": [1.0, float("nan")]})) |
|
|
| with self.assertRaisesRegex(ValueError, "non-finite"): |
| load_semantic_matching(cache_path, offline=True) |
|
|
| def test_readonly_cache_does_not_write_after_api_fill(self): |
| with tempfile.TemporaryDirectory() as tmpdir: |
| cache_path = Path(tmpdir) / "embedding_dict.json" |
| cache_path.write_text(json.dumps({})) |
| original = cache_path.read_text() |
|
|
| module, fake_openai = load_semantic_matching( |
| cache_path, |
| readonly=True, |
| api_key="fake-key", |
| ) |
| module.SAVE_INTERVAL = 1 |
|
|
| self.assertTrue(module.is_semantic_match("alpha", "beta", eval_dimension="s")) |
| self.assertEqual(fake_openai.embeddings.calls, [(["Alpha", "Beta"], "embedding-3")]) |
| self.assertEqual(cache_path.read_text(), original) |
| self.assertFalse((Path(tmpdir) / "embedding_dict.json.save").exists()) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|