Deploy SvaraSetu Docker Space
Browse files- svarasetu/engine.py +6 -8
- svarasetu/types.py +1 -1
- svarasetu/web/index.html +1 -1
- tests/test_api.py +2 -0
- tests/test_gemini_quality.py +30 -0
svarasetu/engine.py
CHANGED
|
@@ -1085,15 +1085,14 @@ class RAGPipelineOrchestrator:
|
|
| 1085 |
stage="gemini_grounded",
|
| 1086 |
ms=round((time.perf_counter() - polish_t) * 1000, 2),
|
| 1087 |
success=False,
|
| 1088 |
-
fallback_used=
|
| 1089 |
-
details="Grounded Gemini abstained;
|
| 1090 |
))
|
| 1091 |
-
return
|
| 1092 |
-
request=request,
|
| 1093 |
query=raw_query_text,
|
| 1094 |
transcript=transcript,
|
| 1095 |
language=target_lang,
|
| 1096 |
-
reason=
|
| 1097 |
guardrails=guardrails,
|
| 1098 |
timings=timings,
|
| 1099 |
start_t=start_pipeline_t,
|
|
@@ -1374,10 +1373,9 @@ class RAGPipelineOrchestrator:
|
|
| 1374 |
"UNSAFE_CONTENT",
|
| 1375 |
"EMPTY_QUERY",
|
| 1376 |
}
|
| 1377 |
-
gemini_requested = bool(getattr(request, "gemini_enabled", False))
|
| 1378 |
use_open = (
|
| 1379 |
-
|
| 1380 |
-
and
|
| 1381 |
and gemini.configured
|
| 1382 |
and not skip
|
| 1383 |
and not blocked
|
|
|
|
| 1085 |
stage="gemini_grounded",
|
| 1086 |
ms=round((time.perf_counter() - polish_t) * 1000, 2),
|
| 1087 |
success=False,
|
| 1088 |
+
fallback_used=False,
|
| 1089 |
+
details="Grounded Gemini abstained; refusing without using general knowledge",
|
| 1090 |
))
|
| 1091 |
+
return self._build_declined_response(
|
|
|
|
| 1092 |
query=raw_query_text,
|
| 1093 |
transcript=transcript,
|
| 1094 |
language=target_lang,
|
| 1095 |
+
reason="The retrieved passages do not contain enough evidence to answer.",
|
| 1096 |
guardrails=guardrails,
|
| 1097 |
timings=timings,
|
| 1098 |
start_t=start_pipeline_t,
|
|
|
|
| 1373 |
"UNSAFE_CONTENT",
|
| 1374 |
"EMPTY_QUERY",
|
| 1375 |
}
|
|
|
|
| 1376 |
use_open = (
|
| 1377 |
+
getattr(config, "USE_GEMINI_OPEN", False)
|
| 1378 |
+
and getattr(config, "ALLOW_NETWORK_CALLS_IN_PIPELINE", False)
|
| 1379 |
and gemini.configured
|
| 1380 |
and not skip
|
| 1381 |
and not blocked
|
svarasetu/types.py
CHANGED
|
@@ -67,7 +67,7 @@ class QueryRequest(BaseModel):
|
|
| 67 |
cross_lingual: bool = False # Multilingual federation across all indexed corpora (default: False)
|
| 68 |
bypass_cache: bool = False # Bypass semantic cache to measure cold-start retrieval latency
|
| 69 |
bypass_gemini: bool = False # Force extractive path for the 200 ms SLA bench
|
| 70 |
-
gemini_enabled: bool = False # Opt in to
|
| 71 |
|
| 72 |
|
| 73 |
class QueryResponse(BaseModel):
|
|
|
|
| 67 |
cross_lingual: bool = False # Multilingual federation across all indexed corpora (default: False)
|
| 68 |
bypass_cache: bool = False # Bypass semantic cache to measure cold-start retrieval latency
|
| 69 |
bypass_gemini: bool = False # Force extractive path for the 200 ms SLA bench
|
| 70 |
+
gemini_enabled: bool = False # Opt in to Gemini synthesis using retrieved evidence only
|
| 71 |
|
| 72 |
|
| 73 |
class QueryResponse(BaseModel):
|
svarasetu/web/index.html
CHANGED
|
@@ -253,7 +253,7 @@
|
|
| 253 |
<label class="setting-row"><span><strong>Save chat history</strong><small>Keep conversations on this device</small></span><input id="saveHistoryToggle" type="checkbox" checked /><i></i></label>
|
| 254 |
<label class="setting-row"><span><strong>Automatic spoken answers</strong><small>Play verified answers after each request</small></span><input id="autoSpeakToggle" type="checkbox" /><i></i></label>
|
| 255 |
<label class="setting-row"><span><strong>Cross-lingual retrieval</strong><small>Search across supported languages</small></span><input id="crossLingualToggle" type="checkbox" /><i></i></label>
|
| 256 |
-
<label class="setting-row"><span><strong>Gemini
|
| 257 |
</section>
|
| 258 |
<section class="settings-group">
|
| 259 |
<h3>Appearance</h3>
|
|
|
|
| 253 |
<label class="setting-row"><span><strong>Save chat history</strong><small>Keep conversations on this device</small></span><input id="saveHistoryToggle" type="checkbox" checked /><i></i></label>
|
| 254 |
<label class="setting-row"><span><strong>Automatic spoken answers</strong><small>Play verified answers after each request</small></span><input id="autoSpeakToggle" type="checkbox" /><i></i></label>
|
| 255 |
<label class="setting-row"><span><strong>Cross-lingual retrieval</strong><small>Search across supported languages</small></span><input id="crossLingualToggle" type="checkbox" /><i></i></label>
|
| 256 |
+
<label class="setting-row"><span><strong>Gemini + RAG</strong><small>Let Gemini answer only from passages retrieved by your RAG index</small></span><input id="geminiToggle" type="checkbox" /><i></i></label>
|
| 257 |
</section>
|
| 258 |
<section class="settings-group">
|
| 259 |
<h3>Appearance</h3>
|
tests/test_api.py
CHANGED
|
@@ -108,6 +108,8 @@ def test_home_ui_and_styles(client):
|
|
| 108 |
assert "ingestBtn" not in home.text
|
| 109 |
assert "Evidence-backed answers across 15 indexed languages" not in home.text
|
| 110 |
assert 'id="geminiToggle"' in home.text
|
|
|
|
|
|
|
| 111 |
assert "lang-btn" not in home.text
|
| 112 |
assert "Tap once. Then talk naturally." in home.text
|
| 113 |
assert "SILENCE_STOP_MS" in home.text
|
|
|
|
| 108 |
assert "ingestBtn" not in home.text
|
| 109 |
assert "Evidence-backed answers across 15 indexed languages" not in home.text
|
| 110 |
assert 'id="geminiToggle"' in home.text
|
| 111 |
+
assert "answer from general knowledge" not in home.text
|
| 112 |
+
assert "Gemini + RAG" in home.text
|
| 113 |
assert "lang-btn" not in home.text
|
| 114 |
assert "Tap once. Then talk naturally." in home.text
|
| 115 |
assert "SILENCE_STOP_MS" in home.text
|
tests/test_gemini_quality.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
| 1 |
"""Gemini quality layer: embed rerank + live client. No network."""
|
| 2 |
|
|
|
|
| 3 |
import sys
|
|
|
|
| 4 |
from pathlib import Path
|
| 5 |
|
| 6 |
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
| 7 |
|
| 8 |
from svarasetu.compose.polish import GeminiClient, _cosine, get_gemini_client
|
|
|
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
class TestCosine:
|
|
@@ -85,6 +89,32 @@ class TestGeminiOpen:
|
|
| 85 |
)
|
| 86 |
assert resp.answer_source == "gemini_open"
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
class TestGeminiClientFactory:
|
| 90 |
def test_unconfigured_without_key(self):
|
|
|
|
| 1 |
"""Gemini quality layer: embed rerank + live client. No network."""
|
| 2 |
|
| 3 |
+
import asyncio
|
| 4 |
import sys
|
| 5 |
+
import time
|
| 6 |
from pathlib import Path
|
| 7 |
|
| 8 |
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
| 9 |
|
| 10 |
from svarasetu.compose.polish import GeminiClient, _cosine, get_gemini_client
|
| 11 |
+
from svarasetu.engine import RAGPipelineOrchestrator
|
| 12 |
+
from svarasetu.types import GuardrailFlags, QueryRequest
|
| 13 |
|
| 14 |
|
| 15 |
class TestCosine:
|
|
|
|
| 89 |
)
|
| 90 |
assert resp.answer_source == "gemini_open"
|
| 91 |
|
| 92 |
+
def test_request_gemini_setting_never_enables_open_answers(self, monkeypatch):
|
| 93 |
+
class FakeGemini:
|
| 94 |
+
configured = True
|
| 95 |
+
|
| 96 |
+
def generate_open(self, *_args, **_kwargs):
|
| 97 |
+
raise AssertionError("open-domain Gemini must not run for the RAG toggle")
|
| 98 |
+
|
| 99 |
+
monkeypatch.setattr("svarasetu.engine.get_gemini_client", lambda: FakeGemini())
|
| 100 |
+
monkeypatch.setattr("svarasetu.engine.config.USE_GEMINI_OPEN", False)
|
| 101 |
+
monkeypatch.setattr("svarasetu.engine.config.ALLOW_NETWORK_CALLS_IN_PIPELINE", False)
|
| 102 |
+
|
| 103 |
+
pipeline = RAGPipelineOrchestrator.__new__(RAGPipelineOrchestrator)
|
| 104 |
+
response = asyncio.run(
|
| 105 |
+
pipeline._finish_without_corpus(
|
| 106 |
+
request=QueryRequest(text="Not in the index", gemini_enabled=True),
|
| 107 |
+
query="Not in the index",
|
| 108 |
+
transcript="Not in the index",
|
| 109 |
+
language="en",
|
| 110 |
+
reason="No indexed evidence",
|
| 111 |
+
guardrails=GuardrailFlags(decline_reason_code="LOW_RETRIEVAL_CONFIDENCE"),
|
| 112 |
+
timings=[],
|
| 113 |
+
start_t=time.perf_counter(),
|
| 114 |
+
)
|
| 115 |
+
)
|
| 116 |
+
assert response.answer_source == "declined"
|
| 117 |
+
|
| 118 |
|
| 119 |
class TestGeminiClientFactory:
|
| 120 |
def test_unconfigured_without_key(self):
|