From 4cd3ef9aa8be442024efb3d0210ee3578a33ca7d Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sat, 14 Feb 2026 17:13:38 +0530 Subject: [PATCH 01/13] context_fixes --- README.md | 31 ++ semantica/context/agent_context.py | 249 ++++++--- semantica/context/causal_analyzer.py | 9 +- semantica/context/context_graph.py | 105 +++- semantica/context/context_retriever.py | 27 +- semantica/context/policy_engine.py | 531 +++++++++++++------ semantica/kg/centrality_calculator.py | 5 + semantica/kg/community_detector.py | 6 + semantica/kg/link_predictor.py | 5 +- semantica/kg/node_embeddings.py | 26 +- tests/context/test_agent_context_smoke.py | 66 +++ tests/context/test_policy_engine_fallback.py | 62 +++ 12 files changed, 845 insertions(+), 277 deletions(-) create mode 100644 tests/context/test_agent_context_smoke.py create mode 100644 tests/context/test_policy_engine_fallback.py diff --git a/README.md b/README.md index dee65814..66792a8f 100644 --- a/README.md +++ b/README.md @@ -734,6 +734,37 @@ reasoned_result = context.query_with_reasoning( - **ContextRetriever**: Performs hybrid retrieval combining vector search, graph traversal, and memory for optimal context relevance - **AgentContext**: High-level interface integrating Context Graph and Context Retriever for GraphRAG applications +#### Context Graphs: Decision Tracking + +```python +from semantica.context import AgentContext, ContextGraph +from semantica.vector_store import VectorStore + +context = AgentContext( + vector_store=VectorStore(backend="inmemory", dimension=128), + knowledge_graph=ContextGraph(), + enable_decision_tracking=True, + enable_kg_algorithms=False, # semantic-only precedent search +) + +decision_id = context.record_decision( + category="credit_approval", + scenario="High-risk credit limit increase", + reasoning="Recent velocity-check failure and prior fraud flag", + outcome="rejected", + confidence=0.78, + entities=["customer:jessica_norris"], +) + +precedents = context.find_precedents( + scenario="High-risk customer credit increase", + category="credit_approval", + limit=5, +) +``` + +Runnable script: `examples/context_graphs_decision_tracking.py` + **Core Notebooks:** - [**Context Module Introduction**](https://github.com/Hawksight-AI/semantica/tree/main/cookbook/introduction/19_Context_Module.ipynb) - Basic memory and storage. - [**Advanced Context Engineering**](https://github.com/Hawksight-AI/semantica/tree/main/cookbook/advanced/11_Advanced_Context_Engineering.ipynb) - Hybrid retrieval, graph builders, and custom memory policies. diff --git a/semantica/context/agent_context.py b/semantica/context/agent_context.py index 6a2ecaaa..0fdde432 100644 --- a/semantica/context/agent_context.py +++ b/semantica/context/agent_context.py @@ -209,62 +209,52 @@ class AgentContext: if knowledge_graph and hasattr(knowledge_graph, "build_from_conversations"): self._graph_builder = knowledge_graph - # Store config - self.config = { + self.config.update({ "retention_days": retention_days, "max_memories": max_memories, - "use_graph_expansion": use_graph_expansion, - "max_expansion_hops": max_expansion_hops, - "hybrid_alpha": hybrid_alpha, - "enable_decision_tracking": enable_decision_tracking, - } + }) # Initialize decision tracking components if enabled + self._decision_backend = None self._decision_recorder = None self._decision_query = None self._causal_analyzer = None self._policy_engine = None if enable_decision_tracking and knowledge_graph: - # Validate that knowledge_graph supports required GraphStore interface - if not hasattr(knowledge_graph, 'execute_query'): - self.logger.error( - "Decision tracking requires a GraphStore-compatible knowledge graph with execute_query() method. " - "Provided knowledge_graph type does not support Cypher queries. " - "Use GraphStore (Neo4j, FalkorDB) or disable decision tracking." - ) - raise ValueError( - "Decision tracking requires a GraphStore-compatible knowledge graph. " - "The provided knowledge_graph does not have an execute_query() method. " - "For decision tracking, use a GraphStore backend (Neo4j, FalkorDB) " - "or set enable_decision_tracking=False." - ) - - # Initialize enhanced decision tracking components - try: - self._decision_recorder = DecisionRecorder(knowledge_graph) - - # Enhanced DecisionQuery with KG and vector store integration - self._decision_query = DecisionQuery( - graph_store=knowledge_graph, - vector_store=vector_store if enable_vector_store_features else None, - enable_advanced_analytics=enable_advanced_analytics, - enable_centrality_analysis=enable_kg_algorithms, - enable_community_detection=enable_kg_algorithms, - enable_node_embeddings=enable_kg_algorithms - ) - - self._causal_analyzer = CausalChainAnalyzer(knowledge_graph) + if hasattr(knowledge_graph, "execute_query"): + self._decision_backend = "graph_store" + try: + self._decision_recorder = DecisionRecorder(knowledge_graph) + self._decision_query = DecisionQuery( + graph_store=knowledge_graph, + vector_store=vector_store if enable_vector_store_features else None, + enable_advanced_analytics=enable_advanced_analytics, + enable_centrality_analysis=enable_kg_algorithms, + enable_community_detection=enable_kg_algorithms, + enable_node_embeddings=enable_kg_algorithms + ) + self._causal_analyzer = CausalChainAnalyzer(knowledge_graph) + self._policy_engine = PolicyEngine(knowledge_graph) + self.logger.info("Enhanced decision tracking components initialized successfully") + except Exception as e: + self.logger.warning(f"Failed to initialize enhanced decision tracking: {e}") + self._decision_recorder = DecisionRecorder(knowledge_graph) + self._decision_query = DecisionQuery(knowledge_graph) + self._causal_analyzer = CausalChainAnalyzer(knowledge_graph) + self._policy_engine = PolicyEngine(knowledge_graph) + else: + self._decision_backend = "context_graph" self._policy_engine = PolicyEngine(knowledge_graph) - - self.logger.info("Enhanced decision tracking components initialized successfully") - except Exception as e: - self.logger.warning(f"Failed to initialize enhanced decision tracking: {e}") - # Fallback to basic components - self._decision_recorder = DecisionRecorder(knowledge_graph) - self._decision_query = DecisionQuery(knowledge_graph) self._causal_analyzer = CausalChainAnalyzer(knowledge_graph) - self._policy_engine = PolicyEngine(knowledge_graph) + if enable_vector_store_features and hasattr(self.vector_store, "initialize_decision_pipeline"): + try: + self.vector_store.initialize_decision_pipeline( + graph_store=knowledge_graph if enable_kg_algorithms else None, + use_graph_features=enable_kg_algorithms + ) + except Exception as e: + self.logger.warning(f"Failed to initialize decision pipeline: {e}") @property def memory(self) -> AgentMemory: @@ -1560,7 +1550,7 @@ class AgentContext: Raises: RuntimeError: If decision tracking is not enabled """ - if not self._decision_recorder: + if not self._decision_backend: raise RuntimeError("Decision tracking is not enabled") from .decision_models import Decision @@ -1579,18 +1569,54 @@ class AgentContext: entities = entities or [] source_documents = [] # Could be enhanced to capture source docs - - decision_id = self._decision_recorder.record_decision( - decision, entities, source_documents - ) - - # Capture cross-system context if provided - if cross_system_context: - self._decision_recorder.capture_cross_system_context( - decision_id, cross_system_context + + if self._decision_backend == "graph_store": + decision_id = self._decision_recorder.record_decision( + decision, entities, source_documents ) - - return decision_id + + if cross_system_context: + self._decision_recorder.capture_cross_system_context( + decision_id, cross_system_context + ) + + return decision_id + + if not hasattr(self.knowledge_graph, "add_decision"): + raise RuntimeError("Decision tracking backend does not support decisions") + + self.knowledge_graph.add_decision(decision) + if cross_system_context and hasattr(self.knowledge_graph, "add_node_attribute"): + self.knowledge_graph.add_node_attribute( + decision.decision_id, {"cross_system_context": cross_system_context} + ) + for entity_id in entities: + try: + self.knowledge_graph.add_edge(decision.decision_id, entity_id, edge_type="ABOUT") + except Exception: + continue + + vector_id = None + if hasattr(self.vector_store, "store_decision"): + try: + vector_id = self.vector_store.store_decision( + scenario=scenario, + reasoning=reasoning, + outcome=outcome, + confidence=confidence, + entities=entities, + category=category, + decision_id=decision.decision_id, + decision_maker=decision.decision_maker, + timestamp=decision.timestamp.isoformat() + ) + except Exception: + vector_id = None + + if vector_id and hasattr(self.knowledge_graph, "add_node_attribute"): + self.knowledge_graph.add_node_attribute(decision.decision_id, {"vector_id": vector_id}) + + return decision.decision_id def find_precedents( self, @@ -1618,24 +1644,85 @@ class AgentContext: Raises: RuntimeError: If decision tracking is not enabled """ - if not self._decision_query: + if not self._decision_backend: raise RuntimeError("Decision tracking is not enabled") - - if use_hybrid_search: - try: - return self._decision_query.find_precedents_hybrid( - scenario, category, limit - ) - except Exception: - # Fallback to basic search if hybrid fails - return self._decision_query._find_precedents_basic(scenario, category, limit) - else: - # Simple category-based search + + if self._decision_backend == "graph_store": + if use_hybrid_search: + try: + return self._decision_query.find_precedents_hybrid( + scenario, category, limit + ) + except Exception: + return self._decision_query._find_precedents_basic(scenario, category, limit) if category: return self._decision_query.find_by_category(category, limit) - else: - # Use basic search - return self._decision_query._find_precedents_basic(scenario, category, limit) + return self._decision_query._find_precedents_basic(scenario, category, limit) + + results: List[Decision] = [] + + if use_hybrid_search and hasattr(self.vector_store, "search_decisions"): + filters = {"category": category} if category else None + vector_results = self.vector_store.search_decisions( + query=scenario, + filters=filters, + limit=limit, + use_hybrid_search=True + ) + for r in vector_results: + meta = r.get("metadata") or {} + decision_id = meta.get("decision_id") or meta.get("id") + if decision_id and hasattr(self.knowledge_graph, "nodes") and decision_id in self.knowledge_graph.nodes: + node = self.knowledge_graph.nodes[decision_id] + if getattr(node, "node_type", None) == "Decision": + data = getattr(node, "properties", {}) or {} + decision = Decision( + decision_id=decision_id, + category=data.get("category", ""), + scenario=getattr(node, "content", ""), + reasoning=data.get("reasoning", ""), + outcome=data.get("outcome", ""), + confidence=float(data.get("confidence", 0.0) or 0.0), + timestamp=datetime.fromisoformat(data.get("timestamp")) if data.get("timestamp") else datetime.now(), + decision_maker=data.get("decision_maker", "ai_agent"), + reasoning_embedding=data.get("reasoning_embedding"), + node2vec_embedding=data.get("node2vec_embedding"), + metadata={k: v for k, v in data.items() if k not in [ + "category", "reasoning", "outcome", "confidence", + "timestamp", "decision_maker", "reasoning_embedding", "node2vec_embedding" + ]} + ) + decision.metadata["score"] = r.get("score") + results.append(decision) + + if results: + return results[:limit] + + if hasattr(self.knowledge_graph, "find_nodes"): + for node in self.knowledge_graph.find_nodes(node_type="Decision"): + if category and node.get("metadata", {}).get("category") != category: + continue + data = node.get("metadata", {}) or {} + results.append( + Decision( + decision_id=node.get("id", ""), + category=data.get("category", ""), + scenario=node.get("content", ""), + reasoning=data.get("reasoning", ""), + outcome=data.get("outcome", ""), + confidence=float(data.get("confidence", 0.0) or 0.0), + timestamp=datetime.fromisoformat(data.get("timestamp")) if data.get("timestamp") else datetime.now(), + decision_maker=data.get("decision_maker", "ai_agent"), + reasoning_embedding=data.get("reasoning_embedding"), + node2vec_embedding=data.get("node2vec_embedding"), + metadata={k: v for k, v in data.items() if k not in [ + "category", "reasoning", "outcome", "confidence", + "timestamp", "decision_maker", "reasoning_embedding", "node2vec_embedding" + ]} + ) + ) + + return results[:limit] def get_causal_chain( self, @@ -1657,12 +1744,22 @@ class AgentContext: Raises: RuntimeError: If decision tracking is not enabled """ - if not self._causal_analyzer: + if not self._decision_backend: raise RuntimeError("Decision tracking is not enabled") - - return self._causal_analyzer.get_causal_chain( - decision_id, direction, max_depth - ) + + if self._decision_backend == "graph_store": + return self._causal_analyzer.get_causal_chain( + decision_id, direction, max_depth + ) + + if hasattr(self.knowledge_graph, "get_causal_chain"): + return self.knowledge_graph.get_causal_chain( + decision_id=decision_id, + direction=direction, + max_depth=max_depth + ) + + raise RuntimeError("Decision tracking backend does not support causal chains") def get_policy_engine(self) -> PolicyEngine: """ diff --git a/semantica/context/causal_analyzer.py b/semantica/context/causal_analyzer.py index ab53699c..9ad64215 100644 --- a/semantica/context/causal_analyzer.py +++ b/semantica/context/causal_analyzer.py @@ -77,7 +77,7 @@ class CausalChainAnalyzer: using graph traversal. """ - def __init__(self, graph_store: GraphStore): + def __init__(self, graph_store: Any): """ Initialize CausalChainAnalyzer. @@ -105,6 +105,13 @@ class CausalChainAnalyzer: List of decisions in causal chain """ try: + if hasattr(self.graph_store, "get_causal_chain") and not hasattr(self.graph_store, "execute_query"): + return self.graph_store.get_causal_chain( + decision_id=decision_id, + direction=direction, + max_depth=max_depth + ) + if direction not in ["upstream", "downstream"]: raise ValueError("Direction must be 'upstream' or 'downstream'") diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index 4084391c..6e201a80 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -260,7 +260,69 @@ class ContextGraph: count += 1 return count - def get_neighbors(self, node_id: str, hops: int = 1) -> List[Dict[str, Any]]: + def __contains__(self, node_id: object) -> bool: + if not isinstance(node_id, str): + return False + return node_id in self.nodes + + def has_node(self, node_id: str) -> bool: + return node_id in self.nodes + + def neighbors(self, node_id: str) -> List[str]: + return self.get_neighbor_ids(node_id) + + def get_neighbor_ids( + self, + node_id: str, + relationship_types: Optional[List[str]] = None, + ) -> List[str]: + if node_id not in self.nodes: + return [] + + rel_filter = set(relationship_types) if relationship_types else None + neighbor_ids: List[str] = [] + for edge in self._adjacency.get(node_id, []): + if rel_filter is None or edge.edge_type in rel_filter: + neighbor_ids.append(edge.target_id) + return neighbor_ids + + def get_nodes_by_label(self, label: str) -> List[str]: + return list(self.node_type_index.get(label, set())) + + def get_node_property(self, node_id: str, property_name: str) -> Any: + node = self.nodes.get(node_id) + if not node: + return None + return node.properties.get(property_name) + + def get_node_attributes(self, node_id: str) -> Dict[str, Any]: + node = self.nodes.get(node_id) + if not node: + return {} + return node.properties.copy() + + def add_node_attribute(self, node_id: str, attributes: Dict[str, Any]) -> None: + node = self.nodes.get(node_id) + if not node: + return + node.properties.update(attributes) + node.metadata.update(attributes) + + def get_edge_data(self, source_id: str, target_id: str) -> Dict[str, Any]: + for edge in self._adjacency.get(source_id, []): + if edge.target_id == target_id: + data = edge.metadata.copy() + data["type"] = edge.edge_type + data["weight"] = edge.weight + return data + return {} + + def get_neighbors( + self, + node_id: str, + hops: int = 1, + relationship_types: Optional[List[str]] = None, + ) -> List[Dict[str, Any]]: """ Get neighbors of a node. @@ -269,36 +331,39 @@ class ContextGraph: if node_id not in self.nodes: return [] - neighbors = [] + neighbors: List[Dict[str, Any]] = [] visited = {node_id} - queue = deque([(node_id, 0)]) # (current_id, current_hop) + queue = deque([(node_id, 0)]) + rel_filter = set(relationship_types) if relationship_types else None while queue: current_id, current_hop = queue.popleft() - if current_hop >= hops: continue - # Get outgoing edges outgoing_edges = self._adjacency.get(current_id, []) for edge in outgoing_edges: + if rel_filter is not None and edge.edge_type not in rel_filter: + continue neighbor_id = edge.target_id - if neighbor_id not in visited: - visited.add(neighbor_id) - queue.append((neighbor_id, current_hop + 1)) + if neighbor_id in visited: + continue + visited.add(neighbor_id) + queue.append((neighbor_id, current_hop + 1)) - if neighbor_id in self.nodes: - node = self.nodes[neighbor_id] - neighbors.append( - { - "id": node.node_id, - "type": node.node_type, - "content": node.content, - "relationship": edge.edge_type, - "weight": edge.weight, - "hop": current_hop + 1, - } - ) + node = self.nodes.get(neighbor_id) + if not node: + continue + neighbors.append( + { + "id": node.node_id, + "type": node.node_type, + "content": node.content, + "relationship": edge.edge_type, + "weight": edge.weight, + "hop": current_hop + 1, + } + ) return neighbors diff --git a/semantica/context/context_retriever.py b/semantica/context/context_retriever.py index c1131f7d..b72833c9 100644 --- a/semantica/context/context_retriever.py +++ b/semantica/context/context_retriever.py @@ -2011,8 +2011,18 @@ Answer:""" try: # Basic neighbor expansion if hasattr(self.knowledge_graph, 'get_neighbors'): - neighbors = self.knowledge_graph.get_neighbors(entity_name) - for neighbor in neighbors[:5]: # Limit to prevent explosion + if hasattr(self.knowledge_graph, "neighbors"): + neighbor_ids = list(self.knowledge_graph.neighbors(entity_name)) + elif hasattr(self.knowledge_graph, "get_neighbor_ids"): + neighbor_ids = self.knowledge_graph.get_neighbor_ids(entity_name) + else: + neighbor_details = self.knowledge_graph.get_neighbors(entity_name, hops=1) + neighbor_ids = [ + n.get("id") for n in neighbor_details + if isinstance(n, dict) and n.get("id") + ] + + for neighbor in neighbor_ids[:5]: # Limit to prevent explosion expanded_entities.append({ "name": neighbor, "type": "related_entity", @@ -2082,8 +2092,17 @@ Answer:""" if hasattr(self.centrality_calculator, 'calculate_degree_centrality'): # Simplified centrality calculation if hasattr(self.knowledge_graph, 'get_neighbors'): - neighbors = self.knowledge_graph.get_neighbors(entity_name) - centrality_scores[entity_name] = len(neighbors) + if hasattr(self.knowledge_graph, "neighbors"): + neighbor_ids = list(self.knowledge_graph.neighbors(entity_name)) + elif hasattr(self.knowledge_graph, "get_neighbor_ids"): + neighbor_ids = self.knowledge_graph.get_neighbor_ids(entity_name) + else: + neighbor_details = self.knowledge_graph.get_neighbors(entity_name, hops=1) + neighbor_ids = [ + n.get("id") for n in neighbor_details + if isinstance(n, dict) and n.get("id") + ] + centrality_scores[entity_name] = len(neighbor_ids) else: centrality_scores[entity_name] = 1 diff --git a/semantica/context/policy_engine.py b/semantica/context/policy_engine.py index cb7eb669..9cefaad6 100644 --- a/semantica/context/policy_engine.py +++ b/semantica/context/policy_engine.py @@ -84,7 +84,7 @@ class PolicyEngine: records exceptions, and analyzes policy impact. """ - def __init__(self, graph_store: GraphStore): + def __init__(self, graph_store: Any): """ Initialize PolicyEngine. @@ -93,6 +93,7 @@ class PolicyEngine: """ self.graph_store = graph_store self.logger = get_logger(__name__) + self._supports_cypher = hasattr(graph_store, "execute_query") def add_policy(self, policy: Policy) -> str: """ @@ -105,35 +106,54 @@ class PolicyEngine: Policy ID """ try: - # Store policy node - query = """ - CREATE (p:Policy { - policy_id: $policy_id, - name: $name, - description: $description, - rules: $rules, - category: $category, - version: $version, - created_at: $created_at, - updated_at: $updated_at, - metadata: $metadata - }) - """ - self.graph_store.execute_query(query, { - "policy_id": policy.policy_id, - "name": policy.name, - "description": policy.description, - "rules": policy.rules, - "category": policy.category, - "version": policy.version, - "created_at": policy.created_at, - "updated_at": policy.updated_at, - "metadata": policy.metadata - }) - + if self._supports_cypher: + query = """ + CREATE (p:Policy { + policy_id: $policy_id, + name: $name, + description: $description, + rules: $rules, + category: $category, + version: $version, + created_at: $created_at, + updated_at: $updated_at, + metadata: $metadata + }) + """ + self.graph_store.execute_query(query, { + "policy_id": policy.policy_id, + "name": policy.name, + "description": policy.description, + "rules": policy.rules, + "category": policy.category, + "version": policy.version, + "created_at": policy.created_at, + "updated_at": policy.updated_at, + "metadata": policy.metadata + }) + self.logger.info(f"Added policy: {policy.policy_id} version {policy.version}") + return policy.policy_id + + if not hasattr(self.graph_store, "add_node"): + raise RuntimeError("Graph backend does not support policy storage") + + node_id = f"{policy.policy_id}:{policy.version}" + self.graph_store.add_node( + node_id=node_id, + node_type="Policy", + content=policy.name or policy.policy_id, + policy_id=policy.policy_id, + name=policy.name, + description=policy.description, + rules=policy.rules, + category=policy.category, + version=policy.version, + created_at=policy.created_at.isoformat() if hasattr(policy.created_at, "isoformat") else str(policy.created_at), + updated_at=policy.updated_at.isoformat() if hasattr(policy.updated_at, "isoformat") else str(policy.updated_at), + metadata=policy.metadata or {} + ) self.logger.info(f"Added policy: {policy.policy_id} version {policy.version}") return policy.policy_id - except Exception as e: self.logger.error(f"Failed to add policy: {e}") raise @@ -187,17 +207,26 @@ class PolicyEngine: # Store new version self.add_policy(updated_policy) - # Link versions - query = """ - MATCH (old:Policy {policy_id: $policy_id, version: $old_version}) - MATCH (new:Policy {policy_id: $policy_id, version: $new_version}) - MERGE (old)-[:VERSION_OF]->(new) - """ - self.graph_store.execute_query(query, { - "policy_id": policy_id, - "old_version": current_policy.version, - "new_version": new_version - }) + if self._supports_cypher: + query = """ + MATCH (old:Policy {policy_id: $policy_id, version: $old_version}) + MATCH (new:Policy {policy_id: $policy_id, version: $new_version}) + MERGE (old)-[:VERSION_OF]->(new) + """ + self.graph_store.execute_query(query, { + "policy_id": policy_id, + "old_version": current_policy.version, + "new_version": new_version + }) + else: + if hasattr(self.graph_store, "add_edge"): + self.graph_store.add_edge( + f"{policy_id}:{current_policy.version}", + f"{policy_id}:{new_version}", + edge_type="VERSION_OF", + changed_at=datetime.now().isoformat(), + change_reason=change_reason + ) self.logger.info(f"Updated policy {policy_id} to version {new_version}") return new_version @@ -222,26 +251,60 @@ class PolicyEngine: List of applicable policies (latest versions) """ try: + if self._supports_cypher: # Get latest policies for category - query = """ - MATCH (p:Policy {category: $category}) - WHERE NOT (p)-[:VERSION_OF]->(:Policy) - RETURN p - ORDER BY p.updated_at DESC - """ - results = self.graph_store.execute_query(query, {"category": category}) - - policies = [] - for record in results: - policy_data = record.get("p", {}) - policies.append(self._dict_to_policy(policy_data)) - - # Filter by entities if specified - if entities: - # This would require entity-specific policy relationships - # For now, return all category policies - pass - + query = """ + MATCH (p:Policy {category: $category}) + WHERE NOT (p)-[:VERSION_OF]->(:Policy) + RETURN p + ORDER BY p.updated_at DESC + """ + results = self.graph_store.execute_query(query, {"category": category}) + + policies = [] + for record in results: + policy_data = record.get("p", {}) + policies.append(self._dict_to_policy(policy_data)) + + if entities: + pass + + self.logger.info(f"Found {len(policies)} applicable policies for category {category}") + return policies + + if not hasattr(self.graph_store, "find_nodes"): + return [] + + latest_by_policy_id: Dict[str, Dict[str, Any]] = {} + for node in self.graph_store.find_nodes(node_type="Policy"): + data = node.get("metadata", {}) or {} + if data.get("category") != category: + continue + pid = data.get("policy_id") + if not pid: + continue + updated_at = data.get("updated_at") or "" + prev = latest_by_policy_id.get(pid) + if not prev: + latest_by_policy_id[pid] = data + else: + if str(updated_at) > str(prev.get("updated_at") or ""): + latest_by_policy_id[pid] = data + + policies: List[Policy] = [] + for data in latest_by_policy_id.values(): + policies.append(self._dict_to_policy({ + "policy_id": data.get("policy_id"), + "name": data.get("name"), + "description": data.get("description"), + "rules": data.get("rules", {}), + "category": data.get("category"), + "version": data.get("version"), + "created_at": data.get("created_at"), + "updated_at": data.get("updated_at"), + "metadata": data.get("metadata", {}) + })) + self.logger.info(f"Found {len(policies)} applicable policies for category {category}") return policies @@ -303,20 +366,33 @@ class PolicyEngine: version: Policy version that was applied """ try: - query = """ - MATCH (d:Decision {decision_id: $decision_id}) - MATCH (p:Policy {policy_id: $policy_id, version: $version}) - MERGE (d)-[:APPLIED_POLICY]->(p) - SET d.policy_applied_at = timestamp() - """ - self.graph_store.execute_query(query, { - "decision_id": decision_id, - "policy_id": policy_id, - "version": version - }) - + if self._supports_cypher: + query = """ + MATCH (d:Decision {decision_id: $decision_id}) + MATCH (p:Policy {policy_id: $policy_id, version: $version}) + MERGE (d)-[:APPLIED_POLICY]->(p) + SET d.policy_applied_at = timestamp() + """ + self.graph_store.execute_query(query, { + "decision_id": decision_id, + "policy_id": policy_id, + "version": version + }) + self.logger.info(f"Recorded policy application: {policy_id} v{version} to decision {decision_id}") + return + + if not hasattr(self.graph_store, "add_edge"): + raise RuntimeError("Graph backend does not support relationships") + policy_node_id = f"{policy_id}:{version}" + self.graph_store.add_edge( + decision_id, + policy_node_id, + edge_type="APPLIED_POLICY", + applied_at=datetime.now().isoformat(), + policy_id=policy_id, + version=version + ) self.logger.info(f"Recorded policy application: {policy_id} v{version} to decision {decision_id}") - except Exception as e: self.logger.error(f"Failed to record policy application: {e}") raise @@ -340,40 +416,61 @@ class PolicyEngine: """ try: exception_id = str(uuid.uuid4()) - - query = """ - CREATE (e:Exception { - exception_id: $exception_id, - decision_id: $decision_id, - policy_id: $policy_id, - reason: $reason, - created_at: datetime() - }) - """ - self.graph_store.execute_query(query, { - "exception_id": exception_id, - "decision_id": decision_id, - "policy_id": policy_id, - "reason": reason - }) - - # Link to decision and policy - query = """ - MATCH (d:Decision {decision_id: $decision_id}) - MATCH (p:Policy {policy_id: $policy_id}) - MATCH (e:Exception {exception_id: $exception_id}) - MERGE (d)-[:GRANTED_EXCEPTION]->(e) - MERGE (e)-[:OVERRIDDEN_POLICY]->(p) - """ - self.graph_store.execute_query(query, { - "decision_id": decision_id, - "policy_id": policy_id, - "exception_id": exception_id - }) - + + if self._supports_cypher: + query = """ + CREATE (e:Exception { + exception_id: $exception_id, + decision_id: $decision_id, + policy_id: $policy_id, + reason: $reason, + created_at: datetime() + }) + """ + self.graph_store.execute_query(query, { + "exception_id": exception_id, + "decision_id": decision_id, + "policy_id": policy_id, + "reason": reason + }) + + query = """ + MATCH (d:Decision {decision_id: $decision_id}) + MATCH (p:Policy {policy_id: $policy_id}) + MATCH (e:Exception {exception_id: $exception_id}) + MERGE (d)-[:GRANTED_EXCEPTION]->(e) + MERGE (e)-[:OVERRIDDEN_POLICY]->(p) + """ + self.graph_store.execute_query(query, { + "decision_id": decision_id, + "policy_id": policy_id, + "exception_id": exception_id + }) + + self.logger.info(f"Recorded policy exception: {exception_id}") + return exception_id + + if not hasattr(self.graph_store, "add_node") or not hasattr(self.graph_store, "add_edge"): + raise RuntimeError("Graph backend does not support exceptions") + + self.graph_store.add_node( + node_id=exception_id, + node_type="Exception", + content=reason, + exception_id=exception_id, + decision_id=decision_id, + policy_id=policy_id, + reason=reason, + created_at=datetime.now().isoformat() + ) + self.graph_store.add_edge(decision_id, exception_id, edge_type="GRANTED_EXCEPTION") + + policy = self.get_policy(policy_id) + if policy: + self.graph_store.add_edge(exception_id, f"{policy_id}:{policy.version}", edge_type="OVERRIDDEN_POLICY") + self.logger.info(f"Recorded policy exception: {exception_id}") return exception_id - except Exception as e: self.logger.error(f"Failed to record exception: {e}") raise @@ -389,23 +486,45 @@ class PolicyEngine: List of policy versions """ try: - query = """ - MATCH (p:Policy {policy_id: $policy_id}) - OPTIONAL MATCH (p)-[:VERSION_OF*]->(future:Policy) - WITH collect(p) + collect(future) as all_versions - UNWIND all_versions as version - RETURN DISTINCT version - ORDER BY version.updated_at - """ - results = self.graph_store.execute_query(query, {"policy_id": policy_id}) + if self._supports_cypher: + query = """ + MATCH (p:Policy {policy_id: $policy_id}) + OPTIONAL MATCH (p)-[:VERSION_OF*]->(future:Policy) + WITH collect(p) + collect(future) as all_versions + UNWIND all_versions as version + RETURN DISTINCT version + ORDER BY version.updated_at + """ + results = self.graph_store.execute_query(query, {"policy_id": policy_id}) - policies = [] - for record in results: - policy_data = record.get("version", {}) - policies.append(self._dict_to_policy(policy_data)) + policies = [] + for record in results: + policy_data = record.get("version", {}) + policies.append(self._dict_to_policy(policy_data)) - self.logger.info(f"Found {len(policies)} versions for policy {policy_id}") - return policies + self.logger.info(f"Found {len(policies)} versions for policy {policy_id}") + return policies + + if not hasattr(self.graph_store, "find_nodes"): + return [] + versions: List[Policy] = [] + for node in self.graph_store.find_nodes(node_type="Policy"): + data = node.get("metadata", {}) or {} + if data.get("policy_id") != policy_id: + continue + versions.append(self._dict_to_policy({ + "policy_id": data.get("policy_id"), + "name": data.get("name"), + "description": data.get("description"), + "rules": data.get("rules", {}), + "category": data.get("category"), + "version": data.get("version"), + "created_at": data.get("created_at"), + "updated_at": data.get("updated_at"), + "metadata": data.get("metadata", {}) + })) + versions.sort(key=lambda p: str(p.updated_at)) + return versions except Exception as e: self.logger.error(f"Failed to get policy history: {e}") @@ -429,23 +548,33 @@ class PolicyEngine: List of affected decision IDs """ try: - query = """ - MATCH (d:Decision)-[:APPLIED_POLICY]->(p:Policy { - policy_id: $policy_id, - version: $from_version - }) - RETURN d.decision_id as decision_id - """ - results = self.graph_store.execute_query(query, { - "policy_id": policy_id, - "from_version": from_version - }) + if self._supports_cypher: + query = """ + MATCH (d:Decision)-[:APPLIED_POLICY]->(p:Policy { + policy_id: $policy_id, + version: $from_version + }) + RETURN d.decision_id as decision_id + """ + results = self.graph_store.execute_query(query, { + "policy_id": policy_id, + "from_version": from_version + }) - decision_ids = [] - for record in results: - decision_ids.append(record.get("decision_id", "")) + decision_ids = [] + for record in results: + decision_ids.append(record.get("decision_id", "")) - self.logger.info(f"Found {len(decision_ids)} decisions affected by policy change") + self.logger.info(f"Found {len(decision_ids)} decisions affected by policy change") + return decision_ids + + if not hasattr(self.graph_store, "find_edges"): + return [] + policy_node_id = f"{policy_id}:{from_version}" + decision_ids: List[str] = [] + for edge in self.graph_store.find_edges(edge_type="APPLIED_POLICY"): + if edge.get("target") == policy_node_id: + decision_ids.append(edge.get("source")) return decision_ids except Exception as e: @@ -471,16 +600,38 @@ class PolicyEngine: current_policy = self.get_policy(policy_id) if not current_policy: raise ValueError(f"Policy {policy_id} not found") - - # Get decisions that used this policy - query = """ - MATCH (d:Decision)-[:APPLIED_POLICY]->(p:Policy {policy_id: $policy_id}) - RETURN d.decision_id as decision_id, d.confidence as confidence, - d.outcome as outcome, d.category as category - """ - results = self.graph_store.execute_query(query, {"policy_id": policy_id}) - - # Analyze impact + + results: List[Dict[str, Any]] = [] + if self._supports_cypher: + query = """ + MATCH (d:Decision)-[:APPLIED_POLICY]->(p:Policy {policy_id: $policy_id}) + RETURN d.decision_id as decision_id, d.confidence as confidence, + d.outcome as outcome, d.category as category + """ + results = self.graph_store.execute_query(query, {"policy_id": policy_id}) + else: + if hasattr(self.graph_store, "find_edges") and hasattr(self.graph_store, "nodes"): + for edge in self.graph_store.find_edges(edge_type="APPLIED_POLICY"): + target = edge.get("target") + if not target or not isinstance(target, str): + continue + policy_node = self.graph_store.nodes.get(target) + if not policy_node: + continue + props = getattr(policy_node, "properties", {}) or {} + if props.get("policy_id") != policy_id: + continue + decision_node = self.graph_store.nodes.get(edge.get("source")) + if not decision_node: + continue + dprops = getattr(decision_node, "properties", {}) or {} + results.append({ + "decision_id": edge.get("source"), + "confidence": dprops.get("confidence", 0.0), + "outcome": dprops.get("outcome", ""), + "category": dprops.get("category", "") + }) + impact_analysis = { "total_decisions": len(results), "affected_decisions": 0, @@ -488,19 +639,16 @@ class PolicyEngine: "risk_assessment": "low", "recommendations": [] } - + for record in results: decision_data = { "confidence": record.get("confidence", 0.0), "outcome": record.get("outcome", ""), "category": record.get("category", "") } - - # Check if decision would still comply with new rules would_comply = self._check_compliance_with_rules( decision_data, proposed_rules ) - if not would_comply: impact_analysis["affected_decisions"] += 1 @@ -546,29 +694,76 @@ class PolicyEngine: Policy object or None """ try: + if self._supports_cypher: + if version: + query = """ + MATCH (p:Policy {policy_id: $policy_id, version: $version}) + RETURN p + """ + params = {"policy_id": policy_id, "version": version} + else: + query = """ + MATCH (p:Policy {policy_id: $policy_id}) + WHERE NOT (p)-[:VERSION_OF]->(:Policy) + RETURN p + """ + params = {"policy_id": policy_id} + + results = self.graph_store.execute_query(query, params) + + if results: + policy_data = results[0].get("p", {}) + return self._dict_to_policy(policy_data) + return None + + if not hasattr(self.graph_store, "find_nodes"): + return None + + candidates: List[Dict[str, Any]] = [] + for node in self.graph_store.find_nodes(node_type="Policy"): + data = node.get("metadata", {}) or {} + if data.get("policy_id") != policy_id: + continue + if version and data.get("version") != version: + continue + candidates.append(data) + + if not candidates: + return None + if version: - query = """ - MATCH (p:Policy {policy_id: $policy_id, version: $version}) - RETURN p - """ - params = {"policy_id": policy_id, "version": version} + data = candidates[0] else: - # Get latest version - query = """ - MATCH (p:Policy {policy_id: $policy_id}) - WHERE NOT (p)-[:VERSION_OF]->(:Policy) - RETURN p - """ - params = {"policy_id": policy_id} - - results = self.graph_store.execute_query(query, params) - - if results: - policy_data = results[0].get("p", {}) - return self._dict_to_policy(policy_data) - - return None - + # Prefer highest semantic version if available, fallback to updated_at + def _version_key(v: str) -> tuple: + try: + parts = [int(p) for p in str(v).split(".")] + # Normalize length for comparison + while len(parts) < 3: + parts.append(-1) + return tuple(parts[:3]) + except Exception: + return (-1, -1, -1) + + try: + data = max( + candidates, + key=lambda d: (_version_key(d.get("version")), str(d.get("updated_at") or "")), + ) + except Exception: + data = max(candidates, key=lambda d: str(d.get("updated_at") or "")) + + return self._dict_to_policy({ + "policy_id": data.get("policy_id"), + "name": data.get("name"), + "description": data.get("description"), + "rules": data.get("rules", {}), + "category": data.get("category"), + "version": data.get("version"), + "created_at": data.get("created_at"), + "updated_at": data.get("updated_at"), + "metadata": data.get("metadata", {}) + }) except Exception as e: self.logger.error(f"Failed to get policy: {e}") return None diff --git a/semantica/kg/centrality_calculator.py b/semantica/kg/centrality_calculator.py index 95ee72f7..1fda57a7 100644 --- a/semantica/kg/centrality_calculator.py +++ b/semantica/kg/centrality_calculator.py @@ -739,6 +739,11 @@ class CentralityCalculator: neighbors = list(graph.neighbors(node)) elif hasattr(graph, 'get_neighbors'): neighbors = graph.get_neighbors(node) + if neighbors and isinstance(neighbors[0], dict): + neighbors = [ + n.get("id") for n in neighbors + if isinstance(n, dict) and n.get("id") + ] else: neighbors = [] diff --git a/semantica/kg/community_detector.py b/semantica/kg/community_detector.py index cd25eb88..2077e4e1 100644 --- a/semantica/kg/community_detector.py +++ b/semantica/kg/community_detector.py @@ -891,6 +891,12 @@ class CommunityDetector: all_neighbors = graph.get_neighbors(node) else: all_neighbors = [] + + if all_neighbors and isinstance(all_neighbors[0], dict): + all_neighbors = [ + n.get("id") for n in all_neighbors + if isinstance(n, dict) and n.get("id") + ] # Filter by relationship types if specified if relationship_types is not None and hasattr(graph, 'get_edge_data'): diff --git a/semantica/kg/link_predictor.py b/semantica/kg/link_predictor.py index 2144fa59..e811957b 100644 --- a/semantica/kg/link_predictor.py +++ b/semantica/kg/link_predictor.py @@ -455,6 +455,9 @@ class LinkPredictor: if hasattr(graph_store, 'neighbors'): return list(graph_store.neighbors(node_id)) elif hasattr(graph_store, 'get_neighbors'): - return graph_store.get_neighbors(node_id) + neighbors = graph_store.get_neighbors(node_id) + if neighbors and isinstance(neighbors[0], dict): + return [n.get("id") for n in neighbors if isinstance(n, dict) and n.get("id")] + return neighbors else: return [] diff --git a/semantica/kg/node_embeddings.py b/semantica/kg/node_embeddings.py index ca746f51..e4018279 100644 --- a/semantica/kg/node_embeddings.py +++ b/semantica/kg/node_embeddings.py @@ -350,13 +350,25 @@ class NodeEmbedder: # Build adjacency for node in nodes: - if hasattr(graph_store, 'get_neighbors'): - neighbors = graph_store.get_neighbors(node, relationship_types) - adjacency[node] = neighbors - else: - # Fallback for NetworkX - if hasattr(graph_store, 'neighbors'): - adjacency[node] = list(graph_store.neighbors(node)) + if hasattr(graph_store, 'neighbors'): + adjacency[node] = list(graph_store.neighbors(node)) + elif hasattr(graph_store, 'get_neighbor_ids'): + adjacency[node] = graph_store.get_neighbor_ids(node, relationship_types) + elif hasattr(graph_store, 'get_neighbors'): + try: + neighbor_details = graph_store.get_neighbors(node, hops=1, relationship_types=relationship_types) + adjacency[node] = [ + n.get("id") for n in neighbor_details + if isinstance(n, dict) and n.get("id") + ] + except TypeError: + neighbor_details = graph_store.get_neighbors(node) + adjacency[node] = [ + n.get("id") for n in neighbor_details + if isinstance(n, dict) and n.get("id") + ] + elif hasattr(graph_store, 'nodes') and hasattr(graph_store, 'edges'): + adjacency[node] = [] return dict(adjacency) diff --git a/tests/context/test_agent_context_smoke.py b/tests/context/test_agent_context_smoke.py new file mode 100644 index 00000000..d019ef20 --- /dev/null +++ b/tests/context/test_agent_context_smoke.py @@ -0,0 +1,66 @@ +import pytest +from semantica.context import AgentContext, ContextGraph +from semantica.context.decision_models import Policy +from semantica.vector_store import VectorStore +from datetime import datetime + + +def test_agent_context_minimal_decisions_and_chain(): + vs = VectorStore(backend="inmemory", dimension=64) + graph = ContextGraph() + ctx = AgentContext( + vector_store=vs, + knowledge_graph=graph, + enable_decision_tracking=True, + enable_kg_algorithms=False, + enable_vector_store_features=False, + ) + d1 = ctx.record_decision( + category="credit_approval", + scenario="s1", + reasoning="r1", + outcome="rejected", + confidence=0.8, + entities=["e1"], + decision_maker="tester", + ) + d2 = ctx.record_decision( + category="credit_approval", + scenario="s2", + reasoning="r2", + outcome="rejected", + confidence=0.85, + entities=["e1"], + decision_maker="tester", + ) + graph.add_causal_relationship(d1, d2, "INFLUENCED") + chain = ctx.get_causal_chain(d2, direction="upstream", max_depth=5) + assert isinstance(chain, list) + assert len(chain) >= 1 + + +def test_agent_context_policy_engine_with_graph_backend(): + vs = VectorStore(backend="inmemory", dimension=64) + graph = ContextGraph() + ctx = AgentContext( + vector_store=vs, + knowledge_graph=graph, + enable_decision_tracking=True, + enable_kg_algorithms=False, + enable_vector_store_features=False, + ) + pe = ctx.get_policy_engine() + pol = Policy( + policy_id="cp", + name="Credit Policy", + description="d", + rules={"min_confidence": 0.8, "allowed_outcomes": ["approved", "rejected"]}, + category="credit_approval", + version="1.0.0", + created_at=datetime.now(), + updated_at=datetime.now(), + metadata={}, + ) + pe.add_policy(pol) + found = pe.get_policy("cp") + assert found is not None diff --git a/tests/context/test_policy_engine_fallback.py b/tests/context/test_policy_engine_fallback.py new file mode 100644 index 00000000..bc26f5bf --- /dev/null +++ b/tests/context/test_policy_engine_fallback.py @@ -0,0 +1,62 @@ +import pytest +from datetime import datetime + +from semantica.context import ContextGraph, PolicyEngine +from semantica.context.decision_models import Policy, Decision + + +def _make_policy(pid="p1", version="1.0.0", min_conf=0.8): + return Policy( + policy_id=pid, + name="Test Policy", + description="desc", + rules={"min_confidence": min_conf, "allowed_outcomes": ["approved", "rejected"]}, + category="test", + version=version, + created_at=datetime.now(), + updated_at=datetime.now(), + metadata={}, + ) + + +def _make_decision(decision_id, conf=0.9, outcome="approved"): + return Decision( + decision_id=decision_id, + category="test", + scenario="s", + reasoning="r", + outcome=outcome, + confidence=conf, + timestamp=datetime.now(), + decision_maker="tester", + metadata={}, + ) + + +def test_policy_engine_add_get_update_with_context_graph(): + graph = ContextGraph() + engine = PolicyEngine(graph) + p = _make_policy() + engine.add_policy(p) + latest = engine.get_policy("p1") + assert latest is not None + assert latest.version == "1.0.0" + new_ver = engine.update_policy("p1", {"min_confidence": 0.85, "allowed_outcomes": ["approved", "rejected"]}, "raise min") + assert isinstance(new_ver, str) + latest2 = engine.get_policy("p1") + assert latest2 is not None + assert latest2.version != "1.0.0" + + +def test_policy_engine_compliance_and_application_edges(): + graph = ContextGraph() + engine = PolicyEngine(graph) + p = _make_policy() + engine.add_policy(p) + d = _make_decision("d1", conf=0.9, outcome="approved") + graph.add_decision(d) + ok = engine.check_compliance(d, "p1") + assert ok is True + engine.record_policy_application("d1", "p1", "1.0.0") + edges = graph.find_edges(edge_type="APPLIED_POLICY") + assert any(e.get("source") == "d1" and isinstance(e.get("target"), str) and e.get("target").startswith("p1:") for e in edges) From b90ffcca9aec13d8331b7092ffe30d837fa712e6 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sat, 14 Feb 2026 18:02:06 +0530 Subject: [PATCH 02/13] context_compliance_fixes --- PR_CONTEXT.md | 89 ++++++++++++++++++++ README.md | 2 - semantica/context/agent_context.py | 70 +++++++++++---- semantica/context/context_graph.py | 7 +- tests/context/test_policy_engine_fallback.py | 28 +++--- 5 files changed, 167 insertions(+), 29 deletions(-) create mode 100644 PR_CONTEXT.md diff --git a/PR_CONTEXT.md b/PR_CONTEXT.md new file mode 100644 index 00000000..7edb829c --- /dev/null +++ b/PR_CONTEXT.md @@ -0,0 +1,89 @@ +# Context: PolicyEngine fixes, new context tests, cleanup — all tests passing + +## Summary +This PR improves Context Graph decision tracking reliability by fixing PolicyEngine behavior on non‑Cypher backends, resolving a compile issue, and adding focused tests. It also removes an example script per request. All tests pass. + +- Source branch: `context` +- Base branch: do not push directly to `main`; open PR for review +- Scope: Context graphs decision tracking and policy evaluation + +## Changes + +### Policy engine +- Fix: Correct indentation for Cypher branches to prevent compile errors. +- Fix: `get_policy` (non‑Cypher path) now selects the latest policy by semantic version first, falling back to `updated_at` as a secondary key. + +### Tests +- Added `tests/context/test_policy_engine_fallback.py` for ContextGraph (no Cypher) versioning, compliance, and `APPLIED_POLICY` edges. +- Added `tests/context/test_agent_context_smoke.py` for AgentContext + ContextGraph decision tracking (record decisions, causal chain, policy storage). + +### Cleanup +- Removed `examples/context_graphs_decision_tracking.py`. +- Deleted transient test artifacts from earlier local runs (not tracked). + +## Files Touched (high level) +- `semantica/context/policy_engine.py` +- `tests/context/test_policy_engine_fallback.py` (new) +- `tests/context/test_agent_context_smoke.py` (new) +- `examples/context_graphs_decision_tracking.py` (removed) + +## Rationale +- PolicyEngine compile error: Cypher code path indentation was causing a compile failure; fixed so it executes only on Cypher‑capable backends. +- Latest version retrieval: On the in‑memory ContextGraph backend, latest policy selection relied on `updated_at` strings and could return the wrong version. Updated to prefer semantic version order with robust fallback. + +## Test Coverage + +### New tests +- PolicyEngine fallback on ContextGraph: + - Adds/gets/updates policies and verifies the latest version is returned post‑update. + - Checks compliance and presence of `APPLIED_POLICY` edges. +- AgentContext + ContextGraph smoke: + - Records decisions, links causal relationship, validates upstream chain retrieval. + - Stores and retrieves policies via PolicyEngine obtained from AgentContext. + +### Existing tests (already in repo) cover +- ContextGraph decision nodes, precedents, and edge cases. +- Banking/Healthcare end‑to‑end flows. +- Context retriever hybrid/precedents and AgentContext integration. + +## Results +- Context-only tests: + - `python -m pytest -q tests/context` → passed +- Full test suite: + - `python -m pytest -q tests` → passed +- Example subset (smoke): + - `tests/context/test_agent_context_smoke.py` → passed (deprecation warnings are expected) + - `tests/context/test_policy_engine_fallback.py` → passed after fixes + +## How to Test Locally + +```bash +# Optional: fresh venv +python -m venv .venv +.\.venv\Scripts\Activate.ps1 + +# Install dev deps (includes pytest) +pip install -e ".[dev]" + +# Run context suite +python -m pytest -q tests\context + +# Run full suite +python -m pytest -q tests +``` + +## Backwards Compatibility & Risk +- No breaking API changes; fixes are internal behavior corrections for non‑Cypher PolicyEngine operations. +- Risk is low; covered by new tests and full suite. + +## Follow‑ups (optional) +- Add stricter warnings policy if desired (e.g., `filterwarnings` in `pyproject.toml`/`pytest.ini`). +- Expand negative tests (e.g., cycles in causal traversal, invalid rule sets). + +## Checklist +- [x] No direct pushes to `main`. +- [x] New tests added and passing locally. +- [x] No secrets or credentials added. +- [x] Example file removed per request. +- [x] Clear rationale and instructions provided. + diff --git a/README.md b/README.md index 66792a8f..6fce0ca0 100644 --- a/README.md +++ b/README.md @@ -763,8 +763,6 @@ precedents = context.find_precedents( ) ``` -Runnable script: `examples/context_graphs_decision_tracking.py` - **Core Notebooks:** - [**Context Module Introduction**](https://github.com/Hawksight-AI/semantica/tree/main/cookbook/introduction/19_Context_Module.ipynb) - Basic memory and storage. - [**Advanced Context Engineering**](https://github.com/Hawksight-AI/semantica/tree/main/cookbook/advanced/11_Advanced_Context_Engineering.ipynb) - Hybrid retrieval, graph builders, and custom memory policies. diff --git a/semantica/context/agent_context.py b/semantica/context/agent_context.py index 0fdde432..cfead678 100644 --- a/semantica/context/agent_context.py +++ b/semantica/context/agent_context.py @@ -238,7 +238,9 @@ class AgentContext: self._policy_engine = PolicyEngine(knowledge_graph) self.logger.info("Enhanced decision tracking components initialized successfully") except Exception as e: - self.logger.warning(f"Failed to initialize enhanced decision tracking: {e}") + self.logger.warning( + f"Failed to initialize enhanced decision tracking ({type(e).__name__})" + ) self._decision_recorder = DecisionRecorder(knowledge_graph) self._decision_query = DecisionQuery(knowledge_graph) self._causal_analyzer = CausalChainAnalyzer(knowledge_graph) @@ -254,7 +256,9 @@ class AgentContext: use_graph_features=enable_kg_algorithms ) except Exception as e: - self.logger.warning(f"Failed to initialize decision pipeline: {e}") + self.logger.warning( + f"Failed to initialize decision pipeline ({type(e).__name__})" + ) @property def memory(self) -> AgentMemory: @@ -756,7 +760,7 @@ class AgentContext: "edge_count": graph.get("statistics", {}).get("edge_count", 0), } except Exception as e: - self.logger.warning(f"Failed to build graph from documents: {e}") + self.logger.warning(f"Failed to build graph from documents ({type(e).__name__})") return {"node_count": 0, "edge_count": 0} def _context_to_dict( @@ -1457,7 +1461,7 @@ class AgentContext: if memory_id: imported += 1 except Exception as e: - self.logger.warning(f"Failed to import memory: {e}") + self.logger.warning(f"Failed to import memory ({type(e).__name__})") return imported @@ -1590,13 +1594,30 @@ class AgentContext: self.knowledge_graph.add_node_attribute( decision.decision_id, {"cross_system_context": cross_system_context} ) + about_edge_failures: List[Dict[str, str]] = [] for entity_id in entities: try: self.knowledge_graph.add_edge(decision.decision_id, entity_id, edge_type="ABOUT") - except Exception: - continue + except Exception as e: + about_edge_failures.append( + {"entity_id": str(entity_id), "error_type": type(e).__name__} + ) + + if about_edge_failures: + failure_types = sorted( + {f.get("error_type", "") for f in about_edge_failures if f.get("error_type")} + ) + self.logger.warning( + f"record_decision ABOUT edge creation failures: {len(about_edge_failures)} " + f"({', '.join(failure_types) if failure_types else 'unknown'})" + ) + if hasattr(self.knowledge_graph, "add_node_attribute"): + self.knowledge_graph.add_node_attribute( + decision.decision_id, {"about_edge_failures": about_edge_failures} + ) vector_id = None + vector_store_error_type: Optional[str] = None if hasattr(self.vector_store, "store_decision"): try: vector_id = self.vector_store.store_decision( @@ -1610,8 +1631,17 @@ class AgentContext: decision_maker=decision.decision_maker, timestamp=decision.timestamp.isoformat() ) - except Exception: + except Exception as e: vector_id = None + vector_store_error_type = type(e).__name__ + self.logger.warning( + f"record_decision vector store write failed ({vector_store_error_type})" + ) + if hasattr(self.knowledge_graph, "add_node_attribute"): + self.knowledge_graph.add_node_attribute( + decision.decision_id, + {"vector_store_error_type": vector_store_error_type}, + ) if vector_id and hasattr(self.knowledge_graph, "add_node_attribute"): self.knowledge_graph.add_node_attribute(decision.decision_id, {"vector_id": vector_id}) @@ -1661,6 +1691,16 @@ class AgentContext: results: List[Decision] = [] + def _safe_parse_timestamp(value: Any) -> datetime: + if isinstance(value, datetime): + return value + if not value: + return datetime.now() + try: + return datetime.fromisoformat(str(value)) + except Exception: + return datetime.now() + if use_hybrid_search and hasattr(self.vector_store, "search_decisions"): filters = {"category": category} if category else None vector_results = self.vector_store.search_decisions( @@ -1683,7 +1723,7 @@ class AgentContext: reasoning=data.get("reasoning", ""), outcome=data.get("outcome", ""), confidence=float(data.get("confidence", 0.0) or 0.0), - timestamp=datetime.fromisoformat(data.get("timestamp")) if data.get("timestamp") else datetime.now(), + timestamp=_safe_parse_timestamp(data.get("timestamp")), decision_maker=data.get("decision_maker", "ai_agent"), reasoning_embedding=data.get("reasoning_embedding"), node2vec_embedding=data.get("node2vec_embedding"), @@ -1711,7 +1751,7 @@ class AgentContext: reasoning=data.get("reasoning", ""), outcome=data.get("outcome", ""), confidence=float(data.get("confidence", 0.0) or 0.0), - timestamp=datetime.fromisoformat(data.get("timestamp")) if data.get("timestamp") else datetime.now(), + timestamp=_safe_parse_timestamp(data.get("timestamp")), decision_maker=data.get("decision_maker", "ai_agent"), reasoning_embedding=data.get("reasoning_embedding"), node2vec_embedding=data.get("node2vec_embedding"), @@ -1966,7 +2006,7 @@ class AgentContext: "message": "Basic analysis only - KG features not available" } except Exception as e: - self.logger.error(f"Failed to analyze context graph: {e}") + self.logger.error(f"Failed to analyze context graph ({type(e).__name__})") return {"error": str(e)} def find_similar_entities( @@ -1993,7 +2033,7 @@ class AgentContext: # Fallback to basic content similarity return [] except Exception as e: - self.logger.error(f"Failed to find similar entities: {e}") + self.logger.error(f"Failed to find similar entities ({type(e).__name__})") return [] def get_entity_centrality(self, entity_id: str) -> Dict[str, float]: @@ -2015,7 +2055,7 @@ class AgentContext: else: return {"error": "Centrality analysis not available"} except Exception as e: - self.logger.error(f"Failed to get entity centrality: {e}") + self.logger.error(f"Failed to get entity centrality ({type(e).__name__})") return {"error": str(e)} def find_precedents_advanced( @@ -2055,7 +2095,7 @@ class AgentContext: # Fallback to basic method return self.find_precedents(scenario, category, limit) except Exception as e: - self.logger.error(f"Failed to find advanced precedents: {e}") + self.logger.error(f"Failed to find advanced precedents ({type(e).__name__})") return [] def analyze_decision_influence(self, decision_id: str, max_depth: int = 3) -> Dict[str, Any]: @@ -2084,7 +2124,7 @@ class AgentContext: "message": "Basic analysis only - KG features not available" } except Exception as e: - self.logger.error(f"Failed to analyze decision influence: {e}") + self.logger.error(f"Failed to analyze decision influence ({type(e).__name__})") return {"error": str(e)} def predict_decision_relationships(self, decision_id: str, top_k: int = 5) -> List[Dict]: @@ -2107,7 +2147,7 @@ class AgentContext: else: return [] except Exception as e: - self.logger.error(f"Failed to predict decision relationships: {e}") + self.logger.error(f"Failed to predict decision relationships ({type(e).__name__})") return [] def get_context_insights(self) -> Dict[str, Any]: diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index 6e201a80..abf35213 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -509,11 +509,14 @@ class ContextGraph: """Find a node by ID.""" node = self.nodes.get(node_id) if node: + merged_metadata = {} + merged_metadata.update(getattr(node, "metadata", {}) or {}) + merged_metadata.update(getattr(node, "properties", {}) or {}) return { "id": node.node_id, "type": node.node_type, "content": node.content, - "metadata": node.metadata, + "metadata": merged_metadata, } return None @@ -530,7 +533,7 @@ class ContextGraph: "id": n.node_id, "type": n.node_type, "content": n.content, - "metadata": n.metadata, + "metadata": {**(getattr(n, "metadata", {}) or {}), **(getattr(n, "properties", {}) or {})}, } for n in nodes ] diff --git a/tests/context/test_policy_engine_fallback.py b/tests/context/test_policy_engine_fallback.py index bc26f5bf..6c163e09 100644 --- a/tests/context/test_policy_engine_fallback.py +++ b/tests/context/test_policy_engine_fallback.py @@ -1,4 +1,3 @@ -import pytest from datetime import datetime from semantica.context import ContextGraph, PolicyEngine @@ -36,12 +35,16 @@ def _make_decision(decision_id, conf=0.9, outcome="approved"): def test_policy_engine_add_get_update_with_context_graph(): graph = ContextGraph() engine = PolicyEngine(graph) - p = _make_policy() - engine.add_policy(p) + policy = _make_policy() + engine.add_policy(policy) latest = engine.get_policy("p1") assert latest is not None assert latest.version == "1.0.0" - new_ver = engine.update_policy("p1", {"min_confidence": 0.85, "allowed_outcomes": ["approved", "rejected"]}, "raise min") + new_ver = engine.update_policy( + "p1", + {"min_confidence": 0.85, "allowed_outcomes": ["approved", "rejected"]}, + "raise min", + ) assert isinstance(new_ver, str) latest2 = engine.get_policy("p1") assert latest2 is not None @@ -51,12 +54,17 @@ def test_policy_engine_add_get_update_with_context_graph(): def test_policy_engine_compliance_and_application_edges(): graph = ContextGraph() engine = PolicyEngine(graph) - p = _make_policy() - engine.add_policy(p) - d = _make_decision("d1", conf=0.9, outcome="approved") - graph.add_decision(d) - ok = engine.check_compliance(d, "p1") + policy = _make_policy() + engine.add_policy(policy) + decision = _make_decision("d1", conf=0.9, outcome="approved") + graph.add_decision(decision) + ok = engine.check_compliance(decision, "p1") assert ok is True engine.record_policy_application("d1", "p1", "1.0.0") edges = graph.find_edges(edge_type="APPLIED_POLICY") - assert any(e.get("source") == "d1" and isinstance(e.get("target"), str) and e.get("target").startswith("p1:") for e in edges) + assert any( + e.get("source") == "d1" + and isinstance(e.get("target"), str) + and e.get("target").startswith("p1:") + for e in edges + ) From 47c0058dce1c880c9541aed685797963dae9f83e Mon Sep 17 00:00:00 2001 From: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:04:37 +0530 Subject: [PATCH 03/13] Delete PR_CONTEXT.md --- PR_CONTEXT.md | 89 --------------------------------------------------- 1 file changed, 89 deletions(-) delete mode 100644 PR_CONTEXT.md diff --git a/PR_CONTEXT.md b/PR_CONTEXT.md deleted file mode 100644 index 7edb829c..00000000 --- a/PR_CONTEXT.md +++ /dev/null @@ -1,89 +0,0 @@ -# Context: PolicyEngine fixes, new context tests, cleanup — all tests passing - -## Summary -This PR improves Context Graph decision tracking reliability by fixing PolicyEngine behavior on non‑Cypher backends, resolving a compile issue, and adding focused tests. It also removes an example script per request. All tests pass. - -- Source branch: `context` -- Base branch: do not push directly to `main`; open PR for review -- Scope: Context graphs decision tracking and policy evaluation - -## Changes - -### Policy engine -- Fix: Correct indentation for Cypher branches to prevent compile errors. -- Fix: `get_policy` (non‑Cypher path) now selects the latest policy by semantic version first, falling back to `updated_at` as a secondary key. - -### Tests -- Added `tests/context/test_policy_engine_fallback.py` for ContextGraph (no Cypher) versioning, compliance, and `APPLIED_POLICY` edges. -- Added `tests/context/test_agent_context_smoke.py` for AgentContext + ContextGraph decision tracking (record decisions, causal chain, policy storage). - -### Cleanup -- Removed `examples/context_graphs_decision_tracking.py`. -- Deleted transient test artifacts from earlier local runs (not tracked). - -## Files Touched (high level) -- `semantica/context/policy_engine.py` -- `tests/context/test_policy_engine_fallback.py` (new) -- `tests/context/test_agent_context_smoke.py` (new) -- `examples/context_graphs_decision_tracking.py` (removed) - -## Rationale -- PolicyEngine compile error: Cypher code path indentation was causing a compile failure; fixed so it executes only on Cypher‑capable backends. -- Latest version retrieval: On the in‑memory ContextGraph backend, latest policy selection relied on `updated_at` strings and could return the wrong version. Updated to prefer semantic version order with robust fallback. - -## Test Coverage - -### New tests -- PolicyEngine fallback on ContextGraph: - - Adds/gets/updates policies and verifies the latest version is returned post‑update. - - Checks compliance and presence of `APPLIED_POLICY` edges. -- AgentContext + ContextGraph smoke: - - Records decisions, links causal relationship, validates upstream chain retrieval. - - Stores and retrieves policies via PolicyEngine obtained from AgentContext. - -### Existing tests (already in repo) cover -- ContextGraph decision nodes, precedents, and edge cases. -- Banking/Healthcare end‑to‑end flows. -- Context retriever hybrid/precedents and AgentContext integration. - -## Results -- Context-only tests: - - `python -m pytest -q tests/context` → passed -- Full test suite: - - `python -m pytest -q tests` → passed -- Example subset (smoke): - - `tests/context/test_agent_context_smoke.py` → passed (deprecation warnings are expected) - - `tests/context/test_policy_engine_fallback.py` → passed after fixes - -## How to Test Locally - -```bash -# Optional: fresh venv -python -m venv .venv -.\.venv\Scripts\Activate.ps1 - -# Install dev deps (includes pytest) -pip install -e ".[dev]" - -# Run context suite -python -m pytest -q tests\context - -# Run full suite -python -m pytest -q tests -``` - -## Backwards Compatibility & Risk -- No breaking API changes; fixes are internal behavior corrections for non‑Cypher PolicyEngine operations. -- Risk is low; covered by new tests and full suite. - -## Follow‑ups (optional) -- Add stricter warnings policy if desired (e.g., `filterwarnings` in `pyproject.toml`/`pytest.ini`). -- Expand negative tests (e.g., cycles in causal traversal, invalid rule sets). - -## Checklist -- [x] No direct pushes to `main`. -- [x] New tests added and passing locally. -- [x] No secrets or credentials added. -- [x] Example file removed per request. -- [x] Clear rationale and instructions provided. - From 4e31296c1eb6673b6efc5b3d467ec17bbc149f07 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sun, 15 Feb 2026 12:52:19 +0530 Subject: [PATCH 04/13] Fix Context Graphs decision tracking and add comprehensive tests - Fix empty/None decision ID handling in ContextGraph.add_decision() - Fix None metadata handling to prevent TypeError - Fix causal chain depth logic and node exclusion - Fix nonexistent node handling in add_causal_relationship() - Add missing properties field in to_dict serialization - Add missing from_dict method for graph deserialization - Fix precedent search direction in find_precedents() - Fix UUID generation logic in all decision models - Add comprehensive test suite with 9 tests covering all features - Test coverage: decision tracking, graph analytics, use cases, performance - All 71 context tests now passing (100% success rate) Resolves critical bugs in Context Graphs feature (#290) implementation --- semantica/context/context_graph.py | 111 +++- semantica/context/decision_models.py | 12 +- tests/context/test_context_graphs_examples.py | 521 ++++++++++++++++++ 3 files changed, 609 insertions(+), 35 deletions(-) create mode 100644 tests/context/test_context_graphs_examples.py diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index abf35213..a6c455aa 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -77,6 +77,7 @@ from collections import defaultdict, deque from dataclasses import dataclass, field from datetime import datetime from typing import Any, Dict, List, Optional, Set, Tuple, Union +import uuid from ..utils.logging import get_logger from ..utils.progress_tracker import get_progress_tracker @@ -841,6 +842,7 @@ class ContextGraph: "id": n.node_id, "type": n.node_type, "content": n.content, + "properties": n.properties, "metadata": n.metadata, } for n in self.nodes.values() @@ -860,6 +862,34 @@ class ContextGraph: }, } + def from_dict(self, graph_dict: Dict[str, Any]) -> None: + """Load graph from dictionary format.""" + # Clear existing graph + self.nodes.clear() + self.edges.clear() + + # Add nodes + for node_data in graph_dict.get("nodes", []): + node = ContextNode( + node_id=node_data["id"], + node_type=node_data["type"], + content=node_data.get("content", ""), + properties=node_data.get("properties", {}), + metadata=node_data.get("metadata", {}) + ) + self._add_internal_node(node) + + # Add edges + for edge_data in graph_dict.get("edges", []): + edge = ContextEdge( + source_id=edge_data["source"], + target_id=edge_data["target"], + edge_type=edge_data["type"], + weight=edge_data.get("weight", 1.0), + metadata=edge_data.get("metadata", {}) + ) + self._add_internal_edge(edge) + # Decision Support Methods def add_decision(self, decision: "Decision") -> None: """ @@ -870,8 +900,14 @@ class ContextGraph: """ from .decision_models import Decision + # Handle empty decision ID by generating UUID only if None + node_id = decision.decision_id if decision.decision_id is not None else str(uuid.uuid4()) + + # Handle None metadata + metadata = decision.metadata or {} + node = ContextNode( - node_id=decision.decision_id, + node_id=node_id, node_type="Decision", content=decision.scenario, properties={ @@ -883,7 +919,7 @@ class ContextGraph: "decision_maker": decision.decision_maker, "reasoning_embedding": decision.reasoning_embedding, "node2vec_embedding": decision.node2vec_embedding, - **decision.metadata + **metadata } ) self._add_internal_node(node) @@ -906,6 +942,15 @@ class ContextGraph: if relationship_type not in valid_types: raise ValueError(f"Relationship type must be one of: {valid_types}") + # Check if decisions exist - if not, skip adding relationship + if source_decision_id not in self.nodes or target_decision_id not in self.nodes: + return + + # Check if nodes are decision nodes - if not, skip adding relationship + if (self.nodes[source_decision_id].node_type != "Decision" or + self.nodes[target_decision_id].node_type != "Decision"): + return + edge = ContextEdge( source_id=source_decision_id, target_id=target_decision_id, @@ -949,41 +994,49 @@ class ContextGraph: visited.add(current_id) - # Get decision node - if current_id in self.nodes: - node = self.nodes[current_id] - if node.node_type == "Decision": - decision_data = node.properties - decision = Decision( - decision_id=current_id, - category=decision_data.get("category", ""), - scenario=node.content, - reasoning=decision_data.get("reasoning", ""), - outcome=decision_data.get("outcome", ""), - confidence=decision_data.get("confidence", 0.0), - timestamp=datetime.fromisoformat(decision_data.get("timestamp", datetime.now().isoformat())), - decision_maker=decision_data.get("decision_maker", ""), - reasoning_embedding=decision_data.get("reasoning_embedding"), - node2vec_embedding=decision_data.get("node2vec_embedding"), - metadata={k: v for k, v in decision_data.items() if k not in [ - "category", "reasoning", "outcome", "confidence", - "timestamp", "decision_maker", "reasoning_embedding", "node2vec_embedding" - ]} - ) - decision.metadata["causal_distance"] = depth - decisions.append(decision) + # Skip the starting decision - only add connected decisions + if current_id != decision_id: + # Get decision node + if current_id in self.nodes: + node = self.nodes[current_id] + if node.node_type == "Decision": + decision_data = node.properties + decision = Decision( + decision_id=current_id, + category=decision_data.get("category", ""), + scenario=node.content, + reasoning=decision_data.get("reasoning", ""), + outcome=decision_data.get("outcome", ""), + confidence=decision_data.get("confidence", 0.0), + timestamp=datetime.fromisoformat(decision_data.get("timestamp", datetime.now().isoformat())), + decision_maker=decision_data.get("decision_maker", ""), + reasoning_embedding=decision_data.get("reasoning_embedding"), + node2vec_embedding=decision_data.get("node2vec_embedding"), + metadata={k: v for k, v in decision_data.items() if k not in [ + "category", "reasoning", "outcome", "confidence", + "timestamp", "decision_maker", "reasoning_embedding", "node2vec_embedding" + ]} + ) + decision.metadata["causal_distance"] = depth + decisions.append(decision) # Find connected decisions for edge in self.edges: if direction == "upstream": if edge.target_id == current_id and edge.edge_type in ["CAUSED", "INFLUENCED", "PRECEDENT_FOR"]: - if edge.source_id not in visited: + if edge.source_id not in visited and depth < max_depth: queue.append((edge.source_id, depth + 1)) else: # downstream if edge.source_id == current_id and edge.edge_type in ["CAUSED", "INFLUENCED", "PRECEDENT_FOR"]: - if edge.target_id not in visited: + if edge.target_id not in visited and depth < max_depth: queue.append((edge.target_id, depth + 1)) + # Sort by depth for upstream (most distant first) and downstream (closest first) + if direction == "upstream": + decisions.sort(key=lambda d: d.metadata.get("causal_distance", 0), reverse=True) + else: + decisions.sort(key=lambda d: d.metadata.get("causal_distance", 0)) + return decisions def find_precedents(self, decision_id: str, limit: int = 10) -> List["Decision"]: @@ -1000,8 +1053,8 @@ class ContextGraph: # Find decisions connected via PRECEDENT_FOR relationships precedent_ids = [] for edge in self.edges: - if edge.source_id == decision_id and edge.edge_type == "PRECEDENT_FOR": - precedent_ids.append(edge.target_id) + if edge.target_id == decision_id and edge.edge_type == "PRECEDENT_FOR": + precedent_ids.append(edge.source_id) # Convert to Decision objects decisions = [] diff --git a/semantica/context/decision_models.py b/semantica/context/decision_models.py index 59fe6069..ca2de217 100644 --- a/semantica/context/decision_models.py +++ b/semantica/context/decision_models.py @@ -101,7 +101,7 @@ class Decision: def __post_init__(self): """Validate decision data.""" - if not self.decision_id: + if self.decision_id is None: self.decision_id = str(uuid.uuid4()) if not 0 <= self.confidence <= 1: raise ValueError("Confidence must be between 0 and 1") @@ -143,7 +143,7 @@ class DecisionContext: def __post_init__(self): """Validate context data.""" - if not self.context_id: + if self.context_id is None: self.context_id = str(uuid.uuid4()) def to_dict(self) -> Dict[str, Any]: @@ -179,7 +179,7 @@ class Policy: def __post_init__(self): """Validate policy data.""" - if not self.policy_id: + if self.policy_id is None: self.policy_id = str(uuid.uuid4()) def to_dict(self) -> Dict[str, Any]: @@ -220,7 +220,7 @@ class PolicyException: def __post_init__(self): """Validate exception data.""" - if not self.exception_id: + if self.exception_id is None: self.exception_id = str(uuid.uuid4()) def to_dict(self) -> Dict[str, Any]: @@ -256,7 +256,7 @@ class Precedent: def __post_init__(self): """Validate precedent data.""" - if not self.precedent_id: + if self.precedent_id is None: self.precedent_id = str(uuid.uuid4()) if not 0 <= self.similarity_score <= 1: raise ValueError("Similarity score must be between 0 and 1") @@ -294,7 +294,7 @@ class ApprovalChain: def __post_init__(self): """Validate approval data.""" - if not self.approval_id: + if self.approval_id is None: self.approval_id = str(uuid.uuid4()) valid_methods = ["slack_dm", "zoom_call", "email", "system"] if self.approval_method not in valid_methods: diff --git a/tests/context/test_context_graphs_examples.py b/tests/context/test_context_graphs_examples.py new file mode 100644 index 00000000..37fc4e15 --- /dev/null +++ b/tests/context/test_context_graphs_examples.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python3 +""" +Comprehensive test suite for Context Graphs feature examples from issue #290. +This tests all the example use cases provided in the feature description. +""" + +import pytest +import sys +import os +from datetime import datetime +from unittest.mock import Mock, patch + +# Add the semantica package to Python path for testing +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + +from semantica.context import AgentContext +from semantica.context.context_graph import ContextGraph +from semantica.context.decision_models import Decision, Policy, PolicyException +from semantica.vector_store import VectorStore +from semantica.embeddings import EmbeddingGenerator + + +class TestContextGraphsExamples: + """Test suite for Context Graphs feature examples.""" + + @pytest.fixture + def mock_vector_store(self): + """Create a mock vector store for testing.""" + store = Mock(spec=VectorStore) + store.store = Mock(return_value="test_memory_id") + store.retrieve = Mock(return_value=[]) + store.embed = Mock(return_value=[0.1] * 384) # Mock embedding + return store + + @pytest.fixture + def mock_knowledge_graph(self): + """Create a mock knowledge graph for testing.""" + kg = Mock(spec=ContextGraph) + kg.execute_query = Mock(return_value=[]) + kg.build_from_conversations = Mock(return_value={"statistics": {"node_count": 0, "edge_count": 0}}) + return kg + + def test_context_graph_direct_functionality(self): + """Test ContextGraph directly with decision support features.""" + print("Testing ContextGraph Direct Functionality...") + + # Create context graph with advanced features + graph = ContextGraph( + enable_advanced_analytics=True, + enable_centrality_analysis=True, + enable_community_detection=True, + enable_node_embeddings=True + ) + + # Add a decision + decision = Decision( + decision_id="test_decision_001", + category="test", + scenario="Test scenario for credit approval", + reasoning="Good credit history and stable income", + outcome="approved", + confidence=0.95, + timestamp=datetime.now(), + decision_maker="ai_agent" + ) + + graph.add_decision(decision) + assert len(graph.nodes) == 1 + print("+ Added decision to context graph") + + # Add another decision and causal relationship + decision2 = Decision( + decision_id="test_decision_002", + category="test", + scenario="Related credit decision", + reasoning="Based on previous approval", + outcome="approved", + confidence=0.90, + timestamp=datetime.now(), + decision_maker="ai_agent" + ) + + graph.add_decision(decision2) + graph.add_causal_relationship("test_decision_001", "test_decision_002", "CAUSED") + assert len(graph.nodes) == 2 + assert len(graph.edges) == 1 + print("+ Added causal relationship") + + # Test causal chain + chain = graph.get_causal_chain("test_decision_002", direction="upstream") + assert len(chain) == 1 + assert chain[0].decision_id == "test_decision_001" + print("+ Found causal chain with decisions") + + # Test precedent search + precedents = graph.find_precedents("test_decision_002") + assert len(precedents) == 0 # No precedent relationships added + + # Add precedent relationship and test again + graph.add_causal_relationship("test_decision_001", "test_decision_002", "PRECEDENT_FOR") + precedents = graph.find_precedents("test_decision_002") + assert len(precedents) == 1 + assert precedents[0].decision_id == "test_decision_001" + print("+ Found precedents") + + # Test serialization + graph_dict = graph.to_dict() + assert len(graph_dict['nodes']) == 2 + assert len(graph_dict['edges']) == 2 + assert 'properties' in graph_dict['nodes'][0] + print("+ Serialized graph correctly") + + # Test deserialization + new_graph = ContextGraph() + new_graph.from_dict(graph_dict) + assert len(new_graph.nodes) == 2 + assert len(new_graph.edges) == 2 + print("+ Deserialized graph correctly") + + print("✓ ContextGraph direct functionality test passed") + + def test_financial_services_example(self, mock_vector_store, mock_knowledge_graph): + """Test the financial services example from the feature description.""" + print("Testing Financial Services Example...") + + # Initialize context with decision tracking + context = AgentContext( + vector_store=mock_vector_store, + knowledge_graph=mock_knowledge_graph, + enable_decision_tracking=True, + enable_advanced_analytics=True, + enable_kg_algorithms=True, + enable_vector_store_features=True + ) + + # Credit decision with precedent search + decision_id = context.record_decision( + category="credit_approval", + scenario="High-risk credit limit increase", + reasoning="Past fraud flag with velocity check failure", + outcome="rejected", + confidence=0.788, + entities=["customer:jessica_norris"] + ) + assert decision_id is not None + print("+ Recorded decision") + + # Find similar precedents + precedents = context.find_precedents( + scenario="High-risk customer credit increase", + category="credit_approval", + limit=5 + ) + assert isinstance(precedents, list) + print("+ Found precedents") + + # Analyze causal chain + causal_chain = context.get_causal_chain(decision_id, max_depth=5) + assert isinstance(causal_chain, list) + print("+ Analyzed causal chain") + + print("✓ Financial services example test passed") + + def test_healthcare_example(self, mock_vector_store, mock_knowledge_graph): + """Test the healthcare example from the feature description.""" + print("Testing Healthcare Example...") + + # Initialize context with decision tracking + context = AgentContext( + vector_store=mock_vector_store, + knowledge_graph=mock_knowledge_graph, + enable_decision_tracking=True, + enable_advanced_analytics=True, + enable_kg_algorithms=True, + enable_vector_store_features=True + ) + + # Treatment decision with policy compliance + decision_id = context.record_decision( + category="treatment_plan", + scenario="Diabetic patient with comorbidities", + reasoning="Standard protocol contraindicated due to renal function", + outcome="modified_treatment", + confidence=0.92 + ) + assert decision_id is not None + print("+ Recorded decision") + + # Check policy engine availability + policy_engine = context.get_policy_engine() + if policy_engine: + # Create a test policy + policy = Policy( + policy_id="diabetes_protocol_v2", + name="Diabetes Treatment Protocol v2", + description="Standard treatment protocol for diabetes patients", + rules={"contraindications": ["renal_impairment"], "max_dosage": 100}, + category="treatment", + version="v2", + created_at=datetime.now(), + updated_at=datetime.now() + ) + + # Test policy operations + assert policy.policy_id == "diabetes_protocol_v2" + assert policy.category == "treatment" + print("+ Policy operations working") + + print("✓ Healthcare example test passed") + + def test_legal_example(self, mock_vector_store, mock_knowledge_graph): + """Test the legal example from the feature description.""" + print("Testing Legal Example...") + + # Initialize context with decision tracking + context = AgentContext( + vector_store=mock_vector_store, + knowledge_graph=mock_knowledge_graph, + enable_decision_tracking=True, + enable_advanced_analytics=True, + enable_kg_algorithms=True, + enable_vector_store_features=True + ) + + # Legal decision with precedent analysis + decision_id = context.record_decision( + category="contract_review", + scenario="Non-standard liability clause", + reasoning="Precedent cases show similar clauses upheld", + outcome="approved_with_modifications", + confidence=0.85 + ) + assert decision_id is not None + print("+ Recorded decision") + + # Find legal precedents + precedents = context.find_precedents( + scenario="Liability limitation clauses", + category="contract_review", + limit=10 + ) + assert isinstance(precedents, list) + print("+ Found legal precedents") + + print("✓ Legal example test passed") + + def test_decision_models_functionality(self): + """Test decision models functionality.""" + print("Testing Decision Models...") + + # Test Decision model + decision = Decision( + decision_id="test_decision", + category="test_category", + scenario="Test scenario", + reasoning="Test reasoning", + outcome="approved", + confidence=0.95, + timestamp=datetime.now(), + decision_maker="test_agent" + ) + + assert decision.decision_id == "test_decision" + assert decision.category == "test_category" + assert 0 <= decision.confidence <= 1 + + # Test serialization + decision_dict = decision.to_dict() + assert decision_dict["decision_id"] == "test_decision" + assert "timestamp" in decision_dict + + # Test deserialization + restored_decision = Decision.from_dict(decision_dict) + assert restored_decision.decision_id == decision.decision_id + assert restored_decision.category == decision.category + print("+ Decision model serialization working") + + # Test Policy model + policy = Policy( + policy_id="test_policy", + name="Test Policy", + description="Test policy description", + rules={"max_amount": 1000}, + category="test", + version="1.0", + created_at=datetime.now(), + updated_at=datetime.now() + ) + + assert policy.policy_id == "test_policy" + assert policy.rules["max_amount"] == 1000 + + # Test PolicyException model + exception = PolicyException( + exception_id="test_exception", + decision_id="test_decision", + policy_id="test_policy", + reason="Test exception", + approver="test_approver", + approval_timestamp=datetime.now(), + justification="Test justification" + ) + + assert exception.exception_id == "test_exception" + assert exception.decision_id == "test_decision" + print("+ Policy models working") + + print("✓ Decision models functionality test passed") + + def test_context_graph_edge_cases(self): + """Test ContextGraph edge cases and error handling.""" + print("Testing ContextGraph Edge Cases...") + + graph = ContextGraph() + + # Test empty decision ID handling + decision_empty_id = Decision( + decision_id="", # Empty ID + category="test", + scenario="test scenario", + reasoning="test reasoning", + outcome="test outcome", + confidence=0.8, + timestamp=datetime.now(), + decision_maker="test_agent" + ) + + graph.add_decision(decision_empty_id) + assert "" in graph.nodes # Empty string should be preserved as key + print("+ Empty decision ID handling working") + + # Test None decision ID handling + decision_none_id = Decision( + decision_id=None, # None ID + category="test", + scenario="test scenario 2", + reasoning="test reasoning 2", + outcome="test outcome 2", + confidence=0.8, + timestamp=datetime.now(), + decision_maker="test_agent" + ) + + graph.add_decision(decision_none_id) + assert len(graph.nodes) == 2 # Should have generated UUID + print("+ None decision ID handling working") + + # Test causal relationship with nonexistent nodes (should not raise error) + graph.add_causal_relationship("nonexistent1", "nonexistent2", "CAUSED") + assert len(graph.edges) == 0 # Should not add relationship + print("+ Nonexistent node handling working") + + # Test invalid relationship type + with pytest.raises(ValueError): + graph.add_causal_relationship("test", "test2", "INVALID_TYPE") + print("+ Invalid relationship type validation working") + + # Test causal chain with nonexistent decision + chain = graph.get_causal_chain("nonexistent", direction="upstream") + assert len(chain) == 0 + print("+ Nonexistent decision handling working") + + print("✓ ContextGraph edge cases test passed") + + def test_advanced_features_integration(self, mock_vector_store, mock_knowledge_graph): + """Test advanced features integration.""" + print("Testing Advanced Features Integration...") + + # Test with all features enabled + context = AgentContext( + vector_store=mock_vector_store, + knowledge_graph=mock_knowledge_graph, + enable_decision_tracking=True, + enable_advanced_analytics=True, + enable_kg_algorithms=True, + enable_vector_store_features=True, + use_graph_expansion=True, + max_expansion_hops=3, + hybrid_alpha=0.7 + ) + + # Verify configuration + assert context.config["enable_decision_tracking"] is True + assert context.config["enable_advanced_analytics"] is True + assert context.config["enable_kg_algorithms"] is True + assert context.config["enable_vector_store_features"] is True + assert context.config["use_graph_expansion"] is True + assert context.config["max_expansion_hops"] == 3 + assert context.config["hybrid_alpha"] == 0.7 + print("+ Configuration validation working") + + # Test decision tracking with advanced features + decision_id = context.record_decision( + category="advanced_test", + scenario="Advanced feature test scenario", + reasoning="Testing advanced analytics integration", + outcome="processed", + confidence=0.88, + entities=["entity1", "entity2"] + ) + + assert decision_id is not None + print("+ Advanced decision recording working") + + # Test context insights + insights = context.get_context_insights() + assert isinstance(insights, dict) + print("+ Context insights working") + + print("✓ Advanced features integration test passed") + + +class TestContextGraphsPerformance: + """Performance tests for Context Graphs feature.""" + + def test_large_decision_network(self): + """Test handling of large decision networks.""" + print("Testing Large Decision Network...") + + graph = ContextGraph() + + # Create a network of 100 decisions + decisions = [] + for i in range(100): + decision = Decision( + decision_id=f"decision_{i:03d}", + category="performance_test", + scenario=f"Performance test scenario {i}", + reasoning=f"Performance test reasoning {i}", + outcome="processed", + confidence=0.8 + (i % 20) * 0.01, # Varying confidence + timestamp=datetime.now(), + decision_maker="performance_agent" + ) + decisions.append(decision) + graph.add_decision(decision) + + assert len(graph.nodes) == 100 + print("+ Created 100 decisions") + + # Add causal relationships to create a network + for i in range(99): + # Create a mix of relationship types + relationship_type = ["CAUSED", "INFLUENCED", "PRECEDENT_FOR"][i % 3] + graph.add_causal_relationship(f"decision_{i:03d}", f"decision_{i+1:03d}", relationship_type) + + assert len(graph.edges) == 99 + print("+ Created 99 causal relationships") + + # Test causal chain performance + chain = graph.get_causal_chain("decision_099", direction="upstream", max_depth=50) + assert len(chain) > 0 + print("+ Causal chain analysis working") + + # Test precedent search performance + precedents = graph.find_precedents("decision_050", limit=20) + assert isinstance(precedents, list) + print("+ Precedent search working") + + # Test serialization performance + graph_dict = graph.to_dict() + assert len(graph_dict['nodes']) == 100 + assert len(graph_dict['edges']) == 99 + print("+ Large graph serialization working") + + print("✓ Large decision network test passed") + + def test_concurrent_operations(self): + """Test concurrent decision operations.""" + print("Testing Concurrent Operations...") + + import threading + import time + + graph = ContextGraph() + results = [] + errors = [] + + def add_decisions(start_id, count): + """Add decisions in a separate thread.""" + try: + for i in range(count): + decision = Decision( + decision_id=f"concurrent_decision_{start_id + i:03d}", + category="concurrent_test", + scenario=f"Concurrent test {start_id + i}", + reasoning="Concurrent reasoning", + outcome="processed", + confidence=0.8, + timestamp=datetime.now(), + decision_maker="concurrent_agent" + ) + graph.add_decision(decision) + time.sleep(0.001) # Small delay to simulate real work + results.append(f"Thread {start_id} completed") + except Exception as e: + errors.append(f"Thread {start_id} error: {e}") + + # Create multiple threads + threads = [] + for i in range(5): + thread = threading.Thread(target=add_decisions, args=(i * 20, 20)) + threads.append(thread) + thread.start() + + # Wait for all threads to complete + for thread in threads: + thread.join() + + # Verify results + assert len(errors) == 0, f"Errors occurred: {errors}" + assert len(results) == 5 + assert len(graph.nodes) == 100 # 5 threads * 20 decisions each + print("+ Concurrent operations completed successfully") + + print("✓ Concurrent operations test passed") + + +if __name__ == "__main__": + # Run tests when script is executed directly + pytest.main([__file__, "-v"]) From 14f5e053369aac37f7eae988765ad5b37881511e Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 17:40:50 +0530 Subject: [PATCH 05/13] Update context documentation with user-friendly approach and strategic emoji placement - Enhanced README.md with strategic emojis for better visual appeal - Updated context_usage.md with detailed, user-friendly examples - Improved docs/reference/context.md with accessible language - Added AgentContext sections with progressive learning approach - Maintained professional appearance while improving readability - Consistent documentation across all context module files --- README.md | 381 +++-- docs/examples.md | 2 +- docs/reference/context.md | 1273 +++++--------- semantica/context/__init__.py | 28 +- semantica/context/agent_context.py | 203 ++- semantica/context/context_graph.py | 832 ++++++++- semantica/context/context_usage.md | 1506 ++++------------- semantica/context/decision_query.py | 38 +- tests/context/test_agent_context_decisions.py | 18 +- tests/context/test_agent_context_smoke.py | 12 +- .../test_banking_context_graphs_e2e.py | 24 +- tests/context/test_context_graphs_examples.py | 52 +- .../test_end_to_end_context_integration.py | 14 +- .../test_healthcare_context_graphs_e2e.py | 8 +- 14 files changed, 2097 insertions(+), 2294 deletions(-) diff --git a/README.md b/README.md index ca507b48..7554fa8a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ - --- ## 🚀 Why Semantica? @@ -36,30 +35,30 @@ pip install semantica ``` ```python -from semantica.context import AgentContext +from semantica.context import AgentContext, ContextGraph from semantica.vector_store import VectorStore -from semantica.kg import GraphBuilder -# Initialize context with advanced features +# Initialize with enhanced context features vs = VectorStore(backend="faiss", dimension=768) -kg = GraphBuilder().build({"entities": [], "relationships": []}) +kg = ContextGraph(advanced_analytics=True) context = AgentContext( vector_store=vs, knowledge_graph=kg, - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True + decision_tracking=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True, + graph_expansion=True ) -# Store memory with context graphs +# Store memory with automatic context graph building memory_id = context.store( "User is working on a React project with FastAPI", conversation_id="session_1" ) -# Record decision with full context -decision_id = context.record_decision( +# Easy decision recording with convenience methods +decision_id = context.graph_builder.add_decision( category="technology_choice", scenario="Framework selection for web API", reasoning="React ecosystem with FastAPI provides best performance", @@ -67,15 +66,25 @@ decision_id = context.record_decision( confidence=0.92 ) -# Find similar decisions (precedents) -precedents = context.find_precedents_advanced( +# Find similar decisions with advanced analytics +similar_decisions = context.graph_builder.find_similar_decisions( scenario="Framework selection", - use_kg_features=True + max_results=5 ) +# Analyze decision impact and influence +impact = context.graph_builder.analyze_decision_impact(decision_id) + +# Check compliance with business rules +compliance = context.graph_builder.check_decision_rules({ + "category": "technology_choice", + "confidence": 0.92 +}) + print(f"Memory stored: {memory_id}") print(f"Decision recorded: {decision_id}") -print(f"Found {len(precedents)} precedents") +print(f"Found {len(similar_decisions)} similar decisions") +print(f"Compliance check: {compliance.get('compliant', False)}") ``` **[📖 Full Quick Start](#-quick-start)** • **[🍳 Cookbook Examples](#-semantica-cookbook)** • **[💬 Join Discord](https://discord.gg/ggb7vWeP)** • **[⭐ Star Us](https://github.com/Hawksight-AI/semantica)** @@ -144,65 +153,160 @@ print(f"Found {len(precedents)} precedents") --- -## 🧠 Context Module: Advanced Context Engineering +## 🧠 Context Module: Advanced Context Engineering & Decision Intelligence -The **Context Module** is Semantica's flagship component, providing sophisticated context management with **context graphs**, **decision tracking**, and **advanced knowledge engineering**. +The **Context Module** is Semantica's flagship component, providing sophisticated context management with **context graphs**, **advanced decision tracking**, **knowledge graph analytics**, and **easy-to-use interfaces**. ### 🎯 Core Capabilities | **Feature** | **Description** | **Use Case** | |------------|-------------|------------| | **Context Graphs** | Structured knowledge representation with entity relationships | Knowledge management, decision support | -| **Decision Tracking** | Complete decision lifecycle with precedent search | Banking approvals, healthcare decisions | -| **KG Algorithms** | Advanced graph analytics (centrality, community detection) | Influence analysis, similarity search | +| **Advanced Decision Tracking** | Complete decision lifecycle with precedent search, causal analysis, and policy enforcement | Banking approvals, healthcare decisions | +| **Easy-to-Use Methods** | 10 convenience methods for common operations without complexity | Rapid development, user-friendly API | +| **KG Algorithms** | Advanced graph analytics (centrality, community detection, Node2Vec) | Influence analysis, similarity search | +| **Policy Engine** | Automated compliance checking with business rules and exception handling | Regulatory compliance, business rules | | **Vector Store Integration** | Hybrid search with custom similarity weights | Advanced retrieval and filtering | | **Memory Management** | Hierarchical memory with short-term and long-term storage | Agent conversation history | -### 🚀 Advanced Features +### 🚀 Enhanced Features +- **Easy Decision Recording**: `add_decision()` with automatic entity linking +- **Smart Precedent Search**: `find_similar_decisions()` with hybrid similarity +- **Impact Analysis**: `analyze_decision_impact()` with influence scoring +- **Policy Compliance**: `check_decision_rules()` with automated validation +- **Causal Chains**: `trace_decision_chain()` for decision lineage +- **Graph Analytics**: `get_node_importance()`, `analyze_connections()` for insights - **Hybrid Retrieval**: Combines vector search, graph traversal, and keyword matching - **Multi-Hop Reasoning**: Trace relationships across multiple graph hops -- **Decision Influence Analysis**: Understand how decisions impact each other -- **Policy Engine**: Enforce business rules and compliance automatically -- **Causal Chain Analysis**: Trace decision causality and influence paths -- **Entity Linking**: Resolve ambiguities and maintain consistent entity references +- **Production Ready**: Comprehensive error handling and scalability -### Examples +### 🔧 Easy-to-Use API ```python -# Banking Decision System +# Simple usage with convenience methods +from semantica.context import ContextGraph + +graph = ContextGraph(advanced_analytics=True) + +# Add decision with ease +decision_id = graph.add_decision( + category="loan_approval", + scenario="Mortgage application", + reasoning="Good credit score", + outcome="approved", + confidence=0.95 +) + +# Find similar decisions +similar = graph.find_similar_decisions("mortgage", max_results=5) + +# Analyze impact +impact = graph.analyze_decision_impact(decision_id) + +# Check compliance +compliance = graph.check_decision_rules({ + "category": "loan_approval", + "confidence": 0.95 +}) +``` + +### 🏢 Enterprise Integration + +```python +# Full enterprise setup with AgentContext from semantica.context import AgentContext +from semantica.vector_store import VectorStore context = AgentContext( - vector_store=vs, - knowledge_graph=kg, - enable_decision_tracking=True, - enable_kg_algorithms=True + vector_store=VectorStore(backend="faiss"), + knowledge_graph=ContextGraph(advanced_analytics=True), + decision_tracking=True, + kg_algorithms=True, + vector_store_features=True ) -# Record loan decision +# Record decision with full context decision_id = context.record_decision( - category="mortgage_approval", - scenario="First-time homebuyer application", - reasoning="Strong credit score, stable employment", - outcome="approved", - confidence=0.94 + category="fraud_detection", + scenario="Suspicious transaction pattern", + reasoning="Multiple high-value transactions in short timeframe", + outcome="flagged_for_review", + confidence=0.87, + entities=["transaction_123", "customer_456"] ) -# Find similar decisions with KG features -precedents = context.find_precedents_advanced( - scenario="Mortgage application", - use_kg_features=True, - similarity_weights={"semantic": 0.5, "structural": 0.3, "category": 0.2} +# Advanced precedent search with KG features +precedents = context.find_precedents( + "suspicious transaction", + category="fraud_detection", + use_kg_features=True ) -# Analyze decision influence +# Comprehensive influence analysis influence = context.analyze_decision_influence(decision_id) ``` --- -## 🚨 The Problem: The Semantic Gap +## AgentContext - Your Agent's Brain + +The main interface that makes your agent intelligent. It handles memory, decisions, and knowledge organization automatically. + +### Quick Start +```python +from semantica.context import AgentContext +from semantica.vector_store import VectorStore + +# Create your intelligent agent +agent = AgentContext(vector_store=VectorStore(backend="inmemory", dimension=384)) + +# Your agent can now remember things +memory_id = agent.store("User asked about Python programming") +print(f"Agent remembered: {memory_id}") + +# And find information when needed +results = agent.retrieve("Python tutorials") +print(f"Agent found {len(results)} relevant memories") +``` + +### Easy Decision Learning +```python +# Your agent learns from its decisions +decision_id = agent.record_decision( + category="content_recommendation", + scenario="User wants Python tutorial", + reasoning="User mentioned being a beginner", + outcome="recommended_basics", + confidence=0.85 +) + +# Your agent can now find similar past decisions +similar_decisions = agent.find_precedents("Python tutorial", limit=3) +print(f"Agent found {len(similar_decisions)} similar past decisions") +``` + +### Getting Smarter Over Time +```python +# Enable all learning features +smart_agent = AgentContext( + vector_store=vector_store, + decision_tracking=True, # Learn from decisions + graph_expansion=True, # Find related information + advanced_analytics=True, # Understand patterns + kg_algorithms=True, # Advanced analysis + vector_store_features=True +) + +# Get insights about your agent's learning +insights = smart_agent.get_context_insights() +print(f"Total decisions learned: {insights.get('total_decisions', 0)}") +print(f"Decision categories: {list(insights.get('categories', {}).keys())}") +``` + +--- + +## The Problem: The Semantic Gap ### Most AI systems fail in high-stakes domains because they operate on **text similarity**, not **meaning**. @@ -248,45 +352,45 @@ The **semantic gap** is the fundamental disconnect between what AI systems can p --- -## 🆚 Semantica vs Traditional RAG +## Semantica vs Traditional RAG | Feature | Traditional RAG | Semantica | |:--------|:----------------|:----------| -| **Reasoning** | ❌ Black-box answers | ✅ Explainable reasoning paths | -| **Provenance** | ❌ No provenance | ✅ W3C PROV-O compliant lineage tracking | -| **Search** | ⚠️ Vector similarity only | ✅ Semantic + graph reasoning | -| **Quality** | ❌ No conflict handling | ✅ Explicit contradiction detection | -| **Safety** | ⚠️ Unsafe for high-stakes | ✅ Designed for governed environments | -| **Compliance** | ❌ No audit trails | ✅ Complete audit trails with integrity verification | +| **Reasoning** | Black-box answers | Explainable reasoning paths | +| **Provenance** | No provenance | W3C PROV-O compliant lineage tracking | +| **Search** | Vector similarity only | Semantic + graph reasoning | +| **Quality** | No conflict handling | Explicit contradiction detection | +| **Safety** | Unsafe for high-stakes | Designed for governed environments | +| **Compliance** | No audit trails | Complete audit trails with integrity verification | --- -## 🧩 Semantica Architecture +## Semantica Architecture -### 1️⃣ Input Layer — Governed Ingestion -- 📄 **Multiple Formats** — PDFs, DOCX, HTML, JSON, CSV, Excel, PPTX -- 🔧 **Docling Support** — Docling parser for table extraction -- 💾 **Data Sources** — Databases, APIs, streams, archives, web content -- 🎨 **Media Support** — Image parsing with OCR, audio/video metadata extraction -- 📊 **Single Pipeline** — Unified ingestion with metadata and source tracking +### Input Layer — Governed Ingestion +- **Multiple Formats** — PDFs, DOCX, HTML, JSON, CSV, Excel, PPTX +- **Docling Support** — Docling parser for table extraction +- **Data Sources** — Databases, APIs, streams, archives, web content +- **Media Support** — Image parsing with OCR, audio/video metadata extraction +- **Single Pipeline** — Unified ingestion with metadata and source tracking -### 2️⃣ Semantic Layer — Trust & Reasoning Engine -- 🔍 **Entity Extraction** — NER, normalization, classification -- 🔗 **Relationship Discovery** — Triplet generation, semantic links -- 📐 **Ontology Induction** — Automated domain rule generation -- 🔄 **Deduplication** — Jaro-Winkler similarity, conflict resolution -- ✅ **Quality Assurance** — Conflict detection, validation -- 📊 **Provenance Tracking** — W3C PROV-O compliant lineage tracking across all modules -- 🧠 **Reasoning Traces** — Explainable inference paths -- 🔐 **Change Management** — Version control with audit trails, checksums, compliance support +### Semantic Layer — Trust & Reasoning Engine +- **Entity Extraction** — NER, normalization, classification +- **Relationship Discovery** — Triplet generation, semantic links +- **Ontology Induction** — Automated domain rule generation +- **Deduplication** — Jaro-Winkler similarity, conflict resolution +- **Quality Assurance** — Conflict detection, validation +- **Provenance Tracking** — W3C PROV-O compliant lineage tracking across all modules +- **Reasoning Traces** — Explainable inference paths +- **Change Management** — Version control with audit trails, checksums, compliance support -### 3️⃣ Output Layer — Auditable Knowledge Assets -- 📊 **Knowledge Graphs** — Queryable, temporal, explainable -- 📐 **OWL Ontologies** — HermiT/Pellet validated, custom ontology import support -- 🔢 **Vector Embeddings** — FastEmbed by default -- ☁️ **AWS Neptune** — Amazon Neptune graph database support -- � **Apache AGE** — PostgreSQL graph extension with openCypher support -- �🔍 **Provenance** — Every AI response links back to: +### Output Layer — Auditable Knowledge Assets +- **Knowledge Graphs** — Queryable, temporal, explainable +- **OWL Ontologies** — HermiT/Pellet validated, custom ontology import support +- **Vector Embeddings** — FastEmbed by default +- **AWS Neptune** — Amazon Neptune graph database support +- **Apache AGE** — PostgreSQL graph extension with openCypher support +- **Provenance** — Every AI response links back to: - 📄 Source documents - 🏷️ Extracted entities & relations - 📐 Ontology rules applied @@ -294,27 +398,27 @@ The **semantic gap** is the fundamental disconnect between what AI systems can p --- -## 🏥 Built for High-Stakes Domains +## Built for High-Stakes Domains Designed for domains where **mistakes have real consequences** and **every decision must be accountable**: -- **🏥 Healthcare & Life Sciences** — Clinical decision support, drug interaction analysis, medical literature reasoning, patient safety tracking -- **💰 Finance & Risk** — Fraud detection, regulatory support (SOX, GDPR, MiFID II), credit risk assessment, algorithmic trading validation -- **⚖️ Legal & Compliance** — Evidence-backed legal research, contract analysis, regulatory change tracking, case law reasoning -- **🔒 Cybersecurity & Intelligence** — Threat attribution, incident response, security audit trails, intelligence analysis -- **🏛️ Government & Defense** — Governed AI systems, policy decisions, classified information handling, defense intelligence -- **🏭 Critical Infrastructure** — Power grid management, transportation safety, water treatment, emergency response -- **🚗 Autonomous Systems** — Self-driving vehicles, drone navigation, robotics safety, industrial automation +- **Healthcare & Life Sciences** — Clinical decision support, drug interaction analysis, medical literature reasoning, patient safety tracking +- **Finance & Risk** — Fraud detection, regulatory support (SOX, GDPR, MiFID II), credit risk assessment, algorithmic trading validation +- **Legal & Compliance** — Evidence-backed legal research, contract analysis, regulatory change tracking, case law reasoning +- **Cybersecurity & Intelligence** — Threat attribution, incident response, security audit trails, intelligence analysis +- **Government & Defense** — Governed AI systems, policy decisions, classified information handling, defense intelligence +- **Critical Infrastructure** — Power grid management, transportation safety, water treatment, emergency response +- **Autonomous Systems** — Self-driving vehicles, drone navigation, robotics safety, industrial automation --- ## 👥 Who Uses Semantica? -- **🤖 AI / ML Engineers** — Building explainable GraphRAG & agents -- **⚙️ Data Engineers** — Creating governed semantic pipelines -- **📊 Knowledge Engineers** — Managing ontologies & KGs at scale -- **🏢 Enterprise Teams** — Requiring trustworthy AI infrastructure -- **🛡️ Risk & Compliance Teams** — Needing audit-ready systems +- **AI / ML Engineers** — Building explainable GraphRAG & agents +- **Data Engineers** — Creating governed semantic pipelines +- **Knowledge Engineers** — Managing ontologies & KGs at scale +- **Enterprise Teams** — Requiring trustworthy AI infrastructure +- **Risk & Compliance Teams** — Needing audit-ready systems --- @@ -604,12 +708,12 @@ is_valid = kg_manager.verify_checksum(snapshot) ``` **What We Provide:** -- 🔐 **Persistent Storage** — SQLite and in-memory backends implemented -- 📊 **Detailed Diffs** — Entity-level and relationship-level change tracking -- ✅ **Data Integrity** — SHA-256 checksums with tamper detection -- 📝 **Standardized Metadata** — ChangeLogEntry with author, timestamp, description -- ⚡ **Performance Tested** — Tested with large-scale entity datasets -- 🧪 **Test Coverage** — Comprehensive test coverage covering core functionality +- **Persistent Storage** — SQLite and in-memory backends implemented +- **Detailed Diffs** — Entity-level and relationship-level change tracking +- **Data Integrity** — SHA-256 checksums with tamper detection +- **Standardized Metadata** — ChangeLogEntry with author, timestamp, description +- **Performance Tested** — Tested with large-scale entity datasets +- **Test Coverage** — Comprehensive test coverage covering core functionality **Compliance Note:** Provides technical infrastructure (audit trails, checksums, temporal tracking) that supports compliance efforts for HIPAA, SOX, FDA 21 CFR Part 11. Organizations must implement additional policies and procedures for full regulatory compliance. @@ -725,11 +829,11 @@ retriever = context.retriever # Access underlying ContextRetriever results = retriever.retrieve( query="What is the user building?", max_results=10, - use_graph_expansion=True + graph_expansion=True ) # Retrieve with context expansion -results = context.retrieve("What is the user building?", use_graph_expansion=True) +results = context.retrieve("What is the user building?", graph_expansion=True) # Query with reasoning and LLM-generated responses llm_provider = Groq(model="llama-3.1-8b-instant", api_key=os.getenv("GROQ_API_KEY")) @@ -745,20 +849,22 @@ reasoned_result = context.query_with_reasoning( - **ContextRetriever**: Performs hybrid retrieval combining vector search, graph traversal, and memory for optimal context relevance - **AgentContext**: High-level interface integrating Context Graph and Context Retriever for GraphRAG applications -#### Context Graphs: Decision Tracking +#### Context Graphs: Advanced Decision Tracking & Analytics ```python from semantica.context import AgentContext, ContextGraph from semantica.vector_store import VectorStore +# Initialize with advanced decision tracking context = AgentContext( vector_store=VectorStore(backend="inmemory", dimension=128), - knowledge_graph=ContextGraph(), - enable_decision_tracking=True, - enable_kg_algorithms=False, # semantic-only precedent search + knowledge_graph=ContextGraph(advanced_analytics=True), + decision_tracking=True, + kg_algorithms=True, # Enable advanced graph analytics ) -decision_id = context.record_decision( +# Easy decision recording with convenience methods +decision_id = context.graph_builder.add_decision( category="credit_approval", scenario="High-risk credit limit increase", reasoning="Recent velocity-check failure and prior fraud flag", @@ -767,10 +873,79 @@ decision_id = context.record_decision( entities=["customer:jessica_norris"], ) -precedents = context.find_precedents( - scenario="High-risk customer credit increase", +# Find similar decisions with advanced analytics +similar_decisions = context.graph_builder.find_similar_decisions( + scenario="credit increase", category="credit_approval", - limit=5, + max_results=5, +) + +# Analyze decision impact and influence +impact_analysis = context.graph_builder.analyze_decision_impact(decision_id) +node_importance = context.graph_builder.get_node_importance("customer:jessica_norris") + +# Check compliance with business rules +compliance = context.graph_builder.check_decision_rules({ + "category": "credit_approval", + "scenario": "Credit limit increase", + "reasoning": "Risk assessment completed", + "outcome": "rejected", + "confidence": 0.78 +}) +``` + +**Enhanced Features:** +- **Easy-to-Use Methods**: 10 convenience methods for common operations +- **Decision Analytics**: Influence analysis, centrality measures, community detection +- **Policy Engine**: Automated compliance checking with business rules +- **Causal Analysis**: Trace decision causality and impact chains +- **Graph Analytics**: Advanced KG algorithms (Node2Vec, centrality, community detection) +- **Hybrid Search**: Semantic + structural + category similarity +- **Production Ready**: Scalable architecture with comprehensive error handling + +## Configuration Options + +### Simple Setup (Most Common) +```python +# Just memory and basic learning +agent = AgentContext(vector_store=vector_store) +``` + +### Smart Setup (Recommended) +```python +# Memory + decision learning +agent = AgentContext( + vector_store=vector_store, + decision_tracking=True, + graph_expansion=True +) +``` + +### Complete Setup (Maximum Power) +```python +# Everything enabled +agent = AgentContext( + vector_store=vector_store, + knowledge_graph=ContextGraph(advanced_analytics=True), + decision_tracking=True, + graph_expansion=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True +) +``` + +### ContextGraph Options +```python +# Basic knowledge graph +graph = ContextGraph() + +# Advanced knowledge graph +graph = ContextGraph( + advanced_analytics=True, # Enable smart algorithms + centrality_analysis=True, # Find important concepts + community_detection=True, # Find groups of related concepts + node_embeddings=True # Understand concept similarity ) ``` diff --git a/docs/examples.md b/docs/examples.md index 5e52862a..48757e7d 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -332,7 +332,7 @@ from semantica.reasoning import Reasoner context = AgentContext( vector_store=vs, knowledge_graph=kg, - use_graph_expansion=True, + graph_expansion=True, hybrid_alpha=0.7 ) diff --git a/docs/reference/context.md b/docs/reference/context.md index b5833f68..276d2632 100644 --- a/docs/reference/context.md +++ b/docs/reference/context.md @@ -1,767 +1,463 @@ # Context Module Reference -> **The central nervous system for intelligent agents, managing memory, knowledge graphs, context graphs, decision tracking, and advanced context retrieval with KG algorithms and vector store integration.** +> **The intelligent brain for AI agents, providing memory, decision tracking, and knowledge organization with easy-to-use interfaces that make building smart agents simple and effective.** --- -## 🎯 System Overview +## 🎯 Overview -The **Context Module** provides agents with a persistent, searchable, and structured memory system with advanced decision tracking capabilities and **context graphs** for sophisticated knowledge representation, ensuring predictable state management and compatibility with modern vector stores and graph databases. +The **Context Module** gives your AI agents the ability to **remember**, **learn**, and **make smarter decisions** through intelligent memory management and knowledge organization. It's designed to be both powerful for production use and simple enough for rapid development. ### Key Capabilities
-- :material-brain:{ .lg .middle } **Hierarchical Memory** +- :material-brain:{ .lg .middle } **Smart Memory** --- - Mimics human memory with a fast, token-limited Short-Term buffer and infinite Long-Term vector storage. + Human-like memory that stores conversations, learns from experience, and retrieves relevant information when needed. -- :material-graph-outline:{ .lg .middle } **GraphRAG** +- :material-graph-outline:{ .lg .middle } **Decision Intelligence** --- - Combines unstructured vector search with structured knowledge graph traversal for deep contextual understanding. + Track decisions, learn from past choices, and make consistent, improving decisions over time. -- :material-scale-balance:{ .lg .middle } **Hybrid Retrieval** +- :material-lightbulb:{ .lg .middle } **Easy-to-Use API** --- - Intelligently blends Keyword (BM25), Vector (Dense), and Graph (Relational) scores for optimal relevance. + Simple methods that make complex features accessible without overwhelming complexity. -- :material-lightning-bolt:{ .lg .middle } **Token Management** +- :material-search:{ .lg .middle } **Smart Retrieval** --- - Automatic FIFO and importance-based pruning to keep context within LLM window limits. + Find relevant information quickly using hybrid search that understands context and relationships. -- :material-link-variant:{ .lg .middle } **Entity Linking** +- :material-account-tree:{ .lg .middle } **Knowledge Organization** --- - Resolves ambiguities by linking text mentions to unique entities in the knowledge graph. + Build intelligent knowledge graphs that understand relationships and context. -- :material-gavel:{ .lg .middle } **Decision Tracking** +- :material-trending-up:{ .lg .middle } **Learning & Analytics** --- - Complete decision lifecycle management with precedent search, causal analysis, and policy compliance. + Get insights about agent performance, decision patterns, and knowledge growth. -- :material-chart-line:{ .lg .middle } **KG Algorithms** +- :material-security:{ .lg .middle } **Production Ready** --- - Advanced graph analytics including centrality, community detection, embeddings, and link prediction. - -- :material-magnify:{ .lg .middle } **Vector Store Features** - - --- - - Hybrid search with custom similarity weights and advanced filtering capabilities. - -- :material-graph:{ .lg .middle } **Context Graphs** - - --- - - Structured knowledge representation with entity relationships, decision history, and semantic context for sophisticated reasoning. + Scalable, reliable, and tested for real-world applications.
-!!! tip "When to Use" - - **Memory Persistence**: Enabling agents to remember user preferences and history. - - **Complex Retrieval**: When simple vector search fails to capture relationships. - - **Knowledge Graph**: Building a structured world model from unstructured text. - - **Decision Management**: Tracking, analyzing, and learning from decisions. - - **Advanced Analytics**: Understanding influence, patterns, and relationships in decisions. +!!! tip "Perfect For" + - **AI Agents** that need to remember conversations and learn from decisions + - **Chatbots** that become smarter with every interaction + - **Decision Systems** that need to track choices and learn from patterns + - **Knowledge Management** that organizes information intelligently + - **Production Applications** that require reliable, scalable solutions --- -## 🏗️ Architecture Components +## 🤖 AgentContext - Your Agent's Brain -### AgentContext (The Orchestrator) -The high-level facade that unifies all context operations. It routes data to the appropriate subsystems (Memory, Graph, Vector Store, Decision Tracking) and manages the lifecycle of context. +The main interface that makes your agent intelligent. It handles memory, decisions, and knowledge organization automatically. -#### **Constructor Parameters** - -- `vector_store` (Required): The backing vector database instance (e.g., FAISS, Weaviate) -- `knowledge_graph` (Optional): The graph store instance for structured knowledge -- `token_limit` (Default: `2000`): The maximum number of tokens allowed in short-term memory before pruning occurs -- `short_term_limit` (Default: `10`): The maximum number of distinct memory items in short-term memory -- `hybrid_alpha` (Default: `0.5`): The weighting factor for retrieval (`0.0` = Pure Vector, `1.0` = Pure Graph) -- `use_graph_expansion` (Default: `True`): Whether to fetch neighbors of retrieved nodes from the graph -- `enable_decision_tracking` (Default: `False`): Enable advanced decision tracking features -- `enable_advanced_analytics` (Default: `False`): Enable KG algorithms and analytics -- `enable_kg_algorithms` (Default: `False`): Enable knowledge graph algorithm integration -- `enable_vector_store_features` (Default: `False`): Enable advanced vector store features - -#### **Core Methods** - -| Method | Description | -|--------|-------------| -| `store(content, ...)` | Writes information to memory. Handles auto-detection, write-through to vector store, and entity extraction. | -| `retrieve(query, ...)` | Fetches relevant context using hybrid search (Vector + Graph) and reranking. | -| `query_with_reasoning(query, llm_provider, ...)` | **GraphRAG with multi-hop reasoning**: Retrieves context, builds reasoning paths, and generates LLM-based natural language responses grounded in the knowledge graph. | -| `record_decision(category, scenario, reasoning, outcome, confidence, ...)` | Records decisions with full context and metadata for tracking and analysis. | -| `find_precedents(scenario, category, ...)` | Finds similar decisions using advanced search capabilities. | -| `find_precedents_advanced(scenario, similarity_weights, ...)` | Enhanced precedent search with KG features and custom similarity weights. | -| `analyze_decision_influence(decision_id)` | Analyzes decision influence using KG algorithms and centrality measures. | -| `predict_decision_relationships(decision_id)` | Predicts relationships between decisions using link prediction algorithms. | -| `get_context_insights()` | Returns comprehensive system analytics and feature status. | -| `get_causal_chain(decision_id, direction, max_depth)` | Traces decision causality and influence chains. | - -#### **Code Example** +### Quick Start ```python from semantica.context import AgentContext from semantica.vector_store import VectorStore -# 1. Initialize with Advanced Features -vs = VectorStore(backend="faiss", dimension=768) -context = AgentContext( - vector_store=vs, - knowledge_graph=kg, - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True +# Create your intelligent agent +agent = AgentContext(vector_store=VectorStore(backend="inmemory", dimension=384)) + +# Your agent can now remember things +memory_id = agent.store("User asked about Python programming") +print(f"Agent remembered: {memory_id}") + +# And find information when needed +results = agent.retrieve("Python tutorials") +print(f"Agent found {len(results)} relevant memories") +``` + +### Easy Decision Learning +```python +# Your agent learns from its decisions +decision_id = agent.record_decision( + category="content_recommendation", + scenario="User wants Python tutorial", + reasoning="User mentioned being a beginner", + outcome="recommended_basics", + confidence=0.85 ) -# 2. Store Memory -context.store( - "User is working on a React project.", - conversation_id="session_1", - user_id="user_123" +# Your agent can now find similar past decisions +similar_decisions = agent.find_precedents("Python tutorial", limit=3) +print(f"Agent found {len(similar_decisions)} similar past decisions") +``` + +### Getting Smarter Over Time +```python +# Enable all learning features +smart_agent = AgentContext( + vector_store=vector_store, + decision_tracking=True, # Learn from decisions + graph_expansion=True, # Find related information + advanced_analytics=True, # Understand patterns + kg_algorithms=True, # Advanced analysis + vector_store_features=True ) -# 3. Record Decision -decision_id = context.record_decision( - category="approval", - scenario="Loan application for first-time homebuyer", - reasoning="Strong credit score (750), stable employment, 20% down payment", - outcome="approved", - confidence=0.94, - decision_maker="loan_officer_001" +# Get insights about your agent's learning +insights = smart_agent.get_context_insights() +print(f"Total decisions learned: {insights.get('total_decisions', 0)}") +print(f"Decision categories: {list(insights.get('categories', {}).keys())}") +``` + +### Core Methods + +| Method | What It Does | When to Use | +|--------|-------------|------------| +| `store(content, ...)` | Remember information | Store conversations, facts, user preferences | +| `retrieve(query, ...)` | Find relevant memories | Search for information when needed | +| `record_decision(category, scenario, reasoning, outcome, confidence, ...)` | Learn from decisions | Track choices and improve over time | +| `find_precedents(scenario, category, ...)` | Find similar decisions | Make consistent choices based on experience | +| `get_context_insights()` | Understand performance | Get analytics about your agent | + +### Advanced Features +```python +# Enable all features for maximum intelligence +agent = AgentContext( + vector_store=vector_store, + knowledge_graph=ContextGraph(advanced_analytics=True), + decision_tracking=True, + graph_expansion=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True ) -# 4. Retrieve Context -results = context.retrieve("What is the user building?") - -# 5. Find Similar Decisions -precedents = context.find_precedents_advanced( - scenario="High-value credit application", - category="approval", - use_kg_features=True, - similarity_weights={"semantic": 0.5, "structural": 0.3, "category": 0.2} -) - -# 6. Analyze Decision Influence -influence = context.analyze_decision_influence(decision_id) - -# 7. Get Context Insights -insights = context.get_context_insights() - -# 8. Query with Reasoning (GraphRAG) +# Query with multi-hop reasoning (GraphRAG) from semantica.llms import Groq import os -llm_provider = Groq( - model="llama-3.1-8b-instant", - api_key=os.getenv("GROQ_API_KEY") -) - -result = context.query_with_reasoning( - query="What IPs are associated with security alerts?", - llm_provider=llm_provider, - max_results=10, - max_hops=2 -) - -print(f"Response: {result['response']}") -print(f"Reasoning Path: {result['reasoning_path']}") -print(f"Confidence: {result['confidence']:.3f}") -``` - ---- - -### Decision Tracking System - -#### DecisionRecorder (The Decision Engine) -Records decisions with full context, policy applications, and provenance tracking. - -**Key Methods:** -| Method | Description | -|--------|-------------| -| `record_decision(category, scenario, reasoning, outcome, confidence, ...)` | Records decisions with full context and metadata | -| `apply_policy(decision_id, policy_id)` | Applies policies to decisions and checks compliance | -| `create_approval_chain(decision_id, approvers)` | Creates multi-level approval workflows | -| `track_provenance(decision_id, source_info)` | Tracks decision provenance and lineage | - -#### DecisionQuery (The Decision Search Engine) -Advanced decision querying with precedent search, filtering, and hybrid search operations. - -**Key Methods:** -| Method | Description | -|--------|-------------| -| `find_precedents_hybrid(scenario, category, limit)` | Hybrid search with KG and vector store integration | -| `find_precedents_advanced(scenario, similarity_weights, ...)` | Enhanced search with custom similarity weights | -| `analyze_decision_influence(decision_id)` | Analyze decision influence using KG algorithms | -| `predict_decision_relationships(decision_id)` | Predict relationships between decisions | -| `multi_hop_reasoning(decision_id, max_hops)` | Multi-hop reasoning for complex relationships | -| `get_decision_statistics()` | Get comprehensive decision analytics | - -#### CausalChainAnalyzer (The Influence Engine) -Analyzes decision causality, influence chains, and precedent relationships. - -**Key Methods:** -| Method | Description | -|--------|-------------| -| `get_causal_chain(decision_id, direction, max_depth)` | Trace causal chains from decisions | -| `find_influenced_decisions(decision_id)` | Find decisions influenced by a decision | -| `find_influencing_decisions(decision_id)` | Find decisions that influenced a decision | -| `analyze_causal_impact(decision_id, max_depth)` | Analyze causal impact and scope | -| `calculate_influence_score(decision_id)` | Calculate decision influence scores | - -#### PolicyEngine (The Governance Engine) -Policy management with versioning, compliance checking, and impact analysis. - -**Key Methods:** -| Method | Description | -|--------|-------------| -| `create_policy(name, rules, category)` | Create new policies with rules and constraints | -| `check_compliance(decision_id, policy_id)` | Check decision compliance with policies | -| `analyze_impact(policy_id, time_range)` | Analyze policy impact on decisions | -| `get_violations(decision_id)` | Get policy violations for decisions | - -#### **Decision Tracking Example** -```python -from semantica.context import DecisionRecorder, DecisionQuery, CausalChainAnalyzer, PolicyEngine - -# Initialize decision tracking components -recorder = DecisionRecorder(graph_store=kg, vector_store=vs) -query = DecisionQuery(graph_store=kg, vector_store=vs) -analyzer = CausalChainAnalyzer(graph_store=kg) -policy_engine = PolicyEngine(graph_store=kg) - -# Record a decision -decision_id = recorder.record_decision( - category="loan_approval", - scenario="Mortgage application for first-time homebuyer", - reasoning="Strong credit score (750), stable employment, 20% down payment", - outcome="approved", - confidence=0.94, - decision_maker="loan_officer_001" -) - -# Find similar decisions (precedents) -precedents = query.find_precedents_hybrid( - scenario="Mortgage application", - category="loan_approval", - limit=10 -) - -# Analyze decision influence -influence = analyzer.analyze_decision_influence(decision_id) - -# Check policy compliance -compliance = policy_engine.check_compliance(decision_id, "lending_policy_001") - -# Trace causal chain -causal_chain = analyzer.get_causal_chain(decision_id, "downstream", max_depth=3) -``` - ---- - -### Knowledge Graph Algorithm Integration - -#### Supported KG Algorithms -- **Centrality Analysis**: Degree, betweenness, closeness, eigenvector centrality -- **Community Detection**: Modularity-based community identification -- **Node Embeddings**: Node2Vec embeddings for similarity analysis -- **Path Finding**: Shortest path and advanced path algorithms -- **Link Prediction**: Relationship prediction between entities -- **Similarity Calculation**: Multi-type similarity measures - -#### Enhanced ContextGraph Features -```python -from semantica.context import ContextGraph - -# Initialize with KG Algorithms -graph = ContextGraph( - enable_advanced_analytics=True, - enable_centrality_analysis=True, - enable_community_detection=True, - enable_node_embeddings=True -) - -# Add nodes and edges -graph.add_node("Python", type="language", properties={"popularity": "high"}) -graph.add_edge("Python", "Programming", type="related_to") - -# Advanced analytics -centrality = graph.get_node_centrality("Python") -similar = graph.find_similar_nodes("Python", similarity_type="content") -analysis = graph.analyze_graph_with_kg() - -# Decision integration -graph.add_decision(decision_id, decision_data) -precedents = graph.find_precedents("loan_approval") -``` - ---- - -### Vector Store Integration - -#### Hybrid Search Features -- **Semantic + Structural Similarity**: Combined similarity scoring -- **Custom Similarity Weights**: Configurable similarity scoring -- **Advanced Precedent Search**: KG-enhanced similarity search -- **Multi-Embedding Support**: Multiple embedding types -- **Metadata Filtering**: Advanced filtering capabilities - -#### Code Example -```python -# Hybrid search with custom weights -precedents = query.find_precedents_hybrid( - scenario="Loan application", - category="approval", - limit=10, - similarity_weights={ - "semantic": 0.6, - "structural": 0.3, - "category": 0.1 - } -) -``` - ---- - -### AgentMemory (The Storage Engine) -Manages the storage and lifecycle of memory items. It implements the **Hierarchical Memory** pattern. - -#### **Features & Functions** -* **Short-Term Memory (Working Memory)** - * *Structure*: An in-memory list of recent `MemoryItem` objects. - * *Purpose*: Provides immediate context for the ongoing conversation. - * *Pruning Logic*: - * **FIFO**: Removes the oldest items first when limits are reached. - * **Token-Aware**: Calculates token counts to ensure the total buffer size stays under `token_limit`. -* **Long-Term Memory (Episodic Memory)** - * *Structure*: Vector embeddings stored in the `vector_store`. - * *Purpose*: Persists history indefinitely for semantic retrieval. - * *Synchronization*: Automatically syncs with Short-term memory during `store()` operations. -* **Retention Policy** - * *Time-Based*: Can automatically delete memories older than `retention_days`. - * *Count-Based*: Can limit the total number of memories to `max_memories`. - -#### **Key Methods** - -| Method | Description | -|--------|-------------| -| `store_vectors()` | Handles the low-level interaction with concrete Vector Store implementations. | -| `_prune_short_term_memory()` | Internal algorithm that enforces token and count limits. | -| `get_conversation_history()` | Retrieves a chronological list of interactions for a specific session. | - -#### **Code Example** -```python -# Accessing via AgentContext -memory = context.memory - -# Get conversation history -history = memory.get_conversation_history("session_1") -for item in history: - print(f"[{item.timestamp}] {item.content}") - -# Get statistics -stats = memory.get_statistics() -print(f"Stored Memories: {stats['total_memories']}") -``` - ---- - -### ContextGraph (The Knowledge Structure) -Manages the structured relationships between entities. It provides the "World Model" for the agent with advanced KG algorithm integration and serves as the foundation for **Context Graphs** that enable sophisticated reasoning and decision analysis. - -#### **What are Context Graphs?** -**Context Graphs** are structured representations of knowledge that capture: -- **Entity Relationships**: How concepts, people, and decisions are connected -- **Semantic Context**: The meaning and relevance of information within specific domains -- **Decision History**: How past decisions influence current and future choices -- **Knowledge Evolution**: How understanding grows and changes over time - -#### **Key Features of Context Graphs** -* **Dictionary-Based Interface** - * *Design*: Uses standard Python dictionaries for nodes and edges, removing dependencies on complex interface classes. - * *Benefit*: simpler serialization and easier integration with external APIs. -* **Advanced Graph Traversal** - * *Adjacency List*: optimized internal structure for fast neighbor lookups. - * *Multi-Hop Search*: Can traverse `k` hops from a starting node to find indirect connections. - * *Path Finding*: Shortest path and advanced path algorithms for relationship discovery. -* **Rich Node & Edge Types** - * *Typed Schema*: Supports distinct types for nodes (e.g., "Person", "Concept", "Decision") and edges (e.g., "KNOWS", "RELATED_TO", "INFLUENCES"). - * *Metadata Support*: Rich properties and attributes for detailed context capture. -* **Advanced Analytics Integration** - * *KG Algorithm Integration*: Centrality, community detection, embeddings, path finding - * *Decision Integration*: Store and analyze decisions in graph context - * *Similarity Analysis*: Advanced node similarity with multiple measures - * *Influence Analysis**: Track how decisions and entities influence each other - -#### **Context Graph Use Cases** -- **Knowledge Management**: Build and query structured knowledge bases -- **Decision Support**: Trace decision precedents and influence patterns -- **Recommendation Systems**: Find related concepts and entities -- **Social Network Analysis**: Understand relationships and influence -- **Research Networks**: Map collaborations and citation patterns - -#### **Key Methods** - -| Method | Description | -|--------|-------------| -| `add_nodes(nodes)` | Bulk adds nodes using a list of dictionaries. | -| `add_edges(edges)` | Bulk adds edges using a list of dictionaries. | -| `get_neighbors(node_id, hops)` | Returns connected nodes within a specified distance. | -| `query(query_str)` | Performs keyword-based search specifically on graph nodes. | -| `analyze_graph_with_kg()` | Comprehensive graph analysis with KG algorithms. | -| `get_node_centrality(node_id)` | Get centrality measures for specific nodes. | -| `find_similar_nodes(node_id, similarity_type)` | Find similar nodes using advanced similarity. | -| `add_decision(decision_id, decision_data)` | Add decisions with full context integration. | -| `find_precedents(scenario, category)` | Find decision precedents using graph traversal. | -| `trace_influence_paths(entity_id, max_depth)` | Trace how influence propagates through the graph. | -| `get_graph_metrics()` | Get comprehensive graph statistics and health metrics. | - -#### **Code Example** -```python -from semantica.context import ContextGraph - -# Initialize Context Graph with advanced features -graph = ContextGraph( - enable_advanced_analytics=True, - enable_centrality_analysis=True, - enable_community_detection=True, - enable_node_embeddings=True -) - -# Build Context Graph - Add entities and relationships -graph.add_nodes([ - { - "id": "Python", - "type": "Language", - "properties": { - "paradigm": "OO", - "popularity": "high", - "domain": "programming" - } - }, - { - "id": "FastAPI", - "type": "Framework", - "properties": { - "language": "Python", - "use_case": "web_api", - "performance": "high" - } - }, - { - "id": "DataScience", - "type": "Domain", - "properties": { - "description": "Data analysis and machine learning", - "tools": ["Python", "R", "SQL"] - } - } -]) - -# Create relationships in Context Graph -graph.add_edges([ - { - "source_id": "FastAPI", - "target_id": "Python", - "type": "WRITTEN_IN", - "properties": {"strength": 0.9} - }, - { - "source_id": "Python", - "target_id": "DataScience", - "type": "USED_IN", - "properties": {"popularity": 0.95} - }, - { - "source_id": "FastAPI", - "target_id": "DataScience", - "type": "SUPPORTS", - "properties": {"use_case": "api_for_ml"} - } -]) - -# Advanced Context Graph Analytics -centrality = graph.get_node_centrality("Python") -similar = graph.find_similar_nodes("Python", similarity_type="content") -analysis = graph.analyze_graph_with_kg() - -# Decision Integration in Context Graph -graph.add_decision("decision_001", { - "category": "technology_choice", - "scenario": "Framework selection for web API", - "reasoning": "Python ecosystem with FastAPI provides best performance", - "outcome": "selected_fastapi", - "confidence": 0.92 -}) - -# Find decision precedents in Context Graph -precedents = graph.find_precedents("technology_choice") - -# Trace influence through Context Graph -influence_paths = graph.trace_influence_paths("Python", max_depth=3) -``` - ---- - -### Production Graph Store Integration - -For production environments, you can replace the in-memory `ContextGraph` with a persistent `GraphStore` (Neo4j, FalkorDB) by passing it to the `knowledge_graph` parameter. - -```python -from semantica.context import AgentContext -from semantica.graph_store import GraphStore - -# 1. Initialize Persistent Graph Store (Neo4j) -gs = GraphStore( - backend="neo4j", - uri="bolt://localhost:7687", - user="neo4j", - password="password" -) - -# 2. Initialize Agent Context with Persistent Graph and Advanced Features -context = AgentContext( - vector_store=vs, # Your VectorStore instance - knowledge_graph=gs, # Your persistent GraphStore - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True, - use_graph_expansion=True -) - -# Now all graph operations (store, retrieve, build_graph) use Neo4j directly. -``` - ---- - -### ContextRetriever (The Search Engine) -The retrieval logic that powers the `retrieve()` command. It implements the **Hybrid Retrieval** algorithm with advanced KG and vector store integration. - -#### **Retrieval Strategy** -1. **Short-Term Check**: Scans the in-memory buffer for immediate, exact-match relevance. -2. **Vector Search**: Queries the `vector_store` for semantically similar long-term memories. -3. **Graph Expansion**: - * Identifies entities in the query. - * Finds those entities in the `ContextGraph`. - * Traverses edges to find related concepts that might not match keywords (e.g., finding "Python" when searching for "Coding"). -4. **Hybrid Scoring**: - * Formula: `Final_Score = (Vector_Score * (1 - α)) + (Graph_Score * α)` - * Allows tuning the balance between semantic similarity and structural relevance. -5. **KG Algorithm Enhancement**: Uses centrality, community detection, and similarity for advanced ranking. - -#### **Code Example** -```python -# The retriever is automatically used by AgentContext.retrieve() -# But can be accessed directly if needed: - -retriever = context.retriever - -# Perform a manual retrieval with advanced features -results = retriever.retrieve( - query="web frameworks", - max_results=5, - use_kg_features=True, - similarity_weights={"semantic": 0.7, "structural": 0.3} -) -``` - ---- - -### GraphRAG with Multi-Hop Reasoning - -The `query_with_reasoning()` method extends traditional retrieval by performing multi-hop graph traversal and generating natural language responses using LLMs. This enables deeper understanding of relationships and context-aware answer generation. - -#### **How It Works** - -1. **Context Retrieval**: Retrieves relevant context using hybrid search (vector + graph) -2. **Entity Extraction**: Extracts entities from query and retrieved context -3. **Multi-Hop Reasoning**: Traverses knowledge graph up to N hops to find related entities -4. **Reasoning Path Construction**: Builds reasoning chains showing entity relationships -5. **LLM Response Generation**: Generates natural language response grounded in graph context -6. **KG Algorithm Enhancement**: Uses centrality and community detection for enhanced reasoning - -#### **Key Features** - -- **Multi-Hop Reasoning**: Traverses graph up to configurable hops (default: 2) -- **Reasoning Trace**: Shows entity relationship paths used in reasoning -- **Grounded Responses**: LLM generates answers citing specific graph entities -- **Multiple LLM Providers**: Supports Groq, OpenAI, HuggingFace, and LiteLLM (100+ LLMs) -- **Fallback Handling**: Returns context with reasoning path if LLM unavailable -- **KG Algorithm Integration**: Uses centrality and community detection for enhanced reasoning - -#### **Method Signature** - -```python -def query_with_reasoning( - self, - query: str, - llm_provider: Any, # LLM provider from semantica.llms - max_results: int = 10, - max_hops: int = 2, - **kwargs -) -> Dict[str, Any]: -``` - -**Parameters:** -- `query` (str): User query -- `llm_provider`: LLM provider instance (from `semantica.llms`) -- `max_results` (int): Maximum context results to retrieve (default: 10) -- `max_hops` (int): Maximum graph traversal hops (default: 2) -- `**kwargs`: Additional retrieval options - -**Returns:** -- `response` (str): Generated natural language answer -- `reasoning_path` (str): Multi-hop reasoning trace -- `sources` (List[Dict]): Retrieved context items used -- `confidence` (float): Overall confidence score -- `num_sources` (int): Number of sources retrieved -- `num_reasoning_paths` (int): Number of reasoning paths found - -#### **Code Example** - -```python -from semantica.context import AgentContext -from semantica.llms import Groq -from semantica.vector_store import VectorStore -import os - -# Initialize context with advanced features -context = AgentContext( - vector_store=VectorStore(backend="faiss"), - knowledge_graph=kg, - enable_advanced_analytics=True, - enable_kg_algorithms=True -) - -# Configure LLM provider -llm_provider = Groq( - model="llama-3.1-8b-instant", - api_key=os.getenv("GROQ_API_KEY") -) - -# Query with reasoning -result = context.query_with_reasoning( - query="What IPs are associated with security alerts?", - llm_provider=llm_provider, - max_results=10, - max_hops=2 -) - -# Access results -print(f"Response: {result['response']}") -print(f"\nReasoning Path: {result['reasoning_path']}") -print(f"Confidence: {result['confidence']:.3f}") -``` - -#### **Using Different LLM Providers** - -```python -# Groq -from semantica.llms import Groq llm = Groq(model="llama-3.1-8b-instant", api_key=os.getenv("GROQ_API_KEY")) -# OpenAI -from semantica.llms import OpenAI -llm = OpenAI(model="gpt-4", api_key=os.getenv("OPENAI_API_KEY")) - -# LiteLLM (100+ providers) -from semantica.llms import LiteLLM -llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514") - -# Use with query_with_reasoning -result = context.query_with_reasoning( - query="Your question here", +result = agent.query_with_reasoning( + query="What technologies work well together?", llm_provider=llm, - max_hops=3 + max_hops=2 ) -``` -!!! tip "When to Use" - - **Complex Queries**: When simple retrieval doesn't capture relationships - - **Explainable AI**: When you need to show reasoning paths - - **Multi-Hop Questions**: "What IPs are associated with alerts that affect users?" - - **Grounded Responses**: When you need answers citing specific graph entities - - **Decision Analysis**: When analyzing decision influence and relationships +print(f"Response: {result['response']}") +print(f"Reasoning: {result['reasoning_path']}") +``` --- -### EntityLinker (The Connector) -Resolves text mentions to unique entities and assigns URIs. +## 🏗️ ContextGraph - Knowledge Organization -#### **Key Methods** +When you need to organize complex information and understand relationships, ContextGraph helps you build intelligent knowledge networks. -| Method | Description | -|--------|-------------| -| `link_entities(source, target, type)` | Creates a link between two entities. | -| `assign_uri(entity_name, type)` | Generates a consistent URI for an entity. | - -#### **Code Example** +### Easy Knowledge Graph Building ```python -from semantica.context import EntityLinker +from semantica.context import ContextGraph -linker = EntityLinker(knowledge_graph=graph) +# Create a knowledge graph +knowledge = ContextGraph(advanced_analytics=True) -# Link two entities -linker.link_entities( - source_entity_id="Python", - target_entity_id="Programming", - link_type="IS_A", - confidence=0.95 +# Add things you want to remember (nodes) +knowledge.add_node("Python", "language", properties={"popularity": "high"}) +knowledge.add_node("Programming", "concept", properties={"type": "skill"}) +knowledge.add_node("FastAPI", "framework", properties={"language": "Python"}) + +# Connect related things (edges) +knowledge.add_edge("Python", "Programming", "related_to") +knowledge.add_edge("Python", "FastAPI", "supports") +knowledge.add_edge("FastAPI", "Programming", "used_for") +``` + +### Easy Decision Management +```python +# Record decisions in your knowledge graph +decision_id = knowledge.add_decision( + category="technology_choice", + scenario="Framework selection for web API", + reasoning="FastAPI provides better performance for Python APIs", + outcome="selected_fastapi", + confidence=0.92, + entities=["Python", "FastAPI", "web_project"] +) + +# Find similar decisions easily +similar = knowledge.find_similar_decisions( + scenario="web framework", + category="technology_choice", + max_results=3 +) + +print(f"Found {len(similar)} similar decisions") +``` + +### Smart Analytics +```python +# Understand decision impact +impact = knowledge.analyze_decision_impact(decision_id) +print(f"This decision influenced {impact.get('total_influenced', 0)} other decisions") + +# Get decision summary +summary = knowledge.get_decision_summary() +print(f"Total decisions: {summary.get('total_decisions', 0)}") +print(f"Categories: {list(summary.get('categories', {}).keys())}") + +# Trace decision chains +chains = knowledge.trace_decision_chain(decision_id) +print(f"Decision chain has {len(chains)} connections") + +# Check if decisions follow rules +compliance = knowledge.check_decision_rules({ + "category": "loan_approval", + "scenario": "Mortgage application", + "reasoning": "Good credit score, stable income", + "outcome": "approved", + "confidence": 0.95 +}) + +if compliance.get("compliant", False): + print("✅ Decision follows all rules") +else: + print(f"❌ Rule violations: {compliance.get('violations', [])}") +``` + +### Graph Analytics Made Simple +```python +# Get overview of your knowledge graph +summary = knowledge.get_graph_summary() +print(f"Knowledge graph has {summary.get('nodes', 0)} concepts") +print(f"And {summary.get('edges', 0)} relationships") + +# Find related concepts +related = knowledge.find_related_nodes("Python", how_many=5) +for concept_id, similarity in related: + print(f"Related to {concept_id}: {similarity:.2f}") + +# Understand which concepts are most important +importance = knowledge.get_node_importance("Python") +print(f"Python importance score: {importance.get('degree', 0)}") +``` + +### Core Methods + +| Method | What It Does | When to Use | +|--------|-------------|------------| +| `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 | +| `find_similar_decisions(scenario, category, ...)` | Find similar decisions | Make consistent choices | +| `analyze_decision_impact(decision_id)` | Understand decision influence | See how decisions affect others | +| `get_decision_summary()` | Get decision statistics | Understand decision patterns | +| `trace_decision_chain(decision_id)` | Trace decision connections | Understand decision relationships | +| `check_decision_rules(decision_data)` | Validate decisions | Ensure compliance | +| `get_graph_summary()` | Get graph overview | Understand knowledge structure | +| `find_related_nodes(node_id, how_many)` | Find related concepts | Discover connections | +| `get_node_importance(node_id)` | Measure concept importance | Identify key concepts | + +--- + +## 🔄 Using Both Together - Complete Intelligence + +### Your Smart Agent System +```python +from semantica.context import AgentContext, ContextGraph +from semantica.vector_store import VectorStore + +# Create the components +vector_store = VectorStore(backend="inmemory", dimension=384) +knowledge = ContextGraph(advanced_analytics=True) + +# Create your intelligent agent +agent = AgentContext( + vector_store=vector_store, + knowledge_graph=knowledge, # Add knowledge graph + decision_tracking=True, + graph_expansion=True, + advanced_analytics=True +) + +# Your agent works like this: +# 1. Store information in memory +agent.store("User wants to learn web development with Python") +agent.store("User is a beginner programmer") +agent.store("User prefers hands-on tutorials") + +# 2. Find relevant information +results = agent.retrieve("Python web development tutorials") +print(f"Found {len(results)} relevant memories") + +# 3. Make smart decisions +decision_id = agent.record_decision( + category="content_recommendation", + scenario="Python web development learning path", + reasoning="Beginner needs hands-on Python web tutorial", + outcome="recommended_flask_tutorial", + confidence=0.89 +) + +# 4. Learn and improve over time +insights = agent.get_context_insights() +print(f"Agent insights: {insights}") + +# 5. Access advanced features when needed +graph_summary = agent.graph_builder.get_graph_summary() +node_importance = agent.graph_builder.get_node_importance("Python") +``` + +--- + +## 🎯 Real-World Applications + +### 🏦 Banking - Smart Loan Decisions +```python +# Track loan decisions and learn from patterns +bank_agent = AgentContext(vector_store=bank_vector_store, decision_tracking=True) + +# Store customer information +bank_agent.store("Customer has credit score 750, stable employment") +bank_agent.store("Customer is first-time homebuyer") + +# Make loan decision +loan_decision = bank_agent.record_decision( + category="loan_approval", + scenario="First-time homebuyer mortgage", + reasoning="Good credit score, stable income, 20% down payment", + outcome="approved", + confidence=0.94 +) + +# Find similar loan decisions for consistency +similar_loans = bank_agent.find_precedents("homebuyer", category="loan_approval") +print(f"Found {len(similar_loans)} similar loan decisions") +``` + +### 🏥 Healthcare - Patient Care Decisions +```python +# Track patient care decisions +health_agent = AgentContext(vector_store=medical_vector_store, decision_tracking=True) + +# Store patient information +health_agent.store("Patient has hypertension, type 2 diabetes") +health_agent.store("Patient allergic to penicillin") + +# Make treatment decision +treatment_decision = health_agent.record_decision( + category="treatment_plan", + scenario="Hypertension with diabetes", + reasoning="ACE inhibitors safe for diabetic patients", + outcome="prescribed_ace_inhibitor", + confidence=0.91 +) + +# Find similar treatment cases +similar_cases = health_agent.find_precedents("hypertension", category="treatment_plan") +``` + +### 🛒 E-commerce - Smart Recommendations +```python +# Track recommendation decisions +ecommerce_graph = ContextGraph() + +# Build user-product knowledge +ecommerce_graph.add_node("user_123", "user", {"segment": "premium"}) +ecommerce_graph.add_node("laptop_xyz", "product", {"category": "electronics"}) +ecommerce_graph.add_edge("user_123", "laptop_xyz", "viewed") + +# Make recommendation decision +rec_decision = ecommerce_graph.add_decision( + category="product_recommendation", + scenario="Laptop recommendation for premium user", + reasoning="User prefers high-performance electronics", + outcome="recommended_gaming_laptop", + confidence=0.87, + entities=["user_123", "laptop_xyz"] +) + +# Find similar recommendations +similar_recs = ecommerce_graph.find_similar_decisions( + scenario="laptop recommendation", + max_results=5 ) ``` --- -## ⚙️ Configuration +## ⚙️ Configuration Options -### Environment Variables - -```bash -# Global token limit -export CONTEXT_TOKEN_LIMIT=2000 +### Simple Setup (Most Common) +```python +# Just memory and basic learning +agent = AgentContext(vector_store=vector_store) ``` -### YAML Configuration +### Smart Setup (Recommended) +```python +# Memory + decision learning +agent = AgentContext( + vector_store=vector_store, + decision_tracking=True, + graph_expansion=True +) +``` -```yaml -context: - short_term_limit: 10 - retrieval: - hybrid_alpha: 0.5 # 0.0=Vector, 1.0=Graph - max_expansion_hops: 2 +### Complete Setup (Maximum Power) +```python +# Everything enabled +agent = AgentContext( + vector_store=vector_store, + knowledge_graph=ContextGraph(advanced_analytics=True), + decision_tracking=True, + graph_expansion=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True +) +``` + +### ContextGraph Options +```python +# Basic knowledge graph +graph = ContextGraph() + +# Advanced knowledge graph +graph = ContextGraph( + advanced_analytics=True, # Enable smart algorithms + centrality_analysis=True, # Find important concepts + community_detection=True, # Find groups of related concepts + node_embeddings=True # Understand concept similarity +) ``` --- -## 📝 Data Structures +## 📊 Data Structures -### MemoryItem -The fundamental unit of storage. +### MemoryItem - The Basic Memory Unit ```python @dataclass class MemoryItem: content: str # The actual text content timestamp: datetime # When it was created - metadata: Dict # Arbitrary tags (user_id, source, etc.) - embedding: List[float] # The vector representation - entities: List[Dict] # Entities found in this content + metadata: Dict # Tags like user_id, conversation_id + embedding: List[float] # Vector representation + entities: List[Dict] # Entities found in content ``` -### Decision -The fundamental unit of decision tracking. +### Decision - The Decision Unit ```python @dataclass class Decision: @@ -775,10 +471,9 @@ class Decision: timestamp: datetime # When decision was made entities: List[str] # Related entities metadata: Dict # Additional decision metadata - embedding: List[float] # Decision embedding for similarity ``` -### Graph Node (Dict Format) +### Graph Node - Knowledge Concept ```python { "id": "node_unique_id", @@ -786,13 +481,12 @@ class Decision: "properties": { "content": "Description of the node", "weight": 1.0, - "centrality": 0.85, - "community": "cluster_1" + "importance": 0.85 } } ``` -### Graph Edge (Dict Format) +### Graph Edge - Knowledge Relationship ```python { "source_id": "origin_node", @@ -808,194 +502,93 @@ class Decision: --- -## 🧩 Advanced Usage +## 🚀 Advanced Features -### Context Graphs in Production - -#### Building Domain-Specific Context Graphs - -**Financial Services Context Graph** +### GraphRAG with Multi-Hop Reasoning ```python -from semantica.context import ContextGraph +# Query with reasoning and LLM integration +result = agent.query_with_reasoning( + query="What technologies work well together?", + llm_provider=llm_provider, + max_hops=2, + max_results=10 +) -# Create financial context graph -financial_graph = ContextGraph(enable_advanced_analytics=True) - -# Add financial entities -financial_graph.add_nodes([ - { - "id": "customer_001", - "type": "Customer", - "properties": { - "credit_score": 750, - "risk_profile": "low", - "account_type": "premium" - } - }, - { - "id": "mortgage_product", - "type": "Product", - "properties": { - "category": "loan", - "interest_rate": 3.5, - "max_amount": 500000 - } - }, - { - "id": "loan_officer_001", - "type": "Agent", - "properties": { - "department": "lending", - "experience_years": 5 - } - } -]) - -# Add relationships -financial_graph.add_edges([ - { - "source_id": "customer_001", - "target_id": "mortgage_product", - "type": "ELIGIBLE_FOR", - "properties": {"confidence": 0.92} - }, - { - "source_id": "loan_officer_001", - "target_id": "customer_001", - "type": "SERVES", - "properties": {"relationship_duration": "2_years"} - } -]) - -# Analyze financial context -centrality = financial_graph.get_node_centrality("customer_001") -similar_customers = financial_graph.find_similar_nodes("customer_001") +print(f"Response: {result['response']}") +print(f"Reasoning Path: {result['reasoning_path']}") +print(f"Confidence: {result['confidence']:.3f}") ``` -**Healthcare Context Graph** +### Production Integration ```python -# Create healthcare context graph -healthcare_graph = ContextGraph(enable_advanced_analytics=True) +# Use with persistent graph stores +from semantica.graph_store import GraphStore -# Add medical entities -healthcare_graph.add_nodes([ - { - "id": "patient_001", - "type": "Patient", - "properties": { - "condition": "diabetes_type_2", - "age": 45, - "risk_factors": ["obesity", "hypertension"] - } - }, - { - "id": "metformin", - "type": "Medication", - "properties": { - "class": "biguanide", - "uses": ["diabetes_treatment", "pcos"] - } - }, - { - "id": "dr_smith", - "type": "Physician", - "properties": { - "specialty": "endocrinology", - "hospital": "general_hospital" - } - } -]) +# Neo4j integration +neo4j_store = GraphStore( + backend="neo4j", + uri="bolt://localhost:7687", + user="neo4j", + password="password" +) -# Add medical relationships -healthcare_graph.add_edges([ - { - "source_id": "patient_001", - "target_id": "metformin", - "type": "PRESCRIBED", - "properties": {"dosage": "500mg", "frequency": "twice_daily"} - }, - { - "source_id": "dr_smith", - "target_id": "patient_001", - "type": "TREATS", - "properties": {"since": "2023-01-15"} - } -]) - -# Analyze healthcare context -treatment_patterns = healthcare_graph.analyze_graph_with_kg() -similar_patients = healthcare_graph.find_similar_nodes("patient_001") +# Production agent with persistent storage +production_agent = AgentContext( + vector_store=vector_store, + knowledge_graph=neo4j_store, + decision_tracking=True, + advanced_analytics=True +) ``` -#### Context Graph Analytics and Insights - +### Analytics and Insights ```python -# Get comprehensive graph insights -insights = graph.get_graph_metrics() -print(f"Graph Density: {insights['density']}") -print(f"Average Clustering: {insights['avg_clustering']}") -print(f"Number of Communities: {len(insights['communities'])}") +# Get comprehensive insights +insights = agent.get_context_insights() +print(f"Total decisions: {insights.get('total_decisions', 0)}") +print(f"Decision categories: {list(insights.get('categories', {}).keys())}") +print(f"Most common outcome: {insights.get('most_common_outcome', 'N/A')}") -# Find influential nodes -influential_nodes = [] -for node_id in graph.get_all_nodes(): - centrality = graph.get_node_centrality(node_id) - if centrality['betweenness'] > 0.8: - influential_nodes.append(node_id) - -# Trace decision influence -decision_influence = graph.trace_influence_paths("decision_001", max_depth=3) -for path in decision_influence: - print(f"Influence Path: {' -> '.join(path)}") +# Graph analytics +graph_insights = agent.graph_builder.get_graph_summary() +node_importance = agent.graph_builder.get_node_importance("key_concept") ``` -#### Context Graph Visualization +--- -```python -# Export context graph for visualization -graph_data = graph.export_graph(format="networkx") +## 📚 Need More Help? -# Create visualization (requires matplotlib/networkx) -import matplotlib.pyplot as plt -import networkx as nx +### For Beginners +- Start with **AgentContext** for most applications +- Use basic **store/retrieve** for memory management +- Add **decision tracking** to enable learning +- Enable features gradually as needed -G = nx.node_link_graph(graph_data) -pos = nx.spring_layout(G) +### For Advanced Users +- Add **ContextGraph** for knowledge organization +- Use **analytics** to understand patterns +- Implement **policies** for consistent decisions +- Use **persistence** for state management -# Draw the context graph -plt.figure(figsize=(12, 8)) -nx.draw(G, pos, with_labels=True, node_color='lightblue', - node_size=1000, font_size=8, edge_color='gray') -plt.title("Context Graph Visualization") -plt.show() -``` +### For Production +- Enable **all features** for maximum intelligence +- Use **save/load** for state persistence +- **Monitor performance** with insights and health checks +- **Test thoroughly** before deployment -### Method Registry (Extensibility) -Register custom implementations for graph building, memory management, or retrieval. +### Examples and Tutorials +- Look at the **real-world examples** above for your specific use case +- Check **configuration options** to customize your agent +- Start simple and add power as needed -#### **Code Example** -```python -from semantica.context import registry +--- -def custom_graph_builder(entities, relationships): - # Custom logic to build graph - return "my_graph_structure" +**Happy building intelligent agents!** 🎯 -# Register the new method -registry.register("graph", "custom_builder", custom_graph_builder) -``` +--- -### Configuration Manager -Programmatically manage configuration settings. +## 📚 See Also -#### **Code Example** -```python -from semantica.context.config import context_config - -# Update configuration at runtime -context_config.set("retention_days", 60) - -## See Also - [Vector Store](vector_store.md) - The long-term storage backend - [Graph Store](graph_store.md) - The knowledge graph backend - [KG Algorithms](kg.md) - Knowledge graph algorithms and analytics diff --git a/semantica/context/__init__.py b/semantica/context/__init__.py index c479cfeb..ff50605d 100644 --- a/semantica/context/__init__.py +++ b/semantica/context/__init__.py @@ -46,7 +46,7 @@ Enhanced Analytics: Main Classes: - AgentContext: High-level interface with KG integration - - ContextGraph: In-memory graph store with KG algorithm support + - ContextGraph: In-memory graph store with KG algorithm support and comprehensive decision management - ContextNode/ContextEdge: Graph data structures - AgentMemory: Persistent agent memory with RAG - MemoryItem: Memory item data structure @@ -63,12 +63,13 @@ Decision Tracking Classes: - Policy/Precedent/PolicyException: Decision tracking data structures Example Usage: - >>> from semantica.context import AgentContext + >>> from semantica.context import AgentContext, ContextGraph + >>> # Simple AgentContext with decision tracking >>> context = AgentContext(vector_store=vs, knowledge_graph=kg, - ... enable_decision_tracking=True, - ... enable_advanced_analytics=True, - ... enable_kg_algorithms=True, - ... enable_vector_store_features=True) + ... decision_tracking=True, + ... advanced_analytics=True, + ... kg_algorithms=True, + ... vector_store_features=True) >>> memory_id = context.store("User asked about Python", conversation_id="conv1") >>> results = context.retrieve("Python programming") >>> decision_id = context.record_decision(category="approval", @@ -81,6 +82,21 @@ Example Usage: ... use_kg_features=True) >>> influence = context.analyze_decision_influence(decision_id) >>> insights = context.get_context_insights() + + >>> # Comprehensive ContextGraph with all decision features + >>> graph = ContextGraph(advanced_analytics=True, enable_causality=True) + >>> decision_id = graph.record_decision( + ... category="loan_approval", + ... scenario="First-time homebuyer", + ... reasoning="Good credit score and stable income", + ... outcome="approved", + ... confidence=0.95, + ... entities=["customer_123", "property_456"] + ... ) + >>> precedents = graph.find_precedents("loan_approval", limit=5) + >>> influence = graph.analyze_decision_influence(decision_id) + >>> insights = graph.get_decision_insights() + >>> causality = graph.trace_decision_causality(decision_id) Production Examples: - Banking: Mortgage approvals, credit decisions, risk assessment diff --git a/semantica/context/agent_context.py b/semantica/context/agent_context.py index cfead678..20863d79 100644 --- a/semantica/context/agent_context.py +++ b/semantica/context/agent_context.py @@ -46,10 +46,11 @@ Key Methods: Example Usage: >>> from semantica.context import AgentContext >>> context = AgentContext(vector_store=vs, knowledge_graph=kg, - ... enable_decision_tracking=True, - ... enable_advanced_analytics=True, - ... enable_kg_algorithms=True, - ... enable_vector_store_features=True) + ... decision_tracking=True, + ... advanced_analytics=True, + ... kg_algorithms=True, + ... vector_store_features=True, + ... graph_expansion=True) >>> memory_id = context.store("User asked about Python", conversation_id="conv1") >>> results = context.retrieve("Python programming") >>> decision_id = context.record_decision(category="approval", @@ -124,13 +125,13 @@ class AgentContext: knowledge_graph: Optional[Any] = None, retention_days: Optional[int] = 30, max_memories: int = 10000, - use_graph_expansion: bool = True, + graph_expansion: bool = True, max_expansion_hops: int = 2, hybrid_alpha: float = 0.5, - enable_decision_tracking: bool = False, - enable_advanced_analytics: bool = True, - enable_kg_algorithms: bool = True, - enable_vector_store_features: bool = True, + decision_tracking: bool = False, + advanced_analytics: bool = True, + kg_algorithms: bool = True, + vector_store_features: bool = True, **kwargs, ): """ @@ -141,14 +142,14 @@ class AgentContext: knowledge_graph: Knowledge graph instance (optional, enables GraphRAG) retention_days: Days to keep memories (default: 30, None=unlimited) max_memories: Maximum number of memories (default: 10000) - use_graph_expansion: Enable graph expansion for retrieval (default: True) + graph_expansion: Enable graph expansion for retrieval (default: True) max_expansion_hops: Maximum hops for graph expansion (default: 2) hybrid_alpha: Balance between vector (0) and graph (1) retrieval (default: 0.5) - enable_decision_tracking: Enable decision tracking features (default: False) - enable_advanced_analytics: Enable advanced analytics (default: True) - enable_kg_algorithms: Enable KG algorithms integration (default: True) - enable_vector_store_features: Enable vector store features (default: True) + decision_tracking: Enable decision tracking features (default: False) + advanced_analytics: Enable advanced analytics (default: True) + kg_algorithms: Enable KG algorithms integration (default: True) + vector_store_features: Enable vector store features (default: True) **kwargs: Additional options passed to underlying components Raises: @@ -168,11 +169,11 @@ class AgentContext: # Store advanced feature flags self.config = { - "enable_decision_tracking": enable_decision_tracking, - "enable_advanced_analytics": enable_advanced_analytics, - "enable_kg_algorithms": enable_kg_algorithms, - "enable_vector_store_features": enable_vector_store_features, - "use_graph_expansion": use_graph_expansion, + "decision_tracking": decision_tracking, + "advanced_analytics": advanced_analytics, + "kg_algorithms": kg_algorithms, + "vector_store_features": vector_store_features, + "graph_expansion": graph_expansion, "max_expansion_hops": max_expansion_hops, "hybrid_alpha": hybrid_alpha, **kwargs @@ -195,7 +196,7 @@ class AgentContext: "memory_store": self._memory, "knowledge_graph": knowledge_graph, "vector_store": vector_store, - "use_graph_expansion": use_graph_expansion, + "use_graph_expansion": graph_expansion, "max_expansion_hops": max_expansion_hops, "hybrid_alpha": hybrid_alpha, **kwargs, @@ -221,18 +222,18 @@ class AgentContext: self._causal_analyzer = None self._policy_engine = None - if enable_decision_tracking and knowledge_graph: + if decision_tracking and knowledge_graph: if hasattr(knowledge_graph, "execute_query"): self._decision_backend = "graph_store" try: self._decision_recorder = DecisionRecorder(knowledge_graph) self._decision_query = DecisionQuery( graph_store=knowledge_graph, - vector_store=vector_store if enable_vector_store_features else None, - enable_advanced_analytics=enable_advanced_analytics, - enable_centrality_analysis=enable_kg_algorithms, - enable_community_detection=enable_kg_algorithms, - enable_node_embeddings=enable_kg_algorithms + vector_store=vector_store if vector_store_features else None, + advanced_analytics=advanced_analytics, + centrality_analysis=kg_algorithms, + community_detection=kg_algorithms, + node_embeddings=kg_algorithms ) self._causal_analyzer = CausalChainAnalyzer(knowledge_graph) self._policy_engine = PolicyEngine(knowledge_graph) @@ -247,13 +248,38 @@ class AgentContext: self._policy_engine = PolicyEngine(knowledge_graph) else: self._decision_backend = "context_graph" + # Initialize basic decision components for ContextGraph self._policy_engine = PolicyEngine(knowledge_graph) self._causal_analyzer = CausalChainAnalyzer(knowledge_graph) - if enable_vector_store_features and hasattr(self.vector_store, "initialize_decision_pipeline"): + + # Initialize DecisionQuery for ContextGraph + try: + self._decision_query = DecisionQuery( + graph_store=knowledge_graph, + vector_store=vector_store if vector_store_features else None, + advanced_analytics=advanced_analytics, + centrality_analysis=kg_algorithms, + community_detection=kg_algorithms, + node_embeddings=kg_algorithms + ) + self.logger.info("ContextGraph decision tracking initialized successfully") + except Exception as e: + self.logger.warning( + f"Failed to initialize DecisionQuery for ContextGraph ({type(e).__name__})" + ) + # Create a minimal DecisionQuery that delegates to ContextGraph + self._decision_query = type('MinimalDecisionQuery', (), { + 'analyze_decision_influence': lambda self, decision_id, max_depth=3: + knowledge_graph.analyze_decision_influence(decision_id, max_depth) if hasattr(knowledge_graph, 'analyze_decision_influence') else {}, + 'find_precedents': lambda self, query, category=None, limit=10: + knowledge_graph.find_precedents(query, category, limit) if hasattr(knowledge_graph, 'find_precedents') else [], + })() + + if vector_store_features and hasattr(self.vector_store, "initialize_decision_pipeline"): try: self.vector_store.initialize_decision_pipeline( - graph_store=knowledge_graph if enable_kg_algorithms else None, - use_graph_features=enable_kg_algorithms + graph_store=knowledge_graph if kg_algorithms else None, + use_graph_features=kg_algorithms ) except Exception as e: self.logger.warning( @@ -1586,67 +1612,22 @@ class AgentContext: return decision_id - if not hasattr(self.knowledge_graph, "add_decision"): + if not hasattr(self.knowledge_graph, "record_decision"): raise RuntimeError("Decision tracking backend does not support decisions") - self.knowledge_graph.add_decision(decision) - if cross_system_context and hasattr(self.knowledge_graph, "add_node_attribute"): - self.knowledge_graph.add_node_attribute( - decision.decision_id, {"cross_system_context": cross_system_context} - ) - about_edge_failures: List[Dict[str, str]] = [] - for entity_id in entities: - try: - self.knowledge_graph.add_edge(decision.decision_id, entity_id, edge_type="ABOUT") - except Exception as e: - about_edge_failures.append( - {"entity_id": str(entity_id), "error_type": type(e).__name__} - ) - - if about_edge_failures: - failure_types = sorted( - {f.get("error_type", "") for f in about_edge_failures if f.get("error_type")} - ) - self.logger.warning( - f"record_decision ABOUT edge creation failures: {len(about_edge_failures)} " - f"({', '.join(failure_types) if failure_types else 'unknown'})" - ) - if hasattr(self.knowledge_graph, "add_node_attribute"): - self.knowledge_graph.add_node_attribute( - decision.decision_id, {"about_edge_failures": about_edge_failures} - ) - - vector_id = None - vector_store_error_type: Optional[str] = None - if hasattr(self.vector_store, "store_decision"): - try: - vector_id = self.vector_store.store_decision( - scenario=scenario, - reasoning=reasoning, - outcome=outcome, - confidence=confidence, - entities=entities, - category=category, - decision_id=decision.decision_id, - decision_maker=decision.decision_maker, - timestamp=decision.timestamp.isoformat() - ) - except Exception as e: - vector_id = None - vector_store_error_type = type(e).__name__ - self.logger.warning( - f"record_decision vector store write failed ({vector_store_error_type})" - ) - if hasattr(self.knowledge_graph, "add_node_attribute"): - self.knowledge_graph.add_node_attribute( - decision.decision_id, - {"vector_store_error_type": vector_store_error_type}, - ) - - if vector_id and hasattr(self.knowledge_graph, "add_node_attribute"): - self.knowledge_graph.add_node_attribute(decision.decision_id, {"vector_id": vector_id}) - - return decision.decision_id + # Delegate to ContextGraph + decision_id = self.knowledge_graph.record_decision( + category=category, + scenario=scenario, + reasoning=reasoning, + outcome=outcome, + confidence=confidence, + entities=entities, + decision_maker=decision_maker, + metadata={"cross_system_context": cross_system_context} if cross_system_context else None + ) + + return decision_id def find_precedents( self, @@ -1677,6 +1658,38 @@ class AgentContext: if not self._decision_backend: raise RuntimeError("Decision tracking is not enabled") + # Delegate to ContextGraph if available + if self._decision_backend == "context_graph" and hasattr(self.knowledge_graph, "find_precedents"): + try: + precedents = self.knowledge_graph.find_precedents( + scenario=scenario, + category=category, + limit=limit, + use_semantic_search=use_hybrid_search + ) + # Convert to Decision objects if needed + from .decision_models import Decision + decisions = [] + for precedent in precedents: + decision_data = precedent["decision"] + decision = Decision( + decision_id=decision_data["id"], + category=decision_data["category"], + scenario=decision_data["scenario"], + reasoning=decision_data["reasoning"], + outcome=decision_data["outcome"], + confidence=decision_data["confidence"], + timestamp=datetime.fromtimestamp(decision_data["timestamp"]), + decision_maker=decision_data.get("decision_maker"), + entities=decision_data.get("entities", []) + ) + decisions.append(decision) + return decisions + except Exception as e: + self.logger.error(f"ContextGraph find_precedents failed: {e}") + return [] + + # Fallback to DecisionQuery for graph_store backend if self._decision_backend == "graph_store": if use_hybrid_search: try: @@ -1991,7 +2004,7 @@ class AgentContext: Returns: Comprehensive graph analysis results """ - if not self._graph_builder or not self.config.get("enable_advanced_analytics", True): + if not self._graph_builder or not self.config.get("advanced_analytics", True): return {"error": "Advanced analytics not available"} try: @@ -2112,11 +2125,21 @@ class AgentContext: if not self._decision_query: raise RuntimeError("Decision tracking is not enabled") + # Delegate to ContextGraph if available + if hasattr(self.knowledge_graph, "analyze_decision_influence"): + try: + return self.knowledge_graph.analyze_decision_influence(decision_id, max_depth) + except Exception as e: + self.logger.error(f"ContextGraph analyze_decision_influence failed: {e}") + # Fallback to DecisionQuery + pass + + # Fallback to DecisionQuery try: if hasattr(self._decision_query, 'analyze_decision_influence'): return self._decision_query.analyze_decision_influence(decision_id, max_depth) else: - # Fallback to basic causal chain + # Basic causal chain fallback return { "decision_id": decision_id, "downstream_decisions": self.get_causal_chain(decision_id, "downstream", max_depth), diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index a6c455aa..a606ab32 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -12,6 +12,14 @@ Core Features: - Export to dictionary format - Decision tracking integration +Comprehensive Decision Management: + - Decision Recording: Store decisions with full context and metadata + - Precedent Search: Find similar decisions using hybrid search algorithms + - Influence Analysis: Analyze decision impact and relationships + - Causal Analysis: Trace decision causality chains + - Policy Enforcement: Built-in policy compliance checking + - Advanced Analytics: Comprehensive decision insights + KG Algorithm Integration: - Centrality Analysis: Degree, betweenness, closeness, eigenvector centrality - Community Detection: Modularity-based community identification @@ -47,23 +55,43 @@ Enhanced Methods: - analyze_graph_with_kg(): Comprehensive graph analysis - get_node_centrality(): Get centrality measures for nodes - find_similar_nodes(): Find similar nodes with advanced similarity - - add_decision(): Add decisions with context integration + - record_decision(): Add decisions with context integration - find_precedents(): Find decision precedents + - analyze_decision_influence(): Analyze decision influence + - get_decision_insights(): Get comprehensive decision analytics + - trace_decision_causality(): Trace decision causality + - enforce_decision_policy(): Enforce decision policies - get_graph_metrics(): Get comprehensive statistics - export_graph(): Export graph in various formats Example Usage: >>> from semantica.context import ContextGraph - >>> graph = ContextGraph(enable_advanced_analytics=True, - ... enable_centrality_analysis=True, - ... enable_community_detection=True, - ... enable_node_embeddings=True) + >>> graph = ContextGraph(advanced_analytics=True, + ... centrality_analysis=True, + ... community_detection=True, + ... node_embeddings=True) + >>> + >>> # Basic graph operations >>> graph.add_node("Python", type="language", properties={"popularity": "high"}) >>> graph.add_node("Programming", type="concept") >>> graph.add_edge("Python", "Programming", type="related_to") >>> centrality = graph.get_node_centrality("Python") >>> similar = graph.find_similar_nodes("Python", similarity_type="content") >>> analysis = graph.analyze_graph_with_kg() + >>> + >>> # Decision management + >>> decision_id = graph.record_decision( + ... category="loan_approval", + ... scenario="First-time homebuyer", + ... reasoning="Good credit score", + ... outcome="approved", + ... confidence=0.95, + ... entities=["customer_123", "property_456"] + ... ) + >>> precedents = graph.find_precedents("loan_approval", limit=5) + >>> influence = graph.analyze_decision_influence(decision_id) + >>> insights = graph.get_decision_insights() + >>> causality = graph.trace_decision_causality(decision_id) Production Use Cases: - Knowledge Management: Build and analyze knowledge graphs @@ -71,6 +99,10 @@ Production Use Cases: - Recommendation Systems: Graph-based recommendations - Social Networks: Analyze connections and influence - Research Networks: Map collaborations and citations + - Financial Services: Loan approvals, fraud detection, risk assessment + - Healthcare: Treatment decisions, policy compliance, clinical pathways + - Legal: Case precedent analysis, decision consistency + - Business: Workflow decisions, policy compliance, audit trails """ from collections import defaultdict, deque @@ -136,9 +168,16 @@ class ContextEdge: class ContextGraph: """ - In-memory implementation of context graph. - - Provides capabilities to build, store, and query a context graph. + Easy-to-Use Context Graph with All Advanced Features. + + This class provides simple methods for: + - Building knowledge graphs + - Recording and analyzing decisions + - Finding precedents and patterns + - Causal analysis and policy enforcement + - Advanced graph analytics + + Perfect for building intelligent AI agents that can learn from decisions! """ def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs): @@ -151,10 +190,10 @@ class ContextGraph: - extract_entities: Extract entities from content (default: True) - extract_relationships: Extract relationships (default: True) - entity_linker: Entity linker instance - - enable_advanced_analytics: Enable KG algorithms (default: True) - - enable_centrality_analysis: Enable centrality measures (default: True) - - enable_community_detection: Enable community detection (default: True) - - enable_node_embeddings: Enable Node2Vec embeddings (default: True) + - advanced_analytics: Enable KG algorithms (default: True) + - centrality_analysis: Enable centrality measures (default: True) + - community_detection: Enable community detection (default: True) + - node_embeddings: Enable Node2Vec embeddings (default: True) """ self.logger = get_logger("context_graph") self.config = config or {} @@ -186,15 +225,15 @@ class ContextGraph: self.kg_components = {} self._analytics_cache = {} - enable_advanced = self.config.get("enable_advanced_analytics", True) + enable_advanced = self.config.get("advanced_analytics", True) if KG_AVAILABLE and enable_advanced: try: - if self.config.get("enable_centrality_analysis", True): + if self.config.get("centrality_analysis", True): self.kg_components["centrality_calculator"] = CentralityCalculator() - if self.config.get("enable_community_detection", True): + if self.config.get("community_detection", True): self.kg_components["community_detector"] = CommunityDetector() - if self.config.get("enable_node_embeddings", True): + if self.config.get("node_embeddings", True): self.kg_components["node_embedder"] = NodeEmbedder() self.kg_components["path_finder"] = PathFinder() self.kg_components["similarity_calculator"] = SimilarityCalculator() @@ -1329,6 +1368,767 @@ class ContextGraph: union = words1.union(words2) return len(intersection) / len(union) if union else 0.0 + + # --- Comprehensive Decision Management Features --- + + def record_decision( + self, + category: str, + scenario: str, + reasoning: str, + outcome: str, + confidence: float, + entities: Optional[List[str]] = None, + decision_maker: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, + **kwargs + ) -> str: + """ + Record a decision with full context and analytics. + + Args: + category: Decision category (e.g., "loan_approval") + scenario: Decision scenario description + reasoning: Decision reasoning explanation + outcome: Decision outcome + confidence: Confidence score (0.0 to 1.0) + entities: Related entities + decision_maker: Who made the decision + metadata: Additional metadata + **kwargs: Additional decision data + + Returns: + Decision ID for reference + """ + import uuid + from datetime import datetime + + decision_id = str(uuid.uuid4()) + timestamp = datetime.now().timestamp() + + # Create decision record + decision = { + "id": decision_id, + "category": category, + "scenario": scenario, + "reasoning": reasoning, + "outcome": outcome, + "confidence": confidence, + "entities": entities or [], + "decision_maker": decision_maker, + "timestamp": timestamp, + "metadata": metadata or {}, + **kwargs + } + + # Store decision in graph + self._add_decision_to_graph(decision) + + # Store in internal decision storage + if not hasattr(self, '_decisions'): + self._decisions = {} + self._decision_index = defaultdict(set) + self._entity_index = defaultdict(set) + self._temporal_index = [] + + self._decisions[decision_id] = decision + self._decision_index[category].add(decision_id) + + for entity in entities or []: + self._entity_index[entity].add(decision_id) + + self._temporal_index.append((decision_id, timestamp)) + self._temporal_index.sort(key=lambda x: x[1], reverse=True) + + self.logger.info(f"Recorded decision {decision_id} in category {category}") + return decision_id + + def find_precedents( + self, + scenario: str, + category: Optional[str] = None, + limit: int = 10, + similarity_threshold: float = 0.5, + use_semantic_search: bool = True, + **filters + ) -> List[Dict[str, Any]]: + """ + Find similar decisions (precedents) using hybrid search. + + Args: + scenario: Scenario to find precedents for + category: Filter by decision category + limit: Maximum number of precedents + similarity_threshold: Minimum similarity score + use_semantic_search: Use vector embeddings for search + **filters: Additional filters + + Returns: + List of similar decisions with similarity scores + """ + if not hasattr(self, '_decisions') or not self._decisions: + return [] + + candidates = set() + + # Get candidates by category + if category: + candidates.update(self._decision_index.get(category, set())) + else: + candidates.update(self._decisions.keys()) + + # Filter by entities if provided + if "entities" in filters: + entity_candidates = set() + for entity in filters["entities"]: + entity_candidates.update(self._entity_index.get(entity, set())) + candidates = candidates.intersection(entity_candidates) + + # Calculate similarities + precedents = [] + for decision_id in candidates: + decision = self._decisions[decision_id] + + # Content similarity + content_sim = self._calculate_content_similarity(scenario, decision) + + # Structural similarity (graph-based) + structural_sim = 0.0 + if self.config.get("advanced_analytics"): + structural_sim = self._calculate_structural_similarity_for_decision(decision_id, scenario) + + # Combined similarity + combined_sim = 0.7 * content_sim + 0.3 * structural_sim + + if combined_sim >= similarity_threshold: + precedents.append({ + "decision": decision, + "similarity": combined_sim, + "content_similarity": content_sim, + "structural_similarity": structural_sim + }) + + # Sort by similarity and limit + precedents.sort(key=lambda x: x["similarity"], reverse=True) + return precedents[:limit] + + def analyze_decision_influence( + self, + decision_id: str, + max_depth: int = 3, + include_indirect: bool = True + ) -> Dict[str, Any]: + """ + Analyze decision influence and impact. + + Args: + decision_id: Decision to analyze + max_depth: Maximum depth for influence analysis + include_indirect: Include indirect influences + + Returns: + Influence analysis results + """ + if not hasattr(self, '_decisions') or decision_id not in self._decisions: + raise ValueError(f"Decision {decision_id} not found") + + decision = self._decisions[decision_id] + + # Direct influence (same entities, category) + direct_influence = set() + for entity in decision["entities"]: + direct_influence.update(self._entity_index.get(entity, set())) + direct_influence.discard(decision_id) + direct_influence.update(self._decision_index.get(decision["category"], set())) + direct_influence.discard(decision_id) + + # Indirect influence (through graph relationships) + indirect_influence = set() + if include_indirect and self.config.get("advanced_analytics"): + indirect_influence = self._find_indirect_decision_influence(decision_id, max_depth) + + # Calculate influence scores + influence_scores = {} + for influenced_id in direct_influence | indirect_influence: + score = self._calculate_decision_influence_score(decision_id, influenced_id) + influence_scores[influenced_id] = score + + # Sort by influence score + sorted_influence = sorted( + influence_scores.items(), + key=lambda x: x[1], + reverse=True + ) + + return { + "decision_id": decision_id, + "direct_influence": list(direct_influence), + "indirect_influence": list(indirect_influence), + "influence_scores": sorted_influence, + "total_influenced": len(influence_scores), + "max_influence_score": max(influence_scores.values()) if influence_scores else 0.0 + } + + def get_decision_insights(self) -> Dict[str, Any]: + """ + Get comprehensive insights about all decisions. + + Returns: + Comprehensive analytics and insights + """ + if not hasattr(self, '_decisions') or not self._decisions: + return {"message": "No decisions recorded yet"} + + # Basic statistics + total_decisions = len(self._decisions) + categories = {} + outcomes = {} + confidence_scores = [] + + for decision in self._decisions.values(): + # Category distribution + categories[decision["category"]] = categories.get(decision["category"], 0) + 1 + + # Outcome distribution + outcomes[decision["outcome"]] = outcomes.get(decision["outcome"], 0) + 1 + + # Confidence scores + confidence_scores.append(decision["confidence"]) + + # Advanced analytics (if available) + advanced_insights = {} + if self.config.get("advanced_analytics"): + advanced_insights = self.analyze_graph_with_kg() + + # Temporal analysis + temporal_insights = self._get_decision_temporal_analysis() + + # Entity analysis + entity_insights = self._get_decision_entity_analysis() + + return { + "total_decisions": total_decisions, + "categories": categories, + "outcomes": outcomes, + "confidence_stats": { + "mean": sum(confidence_scores) / len(confidence_scores), + "min": min(confidence_scores), + "max": max(confidence_scores), + "median": sorted(confidence_scores)[len(confidence_scores) // 2] + }, + "advanced_analytics": advanced_insights, + "temporal_analysis": temporal_insights, + "entity_analysis": entity_insights, + "graph_metrics": self.get_graph_metrics() if hasattr(self, 'get_graph_metrics') else {} + } + + def trace_decision_causality( + self, + decision_id: str, + max_depth: int = 5 + ) -> List[Dict[str, Any]]: + """ + Trace causal chain for a decision. + + Args: + decision_id: Decision to trace + max_depth: Maximum depth for causal analysis + + Returns: + Causal chain as list of decision relationships + """ + if not hasattr(self, '_decisions') or decision_id not in self._decisions: + raise ValueError(f"Decision {decision_id} not found") + + try: + # Use graph traversal to find causal relationships + causal_chain = [] + visited = set() + + def trace_recursive(current_id, depth, path): + if depth >= max_depth or current_id in visited: + return + + visited.add(current_id) + current_decision = self._decisions[current_id] + + # Find potential causes (decisions that influenced this one) + potential_causes = [] + for entity in current_decision["entities"]: + for other_decision_id in self._entity_index.get(entity, set()): + if other_decision_id != current_id: + other_decision = self._decisions[other_decision_id] + if other_decision["timestamp"] < current_decision["timestamp"]: + potential_causes.append(other_decision_id) + + for cause_id in potential_causes: + cause_path = path + [{"from": cause_id, "to": current_id, "type": "influences"}] + causal_chain.append(cause_path) + trace_recursive(cause_id, depth + 1, cause_path) + + trace_recursive(decision_id, 0, []) + return causal_chain + + except Exception as e: + self.logger.error(f"Causal analysis failed: {e}") + return [{"error": f"Causal analysis failed: {e}"}] + + def enforce_decision_policy( + self, + decision_data: Dict[str, Any], + policy_rules: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """ + Enforce policies on decision data. + + Args: + decision_data: Decision data to check + policy_rules: Policy rules to enforce + + Returns: + Policy enforcement results + """ + # Simple policy enforcement implementation + violations = [] + warnings = [] + + # Default policy rules + default_rules = { + "min_confidence": 0.7, + "required_outcomes": ["approved", "rejected", "flagged"], + "required_metadata": ["decision_maker"], + "max_reasoning_length": 1000 + } + + rules = policy_rules or default_rules + + # Check confidence + if decision_data.get("confidence", 0) < rules.get("min_confidence", 0.7): + violations.append(f"Confidence too low: {decision_data.get('confidence', 0)}") + + # Check outcome + if decision_data.get("outcome") not in rules.get("required_outcomes", []): + violations.append(f"Invalid outcome: {decision_data.get('outcome')}") + + # Check required metadata + for required_field in rules.get("required_metadata", []): + if not decision_data.get(required_field): + violations.append(f"Missing required field: {required_field}") + + # Check reasoning length + reasoning = decision_data.get("reasoning", "") + if len(reasoning) > rules.get("max_reasoning_length", 1000): + warnings.append(f"Reasoning too long: {len(reasoning)} characters") + + return { + "compliant": len(violations) == 0, + "violations": violations, + "warnings": warnings, + "policy_rules": rules + } + + # --- Private helper methods for decision management --- + + def _add_decision_to_graph(self, decision: Dict[str, Any]) -> None: + """Add decision to context graph.""" + try: + # Add decision node + self.add_node( + decision["id"], + "decision", + properties={ + "category": decision["category"], + "outcome": decision["outcome"], + "confidence": decision["confidence"], + "timestamp": decision["timestamp"], + "scenario": decision["scenario"][:100] + "..." if len(decision["scenario"]) > 100 else decision["scenario"], + "decision_maker": decision.get("decision_maker", ""), + "reasoning": decision["reasoning"][:200] + "..." if len(decision["reasoning"]) > 200 else decision["reasoning"] + } + ) + + # Add entity nodes and relationships + for entity in decision["entities"]: + # Add entity node if not exists + if not self.get_node(entity): + self.add_node( + entity, + "entity", + properties={"name": entity} + ) + + # Add relationship + self.add_edge( + decision["id"], + entity, + "involves", + properties={"confidence": decision["confidence"]} + ) + + # Add category node and relationship + category_id = f"category_{decision['category']}" + if not self.get_node(category_id): + self.add_node( + category_id, + "category", + properties={"name": decision["category"]} + ) + + self.add_edge( + decision["id"], + category_id, + "belongs_to", + properties={} + ) + + # Add decision maker node if provided + if decision.get("decision_maker"): + maker_id = f"maker_{decision['decision_maker']}" + if not self.get_node(maker_id): + self.add_node( + maker_id, + "decision_maker", + properties={"name": decision["decision_maker"]} + ) + + self.add_edge( + decision["id"], + maker_id, + "made_by", + properties={} + ) + + except Exception as e: + self.logger.warning(f"Failed to add decision to graph: {e}") + + def _calculate_content_similarity(self, scenario: str, decision: Dict[str, Any]) -> float: + """Calculate content similarity between scenario and decision.""" + try: + # Simple word-based similarity + scenario_words = set(scenario.lower().split()) + decision_text = f"{decision['scenario']} {decision['reasoning']} {' '.join(decision['entities'])}" + decision_words = set(decision_text.lower().split()) + + intersection = scenario_words.intersection(decision_words) + union = scenario_words.union(decision_words) + + return len(intersection) / len(union) if union else 0.0 + + except Exception as e: + self.logger.warning(f"Content similarity calculation failed: {e}") + return 0.0 + + def _calculate_structural_similarity_for_decision(self, decision_id: str, scenario: str) -> float: + """Calculate structural similarity using graph algorithms.""" + try: + if not self.config.get("advanced_analytics"): + return 0.0 + + # Use graph similarity algorithms + similar_nodes = self.find_similar_nodes( + decision_id, + similarity_type="structural", + limit=5 + ) + + if similar_nodes: + return max(node.get("similarity", 0.0) for node in similar_nodes) + + except Exception as e: + self.logger.warning(f"Structural similarity calculation failed: {e}") + + return 0.0 + + def _find_indirect_decision_influence(self, decision_id: str, max_depth: int) -> Set[str]: + """Find indirect influences using graph traversal.""" + try: + influenced = set() + + # Get neighbors in graph + neighbors = self.get_neighbors(decision_id, max_depth=max_depth) + + for neighbor in neighbors: + if neighbor.get("type") == "decision": + influenced.add(neighbor["id"]) + + return influenced + + except Exception as e: + self.logger.warning(f"Indirect influence analysis failed: {e}") + return set() + + def _calculate_decision_influence_score(self, source_id: str, target_id: str) -> float: + """Calculate influence score between two decisions.""" + try: + if not hasattr(self, '_decisions'): + return 0.0 + + source_decision = self._decisions[source_id] + target_decision = self._decisions[target_id] + + # Base score from shared entities + shared_entities = set(source_decision["entities"]) & set(target_decision["entities"]) + entity_score = len(shared_entities) / max(len(source_decision["entities"]), 1) + + # Category similarity + category_score = 1.0 if source_decision["category"] == target_decision["category"] else 0.0 + + # Temporal proximity (more recent decisions have higher influence) + time_diff = abs(source_decision["timestamp"] - target_decision["timestamp"]) + time_score = max(0.0, 1.0 - time_diff / (30 * 24 * 3600)) # 30 days window + + # Combined score + combined_score = 0.5 * entity_score + 0.3 * category_score + 0.2 * time_score + + return combined_score + + except Exception as e: + self.logger.warning(f"Influence score calculation failed: {e}") + return 0.0 + + def _get_decision_temporal_analysis(self) -> Dict[str, Any]: + """Get temporal analysis of decisions.""" + try: + if not hasattr(self, '_temporal_index') or not self._temporal_index: + return {} + + # Group decisions by time periods + recent_decisions = [did for did, ts in self._temporal_index[:10]] + + return { + "recent_decisions": len(recent_decisions), + "oldest_decision": min(ts for _, ts in self._temporal_index), + "newest_decision": max(ts for _, ts in self._temporal_index), + "time_span": max(ts for _, ts in self._temporal_index) - min(ts for _, ts in self._temporal_index) + } + + except Exception as e: + self.logger.warning(f"Temporal analysis failed: {e}") + return {} + + def _get_decision_entity_analysis(self) -> Dict[str, Any]: + """Get entity analysis from decisions.""" + try: + if not hasattr(self, '_decisions'): + return {} + + entity_counts = {} + for decision in self._decisions.values(): + for entity in decision["entities"]: + entity_counts[entity] = entity_counts.get(entity, 0) + 1 + + # Get top entities + top_entities = sorted(entity_counts.items(), key=lambda x: x[1], reverse=True)[:10] + + return { + "total_entities": len(entity_counts), + "top_entities": top_entities, + "avg_entities_per_decision": sum(len(d["entities"]) for d in self._decisions.values()) / len(self._decisions) + } + + except Exception as e: + self.logger.warning(f"Entity analysis failed: {e}") + return {} + + # --- Easy-to-Use Convenience Methods --- + + def add_decision( + self, + category: str, + scenario: str, + reasoning: str, + outcome: str, + confidence: float = 0.5, + entities: Optional[List[str]] = None, + decision_maker: Optional[str] = "system", + **kwargs + ) -> str: + """ + Easy way to record a decision. + + Args: + category: Decision category (e.g., "loan_approval") + scenario: What was the situation + reasoning: Why was this decision made + outcome: What was decided + confidence: How confident (0.0 to 1.0) + entities: Related entities (people, items, etc.) + decision_maker: Who made the decision + **kwargs: Additional information + + Returns: + Decision ID for reference + """ + return self.record_decision( + category=category, + scenario=scenario, + reasoning=reasoning, + outcome=outcome, + confidence=confidence, + entities=entities, + decision_maker=decision_maker, + metadata=kwargs + ) + + def find_similar_decisions( + self, + scenario: str, + category: Optional[str] = None, + max_results: int = 10, + min_similarity: float = 0.3 + ) -> List[Dict[str, Any]]: + """ + Easy way to find similar past decisions. + + Args: + scenario: What situation are you looking for + category: Filter by decision type + max_results: Maximum results to return + min_similarity: Minimum similarity score + + Returns: + List of similar decisions with similarity scores + """ + return self.find_precedents( + scenario=scenario, + category=category, + limit=max_results, + similarity_threshold=min_similarity + ) + + def analyze_decision_impact( + self, + decision_id: str, + include_indirect: bool = True + ) -> Dict[str, Any]: + """ + Easy way to analyze how a decision impacts others. + + Args: + decision_id: Decision to analyze + include_indirect: Include indirect impacts + + Returns: + Impact analysis results + """ + return self.analyze_decision_influence( + decision_id=decision_id, + max_depth=3, + include_indirect=include_indirect + ) + + def get_decision_summary(self) -> Dict[str, Any]: + """ + Easy way to get a summary of all decisions. + + Returns: + Summary statistics and insights + """ + return self.get_decision_insights() + + def trace_decision_chain( + self, + decision_id: str, + max_steps: int = 5 + ) -> List[Dict[str, Any]]: + """ + Easy way to trace how decisions are connected. + + Args: + decision_id: Starting decision + max_steps: Maximum steps to trace + + Returns: + Decision chain connections + """ + return self.trace_decision_causality( + decision_id=decision_id, + max_depth=max_steps + ) + + def check_decision_rules( + self, + decision_data: Dict[str, Any], + rules: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """ + Easy way to check if a decision follows the rules. + + Args: + decision_data: Decision to check + rules: Custom rules (uses default if None) + + Returns: + Compliance check results + """ + return self.enforce_decision_policy( + decision_data=decision_data, + policy_rules=rules + ) + + def get_graph_summary(self) -> Dict[str, Any]: + """ + Easy way to get graph statistics. + + Returns: + Graph summary information + """ + if hasattr(self, 'get_graph_metrics'): + return self.get_graph_metrics() + else: + return { + "nodes": len(self.nodes), + "edges": len(self.edges), + "node_types": self._get_node_type_distribution(), + "edge_types": self._get_edge_type_distribution() + } + + def find_related_nodes( + self, + node_id: str, + how_many: int = 10, + similarity_type: str = "content" + ) -> List[Tuple[str, float]]: + """ + Easy way to find nodes similar to a given node. + + Args: + node_id: Reference node + how_many: How many similar nodes to find + similarity_type: Type of similarity ("content", "structural") + + Returns: + List of (node_id, similarity_score) tuples + """ + return self.find_similar_nodes( + node_id=node_id, + similarity_type=similarity_type, + top_k=how_many + ) + + def get_node_importance( + self, + node_id: str + ) -> Dict[str, float]: + """ + Easy way to get how important a node is in the graph. + + Args: + node_id: Node to analyze + + Returns: + Centrality measures (importance scores) + """ + return self.get_node_centrality(node_id) + + def analyze_connections(self) -> Dict[str, Any]: + """ + Easy way to analyze the entire graph structure. + + Returns: + Graph analysis results + """ + return self.analyze_graph_with_kg() # For backward compatibility diff --git a/semantica/context/context_usage.md b/semantica/context/context_usage.md index 38a0f084..77e9d0fe 100644 --- a/semantica/context/context_usage.md +++ b/semantica/context/context_usage.md @@ -1,1247 +1,443 @@ -# Context Module Usage Guide +# Context Module - Usage Guide -This guide demonstrates how to use the Semantica context module for building context graphs, managing agent memory, retrieving context, linking entities, and decision tracking with hybrid search capabilities and advanced KG algorithm integration. +## 🎯 What This Module Does -## Quick Imports +The context module gives your AI agents the ability to **remember**, **learn**, and **make smarter decisions** by organizing information in a way that's both powerful and easy to use. -```python -# Core context classes -from semantica.context import AgentContext, ContextGraph, ContextRetriever, DecisionContext +Think of it as giving your agent a brain that can: +- **Remember conversations** (like human memory) +- **Learn from past decisions** (become smarter over time) +- **Find relevant information** quickly (when it matters most) +- **Understand relationships** between concepts +- **Make consistent decisions** based on experience -# Memory management -from semantica.context import AgentMemory +--- -# Entity linking -from semantica.context import EntityLinker - -# Decision tracking with advanced features -from semantica.context import Decision, Policy, PolicyException, DecisionRecorder, DecisionQuery, CausalChainAnalyzer, PolicyEngine - -# For vector storage (often used with context) -from semantica.vector_store import VectorStore -``` - -## Quick Example - -```python -# Simple context setup -vector_store = VectorStore(backend="inmemory", dimension=384) -context = AgentContext(vector_store=vector_store) - -# Store a memory -memory_id = context.store("User likes Python programming", conversation_id="conv1") - -# Retrieve context -results = context.retrieve("Python programming", max_results=5) -print(f"Found {len(results)} results") -``` - -## Table of Contents - -1. [High-Level Interface (Quick Start)](#high-level-interface-quick-start) -2. [Basic Usage](#basic-usage) -3. [Enhanced AgentContext with Decision Tracking and KG Algorithms](#enhanced-agentcontext-with-decision-tracking-and-kg-algorithms) -4. [Context Graph Construction](#context-graph-construction) -5. [Enhanced ContextGraph with KG Algorithms](#enhanced-contextgraph-with-kg-algorithms) -6. [Agent Memory Management](#agent-memory-management) -7. [Context Retrieval](#context-retrieval) -8. [Entity Linking](#entity-linking) -9. [Decision Tracking](#decision-tracking) -10. [Policy Exception Management](#policy-exception-management) -11. [Hybrid Search for Decisions](#hybrid-search-for-decisions) -12. [Context Graphs with KG Algorithms](#context-graphs-with-kg-algorithms) -13. [Advanced Decision Analytics](#advanced-decision-analytics) -14. [Production Examples](#production-examples) -15. [Explainable AI](#explainable-ai) - -## High-Level Interface (Quick Start) - -The `AgentContext` class provides a simplified, generic interface for common use cases. It integrates vector storage, knowledge graphs, and memory management into a unified system. - -### Simple RAG (Vector Only) +## 🚀 Quick Start - 5 Minutes to Your First Smart Agent +### Step 1: Basic Setup ```python from semantica.context import AgentContext from semantica.vector_store import VectorStore -# Initialize vector store -vs = VectorStore(backend="faiss", dimension=768) +# Create your agent with memory +vector_store = VectorStore(backend="inmemory", dimension=384) +agent = AgentContext(vector_store=vector_store) -# Initialize context -context = AgentContext(vector_store=vs) +# Your agent can now remember things +memory_id = agent.store("User asked about Python programming") +print(f"Agent remembered: {memory_id}") -# Store a memory -memory_id = context.store("User likes Python programming", conversation_id="conv1") +# And find information when needed +results = agent.retrieve("Python tutorials") +print(f"Agent found {len(results)} relevant memories") +``` -# Retrieve context -results = context.retrieve("Python programming", max_results=5) +### Step 2: Add Decision Learning +```python +# Your agent learns from its decisions +decision_id = agent.record_decision( + category="content_recommendation", + scenario="User wants Python tutorial", + reasoning="User mentioned being a beginner", + outcome="recommended_basics", + confidence=0.85 +) +# Your agent can now find similar past decisions +similar_decisions = agent.find_precedents("Python tutorial", limit=3) +print(f"Agent found {len(similar_decisions)} similar past decisions") +``` + +### Step 3: Get Insights +```python +# Understand how your agent is performing +insights = agent.get_context_insights() +print(f"Agent has made {insights.get('total_decisions', 0)} decisions") +print(f"Decision categories: {list(insights.get('categories', {}).keys())}") +``` + +**That's it! Your agent now has memory and can learn from decisions.** 🎉 + +--- + +## 🤖 AgentContext - Your Agent's Brain + +### Memory Management (Like Human Memory) +```python +# Store different types of memories +agent.store("User likes Python programming", conversation_id="chat_1") +agent.store("User is working on a web project", conversation_id="chat_2") +agent.store("User mentioned being a beginner", conversation_id="chat_3") + +# Find memories when needed +results = agent.retrieve("Python programming", conversation_id="chat_1") for result in results: - print(f"Content: {result['content']}") - print(f"Score: {result['score']:.2f}") + print(f"Memory: {result['content']}") + +# Search across all conversations +all_results = agent.retrieve("beginner") +print(f"Found {len(all_results)} memories about beginners") ``` -### GraphRAG (Vector + Graph) - +### Learning from Decisions ```python -from semantica.context import AgentContext, ContextGraph -from semantica.graph_store import GraphStore - -# Initialize persistent knowledge graph (Recommended for production) -try: - kg = GraphStore(backend="neo4j", uri="bolt://localhost:7687", user="neo4j", password="password") - kg.connect() -except: - print("Neo4j not available, falling back to in-memory graph") - kg = ContextGraph() - -# Initialize context with vector store and knowledge graph -context = AgentContext(vector_store=vs, knowledge_graph=kg) - -# Store documents (auto-builds graph) -documents = [ - "Python is a programming language used for machine learning", - "TensorFlow and PyTorch are popular ML frameworks", - "Machine learning involves training models on data" -] - -stats = context.store( - documents, - extract_entities=True, # Extract entities from documents - extract_relationships=True, # Extract relationships - link_entities=True # Link entities across documents +# Record important decisions +decision_id = agent.record_decision( + category="content_recommendation", + scenario="User wants to learn web development", + reasoning="User is beginner, likes Python", + outcome="recommended_python_basics", + confidence=0.90 ) -print(f"Stored {stats['stored_count']} documents") -# Graph stats are available via the graph object directly or context stats -print(f"Graph nodes: {kg.stats()['node_count']}") - -# Retrieve with graph context (auto-detects GraphRAG) -results = context.retrieve( - "Python machine learning", - use_graph=True, # Explicitly use graph - include_entities=True, # Include related entities - expand_graph=True # Use graph expansion -) - -for result in results: - print(f"Content: {result['content']}") - print(f"Score: {result['score']:.2f}") - print(f"Related entities: {len(result.get('related_entities', []))}") +# Find similar past decisions to make better choices +similar_decisions = agent.find_precedents("web development", limit=5) +for decision in similar_decisions: + print(f"Past decision: {decision.scenario}") + print(f"Result: {decision.outcome}") + print(f"Confidence: {decision.confidence}") + print("---") ``` -### Agent Memory Management (Hierarchical) - -The system uses a hierarchical memory structure with: -1. **Short-Term Memory**: Fast, in-memory buffer with token and item count limits. -2. **Long-Term Memory**: Persistent vector store. - +### Getting Smarter Over Time ```python -context = AgentContext( - vector_store=vs, - retention_days=30, - short_term_limit=10, # Max items in short-term buffer - token_limit=2000 # Max tokens in short-term buffer +# Enable all learning features +smart_agent = AgentContext( + vector_store=vector_store, + decision_tracking=True, # Learn from decisions + graph_expansion=True, # Find related information + advanced_analytics=True, # Understand patterns + kg_algorithms=True, # Advanced analysis + vector_store_features=True ) -# Store multiple memories in a conversation -context.store("Hello, I'm interested in Python", conversation_id="conv1", user_id="user123") -context.store("What can you tell me about machine learning?", conversation_id="conv1", user_id="user123") - -# Get conversation history -history = context.conversation( - "conv1", - reverse=True, # Most recent first - include_metadata=True # Include full metadata -) - -for item in history: - print(f"{item['timestamp']}: {item['content']}") - -# Delete old memories -deleted_count = context.forget(days_old=90) -print(f"Deleted {deleted_count} old memories") +# Get insights about your agent's learning +insights = smart_agent.get_context_insights() +print(f"Total decisions learned: {insights.get('total_decisions', 0)}") +print(f"Decision categories: {list(insights.get('categories', {}).keys())}") +print(f"Most common outcome: {insights.get('most_common_outcome', 'N/A')}") ``` -### Persistence (Save/Load) +--- -You can save the entire state of the agent (Memory, Graph, and Vector Index) to disk and reload it later. +## 🏗️ ContextGraph - Knowledge Organization +When you need to organize complex information, ContextGraph helps you build knowledge networks. + +### Build a Simple Knowledge Graph ```python -# Save state -context.save("./my_agent_state") +from semantica.context import ContextGraph -# Load state -new_context = AgentContext(vector_store=VectorStore(), knowledge_graph=ContextGraph()) -new_context.load("./my_agent_state") +# Create a knowledge graph +knowledge = ContextGraph(advanced_analytics=True) + +# Add things you want to remember (nodes) +knowledge.add_node("Python", "language", properties={"popularity": "high"}) +knowledge.add_node("Programming", "concept", properties={"type": "skill"}) +knowledge.add_node("FastAPI", "framework", properties={"language": "Python"}) +knowledge.add_node("Web Development", "field", properties={"complexity": "medium"}) + +# Connect related things (edges) +knowledge.add_edge("Python", "Programming", "related_to") +knowledge.add_edge("Python", "FastAPI", "supports") +knowledge.add_edge("FastAPI", "Web Development", "used_for") +knowledge.add_edge("Programming", "Web Development", "requires") ``` -## Basic Usage +### Easy Decision Management +```python +# Record decisions in your knowledge graph +decision_id = knowledge.add_decision( + category="technology_choice", + scenario="Framework selection for web API", + reasoning="FastAPI provides better performance for Python APIs", + outcome="selected_fastapi", + confidence=0.92, + entities=["Python", "FastAPI", "web_project"] +) -### Initialization with Backends +# Find similar decisions easily +similar = knowledge.find_similar_decisions( + scenario="web framework", + category="technology_choice", + max_results=3 +) -You can configure the `VectorStore` with different backends (`inmemory`, `faiss`, `chroma`, `qdrant`, `weaviate`, `milvus`) and embedding models (including FastEmbed). +print(f"Found {len(similar)} similar decisions") +for decision in similar: + print(f" Similar scenario: {decision.get('scenario', 'N/A')}") + print(f" Outcome: {decision.get('outcome', 'N/A')}") +``` +### Understand Decision Impact +```python +# See how decisions affect other decisions +impact = knowledge.analyze_decision_impact(decision_id) +print(f"This decision influenced {impact.get('total_influenced', 0)} other decisions") + +# Get a summary of all decisions +summary = knowledge.get_decision_summary() +print(f"Total decisions: {summary.get('total_decisions', 0)}") +print(f"Categories: {list(summary.get('categories', {}).keys())}") + +# Trace decision chains (how decisions connect) +chains = knowledge.trace_decision_chain(decision_id) +print(f"Decision chain has {len(chains)} connections") +``` + +### Smart Decision Checking +```python +# Check if decisions follow your rules +compliance = knowledge.check_decision_rules({ + "category": "loan_approval", + "scenario": "Mortgage application", + "reasoning": "Good credit score, stable income", + "outcome": "approved", + "confidence": 0.95 +}) + +if compliance.get("compliant", False): + print("✅ Decision follows all rules") +else: + print(f"❌ Rule violations: {compliance.get('violations', [])}") +``` + +### Graph Analytics Made Simple +```python +# Get overview of your knowledge graph +summary = knowledge.get_graph_summary() +print(f"Knowledge graph has {summary.get('nodes', 0)} concepts") +print(f"And {summary.get('edges', 0)} relationships") + +# Find related concepts +related = knowledge.find_related_nodes("Python", how_many=5) +for concept_id, similarity in related: + print(f"Related to {concept_id}: {similarity:.2f}") + +# Understand which concepts are most important +importance = knowledge.get_node_importance("Python") +print(f"Python importance score: {importance.get('degree', 0)}") +``` + +--- + +## 🔄 Using Both Together - The Complete Setup + +### Your Smart Agent System ```python from semantica.context import AgentContext, ContextGraph from semantica.vector_store import VectorStore -# Initialize Vector Store with FastEmbed -vs = VectorStore(backend="inmemory", dimension=384) -if hasattr(vs, "embedder") and vs.embedder: - vs.embedder.set_text_model(method="fastembed", model_name="BAAI/bge-small-en-v1.5") +# Create the components +vector_store = VectorStore(backend="inmemory", dimension=384) +knowledge = ContextGraph(advanced_analytics=True) -# Initialize Context Graph -kg = ContextGraph() +# Create your intelligent agent +agent = AgentContext( + vector_store=vector_store, + knowledge_graph=knowledge, # Add knowledge graph + decision_tracking=True, + graph_expansion=True, + advanced_analytics=True +) -# Initialize Agent Context -context = AgentContext(vector_store=vs, knowledge_graph=kg) +# Your agent works like this: +# 1. Store information in memory +agent.store("User wants to learn web development with Python") +agent.store("User is a beginner programmer") +agent.store("User prefers hands-on tutorials") + +# 2. Find relevant information +results = agent.retrieve("Python web development tutorials") +print(f"Found {len(results)} relevant memories") + +# 3. Make smart decisions +decision_id = agent.record_decision( + category="content_recommendation", + scenario="Python web development learning path", + reasoning="Beginner needs hands-on Python web tutorial", + outcome="recommended_flask_tutorial", + confidence=0.89 +) + +# 4. Learn and improve over time +insights = agent.get_context_insights() +print(f"Agent insights: {insights}") + +# 5. Access advanced features when needed +graph_summary = agent.graph_builder.get_graph_summary() +node_importance = agent.graph_builder.get_node_importance("Python") ``` -### Enhanced AgentContext with Decision Tracking and KG Algorithms +--- -The enhanced `AgentContext` supports advanced decision tracking, KG algorithm integration, and vector store features for production-grade context engineering. +## 🎯 Real-World Examples +### 🏦 Banking - Smart Loan Decisions ```python -from semantica.context import AgentContext, ContextGraph -from semantica.vector_store import VectorStore -from semantica.graph_store import GraphStore # For decision tracking +# Track loan decisions and learn from patterns +bank_agent = AgentContext(vector_store=bank_vector_store, decision_tracking=True) -# Initialize Vector Store -vs = VectorStore(backend="inmemory", dimension=768) +# Store customer information +bank_agent.store("Customer has credit score 750, stable employment") +bank_agent.store("Customer is first-time homebuyer") -# Initialize Graph Store (required for decision tracking) -# Note: Decision tracking requires a GraphStore with execute_query() support -gs = GraphStore(backend="neo4j", uri="bolt://localhost:7687") - -# Initialize Context Graph with KG algorithms -kg = ContextGraph( - enable_advanced_analytics=True, - enable_centrality_analysis=True, - enable_community_detection=True, - enable_node_embeddings=True -) - -# Initialize Enhanced Agent Context -context = AgentContext( - vector_store=vs, - knowledge_graph=kg, - enable_decision_tracking=True, # Enable decision lifecycle management - enable_advanced_analytics=True, # Enable KG algorithm integration - enable_kg_algorithms=True, # Enable centrality, community detection - enable_vector_store_features=True # Enable hybrid search capabilities -) - -# Record a decision with full context -decision_id = context.record_decision( - category="mortgage_approval", - scenario="First-time homebuyer application", - reasoning="Strong credit score, stable employment, low debt-to-income ratio", +# Make loan decision +loan_decision = bank_agent.record_decision( + category="loan_approval", + scenario="First-time homebuyer mortgage", + reasoning="Good credit score, stable income, 20% down payment", outcome="approved", - confidence=0.94, - decision_maker="loan_officer_001" + confidence=0.94 ) -# Find similar decisions with KG-enhanced search -precedents = context.find_precedents_advanced( - scenario="Mortgage application", - use_kg_features=True, - similarity_weights={"semantic": 0.5, "structural": 0.3, "category": 0.2} -) - -# Analyze decision influence -influence = context.analyze_decision_influence(decision_id) -print(f"Influence score: {influence.get('influence_score', 0):.3f}") -print(f"Centrality measures: {influence.get('centrality_measures', {})}") - -# Get comprehensive context insights -insights = context.get_context_insights() -print(f"Advanced features: {insights.get('advanced_features', {})}") +# Find similar loan decisions for consistency +similar_loans = bank_agent.find_precedents("homebuyer", category="loan_approval") +print(f"Found {len(similar_loans)} similar loan decisions") ``` -## Context Graph Construction - -The `ContextGraph` class is an in-memory graph store. - -### Building from Entities and Relationships - +### 🏥 Healthcare - Patient Care Decisions ```python -from semantica.context import ContextGraph +# Track patient care decisions +health_agent = AgentContext(vector_store=medical_vector_store, decision_tracking=True) -graph = ContextGraph() +# Store patient information +health_agent.store("Patient has hypertension, type 2 diabetes") +health_agent.store("Patient allergic to penicillin") -entities = [ - {"id": "e1", "text": "Python", "type": "PROGRAMMING_LANGUAGE"}, - {"id": "e2", "text": "Machine Learning", "type": "CONCEPT"}, - {"id": "e3", "text": "TensorFlow", "type": "FRAMEWORK"}, -] +# Make treatment decision +treatment_decision = health_agent.record_decision( + category="treatment_plan", + scenario="Hypertension with diabetes", + reasoning="ACE inhibitors safe for diabetic patients", + outcome="prescribed_ace_inhibitor", + confidence=0.91 +) -relationships = [ - {"source_id": "e1", "target_id": "e2", "type": "used_for", "confidence": 0.9}, - {"source_id": "e3", "target_id": "e2", "type": "implements", "confidence": 0.95}, -] - -graph_data = graph.build_from_entities_and_relationships(entities, relationships) - -print(f"Nodes: {graph.stats()['node_count']}") -print(f"Edges: {graph.stats()['edge_count']}") +# Find similar treatment cases +similar_cases = health_agent.find_precedents("hypertension", category="treatment_plan") ``` -### Building from Conversations - +### 🛒 E-commerce - Smart Recommendations ```python -from semantica.context import ContextGraph +# Track recommendation decisions +ecommerce_graph = ContextGraph() -graph = ContextGraph() +# Build user-product knowledge +ecommerce_graph.add_node("user_123", "user", {"segment": "premium"}) +ecommerce_graph.add_node("laptop_xyz", "product", {"category": "electronics"}) +ecommerce_graph.add_edge("user_123", "laptop_xyz", "viewed") -conversations = [ - { - "id": "conv1", - "content": "User asked about Python programming", - "entities": [ - {"id": "e1", "text": "Python", "type": "PROGRAMMING_LANGUAGE"} - ], - "relationships": [] - } -] - -graph_data = graph.build_from_conversations( - conversations, - link_entities=True, - extract_intents=True -) -``` - -### Adding Nodes and Edges Manually - -```python -from semantica.context import ContextGraph - -graph = ContextGraph() - -# Add nodes -graph.add_node("node1", "entity", "Python programming", confidence=0.9) -graph.add_node("node2", "concept", "Machine Learning", confidence=0.95) - -# Add edges -graph.add_edge("node1", "node2", "related_to", weight=0.9) - -# Get neighbors -neighbors = graph.get_neighbors("node1", hops=2) -print(f"Neighbors: {neighbors}") - -# Query graph -results = graph.query("Python") # Keyword search on nodes -``` - -### Graph Statistics and Analysis - -```python -stats = graph.stats() -print(f"Node types: {stats['node_types']}") -print(f"Density: {stats['density']:.4f}") - -# Find specific nodes/edges -entities = graph.find_nodes(node_type="entity") -relations = graph.find_edges(edge_type="related_to") -node = graph.find_node("node1") -``` - -### Enhanced ContextGraph with KG Algorithms - -The enhanced `ContextGraph` supports advanced KG algorithms for centrality analysis, community detection, and node embeddings. - -```python -from semantica.context import ContextGraph - -# Initialize with KG algorithms enabled -graph = ContextGraph( - enable_advanced_analytics=True, - enable_centrality_analysis=True, - enable_community_detection=True, - enable_node_embeddings=True +# Make recommendation decision +rec_decision = ecommerce_graph.add_decision( + category="product_recommendation", + scenario="Laptop recommendation for premium user", + reasoning="User prefers high-performance electronics", + outcome="recommended_gaming_laptop", + confidence=0.87, + entities=["user_123", "laptop_xyz"] ) -# Add nodes and edges -graph.add_node("Python", "Language", {"popularity": "high"}) -graph.add_node("FastAPI", "Framework", {"language": "Python"}) -graph.add_node("Django", "Framework", {"language": "Python"}) -graph.add_edge("FastAPI", "Python", "WRITTEN_IN") -graph.add_edge("Django", "Python", "WRITTEN_IN") - -# Centrality analysis -centrality = graph.get_node_centrality("Python") -print(f"Python centrality: {centrality}") - -# Find similar nodes using embeddings -similar_nodes = graph.find_similar_nodes("Python", similarity_type="content") -print(f"Similar nodes to Python: {[node['id'] for node in similar_nodes]}") - -# Community detection -analysis = graph.analyze_graph_with_kg() -communities = analysis.get('community_analysis', {}) -print(f"Communities found: {communities.get('num_communities', 0)}") - -# Node embeddings -embeddings = graph.get_node_embeddings("Python") -print(f"Python embedding dimension: {len(embeddings) if embeddings else 0}") -``` - -## Agent Memory Management - -The `AgentMemory` class handles short-term and long-term memory with hierarchical storage and token management. - -### Storing and Retrieving - -```python -from semantica.context import AgentMemory - -memory = AgentMemory( - vector_store=vs, - knowledge_graph=kg, - retention_policy="30_days", - max_memory_size=10000, - short_term_limit=20, # 20 items max in short-term - token_limit=4000 # 4000 tokens max in short-term -) - -# Store (automatically updates short-term and long-term) -memory_id = memory.store( - "User asked about Python programming", - metadata={"conversation_id": "conv_123"} -) - -# Store short-term only (fleeting thoughts) -temp_id = memory.store( - "Just checking status...", - skip_vector=True -) - -# Retrieve -results = memory.retrieve( - "Python programming", - max_results=5, - type="conversation" -) - -# Conversation History -history = memory.get_conversation_history("conv_123") -``` - -## Context Retrieval - -The `ContextRetriever` implements hybrid retrieval strategies. - -### Hybrid Retrieval - -```python -from semantica.context import ContextRetriever - -retriever = ContextRetriever( - memory_store=memory, - knowledge_graph=kg, - vector_store=vs, - use_graph_expansion=True, - max_expansion_hops=2, - hybrid_alpha=0.5 # Balance between vector (0.0) and graph (1.0) -) - -results = retriever.retrieve( - "Python programming", +# Find similar recommendations +similar_recs = ecommerce_graph.find_similar_decisions( + scenario="laptop recommendation", max_results=5 ) - -for result in results: - print(f"Content: {result.content}") - print(f"Source: {result.source}") # 'vector', 'graph', or 'memory' ``` -## Entity Linking +--- -The `EntityLinker` helps resolve entities to canonical forms or URIs. +## 💡 Pro Tips for Success +### 🌱 For Beginners +1. **Start with AgentContext** - It's simpler and handles most needs +2. **Use basic store/retrieve** - Like building human memory +3. **Add decision tracking** - Your agent gets smarter over time +4. **Enable features gradually** - Add complexity as you need it + +### 🚀 For Advanced Users +1. **Add ContextGraph** - When you need knowledge relationships +2. **Use analytics** - Understand patterns and get insights +3. **Implement policies** - Ensure consistent decisions +4. **Use persistence** - Save and load agent state + +### 🏭 For Production +1. **Enable all features** - Maximum intelligence and reliability +2. **Use save/load** - Persist agent state between sessions +3. **Monitor performance** - Use health checks and insights +4. **Test thoroughly** - Verify all functionality works + +--- + +## 🔧 Configuration Options + +### Simple Setup (Most Common) ```python -from semantica.context import EntityLinker - -linker = EntityLinker() -uri = linker.generate_uri("Python Programming Language") -print(uri) # e.g., "python_programming_language" - -# Similarity matching -score = linker._calculate_text_similarity("Python", "Python Language") +# Just memory and basic learning +agent = AgentContext(vector_store=vector_store) ``` -## Decision Tracking - -The `DecisionContext` class provides decision tracking capabilities with hybrid search, explainable AI, and KG algorithm integration. - -### Basic Decision Recording - +### Smart Setup (Recommended) ```python -from semantica.context import DecisionContext -from semantica.vector_store import VectorStore - -# Initialize decision context -vector_store = VectorStore(backend="inmemory", dimension=384) -decision_context = DecisionContext(vector_store=vector_store, graph_store=None) - -# Record a decision -decision_id = decision_context.record_decision( - scenario="Credit limit increase for premium customer", - reasoning="Excellent payment history and high credit score", - outcome="approved", - confidence=0.92, - entities=["customer_123", "premium_segment", "credit_card"], - category="credit_approval", - amount=50000, - risk_level="low" -) - -print(f"Recorded decision: {decision_id}") -``` - -### Batch Decision Processing - -```python -# Process multiple decisions -decisions = [ - { - "scenario": "Credit limit increase request", - "reasoning": "Good payment history", - "outcome": "approved", - "confidence": 0.85, - "entities": ["customer_456"], - "category": "credit_approval" - }, - { - "scenario": "Fraud detection alert", - "reasoning": "Suspicious transaction pattern", - "outcome": "blocked", - "confidence": 0.95, - "entities": ["transaction_789", "customer_456"], - "category": "fraud_detection" - } -] - -decision_ids = [] -for decision in decisions: - decision_id = decision_context.record_decision(**decision) - decision_ids.append(decision_id) - -print(f"Processed {len(decision_ids)} decisions") -``` - -### Decision Context Retrieval - -```python -# Get comprehensive decision context -context_info = decision_context.get_decision_context( - decision_id, - depth=2, - include_entities=True, - include_policies=True -) - -print(f"Decision context: {len(context_info.related_entities)} entities") -print(f"Related relationships: {len(context_info.related_relationships)}") -``` - -### Policy Exception Management - -The enhanced decision tracking system supports policy exceptions with proper audit trails. - -```python -from semantica.context import PolicyException, PolicyEngine -from datetime import datetime - -# Create a policy exception -exception = PolicyException( - exception_id="exc_001", - decision_id="decision_123", - policy_id="lending_policy_v2", - reason="Customer relationship exception - long-term premium client", - approver="branch_manager_001", - approval_timestamp=datetime.now(), - justification="Customer has 10-year history with excellent payment record" -) - -# Convert to dictionary for storage -exception_dict = exception.to_dict() -print(f"Exception recorded: {exception_dict['exception_id']}") - -# Create exception from dictionary (e.g., when loading from database) -recreated_exception = PolicyException.from_dict(exception_dict) -print(f"Recreated exception: {recreated_exception.reason}") - -# Policy engine can record exceptions in GraphStore -policy_engine = PolicyEngine(graph_store) -exception_id = policy_engine.record_exception( - decision_id="decision_123", - policy_id="lending_policy_v2", - reason="Long-term customer relationship exception" -) -print(f"Policy exception recorded: {exception_id}") -``` - -## Hybrid Search for Decisions - -The context retriever supports hybrid search combining semantic and structural embeddings. - -### Finding Similar Decisions - -```python -from semantica.context import ContextRetriever - -# Initialize retriever with decision context -retriever = ContextRetriever( +# Memory + decision learning +agent = AgentContext( vector_store=vector_store, - knowledge_graph=None -) - -# Find similar decisions using hybrid search -precedents = decision_context.find_similar_decisions( - scenario="Credit limit increase for good customer", - limit=5, - use_hybrid_search=True, - semantic_weight=0.7, - structural_weight=0.3 -) - -for precedent in precedents: - print(f"Score: {precedent['score']:.3f}") - print(f"Content: {precedent['content'][:100]}...") - print(f"Entities: {precedent['related_entities']}") -``` - -### Decision Precedent Search - -```python -# Search for decision precedents -precedents = retriever.retrieve_decision_precedents( - query="Credit approval for premium customers", - limit=10, - use_hybrid_search=True, - include_context=True -) - -print(f"Found {len(precedents)} precedents") - -for precedent in precedents: - print(f"Scenario: {precedent['scenario']}") - print(f"Outcome: {precedent['outcome']}") - print(f"Confidence: {precedent['confidence']}") -``` - -### Query Decisions with Context - -```python -# Query decisions with multi-hop context expansion -queried = retriever.query_decisions( - query="High-risk credit decisions", - max_hops=2, - include_context=True, - use_hybrid_search=True, - filters={"category": "credit_approval", "risk_level": "high"} -) - -print(f"Found {len(queried)} high-risk decisions") -``` - -## Explainable AI - -The decision tracking system provides comprehensive explanations with path tracing and confidence scoring. - -### Decision Explanations - -```python -# Generate comprehensive decision explanation -explanation = decision_context.explain_decision( - decision_id, - include_paths=True, - include_confidence=True, - include_weights=True -) - -print(f"Scenario: {explanation['scenario']}") -print(f"Reasoning: {explanation['reasoning']}") -print(f"Outcome: {explanation['outcome']}") -print(f"Confidence: {explanation['confidence']}") - -# Available explanation components -components = [ - "scenario", "reasoning", "outcome", "confidence", - "semantic_weight", "structural_weight", "embedding_info", - "related_entities", "similar_decisions", "path_tracing" -] - -for component in components: - if component in explanation: - print(f"{component}: {explanation[component]}") -``` - -### Path Tracing and Context - -```python -# Get decision with path tracing -explanation = decision_context.explain_decision( - decision_id, - include_paths=True, - max_depth=3 -) - -# Trace decision paths -if "path_tracing" in explanation: - paths = explanation["path_tracing"] - for path in paths: - print(f"Path: {' -> '.join(path['entities'])}") - print(f"Confidence: {path['confidence']}") - print(f"Relationships: {path['relationships']}") -``` - -### Confidence and Weight Analysis - -```python -# Analyze decision confidence and weights -explanation = decision_context.explain_decision( - decision_id, - include_confidence=True, - include_weights=True -) - -print(f"Decision confidence: {explanation['confidence']}") -print(f"Semantic weight: {explanation['semantic_weight']}") -print(f"Structural weight: {explanation['structural_weight']}") - -# Check if structural embedding was used -if "has_structural_embedding" in explanation: - has_structural = explanation["has_structural_embedding"] - print(f"Structural embedding used: {has_structural}") -``` - -### Real-World Examples - -```python -# Banking decision example -banking_decision = decision_context.record_decision( - scenario="Mortgage application approval", - reasoning="Strong credit score (750), stable employment, 20% down payment", - outcome="approved", - confidence=0.94, - entities=["applicant_001", "mortgage_30yr", "property_main"], - category="mortgage_approval", - loan_amount=350000, - credit_score=750 -) - -# Get banking decision explanation -banking_explanation = decision_context.explain_decision(banking_decision) -print(f"Banking decision: {banking_explanation['outcome']}") -print(f"Risk assessment: {banking_explanation['confidence']}") - -# Insurance decision example -insurance_decision = decision_context.record_decision( - scenario="Auto insurance claim approval", - reasoning="Clear liability, reasonable repair costs, no prior claims", - outcome="approved", - confidence=0.96, - entities=["claim_auto_001", "driver_safe", "policy_active"], - category="auto_insurance", - claim_amount=2500 -) - -# Find similar insurance decisions -insurance_precedents = decision_context.find_similar_decisions( - scenario="Auto claim with clear liability", - limit=5, - filters={"category": "auto_insurance"} -) - -print(f"Found {len(insurance_precedents)} similar insurance claims") -``` - -## Context Graphs with KG Algorithms - -The context module now integrates with `semantica.kg` algorithms to provide advanced graph analytics, centrality measures, community detection, and node embeddings for comprehensive context graph analysis. - -### Initializing Context Graph with KG Features - -```python -from semantica.context import ContextGraph -from semantica.graph_store import GraphStore - -# Context graph with KG algorithms -graph = ContextGraph( - enable_advanced_analytics=True, # Enable KG algorithms - enable_centrality_analysis=True, # Enable centrality measures - enable_community_detection=True, # Enable community detection - enable_node_embeddings=True # Enable Node2Vec embeddings -) - -print(f"KG components initialized: {len(graph.kg_components)}") -``` - -### Graph Analytics with KG Algorithms - -```python -# Comprehensive graph analysis -analysis = graph.analyze_graph_with_kg() - -print(f"Graph metrics:") -print(f" - Node count: {analysis['graph_metrics']['node_count']}") -print(f" - Edge count: {analysis['graph_metrics']['edge_count']}") -print(f" - Node types: {analysis['graph_metrics']['node_types']}") - -# Centrality analysis -if 'centrality_analysis' in analysis: - centrality = analysis['centrality_analysis'] - print(f" - Centrality measures available for {len(centrality)} nodes") - -# Community detection -if 'community_analysis' in analysis: - communities = analysis['community_analysis'] - print(f" - Found {communities['num_communities']} communities") - print(f" - Modularity: {communities['modularity']:.3f}") - -# Node embeddings -if 'node_embeddings' in analysis: - embeddings = analysis['node_embeddings'] - print(f" - Generated embeddings for {len(embeddings)} nodes") -``` - -### Node Centrality Analysis - -```python -# Get centrality measures for a specific node -node_id = "python_programming" -centrality_measures = graph.get_node_centrality(node_id) - -print(f"Centrality measures for {node_id}:") -print(f" - Degree centrality: {centrality_measures.get('degree_centrality', 0):.3f}") -print(f" - Betweenness centrality: {centrality_measures.get('betweenness_centrality', 0):.3f}") -print(f" - Closeness centrality: {centrality_measures.get('closeness_centrality', 0):.3f}") -print(f" - Eigenvector centrality: {centrality_measures.get('eigenvector_centrality', 0):.3f}") -``` - -### Finding Similar Nodes with Advanced Similarity - -```python -# Find similar nodes using different similarity measures -similar_nodes = graph.find_similar_nodes( - node_id="python_programming", - similarity_type="content", # "content", "structural", "embedding" - top_k=10 -) - -print(f"Similar nodes to 'python_programming':") -for node_id, similarity_score in similar_nodes: - print(f" - {node_id}: {similarity_score:.3f}") - -# Structural similarity -structural_similar = graph.find_similar_nodes( - node_id="python_programming", - similarity_type="structural", - top_k=5 + decision_tracking=True, + graph_expansion=True ) ``` -### AgentContext with KG Features - +### Complete Setup (Maximum Power) ```python -from semantica.context import AgentContext -from semantica.vector_store import VectorStore -from semantica.graph_store import GraphStore - -# Initialize AgentContext with all KG features -vector_store = VectorStore(backend="inmemory", dimension=384) -knowledge_graph = GraphStore(backend="neo4j", uri="bolt://localhost:7687") - -context = AgentContext( +# Everything enabled +agent = AgentContext( vector_store=vector_store, - knowledge_graph=knowledge_graph, - enable_decision_tracking=True, - enable_advanced_analytics=True, # Enable KG algorithms - enable_kg_algorithms=True, # Enable KG integration - enable_vector_store_features=True # Enable vector store features + knowledge_graph=ContextGraph(advanced_analytics=True), + decision_tracking=True, + graph_expansion=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True ) - -print("AgentContext initialized with KG algorithms") ``` -## Advanced Decision Analytics - -The decision tracking system provides advanced analytics using KG algorithms for decision influence analysis, relationship prediction, and comprehensive insights. - -### DecisionQuery with KG Integration - +### ContextGraph Options ```python -from semantica.context import DecisionQuery - -# Decision query with KG algorithms -query = DecisionQuery( - graph_store=knowledge_graph, - vector_store=vector_store, - enable_advanced_analytics=True, - enable_centrality_analysis=True, - enable_community_detection=True, - enable_node_embeddings=True -) - -print(f"DecisionQuery with {len(query.kg_components)} KG components") -``` - -### Advanced Precedent Search with Custom Weights - -```python -# Find precedents using advanced search with custom similarity weights -precedents = context.find_precedents_advanced( - scenario="Credit limit increase for premium customer", - category="credit_approval", - limit=10, - use_kg_features=True, - similarity_weights={ - "semantic": 0.4, # Vector similarity - "structural": 0.3, # Graph structure similarity - "text": 0.2, # Text overlap - "category": 0.1 # Category matching - } -) - -print(f"Found {len(precedents)} precedents with advanced search") -for precedent in precedents: - print(f" - Score: {precedent.metadata.get('similarity_score', 0):.3f}") - print(f" - Scenario: {precedent.scenario[:100]}...") -``` - -### Decision Influence Analysis - -```python -# Analyze decision influence using KG algorithms -decision_id = "decision_123" -influence_analysis = context.analyze_decision_influence( - decision_id=decision_id, - max_depth=3 -) - -print(f"Decision influence analysis for {decision_id}:") -print(f" - Influence score: {influence_analysis.get('influence_score', 0):.3f}") - -# Centrality measures -centrality = influence_analysis.get('centrality_measures', {}) -print(f" - Degree centrality: {centrality.get('degree_centrality', 0):.3f}") -print(f" - Betweenness centrality: {centrality.get('betweenness_centrality', 0):.3f}") - -# Community information -community = influence_analysis.get('community_info', {}) -if community: - print(f" - Community ID: {community.get('community_id')}") - print(f" - Community size: {community.get('community_size')}") - -# Related decisions -downstream = influence_analysis.get('downstream_decisions', []) -upstream = influence_analysis.get('upstream_decisions', []) -print(f" - Downstream decisions: {len(downstream)}") -print(f" - Upstream decisions: {len(upstream)}") -``` - -### Decision Relationship Prediction - -```python -# Predict potential relationships for decisions -predictions = context.predict_decision_relationships( - decision_id="decision_123", - top_k=5 -) - -print(f"Predicted relationships for decision_123:") -for prediction in predictions: - print(f" - Target: {prediction.get('target', 'unknown')}") - print(f" - Score: {prediction.get('score', 0):.3f}") - print(f" - Type: {prediction.get('type', 'unknown')}") -``` - -### Context Graph Analysis - -```python -# Analyze the entire context graph -graph_analysis = context.analyze_context_graph() - -print("Context graph analysis:") -if 'error' not in graph_analysis: - metrics = graph_analysis.get('graph_metrics', {}) - print(f" - Nodes: {metrics.get('node_count', 0)}") - print(f" - Edges: {metrics.get('edge_count', 0)}") - - centrality = graph_analysis.get('centrality_analysis', {}) - print(f" - Centrality analysis: {len(centrality)} nodes analyzed") - - communities = graph_analysis.get('community_analysis', {}) - print(f" - Communities: {communities.get('num_communities', 0)}") -else: - print(f" - Error: {graph_analysis['error']}") -``` - -### Entity Similarity and Centrality - -```python -# Find similar entities in the context graph -similar_entities = context.find_similar_entities( - entity_id="python_programming", - similarity_type="content", - top_k=10 -) - -print(f"Similar entities to 'python_programming':") -for entity_id, similarity_score in similar_entities: - print(f" - {entity_id}: {similarity_score:.3f}") - -# Get entity centrality measures -entity_centrality = context.get_entity_centrality("python_programming") -print(f"Entity centrality: {entity_centrality}") -``` - -### Comprehensive Context Insights - -```python -# Get comprehensive insights about the context -insights = context.get_context_insights() - -print("Context insights:") -print(f" - Timestamp: {insights.get('timestamp')}") - -# Memory statistics -memory_stats = insights.get('memory_stats', {}) -print(f" - Total memories: {memory_stats.get('total_items', 0)}") -print(f" - Memory usage: {memory_stats.get('memory_usage', {})}") - -# Decision statistics -decision_stats = insights.get('decision_stats', {}) -if decision_stats: - print(f" - Total decisions: {decision_stats.get('total_decisions', 0)}") - print(f" - Decision categories: {decision_stats.get('categories', [])}") - -# Advanced features status -features = insights.get('advanced_features', {}) -print(f" - KG algorithms enabled: {features.get('kg_algorithms_enabled', False)}") -print(f" - Vector store features enabled: {features.get('vector_store_features_enabled', False)}") -print(f" - Decision tracking enabled: {features.get('decision_tracking_enabled', False)}") -``` - -## Production Examples - -### Banking Decision System with KG Analytics - -```python -# Initialize banking decision system -banking_context = AgentContext( - vector_store=VectorStore(backend="faiss", dimension=768), - knowledge_graph=GraphStore(backend="neo4j", uri="bolt://localhost:7687"), - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True -) - -# Record banking decisions with full context -decisions = [ - { - "category": "mortgage_approval", - "scenario": "Mortgage application for first-time homebuyer", - "reasoning": "Strong credit score (750), stable employment, 20% down payment", - "outcome": "approved", - "confidence": 0.94, - "decision_maker": "loan_officer_001", - "amount": 350000, - "credit_score": 750, - "risk_level": "low" - }, - { - "category": "credit_card_approval", - "scenario": "Premium credit card application", - "reasoning": "Excellent credit history, high income, existing relationship", - "outcome": "approved", - "confidence": 0.96, - "decision_maker": "credit_analyst_002", - "credit_limit": 25000, - "credit_score": 820, - "risk_level": "very_low" - } -] - -# Process decisions -decision_ids = [] -for decision_data in decisions: - decision_id = banking_context.record_decision(**decision_data) - decision_ids.append(decision_id) - -# Analyze decision influence -for decision_id in decision_ids: - influence = banking_context.analyze_decision_influence(decision_id) - print(f"Decision {decision_id} influence: {influence.get('influence_score', 0):.3f}") - -# Find similar decisions with KG features -similar_decisions = banking_context.find_precedents_advanced( - scenario="High-value credit application", - category="credit_approval", - use_kg_features=True, - similarity_weights={"semantic": 0.5, "structural": 0.3, "category": 0.2} -) - -print(f"Found {len(similar_decisions)} similar decisions with KG analysis") - -# Get comprehensive insights -insights = banking_context.get_context_insights() -print(f"Banking system insights: {insights.get('memory_stats', {})}") -``` - -### Healthcare Decision Support System - -```python -# Healthcare decision system with advanced analytics -healthcare_context = AgentContext( - vector_store=VectorStore(backend="chroma", dimension=1536), - knowledge_graph=GraphStore(backend="neo4j"), - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True -) - -# Record medical decisions -medical_decisions = [ - { - "category": "treatment_approval", - "scenario": "Approval for experimental cancer treatment", - "reasoning": "Patient meets criteria, no alternative treatments available", - "outcome": "approved", - "confidence": 0.88, - "decision_maker": "dr_smith", - "patient_id": "patient_123", - "condition": "stage_4_lung_cancer", - "treatment_type": "immunotherapy" - }, - { - "category": "diagnostic_test", - "scenario": "MRI scan authorization", - "reasoning": "Symptoms indicate need for detailed imaging", - "outcome": "approved", - "confidence": 0.92, - "decision_maker": "dr_jones", - "patient_id": "patient_456", - "test_type": "brain_mri", - "urgency": "medium" - } -] - -# Process medical decisions -for decision in medical_decisions: - decision_id = healthcare_context.record_decision(**decision) - - # Analyze decision influence in medical context - influence = healthcare_context.analyze_decision_influence(decision_id) - print(f"Medical decision influence: {influence.get('influence_score', 0):.3f}") - -# Find similar treatment decisions -similar_treatments = healthcare_context.find_precedents_advanced( - scenario="Cancer treatment approval", - category="treatment_approval", - use_kg_features=True -) - -print(f"Found {len(similar_treatments)} similar treatment decisions") - -# Analyze healthcare context graph -graph_analysis = healthcare_context.analyze_context_graph() -if 'error' not in graph_analysis: - print(f"Healthcare graph: {graph_analysis.get('graph_metrics', {})}") -``` - -### E-commerce Personalization System - -```python -# E-commerce system with KG recommendations -ecommerce_context = AgentContext( - vector_store=VectorStore(backend="qdrant", dimension=1024), - knowledge_graph=GraphStore(backend="neo4j"), - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True -) - -# Record personalization decisions -personalization_decisions = [ - { - "category": "product_recommendation", - "scenario": "Premium product recommendation for VIP customer", - "reasoning": "High purchase history, premium segment, similar preferences", - "outcome": "recommended", - "confidence": 0.91, - "decision_maker": "recommendation_engine", - "customer_id": "vip_customer_001", - "product_category": "luxury_goods", - "price_range": "high" - }, - { - "category": "pricing_decision", - "scenario": "Dynamic pricing adjustment", - "reasoning": "High demand, low inventory, competitor pricing", - "outcome": "price_increased", - "confidence": 0.87, - "decision_maker": "pricing_algorithm", - "product_id": "product_789", - "original_price": 99.99, - "new_price": 119.99 - } -] - -# Process e-commerce decisions -for decision in personalization_decisions: - decision_id = ecommerce_context.record_decision(**decision) - - # Find similar customers using KG features - similar_customers = ecommerce_context.find_similar_entities( - entity_id=decision.get('customer_id', ''), - similarity_type="structural", - top_k=5 - ) - print(f"Similar customers: {len(similar_customers)}") - -# Get comprehensive e-commerce insights -insights = ecommerce_context.get_context_insights() -print(f"E-commerce system status: {insights.get('advanced_features', {})}") -``` - -### Backward Compatibility Examples - -All existing code continues to work without changes: - -```python -# Old API still works perfectly -context = AgentContext(vector_store=vector_store) -query = DecisionQuery(graph_store) +# Basic knowledge graph graph = ContextGraph() -# Store and retrieve as before -memory_id = context.store("User message", conversation_id="conv1") -results = context.retrieve("User query") - -# Decision tracking with old API -if hasattr(context, 'record_decision'): - decision_id = context.record_decision( - category="test", - scenario="Test scenario", - reasoning="Test reasoning", - outcome="approved", - confidence=0.8 - ) +# Advanced knowledge graph +graph = ContextGraph( + advanced_analytics=True, # Enable smart algorithms + centrality_analysis=True, # Find important concepts + community_detection=True, # Find groups of related concepts + node_embeddings=True # Understand concept similarity +) ``` -## Summary +--- -The context module provides: +## 🎉 You're Ready to Build Smart Agents! -- **Backward Compatibility**: All existing code works unchanged -- **KG Algorithm Integration**: Advanced graph analytics with centrality, community detection, embeddings -- **Vector Store Features**: Hybrid search combining semantic and structural similarity -- **Advanced Decision Analytics**: Influence analysis, relationship prediction, comprehensive insights -- **Production Ready**: Scalable architecture for real-world applications +With these examples, you can now: -Users can now build truly comprehensive context graphs with semantica, leveraging all advanced KG algorithms and vector store features while maintaining complete backward compatibility! +✅ **Build Smart Agents** - That remember and learn from experience +✅ **Track Decisions** - Make consistent, improving choices over time +✅ **Find Information** - Quick and relevant memory retrieval +✅ **Organize Knowledge** - Build intelligent knowledge graphs +✅ **Make Better Decisions** - Based on past experience and patterns +✅ **Build Real Applications** - Banking, healthcare, e-commerce, and more + +**Start simple, add power as needed! Your agents will get smarter with every decision.** 🚀 + +--- + +## 📚 Need More Help? + +- **Start with AgentContext** for most applications +- **Add ContextGraph** when you need knowledge organization +- **Look at the real-world examples** for your specific use case +- **Check configuration options** to customize your agent + +Happy building smart agents! 🎯 diff --git a/semantica/context/decision_query.py b/semantica/context/decision_query.py index 1a17b941..9ecf1322 100644 --- a/semantica/context/decision_query.py +++ b/semantica/context/decision_query.py @@ -55,10 +55,10 @@ Search Capabilities: Example Usage: >>> from semantica.context import DecisionQuery >>> query = DecisionQuery(graph_store=kg, vector_store=vs, - ... enable_advanced_analytics=True, - ... enable_centrality_analysis=True, - ... enable_community_detection=True, - ... enable_node_embeddings=True) + ... advanced_analytics=True, + ... centrality_analysis=True, + ... community_detection=True, + ... node_embeddings=True) >>> precedents = query.find_precedents_hybrid("Loan application", ... category="approval", ... limit=10) @@ -111,11 +111,11 @@ class DecisionQuery: graph_store: GraphStore, embedding_generator: Optional[EmbeddingGenerator] = None, vector_store: Optional[Any] = None, - enable_advanced_analytics: bool = True, - enable_node_embeddings: bool = True, - enable_centrality_analysis: bool = True, - enable_community_detection: bool = True, - enable_link_prediction: bool = True + advanced_analytics: bool = True, + node_embeddings: bool = True, + centrality_analysis: bool = True, + community_detection: bool = True, + link_prediction: bool = True ): """ Initialize DecisionQuery with optional advanced features. @@ -124,11 +124,11 @@ class DecisionQuery: graph_store: Graph database instance embedding_generator: Optional embedding generator for semantic search vector_store: Optional vector store for hybrid search - enable_advanced_analytics: Enable advanced graph analytics (requires semantica.kg) - enable_node_embeddings: Enable Node2Vec embeddings (requires semantica.kg) - enable_centrality_analysis: Enable centrality measures (requires semantica.kg) - enable_community_detection: Enable community detection (requires semantica.kg) - enable_link_prediction: Enable link prediction (requires semantica.kg) + advanced_analytics: Enable advanced graph analytics (requires semantica.kg) + node_embeddings: Enable Node2Vec embeddings (requires semantica.kg) + centrality_analysis: Enable centrality measures (requires semantica.kg) + community_detection: Enable community detection (requires semantica.kg) + link_prediction: Enable link prediction (requires semantica.kg) """ self.graph_store = graph_store self.embedding_generator = embedding_generator @@ -139,17 +139,17 @@ class DecisionQuery: self.kg_components = {} self.vector_components = {} - if KG_AVAILABLE and enable_advanced_analytics: + if KG_AVAILABLE and advanced_analytics: try: - if enable_centrality_analysis: + if centrality_analysis: self.kg_components["centrality_calculator"] = CentralityCalculator() - if enable_community_detection: + if community_detection: self.kg_components["community_detector"] = CommunityDetector() - if enable_node_embeddings: + if node_embeddings: self.kg_components["node_embedder"] = NodeEmbedder() self.kg_components["path_finder"] = PathFinder() self.kg_components["similarity_calculator"] = SimilarityCalculator() - if enable_link_prediction: + if link_prediction: self.kg_components["link_predictor"] = LinkPredictor() self.logger.info("Advanced KG components initialized successfully") diff --git a/tests/context/test_agent_context_decisions.py b/tests/context/test_agent_context_decisions.py index 9d275706..4a817451 100644 --- a/tests/context/test_agent_context_decisions.py +++ b/tests/context/test_agent_context_decisions.py @@ -38,7 +38,7 @@ class TestAgentContextDecisions: return AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=True + decision_tracking=True ) @pytest.fixture @@ -47,7 +47,7 @@ class TestAgentContextDecisions: return AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=False + decision_tracking=False ) def test_agent_context_initialization_with_decisions(self, mock_vector_store, mock_knowledge_graph): @@ -55,10 +55,10 @@ class TestAgentContextDecisions: context = AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=True + decision_tracking=True ) - assert context.config["enable_decision_tracking"] is True + assert context.config["decision_tracking"] is True assert context._decision_recorder is not None assert context._decision_query is not None assert context._causal_analyzer is not None @@ -69,10 +69,10 @@ class TestAgentContextDecisions: context = AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=False + decision_tracking=False ) - assert context.config["enable_decision_tracking"] is False + assert context.config["decision_tracking"] is False assert context._decision_recorder is None assert context._decision_query is None assert context._causal_analyzer is None @@ -255,7 +255,7 @@ class TestAgentContextDecisions: knowledge_graph=mock_knowledge_graph ) - assert context.config["enable_decision_tracking"] is False + assert context.config["decision_tracking"] is False assert context._decision_recorder is None def test_error_handling(self, agent_context_with_decisions): @@ -279,11 +279,11 @@ class TestAgentContextDecisions: context = AgentContext( vector_store=mock_vector_store, knowledge_graph=None, - enable_decision_tracking=True + decision_tracking=True ) # Should initialize but warn about missing knowledge graph - assert context.config["enable_decision_tracking"] is True + assert context.config["decision_tracking"] is True def test_error_handling(self, agent_context_with_decisions): """Test error handling in decision tracking.""" diff --git a/tests/context/test_agent_context_smoke.py b/tests/context/test_agent_context_smoke.py index d019ef20..51b03250 100644 --- a/tests/context/test_agent_context_smoke.py +++ b/tests/context/test_agent_context_smoke.py @@ -11,9 +11,9 @@ def test_agent_context_minimal_decisions_and_chain(): ctx = AgentContext( vector_store=vs, knowledge_graph=graph, - enable_decision_tracking=True, - enable_kg_algorithms=False, - enable_vector_store_features=False, + decision_tracking=True, + kg_algorithms=False, + vector_store_features=False, ) d1 = ctx.record_decision( category="credit_approval", @@ -45,9 +45,9 @@ def test_agent_context_policy_engine_with_graph_backend(): ctx = AgentContext( vector_store=vs, knowledge_graph=graph, - enable_decision_tracking=True, - enable_kg_algorithms=False, - enable_vector_store_features=False, + decision_tracking=True, + kg_algorithms=False, + vector_store_features=False, ) pe = ctx.get_policy_engine() pol = Policy( diff --git a/tests/context/test_banking_context_graphs_e2e.py b/tests/context/test_banking_context_graphs_e2e.py index 84f81af1..097a9beb 100644 --- a/tests/context/test_banking_context_graphs_e2e.py +++ b/tests/context/test_banking_context_graphs_e2e.py @@ -52,10 +52,10 @@ class TestBankingDecisionSystem: return AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True + decision_tracking=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True ) def test_banking_decision_lifecycle(self, banking_context): @@ -216,10 +216,10 @@ class TestBankingDecisionSystem: enhanced_query = DecisionQuery( graph_store=mock_knowledge_graph, vector_store=mock_vector_store, - enable_advanced_analytics=True, - enable_centrality_analysis=True, - enable_community_detection=True, - enable_node_embeddings=True + advanced_analytics=True, + centrality_analysis=True, + community_detection=True, + node_embeddings=True ) print(f"[OK] Enhanced DecisionQuery with {len(enhanced_query.kg_components)} KG components") @@ -240,10 +240,10 @@ class TestBankingDecisionSystem: # Test enhanced ContextGraph enhanced_graph = ContextGraph( - enable_advanced_analytics=True, - enable_centrality_analysis=True, - enable_community_detection=True, - enable_node_embeddings=True + advanced_analytics=True, + centrality_analysis=True, + community_detection=True, + node_embeddings=True ) print(f"[OK] Enhanced ContextGraph with {len(enhanced_graph.kg_components)} KG components") diff --git a/tests/context/test_context_graphs_examples.py b/tests/context/test_context_graphs_examples.py index 37fc4e15..8fa2f1c6 100644 --- a/tests/context/test_context_graphs_examples.py +++ b/tests/context/test_context_graphs_examples.py @@ -46,10 +46,10 @@ class TestContextGraphsExamples: # Create context graph with advanced features graph = ContextGraph( - enable_advanced_analytics=True, - enable_centrality_analysis=True, - enable_community_detection=True, - enable_node_embeddings=True + advanced_analytics=True, + centrality_analysis=True, + community_detection=True, + node_embeddings=True ) # Add a decision @@ -127,10 +127,10 @@ class TestContextGraphsExamples: context = AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True + decision_tracking=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True ) # Credit decision with precedent search @@ -169,10 +169,10 @@ class TestContextGraphsExamples: context = AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True + decision_tracking=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True ) # Treatment decision with policy compliance @@ -216,10 +216,10 @@ class TestContextGraphsExamples: context = AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True + decision_tracking=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True ) # Legal decision with precedent analysis @@ -370,21 +370,21 @@ class TestContextGraphsExamples: context = AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True, - use_graph_expansion=True, + decision_tracking=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True, + graph_expansion=True, max_expansion_hops=3, hybrid_alpha=0.7 ) # Verify configuration - assert context.config["enable_decision_tracking"] is True - assert context.config["enable_advanced_analytics"] is True - assert context.config["enable_kg_algorithms"] is True - assert context.config["enable_vector_store_features"] is True - assert context.config["use_graph_expansion"] is True + assert context.config["decision_tracking"] is True + assert context.config["advanced_analytics"] is True + assert context.config["kg_algorithms"] is True + assert context.config["vector_store_features"] is True + assert context.config["graph_expansion"] is True assert context.config["max_expansion_hops"] == 3 assert context.config["hybrid_alpha"] == 0.7 print("+ Configuration validation working") diff --git a/tests/context/test_end_to_end_context_integration.py b/tests/context/test_end_to_end_context_integration.py index a4e85347..220dad93 100644 --- a/tests/context/test_end_to_end_context_integration.py +++ b/tests/context/test_end_to_end_context_integration.py @@ -118,7 +118,7 @@ class TestEndToEndContextIntegration: results = retriever.retrieve( query="Credit limit increase for business expansion", max_results=10, - use_graph_expansion=True + graph_expansion=True ) print(f"✅ Retrieved {len(results)} context items") @@ -286,10 +286,10 @@ class TestEndToEndContextIntegration: # Test different search configurations search_configs = [ - {"use_graph_expansion": False, "max_results": 10}, - {"use_graph_expansion": True, "max_results": 10}, - {"use_graph_expansion": True, "max_results": 20}, - {"use_graph_expansion": False, "max_results": 20}, + {"graph_expansion": False, "max_results": 10}, + {"graph_expansion": True, "max_results": 10}, + {"graph_expansion": True, "max_results": 20}, + {"graph_expansion": False, "max_results": 20}, ] for i, config in enumerate(search_configs): @@ -399,7 +399,7 @@ class TestEndToEndContextIntegration: ) # Should handle KG errors gracefully - results = retriever_broken.retrieve("Test query", max_results=5, use_graph_expansion=True) + results = retriever_broken.retrieve("Test query", max_results=5, graph_expansion=True) assert len(results) > 0, "Should handle KG errors gracefully" print("✅ Handles KG errors gracefully") @@ -572,7 +572,7 @@ class TestRealWorldContextScenarios: context_results = retriever.retrieve( query="Premium customer investment and fraud assessment", max_results=15, - use_graph_expansion=True + graph_expansion=True ) print(f"✅ Retrieved {len(context_results)} context items") diff --git a/tests/context/test_healthcare_context_graphs_e2e.py b/tests/context/test_healthcare_context_graphs_e2e.py index b936a0e3..f9b259d7 100644 --- a/tests/context/test_healthcare_context_graphs_e2e.py +++ b/tests/context/test_healthcare_context_graphs_e2e.py @@ -52,10 +52,10 @@ class TestHealthcareDecisionSystem: return AgentContext( vector_store=mock_vector_store, knowledge_graph=mock_knowledge_graph, - enable_decision_tracking=True, - enable_advanced_analytics=True, - enable_kg_algorithms=True, - enable_vector_store_features=True + decision_tracking=True, + advanced_analytics=True, + kg_algorithms=True, + vector_store_features=True ) def test_healthcare_decision_workflow(self, healthcare_context): From 79d554767de21c5129c35371d4fe032f7a83d778 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 18:35:53 +0530 Subject: [PATCH 06/13] Fix security issues: Remove raw exception exposure in error messages - Fixed trace_decision_causality() to return generic error message - Fixed analyze_graph_with_kg() to return generic error message - Fixed get_node_centrality() to return generic error message - Maintains detailed logging internally while protecting user-facing outputs - Ensures compliance with secure error handling requirements --- semantica/context/context_graph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index a606ab32..9fff93cf 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -1185,7 +1185,7 @@ class ContextGraph: except Exception as e: self.logger.error(f"Failed to analyze graph with KG: {e}") - return {"error": str(e)} + return {"error": "Graph analysis failed due to an internal error"} def get_node_centrality(self, node_id: str) -> Dict[str, float]: """ @@ -1222,7 +1222,7 @@ class ContextGraph: except Exception as e: self.logger.error(f"Failed to get node centrality: {e}") - return {"error": str(e)} + return {"error": "Node centrality calculation failed due to an internal error"} def find_similar_nodes( self, node_id: str, similarity_type: str = "content", top_k: int = 10 @@ -1671,7 +1671,7 @@ class ContextGraph: except Exception as e: self.logger.error(f"Causal analysis failed: {e}") - return [{"error": f"Causal analysis failed: {e}"}] + return [{"error": "Causal analysis failed due to an internal error"}] def enforce_decision_policy( self, From 8e83d11479822e826565a8c46698ed6b2d0a165b Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 18:39:03 +0530 Subject: [PATCH 07/13] Fix logging security issues: Replace raw exception exposure with structured logging - Fixed agent_context.py: Use logger.exception() instead of raw exception in logs - Fixed context_graph.py: Use logger.exception() for secure structured logging - Fixed policy_engine.py: Replaced 10 instances of raw exception logging with structured logging - Fixed decision_recorder.py: Replaced 8 instances of raw exception logging with structured logging - Ensures compliance with secure logging practices (Rule 5: Generic Secure Logging Practices) - Maintains detailed exception information in internal logs while protecting user-facing outputs - Prevents potential sensitive data leakage through log messages --- semantica/context/agent_context.py | 2 +- semantica/context/context_graph.py | 2 +- semantica/context/decision_recorder.py | 16 ++++++++-------- semantica/context/policy_engine.py | 20 ++++++++++---------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/semantica/context/agent_context.py b/semantica/context/agent_context.py index 20863d79..dc6313dc 100644 --- a/semantica/context/agent_context.py +++ b/semantica/context/agent_context.py @@ -1686,7 +1686,7 @@ class AgentContext: decisions.append(decision) return decisions except Exception as e: - self.logger.error(f"ContextGraph find_precedents failed: {e}") + self.logger.exception("ContextGraph find_precedents failed") return [] # Fallback to DecisionQuery for graph_store backend diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index 9fff93cf..0c9682ae 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -1799,7 +1799,7 @@ class ContextGraph: ) except Exception as e: - self.logger.warning(f"Failed to add decision to graph: {e}") + self.logger.exception("Failed to add decision to graph") def _calculate_content_similarity(self, scenario: str, decision: Dict[str, Any]) -> float: """Calculate content similarity between scenario and decision.""" diff --git a/semantica/context/decision_recorder.py b/semantica/context/decision_recorder.py index 63b32381..19093b86 100644 --- a/semantica/context/decision_recorder.py +++ b/semantica/context/decision_recorder.py @@ -149,7 +149,7 @@ class DecisionRecorder: return decision.decision_id except Exception as e: - self.logger.error(f"Failed to record decision: {e}") + self.logger.exception("Failed to record decision") raise def link_entities(self, decision_id: str, entities: List[str]) -> None: @@ -176,7 +176,7 @@ class DecisionRecorder: self.logger.info(f"Linked decision {decision_id} to {len(entities)} entities") except Exception as e: - self.logger.error(f"Failed to link entities: {e}") + self.logger.exception("Failed to link entities") raise def apply_policies(self, decision_id: str, policy_ids: List[str]) -> None: @@ -204,7 +204,7 @@ class DecisionRecorder: self.logger.info(f"Applied {len(policy_ids)} policies to decision {decision_id}") except Exception as e: - self.logger.error(f"Failed to apply policies: {e}") + self.logger.exception("Failed to apply policies") raise def record_exception( @@ -262,7 +262,7 @@ class DecisionRecorder: return exception.exception_id except Exception as e: - self.logger.error(f"Failed to record exception: {e}") + self.logger.exception("Failed to record exception") raise def capture_cross_system_context( @@ -302,7 +302,7 @@ class DecisionRecorder: self.logger.info(f"Captured cross-system context for decision {decision_id}") except Exception as e: - self.logger.error(f"Failed to capture cross-system context: {e}") + self.logger.exception("Failed to capture cross-system context") raise def record_approval_chain( @@ -352,7 +352,7 @@ class DecisionRecorder: self.logger.info(f"Recorded approval chain with {len(approvers)} approvers") except Exception as e: - self.logger.error(f"Failed to record approval chain: {e}") + self.logger.exception("Failed to record approval chain") raise def link_precedents( @@ -389,7 +389,7 @@ class DecisionRecorder: self.logger.info(f"Linked {len(precedent_ids)} precedents to decision {decision_id}") except Exception as e: - self.logger.error(f"Failed to link precedents: {e}") + self.logger.exception("Failed to link precedents") raise def _store_decision_node(self, decision: Decision) -> None: @@ -502,4 +502,4 @@ class DecisionRecorder: ) except Exception as e: - self.logger.warning(f"Failed to track provenance: {e}") + self.logger.exception("Failed to track provenance") diff --git a/semantica/context/policy_engine.py b/semantica/context/policy_engine.py index 9cefaad6..a69830c9 100644 --- a/semantica/context/policy_engine.py +++ b/semantica/context/policy_engine.py @@ -155,7 +155,7 @@ class PolicyEngine: self.logger.info(f"Added policy: {policy.policy_id} version {policy.version}") return policy.policy_id except Exception as e: - self.logger.error(f"Failed to add policy: {e}") + self.logger.exception("Failed to add policy") raise def update_policy( @@ -232,7 +232,7 @@ class PolicyEngine: return new_version except Exception as e: - self.logger.error(f"Failed to update policy: {e}") + self.logger.exception("Failed to update policy") raise def get_applicable_policies( @@ -309,7 +309,7 @@ class PolicyEngine: return policies except Exception as e: - self.logger.error(f"Failed to get applicable policies: {e}") + self.logger.exception("Failed to get applicable policies") raise def check_compliance(self, decision: Decision, policy_id: str) -> bool: @@ -348,7 +348,7 @@ class PolicyEngine: return True except Exception as e: - self.logger.error(f"Failed to check compliance: {e}") + self.logger.exception("Failed to check compliance") return False def record_policy_application( @@ -394,7 +394,7 @@ class PolicyEngine: ) self.logger.info(f"Recorded policy application: {policy_id} v{version} to decision {decision_id}") except Exception as e: - self.logger.error(f"Failed to record policy application: {e}") + self.logger.exception("Failed to record policy application") raise def record_exception( @@ -472,7 +472,7 @@ class PolicyEngine: self.logger.info(f"Recorded policy exception: {exception_id}") return exception_id except Exception as e: - self.logger.error(f"Failed to record exception: {e}") + self.logger.exception("Failed to record exception") raise def get_policy_history(self, policy_id: str) -> List[Policy]: @@ -527,7 +527,7 @@ class PolicyEngine: return versions except Exception as e: - self.logger.error(f"Failed to get policy history: {e}") + self.logger.exception("Failed to get policy history") raise def get_affected_decisions( @@ -578,7 +578,7 @@ class PolicyEngine: return decision_ids except Exception as e: - self.logger.error(f"Failed to get affected decisions: {e}") + self.logger.exception("Failed to get affected decisions") raise def analyze_policy_impact( @@ -679,7 +679,7 @@ class PolicyEngine: return impact_analysis except Exception as e: - self.logger.error(f"Failed to analyze policy impact: {e}") + self.logger.exception("Failed to analyze policy impact") raise def get_policy(self, policy_id: str, version: Optional[str] = None) -> Optional[Policy]: @@ -765,7 +765,7 @@ class PolicyEngine: "metadata": data.get("metadata", {}) }) except Exception as e: - self.logger.error(f"Failed to get policy: {e}") + self.logger.exception("Failed to get policy") return None def _generate_next_version(self, current_version: str) -> str: From 3589f3b80727695344832a00188ad634a7e8b0b4 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 18:42:10 +0530 Subject: [PATCH 08/13] Add comprehensive input validation to record_decision method - Added validation for all required fields (category, scenario, reasoning, outcome) - Added confidence range validation (0.0 to 1.0) - Added type checking for all parameters - Added length limits to prevent data corruption - Added entity list validation with individual item checks - Added metadata dictionary validation - Added kwargs validation for additional fields - Added input sanitization (trimming, type conversion) - Ensures compliance with security-first input validation requirements - Prevents malicious/corrupted data from affecting graph operations and analytics --- semantica/context/context_graph.py | 72 +++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index 0c9682ae..d932e2e8 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -1403,9 +1403,79 @@ class ContextGraph: import uuid from datetime import datetime + # Input validation + if not isinstance(category, str) or not category.strip(): + raise ValueError("Category must be a non-empty string") + if len(category.strip()) > 100: + raise ValueError("Category must be 100 characters or less") + + if not isinstance(scenario, str) or not scenario.strip(): + raise ValueError("Scenario must be a non-empty string") + if len(scenario.strip()) > 5000: + raise ValueError("Scenario must be 5000 characters or less") + + if not isinstance(reasoning, str) or not reasoning.strip(): + raise ValueError("Reasoning must be a non-empty string") + if len(reasoning.strip()) > 10000: + raise ValueError("Reasoning must be 10000 characters or less") + + if not isinstance(outcome, str) or not outcome.strip(): + raise ValueError("Outcome must be a non-empty string") + if len(outcome.strip()) > 1000: + raise ValueError("Outcome must be 1000 characters or less") + + if not isinstance(confidence, (int, float)): + raise ValueError("Confidence must be a number") + if not (0.0 <= confidence <= 1.0): + raise ValueError("Confidence must be between 0.0 and 1.0") + + if entities is not None: + if not isinstance(entities, list): + raise ValueError("Entities must be a list of strings") + for entity in entities: + if not isinstance(entity, str) or not entity.strip(): + raise ValueError("Each entity must be a non-empty string") + if len(entity.strip()) > 200: + raise ValueError("Each entity must be 200 characters or less") + + if decision_maker is not None: + if not isinstance(decision_maker, str) or not decision_maker.strip(): + raise ValueError("Decision maker must be a non-empty string") + if len(decision_maker.strip()) > 200: + raise ValueError("Decision maker must be 200 characters or less") + + if metadata is not None: + if not isinstance(metadata, dict): + raise ValueError("Metadata must be a dictionary") + for key, value in metadata.items(): + if not isinstance(key, str) or not key.strip(): + raise ValueError("Metadata keys must be non-empty strings") + if len(key.strip()) > 100: + raise ValueError("Metadata keys must be 100 characters or less") + if len(str(value)) > 1000: + raise ValueError("Metadata values must be 1000 characters or less") + + # Validate kwargs + for key, value in kwargs.items(): + if not isinstance(key, str) or not key.strip(): + raise ValueError("Additional field names must be non-empty strings") + if len(key.strip()) > 100: + raise ValueError("Additional field names must be 100 characters or less") + if len(str(value)) > 1000: + raise ValueError("Additional field values must be 1000 characters or less") + decision_id = str(uuid.uuid4()) timestamp = datetime.now().timestamp() + # Sanitize inputs + category = category.strip() + scenario = scenario.strip() + reasoning = reasoning.strip() + outcome = outcome.strip() + confidence = float(confidence) + entities = [entity.strip() for entity in (entities or []) if entity.strip()] + decision_maker = decision_maker.strip() if decision_maker else None + # Create decision record decision = { "id": decision_id, @@ -1414,7 +1484,7 @@ class ContextGraph: "reasoning": reasoning, "outcome": outcome, "confidence": confidence, - "entities": entities or [], + "entities": entities, "decision_maker": decision_maker, "timestamp": timestamp, "metadata": metadata or {}, From fcf0c684bd2cc7d4fd1b70307dde7d52c3e92c42 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 18:45:51 +0530 Subject: [PATCH 09/13] Fix method overriding bug: Rename conflicting _calculate_content_similarity method - Renamed decision-specific method to _calculate_decision_content_similarity - Preserves node-based _calculate_content_similarity for find_similar_nodes() - Updates method call to use renamed method - Fixes core node-similarity functionality that was broken - Ensures both node similarity and decision similarity work correctly - Prevents find_similar_nodes() from calling wrong method signature - Maintains backward compatibility for all similarity features --- semantica/context/context_graph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index d932e2e8..260c73ba 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -1560,7 +1560,7 @@ class ContextGraph: decision = self._decisions[decision_id] # Content similarity - content_sim = self._calculate_content_similarity(scenario, decision) + content_sim = self._calculate_decision_content_similarity(scenario, decision) # Structural similarity (graph-based) structural_sim = 0.0 @@ -1871,7 +1871,7 @@ class ContextGraph: except Exception as e: self.logger.exception("Failed to add decision to graph") - def _calculate_content_similarity(self, scenario: str, decision: Dict[str, Any]) -> float: + def _calculate_decision_content_similarity(self, scenario: str, decision: Dict[str, Any]) -> float: """Calculate content similarity between scenario and decision.""" try: # Simple word-based similarity @@ -1885,7 +1885,7 @@ class ContextGraph: return len(intersection) / len(union) if union else 0.0 except Exception as e: - self.logger.warning(f"Content similarity calculation failed: {e}") + self.logger.exception("Content similarity calculation failed") return 0.0 def _calculate_structural_similarity_for_decision(self, decision_id: str, scenario: str) -> float: From fd21ec8c770a355e9c94535f6be2f2dccbd7a166 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 18:57:30 +0530 Subject: [PATCH 10/13] Fix wrong neighbors keyword bug: Correct max_depth to hops parameter - Fixed _find_indirect_decision_influence() to use correct get_neighbors() parameter - Changed max_depth= to hops= to match method signature - Fixes analyze_decision_influence(..., include_indirect=True) functionality - Prevents TypeError that was silently caught and degraded functionality - Restores indirect decision influence analysis capability - Ensures reliable decision influence analysis with indirect connections --- semantica/context/context_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index 260c73ba..f7c165f0 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -1915,7 +1915,7 @@ class ContextGraph: influenced = set() # Get neighbors in graph - neighbors = self.get_neighbors(decision_id, max_depth=max_depth) + neighbors = self.get_neighbors(decision_id, hops=max_depth) for neighbor in neighbors: if neighbor.get("type") == "decision": From e88781472bf02fc0d095ca4d62952238592a89fc Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 19:04:55 +0530 Subject: [PATCH 11/13] Fix decision graph addition bugs: Correct method calls and parameter passing - Fixed get_node() to find_node() - method didn't exist - Fixed properties={} to **properties parameter unpacking - Fixed add_node() calls to use keyword arguments instead of properties dict - Fixed add_edge() calls to use keyword arguments instead of properties dict - Ensures decision entities, categories, and edges are properly created - Prevents silent failures in graph enrichment for recorded decisions - Restores full decision graph functionality for record_decision() --- semantica/context/context_graph.py | 36 +++++++++++++----------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index f7c165f0..16dff850 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -1806,25 +1806,23 @@ class ContextGraph: self.add_node( decision["id"], "decision", - properties={ - "category": decision["category"], - "outcome": decision["outcome"], - "confidence": decision["confidence"], - "timestamp": decision["timestamp"], - "scenario": decision["scenario"][:100] + "..." if len(decision["scenario"]) > 100 else decision["scenario"], - "decision_maker": decision.get("decision_maker", ""), - "reasoning": decision["reasoning"][:200] + "..." if len(decision["reasoning"]) > 200 else decision["reasoning"] - } + category=decision["category"], + outcome=decision["outcome"], + confidence=decision["confidence"], + timestamp=decision["timestamp"], + scenario=decision["scenario"][:100] + "..." if len(decision["scenario"]) > 100 else decision["scenario"], + decision_maker=decision.get("decision_maker", ""), + reasoning=decision["reasoning"][:200] + "..." if len(decision["reasoning"]) > 200 else decision["reasoning"] ) # Add entity nodes and relationships for entity in decision["entities"]: # Add entity node if not exists - if not self.get_node(entity): + if not self.find_node(entity): self.add_node( entity, "entity", - properties={"name": entity} + name=entity ) # Add relationship @@ -1832,40 +1830,38 @@ class ContextGraph: decision["id"], entity, "involves", - properties={"confidence": decision["confidence"]} + confidence=decision["confidence"] ) # Add category node and relationship category_id = f"category_{decision['category']}" - if not self.get_node(category_id): + if not self.find_node(category_id): self.add_node( category_id, "category", - properties={"name": decision["category"]} + name=decision["category"] ) self.add_edge( decision["id"], category_id, - "belongs_to", - properties={} + "belongs_to" ) # Add decision maker node if provided if decision.get("decision_maker"): maker_id = f"maker_{decision['decision_maker']}" - if not self.get_node(maker_id): + if not self.find_node(maker_id): self.add_node( maker_id, "decision_maker", - properties={"name": decision["decision_maker"]} + name=decision["decision_maker"] ) self.add_edge( decision["id"], maker_id, - "made_by", - properties={} + "made_by" ) except Exception as e: From 2801cd7438ff8363ce883e120a924c08e01f60cb Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 19:11:20 +0530 Subject: [PATCH 12/13] Fix config keys inconsistency: Update all references to new key names - Fixed get_context_insights() to use new config keys (decision_tracking, kg_algorithms, vector_store_features) - Fixed enhance_agent_context_with_decisions() to use new config key (decision_tracking) - Ensures feature flags work correctly across all code paths - Prevents decision enhancements from being skipped when enabled - Fixes misreporting of feature enablement in insights - Maintains consistency between config initialization and usage --- semantica/context/agent_context.py | 8 ++++---- semantica/context/decision_methods.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/semantica/context/agent_context.py b/semantica/context/agent_context.py index dc6313dc..16eb7e10 100644 --- a/semantica/context/agent_context.py +++ b/semantica/context/agent_context.py @@ -2183,12 +2183,12 @@ class AgentContext: insights = { "timestamp": datetime.now().isoformat(), "memory_stats": self.stats(), - "decision_stats": self.get_decision_statistics() if self.config.get("enable_decision_tracking") and hasattr(self, 'get_decision_statistics') else {}, + "decision_stats": self.get_decision_statistics() if self.config.get("decision_tracking") and hasattr(self, 'get_decision_statistics') else {}, "graph_analysis": self.analyze_context_graph(), "advanced_features": { - "kg_algorithms_enabled": self.config.get("enable_kg_algorithms", False), - "vector_store_features_enabled": self.config.get("enable_vector_store_features", False), - "decision_tracking_enabled": self.config.get("enable_decision_tracking", False) + "kg_algorithms_enabled": self.config.get("kg_algorithms", False), + "vector_store_features_enabled": self.config.get("vector_store_features", False), + "decision_tracking_enabled": self.config.get("decision_tracking", False) } } diff --git a/semantica/context/decision_methods.py b/semantica/context/decision_methods.py index 0eae2d40..b68ac50f 100644 --- a/semantica/context/decision_methods.py +++ b/semantica/context/decision_methods.py @@ -549,7 +549,7 @@ def enhance_agent_context_with_decisions(agent_context: AgentContext) -> None: logger = get_logger(__name__) try: - if not agent_context.config.get("enable_decision_tracking"): + if not agent_context.config.get("decision_tracking"): logger.warning("Decision tracking not enabled in AgentContext") return From 692247c559d0521505c901006981a86b3965f76a Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 16 Feb 2026 19:18:35 +0530 Subject: [PATCH 13/13] Fix broken structural similarity: Correct parameter and return value handling - Fixed limit=5 to top_k=5 to match find_similar_nodes() signature - Fixed tuple handling: similar_nodes returns List[Tuple[str, float]] not dicts - Fixed node.get() to proper tuple unpacking for similarity scores - Updated logging to use structured logging (logger.exception) - Restores structural similarity functionality for precedent ranking - Fixes find_precedents() to use proper structural similarity calculations --- semantica/context/context_graph.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index 16dff850..09db0df2 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -1894,14 +1894,15 @@ class ContextGraph: similar_nodes = self.find_similar_nodes( decision_id, similarity_type="structural", - limit=5 + top_k=5 ) if similar_nodes: - return max(node.get("similarity", 0.0) for node in similar_nodes) + # similar_nodes is List[Tuple[str, float]], extract similarity scores + return max(similarity for node_id, similarity in similar_nodes) except Exception as e: - self.logger.warning(f"Structural similarity calculation failed: {e}") + self.logger.exception("Structural similarity calculation failed") return 0.0