How to Build and Query Context Graphs for AI Agent Memory with Semantica

Semantica's ContextGraph and GraphBuilder provide a structured knowledge graph system for AI agents that replaces flat vector stores with nodes, edges, and multi-hop Graph-RAG retrieval.

The semantica-agi/semantica repository implements a complete pipeline for agent memory based on context graphs rather than simple embeddings. This article explains how to construct these graphs from raw text and query them using the Graph-RAG pattern implemented in AgnoKnowledgeGraph.

Core Components for Context Graph Memory

Four primary classes work together to provide graph-based agent memory in Semantica:

Component Responsibility Source Location
ContextGraph In-memory graph storage with nodes, edges, and analytics (get_neighbors(), temporal/causal features) semantica/context/context_graph.py (line 22)
GraphBuilder Converts extracted entities and relations into graph structure; handles merging and conflict resolution semantica/kg/graph_builder.py (line 30)
AgnoKnowledgeGraph Agno-compatible wrapper providing load() and Graph-RAG search() — wires NER, relation extraction, and vector retrieval integrations/agno/knowledge_graph.py (line 90)
AgentContext Thin vector-store wrapper for first-hop retrieval before graph expansion Imported in knowledge_graph.py (lines 40-44)

Building a Context Graph from Raw Text

Step 1: Initialize the Builder and Graph

Start by creating a GraphBuilder with your preferred configuration flags, then instantiate a ContextGraph:

from semantica.kg import GraphBuilder
from semantica.context import ContextGraph

builder = GraphBuilder(merge_entities=True, resolve_conflicts=True)
graph = ContextGraph()

Setting merge_entities=True enables deduplication of entities across documents. The resolve_conflicts=True flag activates the conflict resolution strategy for contradictory relationship assertions.

Step 2: Wire the Builder to Graph Storage

Connect the builder to your graph instance so constructed entities and edges populate storage:

builder.graph_store = graph  # Internal wiring — see knowledge_graph.py lines 48-50

Step 3: Ingest Documents with NER and Relation Extraction

The builder consumes source dictionaries containing text and optional pre-extracted entities. In practice, AgnoKnowledgeGraph._ingest_text() (lines 80-110) automates the extraction pipeline:

from semantica.semantic_extract import NERExtractor, RelationExtractor

ner = NERExtractor()
rel = RelationExtractor()

documents = [
    {
        "text": "Acme Corp acquired Beta Ltd in 2022.",
        "entities": [],
        "relationships": [],
        "source": "press_release.txt"
    }
]

# The builder populates the graph with nodes and temporal edges

builder.build(documents)

The _ingest_text() method performs four operations: chunking text, running NERExtractor (self._ner) and RelationExtractor (self._rel), aggregating results, and calling self._graph_builder.build() at line 98.

Step 4: Configure Persistence (Optional)

For production deployments, pass a Neo4j connection URI instead of using the default in-memory store:

graph = ContextGraph(graph_store_uri="bolt://localhost:7687")

The graph backend is swappable — FalkorDB and other stores implementing the same interface work without code changes.

Querying Context Graphs with Graph-RAG

The AgnoKnowledgeGraph.search() method implements multi-hop retrieval that augments vector search with structured graph context.

The Graph-RAG Pipeline

Stage Implementation Code Reference
Vector-first retrieval AgentContext.retrieve() fetches similar chunks knowledge_graph.py lines 94-100
Entity extraction Retrieved items expose entities list for graph lookup _graph_context_for(entities) lines 54-71
Context augmentation Neighbor summary appended under [Graph context] heading Lines 106-108
Keyword fallback Scored search over cached _docs if vector retrieval fails Lines 114-136

Direct Graph Queries

For programmatic access to neighbor relationships, call get_graph_context() or get_neighbors() on any ContextGraph instance:


# Get structured neighbor listing for a specific entity

print(graph.get_graph_context("Acme Corp"))

# Or query neighbors directly with hop control

neighbors = graph.get_neighbors("Acme Corp", hops=2)

Sample output:


Entity: Acme Corp
  --[acquired]--> Beta Ltd (type: organization)
  --[located_in]--> New York (type: location)

Complete Working Example

This standalone script demonstrates building a context graph and querying it through the Graph-RAG interface:

#!/usr/bin/env python
"""
Build and query a ContextGraph for AI agent memory (Semantica + Agno).
"""

