Skip to content

feat: add post-generation citation verifier with offline Quran corpus~ - #46

Merged
zeemscript merged 11 commits into
Deen-Bridge:mainfrom
David-282:feat/citation-verifier
Jul 24, 2026
Merged

feat: add post-generation citation verifier with offline Quran corpus~#46
zeemscript merged 11 commits into
Deen-Bridge:mainfrom
David-282:feat/citation-verifier

Conversation

@David-282

@David-282 David-282 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Closes #40

Description

Implements a post-generation citation verification layer for the Deen Bridge AI service. This prevents the model from serving fabricated or mismatched Quran/Hadith references by validating citations against a bundled Quran corpus and enforcing an honest verification policy.

Key Changes

  • Quran Corpus & Data Accessor (data/quran_uthmani.json & corpus.py):
  • Verification Engine (verifier.py):
    • Added Arabic text normalization (stripping diacritics/tashkeel and unifying Alef forms).
    • Added English normalization and fuzzy matching via difflib.SequenceMatcher to compare generated quotes against corpus text.
    • Implemented Quran reference existence checks and quote similarity evaluation (threshold $\ge$ 0.70).
    • Added honest fallback for Hadith citations (unverified status when no corpus is available).
  • Service Integration & Enforcement (main.py):
    • Updated ISLAMIC_CONTEXT system prompt with citation accuracy directives.
    • Supported CITATION_VERIFY modes (off, annotate, strict).
    • Added single-turn corrective regeneration loop for strict mode when citation mismatches occur.
    • Updated ChatResponse model with citations_verified boolean and per-citation verification_results.
  • Testing & CI (tests/test_verifier.py & .github/workflows/ci.yml):
    • Added comprehensive offline unit tests covering Arabic/English normalization, valid citations, out-of-range ayahs, mismatched quotes, and Hadith fallbacks.
    • Updated CI workflow to lint all project files (flake8 .), compile modules, and execute pytest.

How Has This Been Tested?

  • Ran offline unit tests via pytest (all passing, 0 external API calls).
  • Tested CITATION_VERIFY=annotate mode with sample chat responses containing valid and fabricated citations.
  • Verified CITATION_VERIFY=off preserves legacy behavior verbatim.

Related Issues

Summary by CodeRabbit

  • New Features
    • Added Quran/Hadith citation verification for generated replies, including per-citation statuses (verified, mismatch, not quoted, unavailable).
    • /chat now accepts message/chat_id and returns text, citations_verified, and verification_results, with optional strict correction behavior.
    • Added a built-in Quran corpus for surah/ayah lookup and counts.
  • Bug Fixes
    • Improved citation/reference detection and text normalization for more reliable verification.
  • Chores
    • CI now lint-checks the whole repo, expands syntax checks, and runs offline pytest unit tests.
  • Tests
    • Added pytest coverage for citation extraction and verification.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6efd4f3c-eb20-4aa2-b238-c03a439d6f3a

📥 Commits

Reviewing files that changed from the base of the PR and between 29116b2 and 504c60b.

📒 Files selected for processing (8)
  • conftest.py
  • corpus.py
  • main.py
  • pytest.ini
  • requirements.txt
  • test/test_verifier.py
  • tests/test_session_persistence.py
  • verifier.py
💤 Files with no reviewable changes (3)
  • tests/test_session_persistence.py
  • corpus.py
  • test/test_verifier.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • requirements.txt
  • verifier.py
  • main.py

Walkthrough

Changes

The PR adds a bundled Quran corpus and citation verifier, integrates configurable citation checking and strict correction into the FastAPI chat endpoint, updates session and response contracts, and expands CI with repository linting, syntax checks, and offline pytest coverage.

Citation verification

