mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
245 lines
6.3 KiB
Python
245 lines
6.3 KiB
Python
from dataclasses import dataclass, field
|
|
from typing import Any, Dict, List
|
|
from unittest.mock import patch
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from semantica.context.agent_context import AgentContext
|
|
from semantica.context.agent_memory import AgentMemory
|
|
from semantica.context.context_graph import ContextGraph
|
|
from semantica.context.context_retriever import ContextRetriever, RetrievedContext
|
|
from semantica.context.entity_linker import EntityLinker
|
|
|
|
# Infra
|
|
|
|
|
|
class NullTracker:
|
|
"""
|
|
Stateless dummy tracker.
|
|
"""
|
|
|
|
def start_tracking(self, *args, **kwargs):
|
|
return "dummy_id"
|
|
|
|
def update_tracking(self, *args, **kwargs):
|
|
pass
|
|
|
|
def stop_tracking(self, *args, **kwargs):
|
|
pass
|
|
|
|
def register_pipeline_modules(self, *args, **kwargs):
|
|
pass
|
|
|
|
def clear_pipeline_context(self, *args, **kwargs):
|
|
pass
|
|
|
|
def update_progress(self, *args, **kwargs):
|
|
pass
|
|
|
|
@property
|
|
def enabled(self):
|
|
return False
|
|
|
|
@enabled.setter
|
|
def enabled(self, value):
|
|
pass
|
|
|
|
|
|
# ~~ MOCK STORES ~~
|
|
|
|
|
|
class MockVectorStore:
|
|
"""
|
|
A feather VectorStore sim that does no math.
|
|
We want to measure the MANAGER overhead.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.vectors = {}
|
|
self.dim = 384
|
|
|
|
def embed(self, text):
|
|
return np.random.rand(self.dim).tolist()
|
|
|
|
def add(self, items):
|
|
for item in items:
|
|
self.vectors[item.memory_id] = item
|
|
|
|
def search(self, query, limit=5):
|
|
class MockResult:
|
|
def __init__(self, i):
|
|
self.id = f"mem_{i}"
|
|
self.content = f"Content for result {i} matching {query[:10]}"
|
|
self.score = 0.9 - (i * 0.05)
|
|
self.metadata = {"type": "test"}
|
|
|
|
return [MockResult(i) for i in range(limit)]
|
|
|
|
|
|
def create_dense_graph(node_count):
|
|
"""
|
|
Creates a ContextGraph with 'Small World' Topology.
|
|
Used to stress-test BFS traversal scaling.
|
|
"""
|
|
graph = ContextGraph()
|
|
|
|
graph.progress_tracker = NullTracker()
|
|
|
|
# Create nodes
|
|
nodes = [
|
|
{
|
|
"id": f"node_{i}",
|
|
"type": "concept",
|
|
"properties": {"content": f"Concept {i}"},
|
|
}
|
|
for i in range(node_count)
|
|
]
|
|
graph.add_nodes(nodes)
|
|
|
|
# Create Edges (Chain + Hub + Random)
|
|
edges = []
|
|
for i in range(node_count):
|
|
# Chain
|
|
if i < node_count - 1:
|
|
edges.append(
|
|
{"source_id": f"node_{i}", "target_id": f"node_{i+1}", "type": "next"}
|
|
)
|
|
# Hub
|
|
if i > 0:
|
|
edges.append(
|
|
{"source_id": "node_0", "target_id": f"node_{i}", "type": "hub_link"}
|
|
)
|
|
# Rando
|
|
if i % 5 == 0 and i + 5 < node_count:
|
|
edges.append(
|
|
{
|
|
"source_id": f"node_{i}",
|
|
"target_id": f"node_{i+5}",
|
|
"type": "cross_link",
|
|
}
|
|
)
|
|
|
|
graph.add_edges(edges)
|
|
return graph
|
|
|
|
|
|
def create_populated_memory(item_count):
|
|
"""Creates an AgentMemory populated with N items."""
|
|
vs = MockVectorStore()
|
|
memory = AgentMemory(vector_store=vs)
|
|
memory.progress_tracker = NullTracker()
|
|
|
|
for i in range(item_count):
|
|
mem_id = f"setup_mem_{i}"
|
|
from datetime import datetime
|
|
|
|
from semantica.context.agent_memory import MemoryItem
|
|
|
|
memory.memory_items[mem_id] = MemoryItem(
|
|
content=f"History item {i}",
|
|
timestamp=datetime.now(),
|
|
memory_id=mem_id,
|
|
metadata={"type": "chat"},
|
|
)
|
|
memory.memory_index.append(mem_id)
|
|
|
|
return memory
|
|
|
|
|
|
# ~~ BENCHMARKS ~~
|
|
|
|
|
|
@pytest.mark.parametrize("graph_size", [100, 1000])
|
|
@pytest.mark.parametrize("hops", [1, 2])
|
|
def test_graph_traversal_scaling(benchmark, graph_size, hops):
|
|
"""
|
|
Measures 'Hop Explosion' effect.
|
|
Retrieving multi-hop neighbors on a dense graph.
|
|
"""
|
|
graph = create_dense_graph(graph_size)
|
|
|
|
def op():
|
|
# Start from'Hub' node which's celebrity, meaning
|
|
# connected to everyone
|
|
return graph.get_neighbors("node_0", hops=hops)
|
|
|
|
benchmark.pedantic(op, iterations=5, rounds=5)
|
|
|
|
|
|
@pytest.mark.parametrize("memory_count", [100, 1000])
|
|
def test_retriever_ranking_throughput(benchmark, memory_count):
|
|
"""
|
|
Measures CPU cost of merging and ranking results.
|
|
"""
|
|
retriever = ContextRetriever(
|
|
vector_store=MockVectorStore(),
|
|
memory_store=create_populated_memory(10),
|
|
knowledge_graph=None,
|
|
hybrid_alpha=0.5,
|
|
)
|
|
retriever.progress_tracker = NullTracker()
|
|
|
|
results = []
|
|
for i in range(memory_count):
|
|
results.append(
|
|
RetrievedContext(
|
|
content=f"Vector Item {i}",
|
|
score=np.random.random(),
|
|
source=f"vector:{i}",
|
|
)
|
|
)
|
|
results.append(
|
|
RetrievedContext(
|
|
content=f"Graph Item {i}",
|
|
score=np.random.random(),
|
|
source=f"graph:{i}",
|
|
metadata={"node_id": f"node_{i}"},
|
|
)
|
|
)
|
|
|
|
def op():
|
|
return retriever._rank_and_merge(results, "query context")
|
|
|
|
benchmark.pedantic(op, iterations=5, rounds=10)
|
|
|
|
|
|
@pytest.mark.parametrize("registry_size", [100, 1000])
|
|
def test_entity_linking_speed(benchmark, registry_size):
|
|
"""
|
|
Measures O(N) linear scan speed in `find_similar_entities`.
|
|
"""
|
|
linker = EntityLinker()
|
|
linker.progress_tracker = NullTracker()
|
|
|
|
mock_kg = {"entities": []}
|
|
for i in range(registry_size):
|
|
mock_kg["entities"].append(
|
|
{"id": f"ent_{i}", "text": f"Entity Number {i}", "type": "TEST"}
|
|
)
|
|
linker.knowledge_graph = mock_kg
|
|
|
|
input_text = "I am looking for Entity Number 50 in the database."
|
|
|
|
def op():
|
|
return linker.find_similar_entities(input_text, threshold=0.1)
|
|
|
|
benchmark.pedantic(op, iterations=5, rounds=5)
|
|
|
|
|
|
@pytest.mark.parametrize("batch_size", [1, 10, 50])
|
|
def test_agent_store_throughput(benchmark, batch_size):
|
|
"""
|
|
'store' pipeline test.
|
|
"""
|
|
vs = MockVectorStore()
|
|
context = AgentContext(vector_store=vs)
|
|
context._memory.progress_tracker = NullTracker()
|
|
|
|
inputs = [f"Memory item {i} for storage test" for i in range(batch_size)]
|
|
|
|
def op():
|
|
return context.batch_store(inputs)
|
|
|
|
benchmark.pedantic(op, iterations=5, rounds=5)
|