from semantica.kg import GraphBuilder
from semantica.context import ContextGraph
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.vector_store import VectorStore
from semantica.context import AgentContext

# Initialize components

graph = ContextGraph()                               # In-memory graph

builder = GraphBuilder(merge_entities=True)          # Entity deduplication enabled

ner = NERExtractor()                                 # spaCy-based NER

rel = RelationExtractor()                            # Rule-based relation extraction

# Wire builder to graph storage

builder.graph_store = graph

# Prepare and ingest documents

documents = [
    "OpenAI launched GPT-4 in March 2023.",
    "Semantica provides a knowledge-graph pipeline for LLM-augmented agents."
]

for i, txt in enumerate(documents):
    builder.build([{"text": txt, "source": f"doc_{i}.txt"}])

# Set up vector store for first-hop retrieval

vector = VectorStore(backend="faiss")
agent_ctx = AgentContext(
    vector_store=vector,
    knowledge_graph=graph,
    decision_tracking=False
)

# Store raw texts for later retrieval

for txt in documents:
    agent_ctx.store(txt, conversation_id="example")

# Graph-RAG search implementation

def graph_rag(query: str, top_k: int = 3):
    """Two-step retrieval: vector search → graph neighbor expansion."""
    results = agent_ctx.retrieve(query, max_results=top_k)
    
    enriched = []
    for item in results:
        entities = item.get("entities", [])
        extra = "\n".join(
            f"- {e} → {', '.join(n['node_id'] for n in graph.get_neighbors(e, hops=1)[:2])}"
            for e in entities[:3]
        )
        content = item["content"]
        if extra:
            content += f"\n\n[Graph context]\n{extra}"
        enriched.append(content)
    return enriched

# Execute query

print("\n--- Query: 'What did OpenAI release?' ---")
for doc in graph_rag("What did OpenAI release?"):
    print("\n--- Result ---")
    print(doc)

Advanced Graph Features

The ContextGraph implementation in context_graph.py provides capabilities beyond basic storage:

  • Temporal reasoning — Edges support timestamp fields for time-series queries
  • Causal analysis — Relationship types include causes, enables, prevents
  • Community detection — Built-in algorithms for clustering related entities
  • Multi-hop traversal — get_neighbors() supports arbitrary hop counts with cycle detection

These features enable agents to answer questions requiring structured reasoning: "What acquisitions happened before 2023?" or "Which entities are in the same corporate network as Acme Corp?"

Summary

  • ContextGraph provides the core graph storage with analytics and temporal support
  • GraphBuilder transforms extracted entities and relations into structured graphs
  • AgnoKnowledgeGraph wraps the pipeline for Agno agent compatibility with Graph-RAG search()
  • Graph-RAG retrieval combines vector similarity with multi-hop graph traversal for richer context
  • Persistence options scale from in-memory prototypes to Neo4j/FalkorDB backends

Frequently Asked Questions

What is the difference between ContextGraph and a standard vector store?

A vector store retrieves documents by embedding similarity alone. ContextGraph preserves explicit entity relationships as edges, enabling multi-hop reasoning: if you ask "Who founded the company that acquired Beta Ltd?", the graph can traverse founder_of and acquired edges to return the answer. Vector stores cannot resolve such queries without explicit mention in retrieved text.

How does entity merging work in GraphBuilder?

When merge_entities=True, the GraphBuilder maintains an internal entity resolution index. Identical or near-identical entity mentions (e.g., "Apple Inc." and "Apple") are canonicalized to a single node with aggregated sources. Conflicting attributes trigger the resolution strategy specified by resolve_conflicts — typically preferring the most recent or most frequently attested value.

Can I use ContextGraph without the Agno framework?

Yes. The ContextGraph and GraphBuilder classes have no Agno dependencies. You can construct graphs directly via add_nodes() and add_edges(), query with get_neighbors(), and implement custom retrieval patterns. The AgnoKnowledgeGraph wrapper in integrations/agno/knowledge_graph.py simply provides convenience methods for agents following the Agno AgentKnowledge protocol.

What extraction models power the NER and relation extraction?

The NERExtractor in semantica/semantic_extract/ner_extractor.py uses spaCy with a transformer-based model for domain-agnostic entity recognition. The RelationExtractor in relation_extractor.py implements rule-based patterns augmented with dependency parse features. Both are replaceable — pass custom extractors to GraphBuilder or AgnoKnowledgeGraph constructor parameters.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →