Frequently Asked Questions
- General Questions
- Entity Extraction Pipeline
- GLiNER and Domain Schemas
- spaCy Extraction
- LLM Extraction
- Batch and Streaming Extraction
- Entity Resolution and Deduplication
- Neo4j and Storage
- CLI Tool
- Geocoding and Geospatial Queries
- Observability and Tracing
- Performance and Optimization
- Troubleshooting
- See Also
This document answers common questions about neo4j-agent-memory, with a focus on the entity extraction pipeline and domain schemas.
General Questions
What is neo4j-agent-memory?
neo4j-agent-memory is a Python package that provides a comprehensive memory system for AI agents using Neo4j as the backend. It implements three memory types:
| Short-Term Memory |
Conversations and messages |
| Long-Term Memory |
Entities, preferences, and facts (using the POLE+O data model) |
| Reasoning Memory |
Reasoning traces and tool usage patterns |
See Overview for more details.
What is the POLE+O data model?
POLE+O stands for Person, Object, Location, Event, Organization. It’s a data model commonly used in law enforcement and intelligence analysis for organizing entities and their relationships.
The package extends this with optional subtypes (e.g., OBJECT:VEHICLE, LOCATION:ADDRESS).
See POLE+O Data Model for complete documentation.
Do I need Neo4j to use this package?
Yes, Neo4j is required for storing and querying memory data.
However, you can use the entity extraction features independently without Neo4j — the extraction pipeline works standalone and returns Python objects.
Which Neo4j versions are supported?
Neo4j 5.x is supported, with 5.11+ recommended for vector index support (used for semantic search).
Does neo4j-agent-memory work with AWS?
Yes. The package provides full AWS support including:
| Amazon Bedrock Embeddings |
Use Bedrock embedding models (Titan, Cohere) instead of OpenAI. Install with |
| Strands Agents SDK |
Pre-built tools for context graph operations with the AWS Strands Agents framework. Install with |
| AgentCore Memory |
|
See Strands Integration and Bedrock Embeddings for details.
How do I configure AWS credentials for Bedrock embeddings?
The Bedrock embedder uses standard AWS credential resolution (environment variables, ~/.aws/credentials, IAM roles). You can configure the region and profile:
# Via environment variables
export NAM_EMBEDDING__PROVIDER=bedrock
export NAM_EMBEDDING__AWS_REGION=us-east-1
export NAM_EMBEDDING__AWS_PROFILE=my-profile
Or in Python:
from neo4j_agent_memory import MemorySettings
settings = MemorySettings(
embedding={"provider": "bedrock", "aws_region": "us-east-1"}
)
No API keys are needed if you’re running on an AWS resource with an appropriate IAM role.
Should I use Strands tools or the MemoryClient directly?
Use Strands tools (context_graph_tools()) when building agents with the AWS Strands Agents SDK — the tools are designed to be called by the agent autonomously.
Use the MemoryClient directly when you need programmatic control over memory operations outside of an agent loop, or when using a non-Strands framework.
You can combine both: use Strands tools for agent-facing operations and the MemoryClient for background tasks like batch processing.
Entity Extraction Pipeline
How does the extraction pipeline work?
The pipeline runs multiple extractors in sequence and merges their results:
Text → [spaCy] → [GLiNER] → [LLM] → Merge → Filter → Results
| Stage | Purpose | Speed |
|---|---|---|
spaCy |
Statistical NER for common entity types |
~1ms |
GLiNER |
Zero-shot NER with custom types |
~50ms |
LLM |
Context-aware extraction for complex cases |
~500ms |
Each stage is optional and configurable. See Entity Extraction for details.
Can I use just one extractor instead of the full pipeline?
Yes, you can use any extractor individually:
from neo4j_agent_memory.extraction import (
SpacyEntityExtractor,
GLiNEREntityExtractor,
LLMEntityExtractor,
)
# Just spaCy
extractor = SpacyEntityExtractor()
# Just GLiNER
extractor = GLiNEREntityExtractor.for_schema("podcast")
# Just LLM
extractor = LLMEntityExtractor(model="gpt-4o-mini")
result = await extractor.extract("Your text here")
What are merge strategies?
When using multiple extractors, you need to combine their results. Available strategies:
| Strategy | Description |
|---|---|
|
Keep all unique entities from all stages |
|
Only keep entities found by multiple extractors |
|
Keep highest confidence result per entity (default) |
|
Use first extractor’s results, fill gaps with others |
|
Stop at first stage that returns results |
Which extractor should I use?
| Use Case | Recommended Extractor |
|---|---|
Fast, basic NER (names, orgs, places) |
spaCy alone |
Domain-specific extraction |
GLiNER with domain schema |
Custom entity types without training |
GLiNER with custom schema |
Complex text with context |
Full pipeline with LLM fallback |
Relationship extraction |
LLM extractor |
Cost-sensitive applications |
spaCy + GLiNER (no LLM) |
GLiNER and Domain Schemas
Are domain schemas only used with GLiNER?
|
Yes, domain schemas are specific to GLiNER. They leverage GLiNER’s ability to accept entity type descriptions at inference time, which helps the model understand what to extract. |
| Extractor | Schema Support |
|---|---|
spaCy |
Uses a fixed set of trained entity types (PERSON, ORG, GPE, etc.) — cannot use domain schemas |
LLM |
Uses a prompt-based approach with entity type names but not the detailed descriptions from schemas |
GLiNER |
The only extractor that uses the full schema with descriptions |
What’s the difference between entity labels and domain schemas?
Entity labels are simple strings:
extractor = GLiNEREntityExtractor(entity_labels=["person", "company"])
Domain schemas include descriptions that help GLiNER understand what to extract:
# Schema includes detailed descriptions
extractor = GLiNEREntityExtractor.for_schema("podcast")
# {"person": "A person mentioned in the podcast, including hosts, guests...", ...}
Domain schemas significantly improve extraction accuracy, especially for ambiguous terms.
Can I use domain schemas with the full pipeline?
Yes, when using the ExtractorBuilder, the schema applies to the GLiNER stage:
extractor = (
ExtractorBuilder()
.with_spacy() # Uses spaCy's built-in types
.with_gliner_schema("podcast", threshold=0.5) # Uses podcast schema
.with_llm_fallback() # Uses entity type names
.merge_by_confidence()
.build()
)
Which schema should I use?
| Content Type | Schema |
|---|---|
General purpose, investigations |
|
Podcast transcripts, interviews |
|
News articles, press releases |
|
Research papers, academic content |
|
Business reports, earnings calls |
|
Movie/TV reviews, entertainment news |
|
Clinical notes, medical literature |
|
Legal documents, court cases |
|
Can I create custom schemas?
Yes, create a DomainSchema with your entity types and descriptions:
from neo4j_agent_memory.extraction.domain_schemas import DomainSchema
my_schema = DomainSchema(
name="real_estate",
entity_types={
"property": "A real estate property, building, or land parcel",
"agent": "A real estate agent or broker",
"price": "A property price, asking price, or sale price",
"neighborhood": "A neighborhood, district, or area",
},
)
extractor = GLiNEREntityExtractor(schema=my_schema)
See Creating Custom Schemas for more examples.
Do schema entity types map to POLE+O types?
Yes, GLiNER entity types are automatically mapped to POLE+O types for Neo4j storage:
-
person,author,actor,director→PERSON -
company,organization,institution→ORGANIZATION -
location,city,country→LOCATION -
event,meeting,case→EVENT -
Everything else →
OBJECT(with subtype from the label)
You can also add custom mappings:
extractor.add_label_mapping("podcast_episode", "EVENT", "PODCAST_EPISODE")
spaCy Extraction
What entity types does spaCy recognize?
spaCy’s English models recognize:
| Label | Description |
|---|---|
|
Named persons |
|
Organizations, companies |
|
Geopolitical entities (countries, cities) |
|
Non-GPE locations |
|
Dates and time expressions |
|
Monetary values |
|
Products |
Can I use custom entity types with spaCy?
Not without training a custom model. spaCy’s NER is based on statistical models trained on specific entity types.
| For custom entity types, use GLiNER instead. |
Which spaCy model should I use?
| Model | Size | Speed | Accuracy |
|---|---|---|---|
|
12MB |
Fastest |
Good |
|
40MB |
Fast |
Better |
|
560MB |
Medium |
Best |
|
400MB |
Slow |
Best (transformer) |
For most use cases, en_core_web_sm is sufficient and fastest.
LLM Extraction
When should I use the LLM extractor?
Use LLM extraction when you need:
| Relationship extraction |
LLM is the only extractor that can extract relationships between entities |
| Complex context |
When entity identification requires understanding context |
| Highest accuracy |
When cost is not a concern |
What LLM models are supported?
Any OpenAI-compatible model:
-
gpt-4o-mini(recommended for cost/performance balance) -
gpt-4o(highest accuracy) -
gpt-3.5-turbo(faster, cheaper)
Can I use local LLMs?
The current implementation uses OpenAI’s API. For local LLMs, you would need to:
-
Run a local server with OpenAI-compatible API (e.g., using Ollama, vLLM)
-
Point the
OPENAI_BASE_URLenvironment variable to your local server
Why is LLM extraction slow?
LLM extraction requires an API call to the language model provider, which adds network latency (~200-500ms).
For high-throughput scenarios, consider:
-
Using spaCy + GLiNER without LLM fallback
-
Batching texts and processing in parallel
-
Using a faster model like
gpt-3.5-turbo
Batch and Streaming Extraction
How do I process multiple documents at once?
Use the batch extraction API:
from neo4j_agent_memory.extraction import ExtractionPipeline
pipeline = ExtractionPipeline()
# Extract from multiple texts in parallel
results = await pipeline.extract_batch(
texts=["Document 1...", "Document 2...", "Document 3..."],
batch_size=10, # Process 10 at a time
on_progress=lambda done, total: print(f"{done}/{total}"),
)
Benefits: * Parallel processing with configurable concurrency * Progress tracking callback * GPU batch processing for GLiNER (native batch support)
How do I handle very long documents?
Use streaming extraction for documents over 100K tokens:
from neo4j_agent_memory.extraction import StreamingExtractor
extractor = StreamingExtractor(
chunk_size=4000, # Tokens per chunk
chunk_overlap=200, # Overlap to preserve context
)
# Stream results as they're extracted
async for partial_result in extractor.stream(long_document):
print(f"Found {len(partial_result.entities)} entities in chunk")
# Or get all results with automatic deduplication
result = await extractor.extract(long_document)
Can I extract relationships without LLM calls?
Yes, use GLiREL (GLiNER + Relation Extraction):
from neo4j_agent_memory.extraction import (
ExtractorBuilder,
GLiNEREntityExtractor,
)
# Enable GLiREL for relation extraction
extractor = (
ExtractorBuilder()
.with_gliner_schema("podcast")
.with_gliner_relations(threshold=0.3) # GLiREL for relations
.build()
)
result = await extractor.extract(text)
# result.relations populated without LLM calls
GLiREL extracts relations using a fine-tuned model — no API costs.
Entity Resolution and Deduplication
What is entity resolution?
Entity resolution identifies when different text mentions refer to the same entity. For example, "John Smith", "J. Smith", and "John" might all refer to the same person.
What resolution strategies are available?
| Strategy | Description | Best For |
|---|---|---|
|
Case-insensitive string match |
Exact duplicates |
|
RapidFuzz similarity matching |
Typos, abbreviations |
|
Embedding-based similarity |
Synonyms, paraphrases |
|
Tries exact → fuzzy → semantic |
General use (default) |
Is resolution type-aware?
Yes, the CompositeResolver is type-aware. Entities of different types (e.g., a PERSON named "Paris" vs a LOCATION named "Paris") are never merged, even if they have identical names.
How does entity deduplication on ingest work?
Entity deduplication automatically merges similar entities during extraction:
from neo4j_agent_memory import MemorySettings, DeduplicationConfig
settings = MemorySettings(
deduplication=DeduplicationConfig(
enabled=True,
embedding_threshold=0.92, # High similarity = auto-merge
same_as_threshold=0.85, # Medium similarity = SAME_AS link
create_same_as=True,
)
)
-
High confidence matches (>0.92) are automatically merged
-
Medium confidence matches (0.85-0.92) create
SAME_ASrelationships for manual review -
Type-aware — only compares entities of the same type
What are SAME_AS relationships?
SAME_AS relationships link entities that may refer to the same real-world entity but weren’t merged automatically due to lower confidence:
// Find potential duplicates
MATCH (e1:Entity)-[r:SAME_AS]->(e2:Entity)
RETURN e1.name, e2.name, r.confidence
ORDER BY r.confidence DESC
You can manually merge or keep them separate based on your domain knowledge.
Neo4j and Storage
What indexes does the package create?
The package automatically creates:
-
Unique constraints on ID fields
-
Vector indexes for semantic search (requires Neo4j 5.11+)
-
Regular indexes on frequently queried properties
-
Point index on
Entity.locationfor geospatial queries
How are entities labeled in Neo4j?
Entities get multiple labels based on their type:
| Base label |
|
| Type label |
|
| Subtype label |
|
An entity of type OBJECT with subtype VEHICLE gets labels: :Entity:Object:Vehicle
Can I query entities by type directly?
Yes, the type labels enable efficient Cypher queries:
// Find all people
MATCH (p:Person) RETURN p
// Find all vehicles
MATCH (v:Vehicle) RETURN v
// Find organizations in a specific location
MATCH (o:Organization)-[:LOCATED_IN]->(l:Location {name: "New York"})
RETURN o
CLI Tool
How do I use the CLI?
# Extract entities from text
neo4j-memory extract "John works at Acme Corp in New York"
# Extract with specific schema
neo4j-memory extract --schema podcast "..."
# Extract from file
neo4j-memory extract --input document.txt
# Output as table
neo4j-memory extract --format table "..."
# List available schemas
neo4j-memory schemas list
# Get statistics
neo4j-memory stats
How do I configure the CLI?
The CLI reads from environment variables or a .neo4j-memory.yaml config file:
# .neo4j-memory.yaml
neo4j:
uri: bolt://localhost:7687
username: neo4j
password: your-password
extraction:
extractor_type: pipeline
enable_gliner: true
cli:
output_format: json
Geocoding and Geospatial Queries
How do I enable geocoding for LOCATION entities?
Configure geocoding in your settings:
from neo4j_agent_memory import (
MemorySettings,
GeocodingConfig,
GeocodingProvider,
)
settings = MemorySettings(
geocoding=GeocodingConfig(
enabled=True,
provider=GeocodingProvider.NOMINATIM, # Free, 1 req/sec limit
)
)
Or use environment variables:
NAM_GEOCODING__ENABLED=true
NAM_GEOCODING__PROVIDER=nominatim
What geocoding providers are supported?
| Provider | Pros | Cons |
|---|---|---|
Nominatim |
Free, no API key needed |
Rate-limited to 1 request/second |
Google Maps |
Higher accuracy, better address parsing |
Requires API key, pay-per-use |
For Google Maps:
from pydantic import SecretStr
settings = MemorySettings(
geocoding=GeocodingConfig(
enabled=True,
provider=GeocodingProvider.GOOGLE,
api_key=SecretStr("your-google-api-key"),
)
)
How do I geocode existing locations?
Use the batch geocoding method for LOCATION entities without coordinates:
async with MemoryClient(settings) as memory:
stats = await memory.long_term.geocode_locations(
batch_size=50,
skip_existing=True, # Don't re-geocode
)
print(f"Geocoded {stats['geocoded']} of {stats['processed']} locations")
What geospatial queries are supported?
Once locations are geocoded, you can:
# Find locations near a point
nearby = await memory.long_term.search_locations_near(
latitude=40.7128,
longitude=-74.0060,
radius_km=10.0,
limit=20,
)
# Find locations within a bounding box
in_area = await memory.long_term.search_locations_in_bounds(
min_lat=40.70,
max_lat=40.80,
min_lon=-74.02,
max_lon=-73.95,
)
# Get coordinates for a specific location
coords = await memory.long_term.get_entity_coordinates(entity_id)
Can I disable geocoding for specific entities?
Yes, use the geocode=False parameter:
entity, _ = await memory.long_term.add_entity(
name="Custom Location",
entity_type="LOCATION",
geocode=False, # Skip geocoding
coordinates=(40.7128, -74.0060), # Provide coordinates directly
)
Observability and Tracing
How do I enable tracing?
Install and configure a tracing provider:
# For OpenTelemetry
pip install neo4j-agent-memory[opentelemetry]
# For Opik (LLM-focused)
pip install neo4j-agent-memory[opik]
from neo4j_agent_memory import MemorySettings, ObservabilityConfig, TracingProvider
settings = MemorySettings(
observability=ObservabilityConfig(
enabled=True,
provider=TracingProvider.OPIK, # or OPENTELEMETRY
project_name="my-agent",
)
)
What operations are traced?
With tracing enabled, you get visibility into:
-
Memory operations — reads, writes, searches with timing
-
Extraction pipeline — each stage (spaCy, GLiNER, LLM) with duration
-
LLM calls — tokens, latency, costs
-
Entity resolution — matches, merges, SAME_AS creation
Should I use OpenTelemetry or Opik?
| Aspect | OpenTelemetry | Opik |
|---|---|---|
Focus |
General observability |
LLM-specific observability |
Best for |
Existing APM infrastructure |
LLM application monitoring |
Dashboard |
Jaeger, Zipkin, Datadog |
Opik UI (local or cloud) |
LLM metrics |
Basic spans |
Token counts, costs, evaluations |
Performance and Optimization
How can I speed up extraction?
-
Disable unused extractors:
config = ExtractionConfig( enable_spacy=True, enable_gliner=True, enable_llm_fallback=False, # Skip LLM ) -
Use GPU acceleration for GLiNER:
extractor = GLiNEREntityExtractor(device="cuda") # or "mps" for Mac -
Use batch extraction for multiple documents:
results = await pipeline.extract_batch(texts, batch_size=10) -
Use GLiREL instead of LLM for relations:
extractor = ExtractorBuilder().with_gliner_relations().build() -
Use a smaller GLiNER model:
extractor = GLiNEREntityExtractor(model="gliner-community/gliner_small-v2.5")
What’s the memory footprint?
| Component | Memory |
|---|---|
spaCy ( |
~50MB RAM |
GLiNER ( |
~500MB RAM + ~500MB disk for model |
LLM |
Minimal (API-based) |
Can I cache extraction results?
The package doesn’t include built-in caching, but you can implement it:
import hashlib
from functools import lru_cache
@lru_cache(maxsize=1000)
async def cached_extract(text_hash: str, text: str):
return await extractor.extract(text)
# Use hash as cache key
text_hash = hashlib.md5(text.encode()).hexdigest()
result = await cached_extract(text_hash, text)
Troubleshooting
GLiNER model download fails
If the model download fails, try:
-
Check your internet connection
-
Set
HF_HUB_OFFLINE=0to allow downloads -
Manually download from HuggingFace and set local path
Low extraction accuracy
-
Use the right schema — Domain schemas significantly improve accuracy
-
Lower the threshold — Try
threshold=0.3instead of0.5 -
Check for noise — Filter results with
result.filter_invalid_entities() -
Consider LLM fallback — Enable for complex texts
Entities being filtered incorrectly
Check if your entity is in the stopword list:
from neo4j_agent_memory.extraction import is_valid_entity_name, ENTITY_STOPWORDS
is_valid_entity_name("your entity") # Check if it passes
print("they" in ENTITY_STOPWORDS) # Check specific word
Memory errors with large texts
GLiNER has a context window limit. For large texts:
-
Split into smaller chunks
-
Process chunks separately
-
Merge and deduplicate results
ImportError: GLiNER not installed
GLiNER is an optional dependency. Install with:
pip install neo4j-agent-memory[gliner]
# or
uv sync --all-extras
Check availability in code:
from neo4j_agent_memory.extraction import is_gliner_available
if is_gliner_available():
extractor = GLiNEREntityExtractor.for_schema("podcast")
else:
print("GLiNER not available, using spaCy only")
extractor = SpacyEntityExtractor()
See Also
-
Getting Started — Quick start guide
-
Entity Extraction Guide — Detailed extraction documentation
-
Memory Types — Short-term, long-term, and reasoning memory
-
POLE+O Data Model — Entity type classification
-
Configuration Reference — All configuration options
-
Framework Integrations — PydanticAI, LangChain, LlamaIndex, CrewAI, Strands
-
Strands Integration — AWS Strands Agents
-
Bedrock Embeddings — Amazon Bedrock embedding models
-
Domain Schema Examples — Working code examples
-
GitHub Issues — Report bugs or request features