Layer / File(s) Summary
Quran corpus access
corpus.py, data/quran_uthmani.json
Loads Quran metadata and ayah text into cached dictionaries and exposes lookup helpers plus a shared corpus instance.
Citation extraction and verification
verifier.py, test/test_verifier.py
Extracts Quran and Hadith references, normalizes text, compares Quran quotes, reports Hadith citations as unverified, and tests these behaviors.
Chat verification integration
main.py
Updates chat contracts and sessions, adds off/annotate/strict verification modes, performs one corrective regeneration in strict mode, and returns structured results.
Offline validation and CI coverage
requirements.txt, pytest.ini, conftest.py, .github/workflows/ci.yml, tests/test_session_persistence.py
Adds pytest configuration and import setup, updates test dependencies, preserves the TTL test behavior, and runs repository-wide linting, syntax checks, and offline tests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ChatEndpoint
  participant GeminiChatSession
  participant CitationVerifier
  participant QuranCorpus
  Client->>ChatEndpoint: Submit chat message
  ChatEndpoint->>GeminiChatSession: Generate response
  GeminiChatSession-->>ChatEndpoint: Return generated text
  ChatEndpoint->>CitationVerifier: Extract and verify citations
  CitationVerifier->>QuranCorpus: Look up referenced ayah
  QuranCorpus-->>CitationVerifier: Return ayah data
  CitationVerifier-->>ChatEndpoint: Return verification results
  ChatEndpoint-->>Client: Return text and citation metadata
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The verifier core is implemented, but the summary lacks evidence for required README/docs, corpus licensing, and no-source enforcement called for by #40. Add the missing docs and policy updates: document corpus source/license and thresholds, and enforce the no-source rule in the prompt/verification flow.
Out of Scope Changes check ⚠️ Warning main.py also drops session persistence and external router/bootstrap wiring, which are unrelated to citation verification and broaden the PR beyond #40. Split the unrelated API/bootstrap refactor into a separate PR and keep this change focused on verification, corpus loading, and response metadata.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main change: a post-generation citation verifier backed by an offline Quran corpus.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 31-40: Add an explicit least-privilege permissions block near the
workflow or job definition in the CI workflow, granting only contents read
access. Keep the existing checkout, syntax-check, and pytest steps unchanged.

In `@data/quran_uthmani.json`:
- Around line 2-16: The quran_uthmani corpus is incomplete and must contain the
full dataset. Populate surahs with metadata for all 114 surahs and ayat with
Arabic and English text for every ayah, including references such as 2:1, 3:1,
and the complete 2:255 text; add an offline coverage test that detects missing
or partial surah and ayah entries.

In `@main.py`:
- Around line 155-161: Update delete_chat to authenticate the caller and verify
that the requested chat_id belongs to that caller before removing it from
sessions. Reject unauthorized ownership mismatches without deleting the session,
while preserving the existing success response for authorized deletions and 404
behavior for nonexistent sessions.
- Around line 92-104: Update the chat handler to use Gemini’s asynchronous send
method instead of blocking send_message, and wrap the call with a timeout and
exception handling that returns controlled HTTP errors. Before accessing
sessions via chat_id, enforce the same ownership/authentication check
established for session storage so clients cannot attach to another user’s
session.
- Around line 54-55: Replace the global sessions store with authenticated,
owner-scoped session handling: require a validated caller identity, associate
each chat session with that identity, and reject access when ownership does not
match. Remove the shared fallback “default” chat key by generating or requiring
a per-caller session identifier, and add TTL/eviction cleanup so inactive
sessions cannot accumulate indefinitely.
- Around line 42-44: Update the ChatRequest model’s message field to enforce
basic Pydantic validation: reject empty or whitespace-only messages and impose a
reasonable maximum length before requests reach the Gemini API. Keep chat_id
behavior unchanged and use the model’s existing validation conventions.
- Around line 32-51: Extend CitationVerificationResult with optional similarity
and correct_text fields matching the verifier output, then update the
formatted_results construction to pass both values through for Quran citations.
Preserve existing behavior when either value is absent and continue exposing the
remaining citation fields unchanged.
- Around line 82-89: Update run_strict_corrective_loop to remove the unused f
prefix from the static first string literal, and replace the synchronous
chat_session.send_message call with an awaited chat_session.send_message_async
call so the corrective loop remains fully asynchronous.

