A retriever router selects the most appropriate retriever for a user question and may extract the arguments required to call it. It forms the main interface of agentic RAG, where specialized retrieval agents can handle different kinds of questions. section 5.0 section 5.1
The router usually uses an LLM with tool or function-calling support. It receives the question and available retriever descriptions, chooses one or more tools, and supplies complete arguments. The surrounding application—not the LLM—executes the selected retriever. section 5.1.2 section 5.3.1
Build the routing loop around four operational steps:
- Describe each retriever clearly, including its purpose and parameter schema. section 5.3.1
- Update follow-up questions with answers from earlier turns so each routed question becomes atomic, specific, and complete. section 5.3.2
- Ask the LLM to select a tool, then dispatch its returned calls through a tools dictionary. section 5.3.2
- Add specialized retrievers incrementally and retain a broad fallback such as text-to-Cypher when narrower tools do not fit. section 5.3.1 section 5.4
Routing a country-capital question
Given “What is the capital of France?” and two database retrievers—capital_by_country and country_by_capital—the router chooses capital_by_country and extracts “France,” producing the call capital_by_country("France"). section 5.1.2
Listing 5.1 Available retriever tools
text2cypher_description = {
"type": "function",
"function": {
"name": "text2cypher",
"description": "Query the database with a user question. When other tools don't fit, fallback to use this one.",
"parameters": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The user question to find the answer for",
}
},
"required": ["question"],
},
},
}
def text2cypher(question: str):
"""Query the database with a user question."""
t2c = Text2Cypher(neo4j_driver)
t2c.set_prompt_section("question", question)
cypher = t2c.generate_cypher()
records, _, _ = neo4j_driver.execute_query(cypher)
return [record.data() for record in records]
movie_info_by_title_description = {
"type": "function",
"function": {
"name": "movie_info_by_title",
"description": "Get information about a movie by providing the title",
"parameters": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The movie title",
}
},
"required": ["title"],
},
},
}
def movie_info_by_title(title: str):
"""Return movie information by title."""
query = """
MATCH (m:Movie)
WHERE toLower(m.title) CONTAINS $title
OPTIONAL MATCH (m)<-[:ACTED_IN]-(a:Person)
OPTIONAL MATCH (m)<-[:DIRECTED]-(d:Person)
RETURN m AS movie, collect(a.name) AS cast, collect(d.name) AS directors
"""
records, _, _ = neo4j_driver.execute_query(query, title=title.lower())
return [record.data() for record in records]
movies_info_by_actor_description = {
"type": "function",
"function": {
"name": "movies_info_by_actor",
"description": "Get information about a movie by providing an actor",
"parameters": {
"type": "object",
"properties": {
"actor": {
"type": "string",
"description": "The actor name",
}
},
"required": ["actor"],
},
},
}
def movies_info_by_actor(actor: str):
"""Return movie information by actor."""
query = """
MATCH (a:Person)-[:ACTED_IN]->(m:Movie)
OPTIONAL MATCH (m)<-[:ACTED_IN]-(a:Person)
OPTIONAL MATCH (m)<-[:DIRECTED]-(d:Person)
WHERE toLower(a.name) CONTAINS $actor
RETURN m AS movie, collect(a.name) AS cast, collect(d.name) AS directors
"""
records, _, _ = neo4j_driver.execute_query(query, actor=actor.lower())
return [record.data() for record in records]
Inspect a concrete tool schema containing a text-to-Cypher fallback and a generic answer_given retriever for answers already present in context. section 5.3.1
For multi-question interactions, route questions sequentially and append retrieved answers to the conversation. An answer critic can detect missing information, generate new complete questions, and send them through the router again; enforce exit criteria to prevent loops. section 5.3.2 section 5.3.3
