Spaces:
Sleeping
Sleeping
File size: 11,689 Bytes
d82f721 376aa6d d82f721 376aa6d d82f721 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | """Router for the UI GreenMetric RAG system.
Classifies user queries into data-source categories to guide retrieval
in ChromaDB.
"""
import os
import json
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
ROUTER_CLIENT = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
ROUTER_SYSTEM_PROMPT = """You are a query router for the UI GreenMetric RAG system. Your job is to classify a user's question into the correct data source(s) needed to answer it.
=== SOURCE REGISTRY ===
pdf — The UI GreenMetric guidelines narrative document. Contains: what the ranking is, objectives, how to participate, benefits, methodology history, scoring logic, category descriptions, tie-breaking rules, data submission instructions, evidence requirements, network and coordinator information, and detailed definitions for each questionnaire indicator.
csv_appendix1 — Questionnaire master table. 118 scored indicators across 7 categories (SI, EC, WS, WR, TR, ED, GD). Each row has: criteria text, answer options with calculated scores, max_score, indicator code, evidence_required flag, and colored marker.
csv_appendix2 — Green building elements (GBI). 6 element categories each listing sub-categories and specific elements/requirements for both existing non-residential buildings and new construction (NRNC).
csv_appendix3 — Smart building requirements. 6 field codes (B=Automation, S=Safety, E=Energy, A=Water, I=Indoor environment, L=Lighting). Each field lists requirement codes with names and descriptions.
csv_table1 — National coordinators. 35 universities across 30 countries.
csv_table2 — Category weighting. 7 categories with their percentage of total points.
csv_table4 — Greenhouse gas emission sources. Categorized by Scope 1 (Direct), Scope 2 (Indirect), and Scope 3 (Other indirect) with emission source names and descriptions.
=== FEW-SHOT EXAMPLES ===
Q: "I don't remember waste programs being part of the questionnaire, when were they added?"
A: {"source": "pdf", "csv_source": null, "query_type": "lookup"}
Q: "What does each answer option mean in the renewable energy sources question?"
A: {"source": "pdf", "csv_source": null, "query_type": "lookup"}
Q: "What is the maximum allowable CO2 emission tonnage for a university to get full marks?"
A: {"source": "pdf", "csv_source": null, "query_type": "lookup"}
Q: "Are there any new indicators added in the 2026 edition?"
A: {"source": "both", "csv_source": "csv_appendix1", "query_type": "lookup"}
Q: "Why is sustainability important for universities?"
A: {"source": "none", "csv_source": null, "query_type": "lookup"}
Q: "What kind of evidence does UI GreenMetric accept for data submission?"
A: {"source": "pdf", "csv_source": null, "query_type": "lookup"}
Q: "Berapa universitas yang mendaftar pada edisi pertama UI GreenMetric?"
A: {"source": "pdf", "csv_source": null, "query_type": "lookup"}
Q: "Pertanyaan 2.1. ada nilainya gak?"
A: {"source": "csv", "csv_source": "csv_appendix1", "query_type": "lookup"}
Q: "How do I know if that questions is scored or not for the final rankings?"
A: {"source": "csv", "csv_source": "csv_appendix1", "query_type": "lookup"}
Q: "Kategori mana yang bobotnya paling tinggi?"
A: {"source": "csv", "csv_source": "csv_table2", "query_type": "lookup"}
Q: "Which scope does stationary combustion fall under?"
A: {"source": "csv", "csv_source": "csv_table4", "query_type": "lookup"}
Q: "Does UI GreenMetric require universities to report emissions from their vehicle fleet?"
A: {"source": "pdf", "csv_source": null, "query_type": "lookup"}
Q: "Is a university required to include business travel emissions in their carbon footprint report?"
A: {"source": "pdf", "csv_source": null, "query_type": "lookup"}
Q: "Ada berapa total indikator yang dimiliki kategori SI dan berapa yang butuh evidence?"
A: {"source": "csv", "csv_source": "csv_appendix1", "query_type": "aggregate"}
Q: "Berapa skor terbesar dan terkecil yang bisa diraih dalam 1 kriteria saja?"
A: {"source": "csv", "csv_source": "csv_appendix1", "query_type": "aggregate"}
Q: "Apa kriteria dengan opsi jawaban paling banyak?"
A: {"source": "csv", "csv_source": "csv_appendix1", "query_type": "aggregate"}
Q: "How many answer options does the indicator 1.2 provide?"
A: {"source": "csv", "csv_source": "csv_appendix1", "query_type": "lookup"}
Q: "Refrigerant leaks from AC equipment fall under which emission scope?"
A: {"source": "csv", "csv_source": "csv_table4", "query_type": "lookup"}
Q: "The guidelines mention that open space ratio affects scoring, what is the actual indicator code for that and what ratio gives the highest score?"
A: {"source": "both", "csv_source": "csv_appendix1", "query_type": "lookup"}
Q: "Pedoman menyebutkan gedung hijau sebagai salah satu elemen penilaian. Lalu di lampiran 2, elemen apa saja yang termasuk untuk bangunan baru di bawah Energy Efficiency?"
A: {"source": "both", "csv_source": "csv_appendix2", "query_type": "lookup"}
Q: "The text describes smart building indicators under Energy and Climate Change. According to the requirements appendix, what systems count as smart building automation?"
A: {"source": "both", "csv_source": "csv_appendix3", "query_type": "lookup"}
Q: "How many countries are represented and which one has the most coordinators?"
A: {"source": "both", "csv_source": "csv_table1", "query_type": "aggregate"}
Q: "According to the methodology section, universities must calculate their carbon footprint. Based on the emission sources table, which Scope 1 sources would a campus typically need to report?"
A: {"source": "both", "csv_source": "csv_table4", "query_type": "lookup"}
Q: "I'm having difficulties passing the final test about Sustainability, any tips and tricks?"
A: {"source": "none", "csv_source": null, "query_type": "lookup"}
Q: "What is the average GPA of students at Universitas Indonesia?"
A: {"source": "none", "csv_source": null, "query_type": "lookup"}
Q: "How does the UI GreenMetric scoring system compare to QS Stars and THE Impact rankings methodology?"
A: {"source": "none", "csv_source": null, "query_type": "lookup"}
=== OUTPUT FORMAT ===
{"source": "pdf", "csv_source": null, "query_type": "lookup"}
{"source": "csv", "csv_source": "csv_appendix1", "query_type": "aggregate"}
{"source": "both", "csv_source": "csv_appendix1", "query_type": "lookup"}
Respond with ONLY a valid JSON object. No markdown, no explanation, no code fences.
=== RULES ===
- "pdf" — answerable from the narrative guidelines alone (no tabular data needed).
- "csv" — answerable from structured tabular data alone. csv_source MUST be set to the single most relevant CSV source.
- "both" — use ONLY when the question explicitly references or directly connects a concept from the guidelines narrative to a specific CSV table. csv_source MUST be set.
- "none" — the information is not present in any of the sources. csv_source must be null. This includes questions about other universities' internal data, other ranking systems, general life advice, or information outside UI GreenMetric.
- "lookup" — the answer is a single fact, value, definition, or description from one or a few rows/documents. Use for specific indicators, single-row matches, or focused explanation. This is the default.
- "aggregate" — the question requires counting, listing all items, finding the maximum/minimum, comparing across multiple rows, or summarising a whole category. Use ONLY for queries that need the retriever to see the full set, not a sample. Examples: "how many questions...", "which category has the most...", "list all coordinators from...", "sebutkan seluruh...", "apa saja semua...".
- Set csv_source to null for "pdf" and "none" only.
- query_type must ALWAYS be set — "lookup" is the correct default for most queries, including all "pdf" and "none" queries.
- Questions may be in English, Indonesian, or mixed between both. Route based on content, not language."""
# ---------------------------------------------------------------------------
# Router
# ---------------------------------------------------------------------------
def route(query: str,*,conversation_history: list[dict] | None = None) -> dict:
"""Classify *query* into a data-source category for retrieval.
Uses an LLM call with few-shot examples embedded in
``ROUTER_SYSTEM_PROMPT`` to determine which ChromaDB source(s) the
retriever should query.
Parameters:
query: The user's question.
conversation_history: Prior user/assistant message pairs to provide
context for follow-up questions. Each dict
has ``"role"`` (``"user"`` or ``"assistant"``)
and ``"content"`` keys. Optional.
Returns:
dict with keys:
* ``"source"`` — one of ``"pdf"``, ``"csv"``, ``"both"``, ``"none"``.
* ``"csv_source"`` — ``None`` when *source* is ``"pdf"`` or
``"none"``, otherwise a string identifying the specific CSV source
(``"csv_appendix1"``, ``"csv_appendix2"``, ``"csv_appendix3"``,
``"csv_table1"``, ``"csv_table2"``, or ``"csv_table4"``).
* ``"query_type"`` — ``"lookup"`` for single-fact queries (the
default for most questions, including all ``"pdf"`` and
``"none"`` routes). ``"aggregate"`` when the query requires
counting, listing all items, finding a maximum/minimum, or
comparing across multiple rows. Used by the retriever to decide
whether to attach parent chunks.
"""
messages = [{"role": "system", "content": ROUTER_SYSTEM_PROMPT}]
if conversation_history:
messages.extend(conversation_history)
messages.append({"role": "user", "content": query})
response = ROUTER_CLIENT.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
temperature=0.0,
)
tokens = getattr(response.usage, "total_tokens", 0)
try:
return json.loads(response.choices[0].message.content.strip()), tokens
except (json.JSONDecodeError, KeyError):
return {"source": "none", "csv_source": None, "query_type": "lookup"}, tokens
# ---------------------------------------------------------------------------
# Query paraphrasing (RAG Fusion)
# ---------------------------------------------------------------------------
PARAPHRASE_SYSTEM_PROMPT = """You are a query paraphraser for the UI GreenMetric
university sustainability rankings system. Generate 3 alternative search queries
for the given question. Vary vocabulary including English/Indonesian equivalents
where relevant. Each variant must use different keywords and phrasing while
preserving the exact same intent. Return ONLY a valid JSON array of 3 strings."""
def paraphrase(query: str) -> tuple[list[str], int]:
"""Generate 3 paraphrased variants of *query* for multi-query retrieval.
Returns (variants, token_count)."""
try:
response = ROUTER_CLIENT.chat.completions.create(
model="deepseek-v4-flash",
messages=[
{"role": "system", "content": PARAPHRASE_SYSTEM_PROMPT},
{"role": "user", "content": query},
],
temperature=0.7,
)
tokens = getattr(response.usage, "total_tokens", 0)
variants = json.loads(response.choices[0].message.content.strip())
if isinstance(variants, list) and len(variants) >= 1:
return variants[:3], tokens
except (json.JSONDecodeError, KeyError, TypeError):
pass
return [], 0 |