In `@requirements.txt`:
- Line 7: Pin the pytest dependency in requirements.txt to a specific version,
matching the existing pinned-dependency format and the project’s supported test
environment.

In `@test/test_verifier.py`:
- Line 2: Remove the unused pytest import from test_verifier.py, leaving the
remaining test code unchanged.

In `@verifier.py`:
- Around line 88-100: Update the quote comparison flow around
calculate_similarity to detect Arabic input and compare its normalized text
against ayah_data["arabic"], using exact normalized matching for Arabic
quotations. Preserve the existing fuzzy comparison against corpus_english for
non-Arabic translations, and add regression coverage for both exact and altered
Arabic quotes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ca06fad-c02f-4e09-b486-2a72158b379f

📥 Commits

Reviewing files that changed from the base of the PR and between b9b72f3 and ad62369.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • corpus.py
  • data/quran_uthmani.json
  • main.py
  • requirements.txt
  • test/test_verifier.py
  • verifier.py

Comment thread .github/workflows/ci.yml Outdated
Comment on lines +31 to +40
run: flake8 . --max-line-length=120 --ignore=E501,W503

- name: Check syntax
run: python -m py_compile main.py
run: |
python -m py_compile main.py
python -m py_compile corpus.py
python -m py_compile verifier.py

- name: Run offline unit tests
run: pytest No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict the workflow permissions.

The job currently relies on GitHub’s default token permissions. Add an explicit least-privilege block, such as contents: read, near the workflow or job definition; checkout does not require broader write access.

 jobs:
   lint-and-test:
+    permissions:
+      contents: read
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: flake8 . --max-line-length=120 --ignore=E501,W503
- name: Check syntax
run: python -m py_compile main.py
run: |
python -m py_compile main.py
python -m py_compile corpus.py
python -m py_compile verifier.py
- name: Run offline unit tests
run: pytest
jobs:
lint-and-test:
permissions:
contents: read
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 10-40: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 31 - 40, Add an explicit
least-privilege permissions block near the workflow or job definition in the CI
workflow, granting only contents read access. Keep the existing checkout,
syntax-check, and pytest steps unchanged.

Source: Linters/SAST tools

