Frequently Asked Questions

Table of Contents

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 pip install neo4j-agent-memory[bedrock].

Strands Agents SDK

Pre-built tools for context graph operations with the AWS Strands Agents framework. Install with pip install neo4j-agent-memory[strands].

AgentCore Memory

HybridMemoryProvider for routing between short-term and long-term memory stores.

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

union

Keep all unique entities from all stages

intersection

Only keep entities found by multiple extractors

confidence

Keep highest confidence result per entity (default)

cascade

Use first extractor’s results, fill gaps with others

first_success

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

poleo

Podcast transcripts, interviews

podcast

News articles, press releases

news

Research papers, academic content

scientific

Business reports, earnings calls

business

Movie/TV reviews, entertainment news

entertainment

Clinical notes, medical literature

medical

Legal documents, court cases

legal

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, directorPERSON

  • company, organization, institutionORGANIZATION

  • location, city, countryLOCATION

  • event, meeting, caseEVENT

  • 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

PERSON

Named persons

ORG

Organizations, companies

GPE

Geopolitical entities (countries, cities)

LOC

Non-GPE locations

DATE

Dates and time expressions

MONEY

Monetary values

PRODUCT

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

en_core_web_sm

12MB

Fastest

Good

en_core_web_md

40MB

Fast

Better

en_core_web_lg

560MB

Medium

Best

en_core_web_trf

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:

  1. Run a local server with OpenAI-compatible API (e.g., using Ollama, vLLM)

  2. Point the OPENAI_BASE_URL environment 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

exact

Case-insensitive string match

Exact duplicates

fuzzy

RapidFuzz similarity matching

Typos, abbreviations

semantic

Embedding-based similarity

Synonyms, paraphrases

composite

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_AS relationships 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.location for geospatial queries

How are entities labeled in Neo4j?

Entities get multiple labels based on their type:

Base label

:Entity

Type label

:Person, :Organization, :Location, :Event, :Object

Subtype label

:Vehicle, :Address, :Company, etc. (if specified)

Example 1. Example

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 install the CLI?

Install with the CLI extra:

pip install neo4j-agent-memory[cli]

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?

  1. Disable unused extractors:

    config = ExtractionConfig(
        enable_spacy=True,
        enable_gliner=True,
        enable_llm_fallback=False,  # Skip LLM
    )
  2. Use GPU acceleration for GLiNER:

    extractor = GLiNEREntityExtractor(device="cuda")  # or "mps" for Mac
  3. Use batch extraction for multiple documents:

    results = await pipeline.extract_batch(texts, batch_size=10)
  4. Use GLiREL instead of LLM for relations:

    extractor = ExtractorBuilder().with_gliner_relations().build()
  5. Use a smaller GLiNER model:

    extractor = GLiNEREntityExtractor(model="gliner-community/gliner_small-v2.5")

What’s the memory footprint?

Component Memory

spaCy (en_core_web_sm)

~50MB RAM

GLiNER (medium-v2.5)

~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:

  1. Check your internet connection

  2. Set HF_HUB_OFFLINE=0 to allow downloads

  3. Manually download from HuggingFace and set local path

spaCy model not found

Install the model:

python -m spacy download en_core_web_sm

Low extraction accuracy

  1. Use the right schema — Domain schemas significantly improve accuracy

  2. Lower the threshold — Try threshold=0.3 instead of 0.5

  3. Check for noise — Filter results with result.filter_invalid_entities()

  4. 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:

  1. Split into smaller chunks

  2. Process chunks separately

  3. 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