personabot-api / app /core /quality.py
GitHub Actions
Deploy 1ba9ba6
9563e4a
"""
backend/app/core/quality.py
Shared quality-gate logic used by both the generate node (Groq responses) and
the gemini_fast node (Gemini fast-path responses).
Centralised here — rather than in generate.py — so the same hedge-detection and
trust scoring logic runs on every answer regardless of which pipeline branch produced
it. Duplicating the list of hedge phrases across two modules was the root cause of
Bug A (Issue 2): Gemini fast-path answers were never checked for hedge phrases.
"""
from __future__ import annotations
import re
# Phrases that indicate the model hedged despite having been told not to.
# Applies to both Groq (generate node) and Gemini (gemini_fast node) outputs.
_HEDGE_PHRASES: tuple[str, ...] = (
"unfortunately",
"limited information",
"passages only",
"passages do not",
"passages don't",
"you may need to",
"you may want to",
"i don't have",
"i cannot provide",
"not able to provide",
"does not provide",
"does not offer",
"no detailed information",
"not explicitly state",
"not explicitly stated",
"cannot be verified",
)
_RAW_TAG_RE = re.compile(r"</?[a-zA-Z][^>]*>")
def is_low_trust(answer: str, chunks: list, complexity: str) -> bool:
"""
Return True when the answer is likely poor quality and should be reformatted
or rerouted to the full RAG pipeline.
Three signals, checked in order of cost (cheapest first):
1. A hedge phrase survived the system-prompt prohibition.
2. Chunks were retrieved but the model cited nothing (no [N] markers).
Not applicable to Gemini fast-path answers (chunks is always empty there).
3. Answer is suspiciously short for a complex query (< 30 words).
"""
lowered = answer.lower()
if any(phrase in lowered for phrase in _HEDGE_PHRASES):
return True
if _RAW_TAG_RE.search(answer):
return True
if chunks and not re.search(r"\[\d+\]", answer):
return True
if complexity == "complex" and len(answer.split()) < 30:
return True
return False