Logical Routing

Pritom BiswasPritom Biswas
8 min read

What is Logical Routing?

Previously, we saw what “Routing” in RAG means. So, we’re going to start from the definition in here.

Definition:

Logical routing is a query handling approach that uses explicit rules, patterns, and conditional logic to determine where to direct incoming queries in an information system. Unlike semantic approaches that analyze meaning, logical routing relies on precise, predefined criteria to make routing decisions.

💡
Simply speaking, “Logical Routing” guides queries based on specific rules that are set in advance.

Core Parts:

Logical routing mainly consists of these 4 parts:

  1. Rule Engine:

    The central component that evaluates conditions and executes routing decisions based on predefined logic.

  2. Pattern Matchers:

    Tools that identify specific patterns in queries (keywords, phrases, question types).

  3. Decision Trees:

    Structured flow-charts that guide the routing process through a series of yes/no questions

  4. Routing Destinations:

    The various endpoints where queries can be directed (knowledge bases, specialized handlers, etc.)

How does it work?

Look at the following diagram:

💡
Previously, we learnt that “Query Analysis & Clarification”, “Route Decision”, “Execution”, and “Response Generation” are the fundamental workflows of Routing.

Let’s take an example and see what exactly happens here:

Query: “How do I implement authentication in a React app with Node.js backend?

  1. Pre-processing:

    • Generally, the query is converted to lowercase.

    • Then the query is tokenized: ["how", "do", "i", "implement", "authentication", "in", "a", "react", "app", "with", "node.js", "backend"]

  2. Logical Routing:

    1. Rule Matching:

      • System checks predefined rules in order of specificity

      • Matches rule: "IF query contains React AND Node.js AND authentication → route to full-stack authentication guides"

    2. Pattern Matching:

      • Languages/Frameworks identified: "React", "Node.js"

      • Concepts identified: "authentication"

      • Operation type: "implementation" (how-to)

    3. Decision Trees:

      Look at this:

    4. Route Decisions:

      • Based on the rule match, the system selects "javascript_fullstack_auth" as the routing destination.
  3. Response Phase:

    The query is directed specifically to the "JavaScript Full-Stack Authentication Documentation" section, which contains:

    • React frontend authentication patterns

    • Node.js backend authentication implementations

    • JWT/session management guides

    • Security best practices

The system retrieves information specifically from this targeted section rather than searching the entire database, resulting in:

  • More relevant results

  • Faster response times

  • Content specifically about implementing authentication in React+Node.js applications.

Quick question: Is it necessary to use “Rule Matching”, “Pattern Matching”, and “Decision Trees” together in all the queries? If yes, why? If not, why?

Is it always good?

Will answer it in the next article. Now, let’s do some code🤓

Let’s Code:

In this part, we will code Logical Routing. We will use a dummy Qdrant DB and simulate what happens when we use this kind of “Routing”

  1. Feed the context (Give Knowledge):

     # These should be valid files in your system
     def _init_knowledge_base(self):
             """Initialize the knowlegde base"""
             # Let's work with a dummy knowledge base. In real case, there might be some website data, database data and so on
             self.kb_files = {
                 "javascript": "javascript_docs.pdf",
                 "python": "python_docs.pdf",
                 "ruby": "ruby_docs.pdf",
                 "react": "react_docs.pdf",
                 "nodejs": "nodejs_docs.pdf", 
                 "django": "django_docs.pdf",
                 "api": "api_docs.pdf",
                 "database": "database_docs.pdf",
                 "authentication": "auth_docs.pdf",
                 "general": "general_web_docs.pdf"
             }
    
             # This should be a proper knowledge base when implemented with real information
             self.knowledge_bases = {}
    
  2. Define the patterns (Define Rules):

     def _init_patterns(self):
             # In ideal cases, these patters should de generated using LLMs in massive amount and
             # stored in the vector store beforehand.
             """Initialize pattern matching rules"""
             # Language patterns
             self.language_patterns = {
                 "javascript": ["javascript", "js", "ecmascript", ".js"],
                 "python": ["python", "py", ".py", "pip"],
                 "ruby": ["ruby", "rails", "erb", "gem", ".rb"]
             }
    
             # Framework patterns
             self.framework_patterns = {
                 "react": ["react", "jsx", "component", "hook", "props", "state"],
                 "nodejs": ["node", "nodejs", "npm", "express", "package.json"],
                 "django": ["django", "drf", "django-rest-framework"]
             }
    
             # Concept patterns
             self.concept_patterns = {
                 "api": ["api", "rest", "endpoint", "http", "request", "response"],
                 "database": ["database", "db", "sql", "query", "mongodb", "schema", "model"],
                 "authentication": ["auth", "login", "jwt", "token", "session", "password"]
             }
    
             # Operation patterns
             self.operation_patterns = {
                 "how_to": ["how to", "how do i", "steps to", "guide for", "tutorial", "implement"],
                 "definition": ["what is", "define", "explain", "meaning of", "understand", "concept of"],
                 "comparison": ["vs", "versus", "compare", "difference between", "better than", "pros and cons"],
                 "troubleshooting": ["fix", "error", "bug", "issue", "problem", "not working", "debug"]
             }
    
  3. Match the Patterns (Pattern Matching):

     # Match the patterns with pre-defined rules.
     def _match_patterns(self, query, pattern_dict):
             """Match query against a pattern dictionary and return the matched results"""
             query_lower = query.lower()
             matches = []
             matched_patterns = {}
    
             for category, patterns in pattern_dict.items():
                 for pattern in patterns:
                     if pattern in query_lower:
                         if category not in matches:
                             matches.append(category)
                             matched_patterns[category] = [pattern]
                         else:
                             matched_patterns[category].append(pattern)
    
             return matches, matched_patterns
    
  4. Analyze Query (between rules matching and routing):

    ```python def analyze_query(self, query): """Analyze the query and get the results"""

    languages, lang_patterns = self._match_patterns(query, self.language_patterns) frameworks, framework_patterns = self._match_patterns(query, self.framework_patterns) concepts, concept_patterns = self._match_patterns(query, self.concept_patterns) operations, op_patterns = self._match_patterns(query, self.operation_patterns)

analysis = { "languages": languages, "frameworks": frameworks, "concepts": concepts, "operations": operations, "matched_patterns": { "languages": lang_patterns, "frameworks": framework_patterns, "concepts": concept_patterns, "operations": op_patterns }, "original_query": query }

return analysis


5. **Route the query:**

    ```python
    # Routing started
    def route_query(self, query):

            """Apply logical routing rules to determine the best knowledge base available"""
            analysis = self.analyze_query(query=query)
            route_info = {
                "query": query,
                "analysis": analysis,
                "route_decision": None,
                "decision_path": [],
                "knowledge_base": None
            }

            if analysis["languages"]:
                primary_language = analysis["languages"][0]
                route_info["decision_path"].append(f"Language detected: {primary_language}")
                if analysis["frameworks"]:
                    framework = analysis["frameworks"][0]

                    compatible = False
                    if primary_language == "javascript" and framework in ["react", "nodejs"]:
                        compatible = True
                    elif primary_language == "python" and framework == "django":
                        compatible = True

                    if compatible:
                        route_info["decision_path"].append(f"Compatible framework found: {framework}")
                        route_info["route_decision"] = framework
                        route_info["knowledge_base"] = self._get_kb(framework)
                        return route_info

                route_info["route_decision"] = primary_language
                route_info["knowledge_base"] = self._get_kb(primary_language)
                return route_info

            elif analysis["frameworks"]:
                framework = analysis["frameworks"][0]
                route_info["decision_path"].append(f"Framework detected: {framework}")
                route_info["route_decision"] = framework
                route_info["knowledge_base"] = self._get_kb(framework)
                return route_info

            elif analysis["concepts"]:
                concept = analysis["concepts"][0]
                route_info["decision_path"].append(f"Concept detected: {concept}")
                route_info["route_decision"] = concept
                route_info["knowledge_base"] = self._get_kb(concept)
                return route_info

            route_info["decision_path"].append("No specific domain detected, using general knowledge base")
            route_info["route_decision"] = "general"
            route_info["knowledge_base"] = self._get_kb("general")
            return route_info


    # Routing done. Now Search the final decision on the vector store
        def search(self, query, k=5):
            """Route the query and perform search"""

            route_info = self.route_query(query)
            kb = route_info["knowledge_base"]

            if not kb:
                print("No valid knowledge base found for routing decision")
                return []

            results = kb.search(query, k=k)

            return {
                "routing": route_info,
                "results": results
            }
  1. Dummy Checker Function:

     # Dummy Checking Function. In real time, real queries will be sent to the server an the backend 
     # will validate and route it.
     def demonstration_logical_routing():
         """Show examples of the logical routing system with real queries"""
         router = LogicalRouter()
    
         example_queries = [
             "How do I create an array in JavaScript?",
             "What's the best way to connect Python to a SQL database?",
             "How to fix React component rendering error",
             "What is authentication in web applications?",
             "Explain the difference between Node.js and Django",
             "How to implement REST APIs?",
             "What is a closure in JavaScript?",
             "Django vs Flask - which should I use?"
         ]
    
         print("\n" + "="*80)
         print(" "*30 + "LOGICAL ROUTING DEMO")
         print("="*80 + '\n')
    
         for index, query in enumerate(example_queries, 1):
             print(f"Query no. {index}: {query}")
             print('\n')
    
             route_info = router.route_query(query)
             print("\nQUERY ANALYSIS:")
    
             for category, items in route_info["analysis"].items():
                 if category not in ["mathched_patters", "original_query"]:
                     print(f"-> {category.capitalize()}: {', '.join(items)}")
    
             print("\n ➡️ Routing Decision Paths: ")
             for step in route_info["decision_path"]:
                 print(f" -> {step}")
    
             print(f"\n 🤚 Final Decision Here: {route_info["route_decision"]}")
    
             if route_info["knowledge_base"]:
                 kb_name = route_info["route_decision"]
                 print(f" Routed to Knowledge base: {kb_name}")
             else:
                 print(" No valid knowledge base found")
    
             print("-"*80)
    
  2. Demo Response:

Initializing the logical router with base path
Logical router base path initilalized successfully

================================================================================
                              LOGICAL ROUTING DEMO
================================================================================

Query no. 1: How do I create an array in JavaScript?


Initializing knowldege base: javascript
Creating new collection: 'javascript_docs'
Error creating new collection: {e}

QUERY ANALYSIS:
-> Languages: javascript
-> Frameworks:
-> Concepts:
-> Operations: how_to
-> Matched_patterns: languages, frameworks, concepts, operations

 ➡️ Routing Decision Paths:
 -> Language detected: javascript

 🤚 Final Decision Here: javascript
 Routed to Knowledge base: javascript
--------------------------------------------------------------------------------
Query no. 2: What's the best way to connect Python to a SQL database?


Initializing knowldege base: python
Creating new collection: 'python_docs'
Error creating new collection: {e}

QUERY ANALYSIS:
-> Languages: python
-> Frameworks:
-> Concepts: database
-> Operations:
-> Matched_patterns: languages, frameworks, concepts, operations

 ➡️ Routing Decision Paths:
 -> Language detected: python

 🤚 Final Decision Here: python
 Routed to Knowledge base: python
--------------------------------------------------------------------------------
Query no. 3: How to fix React component rendering error


Initializing knowldege base: react
Creating new collection: 'react_docs'
Error creating new collection: {e}

QUERY ANALYSIS:
-> Languages:
-> Frameworks: react
-> Concepts:
-> Operations: how_to, troubleshooting
-> Matched_patterns: languages, frameworks, concepts, operations

 ➡️ Routing Decision Paths:
 -> Framework detected: react

 🤚 Final Decision Here: react
 Routed to Knowledge base: react
--------------------------------------------------------------------------------
Query no. 4: What is authentication in web applications?


Initializing knowldege base: authentication
Creating new collection: 'auth_docs'
Error creating new collection: {e}

QUERY ANALYSIS:
-> Languages:
-> Frameworks:
-> Concepts: authentication
-> Operations: definition
-> Matched_patterns: languages, frameworks, concepts, operations

 ➡️ Routing Decision Paths:
 -> Concept detected: authentication

 🤚 Final Decision Here: authentication
 Routed to Knowledge base: authentication
--------------------------------------------------------------------------------
Query no. 5: Explain the difference between Node.js and Django


Initializing knowldege base: nodejs
Creating new collection: 'nodejs_docs'
Error creating new collection: {e}

QUERY ANALYSIS:
-> Languages: javascript
-> Frameworks: nodejs, django
-> Concepts:
-> Operations: definition, comparison
-> Matched_patterns: languages, frameworks, concepts, operations

 ➡️ Routing Decision Paths:
 -> Language detected: javascript
 -> Compatible framework found: nodejs

 🤚 Final Decision Here: nodejs
 Routed to Knowledge base: nodejs
--------------------------------------------------------------------------------
Query no. 6: How to implement REST APIs?


Initializing knowldege base: api
Creating new collection: 'api_docs'
Error creating new collection: {e}

QUERY ANALYSIS:
-> Languages:
-> Frameworks:
-> Concepts: api
-> Operations: how_to
-> Matched_patterns: languages, frameworks, concepts, operations

 ➡️ Routing Decision Paths:
 -> Concept detected: api

 🤚 Final Decision Here: api
 Routed to Knowledge base: api
--------------------------------------------------------------------------------
Query no. 7: What is a closure in JavaScript?



QUERY ANALYSIS:
-> Languages: javascript
-> Frameworks:
-> Concepts:
-> Operations: definition
-> Matched_patterns: languages, frameworks, concepts, operations

 ➡️ Routing Decision Paths:
 -> Language detected: javascript

 🤚 Final Decision Here: javascript
 Routed to Knowledge base: javascript
--------------------------------------------------------------------------------
Query no. 8: Django vs Flask - which should I use?


Initializing knowldege base: django
Creating new collection: 'django_docs'
Error creating new collection: {e}

QUERY ANALYSIS:
-> Languages:
-> Frameworks: django
-> Concepts:
-> Operations: comparison
-> Matched_patterns: languages, frameworks, concepts, operations

 ➡️ Routing Decision Paths:
 -> Framework detected: django

 🤚 Final Decision Here: django
 Routed to Knowledge base: django
--------------------------------------------------------------------------------

Full Code:

Get the full code here (Github Gist)

There is an implementation of Vector Store; you can ignore it. I am using Docker for running Qdrant Store locally.

Additional Resource:

llm-rag (github link)

Conclusion:

Next, we will see the Semantic Routing and see some comparisons.

0
Subscribe to my newsletter

Read articles from Pritom Biswas directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pritom Biswas
Pritom Biswas