mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
- Implemented 'Bring Your Own Model' (BYOM) support for NER, Relation, and Triplet extraction - Added NER aggregation strategies (simple, max, average) - Implemented Relation Extraction via Sequence Classification with entity markers - Enhanced Triplet Extraction with REBEL post-processing and lazy loading - Updated all extractors to prioritize runtime options over config defaults - Added extensive tests and examples (huggingface_demo.py) - Updated documentation and CHANGELOG
179 lines
6.6 KiB
Python
179 lines
6.6 KiB
Python
|
|
import sys
|
|
import os
|
|
import traceback
|
|
from unittest.mock import MagicMock
|
|
|
|
print("Starting test script...", flush=True)
|
|
|
|
# Mock transformers and torch BEFORE any project imports
|
|
try:
|
|
mock_transformers = MagicMock()
|
|
mock_pipeline = MagicMock()
|
|
mock_transformers.pipeline = mock_pipeline
|
|
sys.modules["transformers"] = mock_transformers
|
|
sys.modules["torch"] = MagicMock()
|
|
sys.modules["torch"].cuda.is_available.return_value = False
|
|
|
|
# Mock spacy
|
|
mock_spacy = MagicMock()
|
|
sys.modules["spacy"] = mock_spacy
|
|
|
|
# Mock instructor
|
|
sys.modules["instructor"] = MagicMock()
|
|
|
|
# Also mock semantica.semantic_extract.config to avoid initialization issues
|
|
mock_config_module = MagicMock()
|
|
mock_config_instance = MagicMock()
|
|
# Setup default return values for config
|
|
mock_config_instance.get.return_value = {}
|
|
mock_config_instance.get_optimization_config.return_value = {"enable_cache": False}
|
|
|
|
mock_config_module.config = mock_config_instance
|
|
mock_config_module.Config = MagicMock(return_value=mock_config_instance)
|
|
sys.modules["semantica.semantic_extract.config"] = mock_config_module
|
|
|
|
print("Mocks setup complete.", flush=True)
|
|
except Exception as e:
|
|
print(f"Error setting up mocks: {e}", flush=True)
|
|
sys.exit(1)
|
|
|
|
# Add project root
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
print(f"Added to path: {sys.path[0]}", flush=True)
|
|
|
|
try:
|
|
print("Importing methods...", flush=True)
|
|
from semantica.semantic_extract.methods import extract_entities_huggingface, extract_relations_huggingface, extract_triplets_huggingface
|
|
print("Importing Entity class...", flush=True)
|
|
from semantica.semantic_extract.ner_extractor import Entity
|
|
print("Imports successful.", flush=True)
|
|
except Exception as e:
|
|
print(f"Import failed: {e}", flush=True)
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
def test_enhanced_impl():
|
|
print("Testing enhanced implementation...", flush=True)
|
|
|
|
try:
|
|
# 1. Test NER with aggregation strategy
|
|
print("\n--- Testing NER ---", flush=True)
|
|
|
|
# Setup mock pipeline return value
|
|
mock_ner_pipeline = MagicMock()
|
|
mock_ner_pipeline.return_value = [
|
|
{"entity_group": "PERSON", "score": 0.99, "word": "Elon Musk", "start": 0, "end": 9},
|
|
]
|
|
|
|
# Configure pipeline side effect
|
|
def pipeline_side_effect(task, **kwargs):
|
|
if task == "ner": return mock_ner_pipeline
|
|
return MagicMock()
|
|
|
|
mock_pipeline.side_effect = pipeline_side_effect
|
|
|
|
# Test calling with aggregation_strategy
|
|
entities = extract_entities_huggingface(
|
|
"Elon Musk founded SpaceX.",
|
|
model="dslim/bert-base-NER",
|
|
aggregation_strategy="max"
|
|
)
|
|
print(f"Entities: {entities}", flush=True)
|
|
|
|
# Verify aggregation_strategy was passed
|
|
mock_pipeline.assert_any_call(
|
|
"ner",
|
|
model="dslim/bert-base-NER",
|
|
device=-1,
|
|
aggregation_strategy="max",
|
|
tokenizer=None
|
|
)
|
|
|
|
# 2. Test Relations with Input Formatting
|
|
print("\n--- Testing Relations ---", flush=True)
|
|
e1 = Entity(text="Elon Musk", label="PERSON", start_char=0, end_char=9)
|
|
e2 = Entity(text="SpaceX", label="ORG", start_char=18, end_char=24)
|
|
|
|
mock_rel_pipeline = MagicMock()
|
|
mock_rel_pipeline.return_value = [{"label": "founded", "score": 0.9}]
|
|
|
|
# Update pipeline mock to return rel pipeline
|
|
def pipeline_side_effect_rel(task, **kwargs):
|
|
if task == "ner": return mock_ner_pipeline
|
|
if task == "text-classification": return mock_rel_pipeline
|
|
return MagicMock()
|
|
|
|
mock_pipeline.side_effect = pipeline_side_effect_rel
|
|
|
|
relations = extract_relations_huggingface(
|
|
"Elon Musk founded SpaceX.",
|
|
entities=[e1, e2],
|
|
model="some-relation-model"
|
|
)
|
|
print(f"Relations: {relations}", flush=True)
|
|
|
|
# Verify input formatting
|
|
# Check if ANY call contained the correct formatting
|
|
found_match = False
|
|
for call in mock_rel_pipeline.call_args_list:
|
|
args, _ = call
|
|
if "<subj> Elon Musk </subj>" in args[0] and "<obj> SpaceX </obj>" in args[0]:
|
|
found_match = True
|
|
break
|
|
|
|
if not found_match:
|
|
print("Failed to find expected call args in:", flush=True)
|
|
for call in mock_rel_pipeline.call_args_list:
|
|
print(f" {call[0]}", flush=True)
|
|
|
|
assert found_match, "Did not find relation call with Elon Musk as subject"
|
|
|
|
# 3. Test Triplets with REBEL parsing
|
|
print("\n--- Testing Triplets ---", flush=True)
|
|
|
|
# Mock Tokenizer and Model
|
|
mock_tokenizer_instance = MagicMock()
|
|
mock_transformers.AutoTokenizer.from_pretrained.return_value = mock_tokenizer_instance
|
|
mock_tokenizer_instance.encode.return_value = MagicMock()
|
|
# Mock decode to return REBEL format
|
|
mock_tokenizer_instance.decode.return_value = "<s><triplet> Elon Musk <subj> founded <obj> SpaceX <triplet> SpaceX <subj> created <obj> Starship</s>"
|
|
|
|
mock_model_instance = MagicMock()
|
|
mock_transformers.AutoModelForSeq2SeqLM.from_pretrained.return_value = mock_model_instance
|
|
mock_model_instance.generate.return_value = [MagicMock()]
|
|
|
|
triplets = extract_triplets_huggingface(
|
|
"Elon Musk founded SpaceX and created Starship.",
|
|
model="Babelscape/rebel-large"
|
|
)
|
|
print(f"Triplets: {triplets}", flush=True)
|
|
|
|
# Verify parsing
|
|
assert len(triplets) == 2
|
|
assert triplets[0].subject == "Elon Musk"
|
|
assert triplets[0].predicate == "founded"
|
|
assert triplets[0].object == "SpaceX"
|
|
assert triplets[1].subject == "SpaceX"
|
|
assert triplets[1].predicate == "created"
|
|
assert triplets[1].object == "Starship"
|
|
|
|
# Verify skip_special_tokens=False was passed
|
|
mock_tokenizer_instance.decode.assert_called_with(
|
|
mock_model_instance.generate.return_value[0],
|
|
skip_special_tokens=False
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"Error during test execution: {e}", flush=True)
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
test_enhanced_impl()
|
|
print("\nAll tests passed!", flush=True)
|
|
except Exception as e:
|
|
print(f"\nTest failed: {e}", flush=True)
|
|
traceback.print_exc()
|