PromptRouter, QuestionType

On this page

core.prompt_router.PromptRouter, core.prompt_router.QuestionType

Overview

PromptRouter detects the language (Arabic/English) and rhetorical type of a question, then builds a prompt from a matching template — including optional few-shot examples. It exists so RAGSystem can automatically tailor its instructions to the user (e.g. "explain the steps" for a how-to question vs. "compare the elements" for a comparative one) without the caller having to classify questions manually.

QuestionType

Python · 9 lines
class QuestionType(Enum):
    FACTUAL      = "factual"
    ANALYTICAL   = "analytical"
    COMPARATIVE  = "comparative"
    PROCEDURAL   = "procedural"
    DEFINITIONAL = "definitional"
    EVALUATIVE   = "evaluative"
    CAUSAL       = "causal"
    GENERAL      = "general"

Eight classifications. GENERAL is the fallback when no keyword pattern matches.

Constructor

Exact signature

Python · 2 lines
class PromptRouter:
    def __init__(self, include_few_shot: bool = True): ...

Parameters

NameTypeRequiredDefaultNotes
include_few_shotboolNoTrueWhether build() injects a matching few-shot example into the prompt.

detect_language

Python
def detect_language(self, text: str) -> str

Algorithm

  1. Empty/falsy text → return "ar" (Arabic is the default for empty input).
  2. Count characters matching the Unicode ranges \u0600–\u06FF, \u0750–\u077F, \uFB50–\uFDFF, \uFE70–\uFEFF (covers Arabic, Arabic Supplement, Arabic Presentation Forms-A and -B).
  3. Compute ratio = arabic_chars / max(len(text.replace(" ", "")), 1).
  4. Return "ar" if ratio > 0.25, else "en".

Returns

"ar" or "en".

Complexity

O(n) in text length (single regex scan).

Examples

Python · 5 lines
router = PromptRouter()
router.detect_language("ما هو الذكاء الاصطناعي؟")   # "ar"
router.detect_language("What is AI?")                # "en"
router.detect_language("")                            # "ar"  (empty-string default)
router.detect_language("AI مصطلح")                    # depends on the >25% ratio threshold

detect_question_type

Python
def detect_question_type(self, query: str, language: Optional[str] = None) -> QuestionType

Parameters

NameTypeRequiredDefaultNotes
querystrYes—Falsy query returns QuestionType.GENERAL immediately.
languageOptional[str]NoNone"ar" / "en"; auto-detected via detect_language(query) if None.

Algorithm

Selects the pattern list (_PATTERNS_AR or _PATTERNS_EN) for the resolved language, then tests each (QuestionType, compiled_regex) pair in a fixed priority order and returns the first match:

Arabic priority order: COMPARATIVE → PROCEDURAL → DEFINITIONAL → ANALYTICAL → CAUSAL → EVALUATIVE → FACTUAL

English priority order: identical ordering.

If nothing matches, returns QuestionType.GENERAL.

[!NOTE] Pattern order matters Because the check is first-match-wins in a fixed order, a query matching multiple patterns (e.g. "Why is X better than Y?" — both ANALYTICAL-ish and COMPARATIVE-ish) resolves to whichever pattern is checked first — COMPARATIVE in this case, since it's listed before ANALYTICAL.

Keyword patterns (as implemented)

TypeEnglish trigger pattern (abridged)
COMPARATIVEvs, versus, compare, difference between, which is better
PROCEDURALhow to/do/can/should, steps to, guide to, way to
DEFINITIONALwhat is/are/does, define, definition, meaning of
ANALYTICALwhy, analyze, explain, discuss, causes of, effects of
CAUSALcause, leads to, results in, impact of, effect of
EVALUATIVEshould, evaluate, is it better, recommend, pros and cons
FACTUALwho, when, where, how many, how much, which

(Arabic patterns mirror the same semantic categories using Arabic keywords — see source for exact regex.)

Returns

QuestionType.

Examples

Python · 5 lines
router.detect_question_type("How do I reset my password?")     # PROCEDURAL
router.detect_question_type("What is the difference between AI and ML?")  # COMPARATIVE
router.detect_question_type("Who is the CEO?")                  # FACTUAL
router.detect_question_type("")                                 # GENERAL
router.detect_question_type("كيف أنشئ حساباً؟")                  # PROCEDURAL

build

Python · 2 lines
def build(self, query: str, context: str, language: Optional[str] = None,
          question_type: Optional[QuestionType] = None) -> str

Parameters

NameTypeRequiredDefaultNotes
querystrYes—The user question.
contextstrYes—Retrieved context text to embed in the prompt.
languageOptional[str]NoNoneAuto-detected if omitted.
question_typeOptional[QuestionType]NoNoneAuto-detected (via detect_question_type) if omitted — overriding this skips classification.

Returns

str — a complete, ready-to-send LLM prompt.

Algorithm

  1. Resolve lang and q_type (auto-detect any that weren't passed explicitly).
  2. If include_few_shot, look up a matching example string from _FEW_SHOT_AR/_FEW_SHOT_EN (keyed by q_type; GENERAL maps to an empty string in both tables).
  3. Dispatch to _build_arabic or _build_english with (query, context, q_type, few_shot).
  4. Log a DEBUG line with lang, q_type.value, and prompt length.

Internal builders — instruction mapping

Both _build_arabic and _build_english select a per-QuestionType instruction sentence (e.g. FACTUAL → "Answer factually and concisely using only the provided information", COMPARATIVE → "Compare the elements, highlighting similarities and differences") and assemble a fixed template:

Text · 15 lines
{persona sentence}

Instruction: {instruction}

Rules:
• Base your answer solely on the provided information.
• If information is insufficient, say: "Insufficient information to answer."
• Answer in {language}.

{few_shot examples, if any}
Available Information:
{context}

Question: {query}
Answer:

(Arabic version uses the equivalent Arabic wording and RTL-appropriate phrasing.)

Side effects

None beyond a DEBUG log line — build() is a pure string-construction function.

Complexity

O(n) in the combined length of context + few-shot text (string concatenation/formatting).

Examples

Basic — full auto-detection:

Python · 5 lines
router = PromptRouter()
prompt = router.build(
    query="What is the return policy?",
    context="Items may be returned within 30 days of purchase.",
)

Forcing language and type:

Python · 6 lines
prompt = router.build(
    query="مقارنة بين الخطط",
    context="...",
    language="ar",
    question_type=QuestionType.COMPARATIVE,
)

Disabling few-shot examples:

Python · 2 lines
router = PromptRouter(include_few_shot=False)
prompt = router.build(query="How do I cancel my subscription?", context=context)

Inspecting classification before building:

Python · 4 lines
lang = router.detect_language(query)
qtype = router.detect_question_type(query, lang)
print(f"{lang} / {qtype.value}")
prompt = router.build(query, context, language=lang, question_type=qtype)

Production-ready usage — inside a custom RAG loop:

Python · 4 lines
router = PromptRouter(include_few_shot=True)
context = context_manager.build(query, retrieved_chunks)
prompt = router.build(query=query, context=context)
answer = llm.generate(prompt)

Thread safety

Stateless aside from the immutable include_few_shot flag set at construction — safe to share a single PromptRouter instance across threads.

Best practices

  • Let build() auto-detect language/type unless you have a specific reason to override — the priority-ordered pattern matching is already tuned per language.
  • If you need a question type your query doesn't trigger via keywords, pass question_type explicitly rather than trying to game the regex patterns.

Common mistakes

  • Assuming pattern matching is exhaustive/ML-based — it's simple regex keyword matching, so paraphrased questions without trigger words fall through to GENERAL.
  • Expecting QuestionType values to compare equal to their string values directly (QuestionType.FACTUAL == "factual" is False; use .value).

Related pages