Merge pull request #434 from Hawksight-AI/utils

Utilsfix: add_decision kwargs support and quickstart VectorStore backend
This commit is contained in:
Mohd Kaif
2026-04-02 20:45:51 +05:30
committed by GitHub
9 changed files with 125 additions and 11 deletions
+1 -1
View File
@@ -354,7 +354,7 @@ from semantica.context import AgentContext, AgentMemory
from semantica.vector_store import VectorStore
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
vector_store=VectorStore(backend="inmemory"),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
graph_expansion=True,
+1 -1
View File
@@ -44,7 +44,7 @@ from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
vector_store=VectorStore(backend="inmemory"),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
)
+1 -1
View File
@@ -65,7 +65,7 @@ from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
vector_store=VectorStore(backend="inmemory"),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
)
+1 -1
View File
@@ -275,7 +275,7 @@ print(f"Python importance score: {importance.get('degree', 0)}")
|--------|-------------|------------|
| `add_node(node_id, node_type, properties)` | Add concepts to remember | Build knowledge base |
| `add_edge(source, target, relation)` | Connect related concepts | Show relationships |
| `add_decision(category, scenario, reasoning, outcome, confidence, ...)` | Record decisions | Track choices and learn |
| `add_decision(decision)` or `add_decision(category, scenario, reasoning, outcome, ...)` | Record decisions | Track choices and learn |
| `add_decision_simple(category, scenario, reasoning, outcome, confidence, ...)` | Easy decision recording | Quick decision tracking |
| `find_precedents(decision_id, limit)` | Find precedents by ID | Get connected decisions |
| `find_precedents_by_scenario(scenario, category, ...)` | Find similar decisions | Make consistent choices |
+68 -7
View File
@@ -1478,25 +1478,85 @@ class ContextGraph:
}
# Decision Support Methods
def add_decision(self, decision: "Decision") -> None:
def add_decision(
self,
decision: "Decision" = None,
*,
category: str = None,
scenario: str = None,
reasoning: str = None,
outcome: str = None,
confidence: float = 0.5,
entities: Optional[List[str]] = None,
decision_maker: Optional[str] = "system",
valid_from=None,
valid_until=None,
**kwargs,
) -> str:
"""
Add decision node to graph.
Accepts either a Decision object or keyword arguments:
# From a Decision object
graph.add_decision(Decision(category="x", scenario="y", ...))
# From keyword arguments (convenience form)
graph.add_decision(category="x", scenario="y", reasoning="z",
outcome="o", confidence=0.9)
Args:
decision: Decision object to add
decision: Decision object to add (mutually exclusive with kwargs)
category: Decision category
scenario: Decision scenario description
reasoning: Reasoning behind the decision
outcome: Decision outcome
confidence: Confidence score (0.01.0)
entities: Related entity labels
decision_maker: Who made the decision
valid_from: Start of validity window (ISO string or datetime)
valid_until: End of validity window (ISO string or datetime)
**kwargs: Extra metadata stored on the decision node
Returns:
Decision ID
"""
from .decision_models import Decision
if decision is not None and (
any(v is not None for v in (
category, scenario, reasoning, outcome, entities, valid_from, valid_until,
)) or kwargs
):
raise ValueError(
"Pass either a Decision object or keyword arguments, not both."
)
if decision is None:
# Build from kwargs — delegate to record_decision which handles ID gen
return self.record_decision(
category=category,
scenario=scenario,
reasoning=reasoning,
outcome=outcome,
confidence=confidence,
entities=entities,
decision_maker=decision_maker,
valid_from=valid_from,
valid_until=valid_until,
metadata=kwargs,
)
# Handle empty decision ID by generating UUID for both None and empty string
# This ensures consistent behavior with Decision model's __post_init__ method
node_id = decision.decision_id if decision.decision_id else str(uuid.uuid4())
# Handle None metadata
metadata = decision.metadata or {}
# Normalize timestamp to ensure consistent storage format
normalized_timestamp = self._normalize_timestamp(decision.timestamp)
node = ContextNode(
node_id=node_id,
node_type="Decision",
@@ -1516,6 +1576,7 @@ class ContextGraph:
valid_until=decision.valid_until,
)
self._add_internal_node(node)
return node_id
def add_causal_relationship(
self,
+18
View File
@@ -39,6 +39,24 @@ def test_agent_context_minimal_decisions_and_chain():
assert len(chain) >= 1
def test_agent_context_inmemory_store_and_retrieve():
"""VectorStore(backend="inmemory") stores memories without faiss-cpu."""
vs = VectorStore(backend="inmemory")
ctx = AgentContext(
vector_store=vs,
knowledge_graph=ContextGraph(),
decision_tracking=True,
kg_algorithms=False,
vector_store_features=False,
)
memory_id = ctx.store(
"GPT-4 outperforms GPT-3.5 on reasoning benchmarks by 40%",
conversation_id="test_session",
)
assert isinstance(memory_id, str)
assert len(memory_id) > 0
def test_agent_context_policy_engine_with_graph_backend():
vs = VectorStore(backend="inmemory", dimension=64)
graph = ContextGraph()
@@ -49,6 +49,38 @@ class TestContextGraphDecisions:
assert node.properties["confidence"] == sample_decision.confidence
assert node.properties["decision_maker"] == sample_decision.decision_maker
def test_add_decision_kwargs_form(self, context_graph):
"""add_decision() accepts kwargs directly (no Decision object required)."""
decision_id = context_graph.add_decision(
category="loan_approval",
scenario="Mortgage application — 780 credit score",
reasoning="Strong credit history, low DTI",
outcome="approved",
confidence=0.95,
)
assert isinstance(decision_id, str)
assert len(decision_id) > 0
node = context_graph.nodes[decision_id]
assert node.node_type in ("Decision", "decision")
assert node.properties["category"] == "loan_approval"
assert node.properties["outcome"] == "approved"
assert node.properties["confidence"] == 0.95
def test_add_decision_kwargs_and_object_both_return_id(self, context_graph, sample_decision):
"""Both call forms return a non-empty decision ID string."""
id_from_object = context_graph.add_decision(sample_decision)
id_from_kwargs = context_graph.add_decision(
category="test",
scenario="test scenario",
reasoning="test reasoning",
outcome="approved",
confidence=0.8,
)
assert isinstance(id_from_object, str) and len(id_from_object) > 0
assert isinstance(id_from_kwargs, str) and len(id_from_kwargs) > 0
def test_add_decision_with_embeddings(self, context_graph):
"""Test adding decision with embeddings."""
decision = Decision(
@@ -1062,6 +1062,8 @@ class TestFindPrecedentsAsOf:
# Bob's decision should be reachable; Alice's should not appear
# (implementation may not filter on valid_from, just check it doesn't crash)
assert isinstance(precedents, list)
assert "approve loan for Bob" in scenarios
assert "approve loan for Alice" not in scenarios
def test_find_precedents_no_as_of_returns_list(self):
self.graph.record_decision(
@@ -873,6 +873,7 @@ class TestOllamaProviderBaseURLGap:
ollama_mock.Client = MagicMock(return_value=MagicMock())
with patch.dict("sys.modules", {"ollama": ollama_mock}):
from semantica.semantic_extract.providers import OllamaProvider
OllamaProvider(
provider = OllamaProvider(
model_name="llama3",
base_url="http://192.168.1.10:11434",