Comment thread data/quran_uthmani.json
Comment on lines +2 to +16
"surahs": {
"1": { "name": "Al-Fatiha", "ayahs_count": 7 },
"2": { "name": "Al-Baqarah", "ayahs_count": 286 },
"114": { "name": "An-Nas", "ayahs_count": 6 }
},
"ayat": {
"1:1": {
"arabic": "بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ",
"english": "In the name of Allah, the Entirely Merciful, the Especially Merciful."
},
"2:255": {
"arabic": "اللَّهُ لَا إِلَٰهَ إِلَّا هُوَ الْحَيُّ الْقَيُّومُ",
"english": "Allah - there is no deity except Him, the Ever-Living, the Sustainer of all existence."
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Bundle the complete Quran corpus, not a fixture subset.

Only 3 surahs and 2 ayahs are present. Valid references such as 2:1 or 3:1 will be marked nonexistent/mismatched, and the supplied 2:255 text is incomplete. Ship all surah metadata and per-ayah Arabic/English text, with an offline coverage test preventing partial datasets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data/quran_uthmani.json` around lines 2 - 16, The quran_uthmani corpus is
incomplete and must contain the full dataset. Populate surahs with metadata for
all 114 surahs and ayat with Arabic and English text for every ayah, including
references such as 2:1, 3:1, and the complete 2:255 text; add an offline
coverage test that detects missing or partial surah and ayah entries.

Comment thread main.py
Comment on lines +32 to +51
class CitationVerificationResult(BaseModel):
source: str # "quran" | "hadith"
surah: Optional[int] = None
ayah: Optional[int] = None
collection: Optional[str] = None
number: Optional[str] = None
status: str # "verified" | "mismatch" | "unverified" | "not_quoted"
reason: Optional[str] = None


class ChatRequest(BaseModel):
prompt: str
chat_id: Optional[str] = None
context: Optional[str] = None # Additional context for specific queries
message: str
chat_id: Optional[str] = "default"


class ChatResponse(BaseModel):
response: str
text: str
chat_id: str
history: List[Message]


def get_safety_settings():
return [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
}
]
citations_verified: bool = True
verification_results: List[CitationVerificationResult] = []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

CitationVerificationResult drops similarity/correct_text from the verifier's output.

verify_quran_citation in verifier.py returns similarity for VERIFIED/MISMATCH and correct_text for MISMATCH, but this model only exposes source, surah, ayah, collection, number, status, reason. Since formatted_results (lines 134-145) builds CitationVerificationResult explicitly field-by-field, the correct Quran text and similarity score are silently dropped before reaching the client — undermining the PR's goal of surfacing per-citation verification data (the client can be told a quote is wrong but never shown the correct one).

🩹 Proposed fix
 class CitationVerificationResult(BaseModel):
     source: str  # "quran" | "hadith"
     surah: Optional[int] = None
     ayah: Optional[int] = None
     collection: Optional[str] = None
     number: Optional[str] = None
     status: str  # "verified" | "mismatch" | "unverified" | "not_quoted"
     reason: Optional[str] = None
+    similarity: Optional[float] = None
+    correct_text: Optional[str] = None
         CitationVerificationResult(
             source=res["source"],
             surah=res.get("surah"),
             ayah=res.get("ayah"),
             collection=res.get("collection"),
             number=res.get("number"),
             status=res["status"],
             reason=res.get("reason"),
+            similarity=res.get("similarity"),
+            correct_text=res.get("correct_text"),
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class CitationVerificationResult(BaseModel):
source: str # "quran" | "hadith"
surah: Optional[int] = None
ayah: Optional[int] = None
collection: Optional[str] = None
number: Optional[str] = None
status: str # "verified" | "mismatch" | "unverified" | "not_quoted"
reason: Optional[str] = None
class ChatRequest(BaseModel):
prompt: str
chat_id: Optional[str] = None
context: Optional[str] = None # Additional context for specific queries
message: str
chat_id: Optional[str] = "default"
class ChatResponse(BaseModel):
response: str
text: str
chat_id: str
history: List[Message]
def get_safety_settings():
return [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
}
]
citations_verified: bool = True
verification_results: List[CitationVerificationResult] = []
class CitationVerificationResult(BaseModel):
source: str # "quran" | "hadith"
surah: Optional[int] = None
ayah: Optional[int] = None
collection: Optional[str] = None
number: Optional[str] = None
status: str # "verified" | "mismatch" | "unverified" | "not_quoted"
reason: Optional[str] = None
similarity: Optional[float] = None
correct_text: Optional[str] = None
class ChatRequest(BaseModel):
message: str
chat_id: Optional[str] = "default"
class ChatResponse(BaseModel):
text: str
chat_id: str
citations_verified: bool = True
verification_results: List[CitationVerificationResult] = []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 32 - 51, Extend CitationVerificationResult with
optional similarity and correct_text fields matching the verifier output, then
update the formatted_results construction to pass both values through for Quran
citations. Preserve existing behavior when either value is absent and continue
exposing the remaining citation fields unchanged.

Comment thread main.py
Comment on lines 42 to +44
class ChatRequest(BaseModel):
prompt: str
chat_id: Optional[str] = None
context: Optional[str] = None # Additional context for specific queries
message: str
chat_id: Optional[str] = "default"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add basic validation on ChatRequest.message.

message has no length/non-empty constraints, so an empty or arbitrarily large prompt is forwarded straight to the Gemini API on every request. As per path instructions, missing Pydantic validation on request bodies should be flagged.

🩹 Proposed fix
-class ChatRequest(BaseModel):
-    message: str
-    chat_id: Optional[str] = "default"
+class ChatRequest(BaseModel):
+    message: str = Field(..., min_length=1, max_length=4000)
+    chat_id: Optional[str] = "default"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class ChatRequest(BaseModel):
prompt: str
chat_id: Optional[str] = None
context: Optional[str] = None # Additional context for specific queries
message: str
chat_id: Optional[str] = "default"
class ChatRequest(BaseModel):
message: str = Field(..., min_length=1, max_length=4000)
chat_id: Optional[str] = "default"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 42 - 44, Update the ChatRequest model’s message field
to enforce basic Pydantic validation: reject empty or whitespace-only messages
and impose a reasonable maximum length before requests reach the Gemini API.
Keep chat_id behavior unchanged and use the model’s existing validation
conventions.

Source: Path instructions

Comment thread main.py
Comment on lines +54 to +55
# In-memory session store for demo purposes
sessions: Dict[str, Any] = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Unauthenticated, unbounded in-memory session store.

sessions is keyed only by client-supplied chat_id with no ownership check, so any caller who knows/guesses a chat_id can continue or read someone else's conversation (IDOR). Worse, any request without an explicit chat_id falls back to the shared "default" key (line 97), mixing conversations from different anonymous callers into one session. There's also no TTL/eviction, so sessions grows unbounded for the life of the process.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 54 - 55, Replace the global sessions store with
authenticated, owner-scoped session handling: require a validated caller
identity, associate each chat session with that identity, and reject access when
ownership does not match. Remove the shared fallback “default” chat key by
generating or requiring a per-caller session identifier, and add TTL/eviction
cleanup so inactive sessions cannot accumulate indefinitely.

Comment thread main.py
Comment on lines 92 to +104
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
try:
logger.info(f"Received chat request: {request.prompt[:100]}...")

# Get or create chat session
chat_id = request.chat_id or str(uuid.uuid4())
if chat_id not in active_chats:
logger.info(f"Creating new chat session: {chat_id}")
model = genai.GenerativeModel(
'gemini-2.5-flash-preview-05-20',
safety_settings=get_safety_settings()
)
# Initialize chat without sending context message
active_chats[chat_id] = model.start_chat(history=[])

chat = active_chats[chat_id]

# Prepare the prompt with context if provided
full_prompt = request.prompt
if request.context:
full_prompt = f"Context: {request.context}\n\nQuestion: {ISLAMIC_CONTEXT, request.prompt}"

# Send message and get response
logger.info("Sending message to chat...")
response = chat.send_message(
full_prompt,
generation_config={
"temperature": 0.7,
"top_p": 0.8,
"top_k": 40,
"max_output_tokens": 2048,
}
)
if not GEMINI_API_KEY:
raise HTTPException(status_code=500, detail="GEMINI_API_KEY is not configured.")

chat_id = request.chat_id or "default"
if chat_id not in sessions:
model = get_model()
sessions[chat_id] = model.start_chat(history=[])

if not response.text:
logger.error("Empty response received from model")
raise HTTPException(status_code=500, detail="Empty response from AI model")

# Get chat history
history = []
for message in chat.history:
try:
if hasattr(message, 'parts') and message.parts:
content = message.parts[0].text if hasattr(message.parts[0], 'text') else str(message.parts[0])
else:
content = str(message)

history.append(Message(
role="user" if message.role == "user" else "model",
content=content
))
except Exception as e:
logger.warning(f"Error processing message in history: {str(e)}")
continue

logger.info("Chat response generated successfully")
chat_session = sessions[chat_id]
response = chat_session.send_message(request.message)
response_text = response.text

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Blocking Gemini call and unauthenticated chat_id lookup in the request path.

Two concerns here:

  1. chat_session.send_message(request.message) (line 103) is a blocking, synchronous network call inside an async def handler — it stalls the whole event loop, so a single slow Gemini response freezes every other in-flight request regardless of chat_id. Prefer await chat_session.send_message_async(...).
  2. sessions[chat_id] (lines 98-102) is accessed purely by client-supplied chat_id with no ownership check — same IDOR concern as the store declaration; any client can attach to/continue another user's session by supplying their chat_id.

There's also no timeout or exception handling around the Gemini call — an API error, rate-limit, or network hang propagates as an unhandled exception (or hangs indefinitely) rather than a controlled error response.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 92 - 104, Update the chat handler to use Gemini’s
asynchronous send method instead of blocking send_message, and wrap the call
with a timeout and exception handling that returns controlled HTTP errors.
Before accessing sessions via chat_id, enforce the same ownership/authentication
check established for session storage so clients cannot attach to another user’s
session.

Source: Path instructions

Comment thread main.py
Comment on lines 155 to +161
@app.delete("/chat/{chat_id}")
async def delete_chat(chat_id: str):
try:
if chat_id in active_chats:
del active_chats[chat_id]
logger.info(f"Deleted chat session: {chat_id}")
return {"message": "Chat session deleted successfully"}
return {"message": "Chat session not found"}
except Exception as e:
error_msg = f"❌ Error deleting chat: {str(e)}"
logger.error(error_msg)
raise HTTPException(status_code=500, detail=error_msg)

if __name__ == "__main__":
import uvicorn
logger.info("Starting server...")
uvicorn.run(app, host="0.0.0.0", port=8000)
if chat_id in sessions:
del sessions[chat_id]
return {"status": "success", "message": f"Session {chat_id} deleted."}
raise HTTPException(status_code=404, detail="Session not found.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Same IDOR concern: any client can delete any session.

DELETE /chat/{chat_id} deletes whatever session matches the path parameter with no check that the caller owns that chat_id, letting one user terminate another's conversation just by knowing/guessing the ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 155 - 161, Update delete_chat to authenticate the
caller and verify that the requested chat_id belongs to that caller before
removing it from sessions. Reject unauthorized ownership mismatches without
deleting the session, while preserving the existing success response for
authorized deletions and 404 behavior for nonexistent sessions.

Comment thread requirements.txt Outdated
google-generativeai==0.8.3
pydantic==2.5.3
stellar-sdk>=12.0.0
pytest No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- requirements.txt ---\n'
nl -ba requirements.txt | sed -n '1,80p'

printf '\n--- test/test_verifier.py ---\n'
nl -ba test/test_verifier.py | sed -n '1,200p'

printf '\n--- other pinned dependencies in requirements.txt ---\n'
grep -nE '^[A-Za-z0-9_.-]+(==|>=|<=|~=|!=)' requirements.txt || true

printf '\n--- pytest mentions in repo ---\n'
rg -n --hidden --glob '!**/.git/**' '\bpytest\b' .

Repository: Deen-Bridge/dnb-ai

Length of output: 247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- requirements.txt ---'
nl -ba requirements.txt | sed -n '1,120p'

echo
echo '--- test/test_verifier.py ---'
nl -ba test/test_verifier.py | sed -n '1,220p'

echo
echo '--- pinned dependencies in requirements.txt ---'
grep -nE '^[A-Za-z0-9_.-]+(==|>=|<=|~=|!=)' requirements.txt || true

echo
echo '--- pytest mentions in repo ---'
rg -n --hidden --glob '!**/.git/**' '\bpytest\b' .

Repository: Deen-Bridge/dnb-ai

Length of output: 221


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- requirements.txt ---'
cat -n requirements.txt | sed -n '1,120p'

echo
echo '--- test/test_verifier.py ---'
cat -n test/test_verifier.py | sed -n '1,220p'

echo
echo '--- pinned dependencies in requirements.txt ---'
grep -nE '^[A-Za-z0-9_.-]+(==|>=|<=|~=|!=)' requirements.txt || true

echo
echo '--- pytest mentions in repo ---'
rg -n --hidden --glob '!**/.git/**' '\bpytest\b' .

Repository: Deen-Bridge/dnb-ai

Length of output: 4210


Pin pytest in requirements.txt:7.
This file pins the other dependencies, so leaving the test dependency floating makes local and CI test environments drift between runs.

Proposed fix
-pytest
+pytest==<approved-version>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@requirements.txt` at line 7, Pin the pytest dependency in requirements.txt to
a specific version, matching the existing pinned-dependency format and the
project’s supported test environment.

Source: Path instructions

Comment thread test/test_verifier.py Outdated
Comment thread verifier.py
Comment on lines +88 to +100
# 2. Check quote similarity (English translation)
corpus_english = ayah_data.get("english", "") if ayah_data else ""
similarity = calculate_similarity(quote, corpus_english)

# Threshold of 0.70 accounts for variations across translation editions
if similarity >= 0.70:
return {
"source": "quran",
"surah": surah,
"ayah": ayah,
"status": VerificationStatus.VERIFIED,
"similarity": round(similarity, 2)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Verify Arabic quotes against the Arabic corpus text.

Arabic quotations are always compared with corpus_english, so even an exact normalized Arabic quote is marked mismatch. Detect Arabic input and compare normalized Arabic text against ayah_data["arabic"]; retain fuzzy English matching for translations. Add exact- and altered-Arabic regression cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@verifier.py` around lines 88 - 100, Update the quote comparison flow around
calculate_similarity to detect Arabic input and compare its normalized text
against ayah_data["arabic"], using exact normalized matching for Arabic
quotations. Preserve the existing fuzzy comparison against corpus_english for
non-Arabic translations, and add regression coverage for both exact and altered
Arabic quotes.

@zeemscript

Copy link
Copy Markdown
Contributor

@David-282 fix CI and conflict too, so I can review and merge asap

@zeemscript

Copy link
Copy Markdown
Contributor

@David-282 CI is still failing, please fix, am ready to merge asap

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.py (1)

106-113: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The top-level verification flag can falsely report trust.

  • main.py#L106-L113: do not set citations_verified=True when verification is disabled.
  • main.py#L117-L132: include UNVERIFIED results, and define handling for NOT_QUOTED, when calculating the aggregate status and strict-mode corrections.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 106 - 113, The citation verification flow must not
report trust when verification is disabled. In main.py lines 106-113, change the
off-mode ChatResponse citations_verified value to indicate unverified citations;
in main.py lines 117-132, include UNVERIFIED results in aggregate status
calculation and explicitly define how NOT_QUOTED affects the aggregate status
and strict-mode corrections.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@main.py`:
- Around line 106-113: The citation verification flow must not report trust when
verification is disabled. In main.py lines 106-113, change the off-mode
ChatResponse citations_verified value to indicate unverified citations; in
main.py lines 117-132, include UNVERIFIED results in aggregate status
calculation and explicitly define how NOT_QUOTED affects the aggregate status
and strict-mode corrections.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 934a8a5e-4342-4704-b4bb-6bcb3e3bc725

📥 Commits

Reviewing files that changed from the base of the PR and between 7cfc97c and 29116b2.

📒 Files selected for processing (3)
  • corpus.py
  • main.py
  • test/test_verifier.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • corpus.py
  • test/test_verifier.py

@David-282

Copy link
Copy Markdown
Contributor Author

Kindly review, I've fixed the cl issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] Citation verification: check quoted Quran text against a bundled corpus and flag unverifiable references

2 participants