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
ContextGraphprovides the core graph storage with analytics and temporal supportGraphBuildertransforms extracted entities and relations into structured graphsAgnoKnowledgeGraphwraps the pipeline for Agno agent compatibility with Graph-RAGsearch()- 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →