diff --git a/backend/rag/template_sparql.py b/backend/rag/template_sparql.py index 990760ecb2..db135618cc 100644 --- a/backend/rag/template_sparql.py +++ b/backend/rag/template_sparql.py @@ -641,6 +641,12 @@ class TemplateDefinition(BaseModel): response_modes: list[str] = Field(default_factory=lambda: ["prose"]) # Optional per-language UI templates for formatting simple answers ui_template: Optional[dict[str, str]] = None + # Database routing configuration + # Available databases: "oxigraph" (SPARQL/KG), "qdrant" (vector search) + # When specified, only the listed databases are queried (skipping others) + # Default: query both databases (backward compatible) + # Use ["oxigraph"] for factual/geographic queries where vector search adds noise + databases: list[str] = Field(default_factory=lambda: ["oxigraph", "qdrant"]) class FollowUpPattern(BaseModel): @@ -716,6 +722,9 @@ class TemplateMatchResult(BaseModel): # Response rendering configuration (passed through from template definition) response_modes: list[str] = Field(default_factory=lambda: ["prose"]) ui_template: Optional[dict[str, str]] = None + # Database routing configuration (passed through from template definition) + # When ["oxigraph"] only, vector search is skipped for faster, deterministic results + databases: list[str] = Field(default_factory=lambda: ["oxigraph", "qdrant"]) def requires_llm(self) -> bool: """Check if this template requires LLM prose generation. @@ -723,6 +732,14 @@ class TemplateMatchResult(BaseModel): Fast path rule: If "prose" is NOT in response_modes, LLM generation is skipped. """ return "prose" in self.response_modes + + def use_oxigraph(self) -> bool: + """Check if this template should query Oxigraph (SPARQL/KG).""" + return "oxigraph" in self.databases + + def use_qdrant(self) -> bool: + """Check if this template should query Qdrant (vector search).""" + return "qdrant" in self.databases class ResolvedQuestion(BaseModel): @@ -2544,6 +2561,9 @@ class TemplateClassifier(dspy.Module): # Response rendering configuration (template-driven) response_modes=template_data.get("response_modes", ["prose"]), ui_template=template_data.get("ui_template"), + # Database routing configuration (template-driven) + # Default to both databases for backward compatibility + databases=template_data.get("databases", ["oxigraph", "qdrant"]), ) except Exception as e: logger.warning(f"Failed to parse template {template_id}: {e}") @@ -3268,6 +3288,7 @@ class TemplateSPARQLPipeline(dspy.Module): template_def = self.instantiator._get_template(template_id) response_modes = template_def.response_modes if template_def else ["prose"] ui_template = template_def.ui_template if template_def else None + databases = template_def.databases if template_def else ["oxigraph", "qdrant"] return TemplateMatchResult( matched=True, @@ -3277,7 +3298,8 @@ class TemplateSPARQLPipeline(dspy.Module): sparql=sparql, reasoning=match_result.reasoning, response_modes=response_modes, - ui_template=ui_template + ui_template=ui_template, + databases=databases )