Retrieve and Answer
This guide shows how to use SearchEngine to retrieve entities and chunks from a knowledge graph, and how to use build_agent to get cited answers.
Prerequisites
Install the retrieval and agent extras:
pip install 'agentic-graphrag[neo4j,llm]'
# For the agent layer:
pip install 'agentic-graphrag[agents]'
Direct SearchEngine Usage
import asyncio
from agrag.common.data_models.graph_schema import GENERIC
from agrag.graphdb.neo4j import Neo4jGraphStore, Neo4jSettings
from agrag.embedding.sentence_transformers import SentenceTransformerEmbedder
from agrag.retrieval.search_engine import SearchEngine
from agrag.retrieval.recipes import HYBRID
async def main():
graph_store = Neo4jGraphStore(settings=Neo4jSettings())
embedder = SentenceTransformerEmbedder()
engine = SearchEngine(
graph_store=graph_store,
embedder=embedder,
entity_labels=[entity.label for entity in GENERIC.entities],
)
results = await engine.search(
"What treats headaches?",
HYBRID,
)
for result in results:
print(f"{result.item} (score={result.score:.3f})")
asyncio.run(main())
Child chunks and their parents
When you chunk with ParentChildChunker, a chunk search returns child chunks. Each SearchResult has the parent chunk in SearchResult.parent; it is None for every other result. The agent sees the parent text under the citation key of the first child hit, for example [C1]. A later child of the same parent shows its own text and the note (part of [C1]). A citation always names the child. See Configure chunking for the setup.
Using build_agent
import asyncio
from agrag.agents.build import build_agent
from agrag.agents.settings import AgentLLMSettings
from agrag.common.data_models.graph_schema import GENERIC
from agrag.embedding.sentence_transformers import SentenceTransformerEmbedder
from agrag.graphdb.neo4j import Neo4jGraphStore, Neo4jSettings
from agrag.retrieval.search_engine import SearchEngine
async def main():
graph_store = Neo4jGraphStore(settings=Neo4jSettings())
embedder = SentenceTransformerEmbedder()
engine = SearchEngine(
graph_store=graph_store,
embedder=embedder,
)
agent = build_agent(
engine=engine,
llm_settings=AgentLLMSettings.from_openai_compatible_env(),
graph_schema=GENERIC,
)
result = await agent.ainvoke({
"messages": [
{"role": "user", "content": "What treats headaches?"}
]
})
last_message = result["messages"][-1]
print(last_message["content"] if isinstance(last_message, dict) else last_message.content)
asyncio.run(main())
result["ledger"] holds the citation keys the agent assigned during the run. Call result["ledger"].resolve("C1") to get the SearchResult behind a key in the answer. It returns None for a key the run never assigned. Each call to ainvoke returns a new ledger.
The deep-agent path returns LangChain message objects, so the last message's text is .content; the optional-dependency fallback returns dictionaries, as shown above. graph_schema tells the researcher's prompt and the generated-query tool which labels and relationship types the graph uses. Omit it to use the engine's own schema, which falls back to GENERIC when the engine was built without one. Pass a schema that differs from the engine's schema, and build_agent raises ValueError, so the prompt can never describe a graph the engine does not search.
Scoping agent retrieval
By default, the agent's tools search the whole graph. Pass SearchFilters to
limit every tool search by document set, tenant, or label:
from agrag.retrieval.filters import SearchFilters
agent = build_agent(
engine=engine,
llm_settings=AgentLLMSettings.from_openai_compatible_env(),
filters=SearchFilters(document_ids=["doc-1", "doc-2"]),
)
Set the filters when your application builds the agent. The tools enforce the filters. The model cannot see or change them.
Build-time filters and per-call tool parameters have different roles. The
caller sets filters, and the model cannot see them. The model chooses the
tool parameters for each call, such as labels, document_ids, limit,
min_score, relation_type, and direction. These parameters can only
narrow the build-time scope. They cannot widen it. The tools refuse requests
outside the scope.
The document_ids filter applies to the five discovery tools and entity
lookup. It also limits neighbors from find_related_entities and
traverse_from_entity. The labels and properties filters apply to entity
lookup and traversal neighbors. relation_types is a traversal allowlist.
list_relationship_types uses only this allowlist. The eleventh tool,
query_graph_directly, is outside both groups. Unscoped agents receive it.
Caller-scoped agents do not.
Researcher tools
The researcher subagent receives eleven tools when the agent is unscoped, ten when caller-set base filters are non-empty. The tools fall into three groups plus a calculator.
Discovery tools find entry points into the graph:
| Tool | Purpose | Key parameters |
|---|---|---|
search_source_text | Find passages of source text that mention the query terms | query, limit, document_ids |
look_up_entity | Find entities that match a name or description | query, limit, labels |
explore_related | Search entities and source text together for a topic | query, limit, labels, document_ids |
answer_from_graph_structure | Answer from reranked entity and passage evidence | query, limit, min_score |
answer_thematic_question | Answer a broad question from community summaries | query, limit |
Structural navigation tools read what the graph stores about one entity:
| Tool | Purpose | Key parameters |
|---|---|---|
describe_entity | Show an entity's own recorded properties, not its neighbours | entity |
list_relationship_types | List the relationship types attached to an entity | entity, relation_type_filter |
Relational tools follow relationships between entities:
| Tool | Purpose | Key parameters |
|---|---|---|
find_related_entities | Find the entities one relationship type away from an entity | entity, relation_type, direction, community_expand |
traverse_from_entity | Walk outward from an entity, with a fallback to relationship types when the neighbourhood is too wide | entity, relation_type, direction, depth |
query_graph_directly | Ask a structural question in one generated read-only Cypher query; present only for unscoped agents | query |
compute_over_evidence stands outside the three groups. It computes a count, a sum, or a comparison over numbers the researcher already read from earlier tool results. It never queries the graph. The design rationale for this tool set is recorded in agrag_agents.md in the test-agentic-graphrag companion repository's thoughts notes.
Available Recipes
| Recipe | Methods | BFS | Reranker |
|---|---|---|---|
ENTITY | entity | No | None |
CHUNK | chunk | No | None |
HYBRID | entity, chunk | No | None |
HYBRID_RERANKED | entity, chunk | No | cross_encoder |
GRAPH_EXPAND | entity | Yes | None |
THEMATIC | community | No | None |
TEXT2CYPHER | text2cypher | No | None |
Configuration
Set environment variables with the RETRIEVAL_ prefix:
RETRIEVAL_ENTITY_LABELS='["Drug","Condition"]'
RETRIEVAL_ENTITY_TOP_K=10
RETRIEVAL_CHUNK_TOP_K=10
RETRIEVAL_HYBRID_ALPHA=0.5
RETRIEVAL_TRAVERSAL_DEPTH=2
RETRIEVAL_ENTITY_LABELS (or the entity_labels argument) names the schema
entity labels that entity search runs against. Ingestion creates one native
vector index per label, so search needs the labels themselves. The
RETRIEVAL_ENTITY_COLLECTION name does not apply here; it applies only when
you configure a separate VectorStore.