Spaces:
Running
Running
feat: improve chatbot search quality, implement refusal filtering.
Browse files- rag/constants.py +4 -2
- rag/rag.py +22 -3
- rag/routes.py +11 -2
rag/constants.py
CHANGED
|
@@ -7,8 +7,10 @@ WARMUP_INTERVAL_S = 300
|
|
| 7 |
# Web search configuration — Wiki + DDG for topics, DDG only for PDF/URL materials.
|
| 8 |
WIKI_TOP_K_RESULTS = 1
|
| 9 |
WIKI_DOC_CONTENT_CHARS_MAX = 1200 # max chars from Wikipedia result
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
|
| 13 |
RAG_PROMPT_TEMPLATE_BASE = """\
|
| 14 |
<role>
|
|
|
|
| 7 |
# Web search configuration — Wiki + DDG for topics, DDG only for PDF/URL materials.
|
| 8 |
WIKI_TOP_K_RESULTS = 1
|
| 9 |
WIKI_DOC_CONTENT_CHARS_MAX = 1200 # max chars from Wikipedia result
|
| 10 |
+
|
| 11 |
+
# DuckDuckGO Search
|
| 12 |
+
DUCKDUCKGO_NUM_RESULTS = 5 # number of DDG snippet results returned per search
|
| 13 |
+
DUCKDUCKGO_DOC_CONTENT_CHARS_MAX = 3000 # max chars kept from the combined DDG result block
|
| 14 |
|
| 15 |
RAG_PROMPT_TEMPLATE_BASE = """\
|
| 16 |
<role>
|
rag/rag.py
CHANGED
|
@@ -337,7 +337,10 @@ def rag_answer(
|
|
| 337 |
context_parts.append(f"Wikipedia Results:\n{wiki_snippets}")
|
| 338 |
|
| 339 |
# --- DuckDuckGo search: ALL material types (topics, PDFs, URLs) ---
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
| 341 |
if ddg_snippets:
|
| 342 |
context_parts.append(f"Web Search Results (DuckDuckGo):\n{ddg_snippets}")
|
| 343 |
|
|
@@ -352,6 +355,18 @@ def rag_answer(
|
|
| 352 |
subject=subject_title,
|
| 353 |
)
|
| 354 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
try:
|
| 356 |
# All queries now use the simple LLM call (no agent loop).
|
| 357 |
# DDG results are pre-fetched and injected into context above.
|
|
@@ -368,7 +383,9 @@ def rag_answer(
|
|
| 368 |
})
|
| 369 |
|
| 370 |
answer = response.content
|
| 371 |
-
|
|
|
|
|
|
|
| 372 |
return answer, memory
|
| 373 |
except Exception as e:
|
| 374 |
logger.warning(f"Groq API call failed or rate-limited: {e}. Falling back to google/gemma-4-31b-it immediately.")
|
|
@@ -385,7 +402,9 @@ def rag_answer(
|
|
| 385 |
"agent_scratchpad": "",
|
| 386 |
})
|
| 387 |
answer = response.content
|
| 388 |
-
|
|
|
|
|
|
|
| 389 |
return answer, memory
|
| 390 |
except Exception as fallback_err:
|
| 391 |
logger.error(f"Fallback LLM call also failed: {fallback_err}")
|
|
|
|
| 337 |
context_parts.append(f"Wikipedia Results:\n{wiki_snippets}")
|
| 338 |
|
| 339 |
# --- DuckDuckGo search: ALL material types (topics, PDFs, URLs) ---
|
| 340 |
+
# Enrich the search query with the subject title so follow-up / short
|
| 341 |
+
subject_title = mat.get("title") if mat and mat.get("title") else ""
|
| 342 |
+
ddg_query = f"{query} {subject_title}".strip() if subject_title else query
|
| 343 |
+
ddg_snippets = direct_ddg_search(ddg_query)
|
| 344 |
if ddg_snippets:
|
| 345 |
context_parts.append(f"Web Search Results (DuckDuckGo):\n{ddg_snippets}")
|
| 346 |
|
|
|
|
| 355 |
subject=subject_title,
|
| 356 |
)
|
| 357 |
|
| 358 |
+
# Safety/refusal responses must NOT be saved to memory, otherwise the
|
| 359 |
+
_REFUSAL_PREFIXES = (
|
| 360 |
+
"I can't respond on a gibberish",
|
| 361 |
+
"I can't respond on a NSFW",
|
| 362 |
+
"I can't respond on a political",
|
| 363 |
+
"I can't respond on a religious",
|
| 364 |
+
)
|
| 365 |
+
|
| 366 |
+
def _is_refusal(text: str) -> bool:
|
| 367 |
+
t = text.strip()
|
| 368 |
+
return any(t.startswith(p) for p in _REFUSAL_PREFIXES)
|
| 369 |
+
|
| 370 |
try:
|
| 371 |
# All queries now use the simple LLM call (no agent loop).
|
| 372 |
# DDG results are pre-fetched and injected into context above.
|
|
|
|
| 383 |
})
|
| 384 |
|
| 385 |
answer = response.content
|
| 386 |
+
# Only persist non-refusal answers to memory
|
| 387 |
+
if not _is_refusal(answer):
|
| 388 |
+
memory.save_context({"input": query}, {"output": answer})
|
| 389 |
return answer, memory
|
| 390 |
except Exception as e:
|
| 391 |
logger.warning(f"Groq API call failed or rate-limited: {e}. Falling back to google/gemma-4-31b-it immediately.")
|
|
|
|
| 402 |
"agent_scratchpad": "",
|
| 403 |
})
|
| 404 |
answer = response.content
|
| 405 |
+
# Only persist non-refusal answers to memory
|
| 406 |
+
if not _is_refusal(answer):
|
| 407 |
+
memory.save_context({"input": query}, {"output": answer})
|
| 408 |
return answer, memory
|
| 409 |
except Exception as fallback_err:
|
| 410 |
logger.error(f"Fallback LLM call also failed: {fallback_err}")
|
rag/routes.py
CHANGED
|
@@ -94,8 +94,17 @@ async def ask_tutor(
|
|
| 94 |
cleaned_answer = clean_summary(answer)
|
| 95 |
elapsed = time.time() - start
|
| 96 |
|
| 97 |
-
#
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
try:
|
| 100 |
append_session_message(body.session_id, "assistant", cleaned_answer)
|
| 101 |
except Exception:
|
|
|
|
| 94 |
cleaned_answer = clean_summary(answer)
|
| 95 |
elapsed = time.time() - start
|
| 96 |
|
| 97 |
+
# Safety/refusal responses must not be saved to DB history either.
|
| 98 |
+
_REFUSAL_PREFIXES = (
|
| 99 |
+
"I can't respond on a gibberish",
|
| 100 |
+
"I can't respond on a NSFW",
|
| 101 |
+
"I can't respond on a political",
|
| 102 |
+
"I can't respond on a religious",
|
| 103 |
+
)
|
| 104 |
+
is_refusal = any(cleaned_answer.strip().startswith(p) for p in _REFUSAL_PREFIXES)
|
| 105 |
+
|
| 106 |
+
# Persist assistant response (only if not a refusal)
|
| 107 |
+
if body.session_id and not is_refusal:
|
| 108 |
try:
|
| 109 |
append_session_message(body.session_id, "assistant", cleaned_answer)
|
| 110 |
except Exception:
|