From 8e9f8cd9158ee193c554370d0672410b3246c050 Mon Sep 17 00:00:00 2001 From: tejask011 Date: Fri, 12 Jun 2026 11:36:55 +0530 Subject: [PATCH 1/5] feat: implement agentic DuckDuckGo web-search fallback Adds fallback logic to the RAG service when local FAISS context fails, injecting duckduckgo_search results as custom context. Updates the React UI to display web source citations with proper external links. --- .../components/ChatPanel/MessageBubble.jsx | 15 +- rag-service/main.py | 146 ++++++++++++------ rag-service/requirements.txt | 3 +- 3 files changed, 111 insertions(+), 53 deletions(-) diff --git a/frontend/src/components/ChatPanel/MessageBubble.jsx b/frontend/src/components/ChatPanel/MessageBubble.jsx index 57a0ef8..961e711 100644 --- a/frontend/src/components/ChatPanel/MessageBubble.jsx +++ b/frontend/src/components/ChatPanel/MessageBubble.jsx @@ -355,6 +355,7 @@ const MessageBubble = ({ {uniqueSources.map((source, index) => { const sourceLabel = getSourceLabel(source); const canOpenPage = hasOpenablePage(source); + const isWebSource = source.type === "web"; const truncatedLabel = sourceLabel.length > 24 ? sourceLabel.substring(0, 21) + "..." : sourceLabel; @@ -362,7 +363,13 @@ const MessageBubble = ({ return ( ); })} diff --git a/rag-service/main.py b/rag-service/main.py index ed8d416..d5a6bb1 100644 --- a/rag-service/main.py +++ b/rag-service/main.py @@ -1988,6 +1988,40 @@ def synthesize_with_ollama(prompt: str) -> Optional[str]: return None +def perform_web_search(query: str, max_results: int = 3): + try: + from duckduckgo_search import DDGS + except ImportError: + logger.error("duckduckgo_search not installed.") + return [] + + try: + from langchain_core.documents import Document as _Doc + except Exception: + from langchain.schema import Document as _Doc + + try: + with DDGS() as ddgs: + results = list(ddgs.text(query, max_results=max_results)) + + docs = [] + for idx, r in enumerate(results): + meta = { + "filename": "Web: " + r.get("title", "Search Result"), + "source": r.get("href"), + "page": 0, + "document_id": f"web-{idx}", + "type": "web", + "url": r.get("href"), + "chunk_index": idx, + } + docs.append(_Doc(page_content=r.get("body", ""), metadata=meta)) + return docs + except Exception as e: + logger.error("Web search fallback failed: %s", e) + return [] + + def build_answer_from_documents(question, documents, intent, source_id_by_key=None): if not has_grounded_keyword_overlap(question, documents) and intent != "overview": return INSUFFICIENT_CONTEXT_MESSAGE @@ -2393,6 +2427,8 @@ def citation_source_for_document(document, index): "text": text, "preview": concise_excerpt(document.page_content, 180), "chunk_index": document.metadata.get("chunk_index", index), + "type": document.metadata.get("type"), + "url": document.metadata.get("url"), } @@ -3440,33 +3476,40 @@ def ask_question(data: Question): best_score = scored_candidates[0][1] if scored_candidates else None if not passes_evidence_gate(question, docs, best_score, intent): - logger.info( - "Evidence gate refused answer session_id=%s intent=%s best_score=%s retrieved_chunks=%s", - session_id, - intent, - best_score, - len(docs), - ) - response_payload = { - "answer": INSUFFICIENT_CONTEXT_MESSAGE, - "sources": [], - "retrieval_type": "refusal", - "answer_mode": "refusal", - "mode": mode, - "cache_hit": cache_hit, - } - with sessions_lock: - session = sessions.get(session_id) - if session: - append_chat_exchange( - session, - question, - INSUFFICIENT_CONTEXT_MESSAGE, - [], - mode, - ) - _mark_session_dirty(session_id) - return response_payload + logger.info("Evidence gate refused answer session_id=%s. Attempting web search fallback...", session_id) + web_docs = perform_web_search(question) + if web_docs: + docs = web_docs + intent = "overview" # Force overview intent to synthesize across web results + logger.info("Web search fallback successful, retrieved %s documents", len(docs)) + else: + logger.info( + "Evidence gate refused answer session_id=%s intent=%s best_score=%s retrieved_chunks=%s", + session_id, + intent, + best_score, + len(docs), + ) + response_payload = { + "answer": INSUFFICIENT_CONTEXT_MESSAGE, + "sources": [], + "retrieval_type": "refusal", + "answer_mode": "refusal", + "mode": mode, + "cache_hit": cache_hit, + } + with sessions_lock: + session = sessions.get(session_id) + if session: + append_chat_exchange( + session, + question, + INSUFFICIENT_CONTEXT_MESSAGE, + [], + mode, + ) + _mark_session_dirty(session_id) + return response_payload pages = sorted(set( doc.metadata["page"] + 1 @@ -3883,29 +3926,36 @@ def _sse_done() -> str: best_score = scored_candidates[0][1] if scored_candidates else None if not passes_evidence_gate(question, docs, best_score, intent): - logger.info( - "Stream evidence gate refused session_id=%s intent=%s best_score=%s", - session_id, - intent, - best_score, - ) - with sessions_lock: - current_session = sessions.get(session_id) - if current_session: - append_chat_exchange( - current_session, - question, - INSUFFICIENT_CONTEXT_MESSAGE, - [], - mode, - ) - _mark_session_dirty(session_id) + logger.info("Stream evidence gate refused session_id=%s. Attempting web search fallback...", session_id) + web_docs = perform_web_search(question) + if web_docs: + docs = web_docs + intent = "overview" # Force overview intent to synthesize across web results + logger.info("Web search fallback successful, retrieved %s documents", len(docs)) + else: + logger.info( + "Stream evidence gate refused session_id=%s intent=%s best_score=%s", + session_id, + intent, + best_score, + ) + with sessions_lock: + current_session = sessions.get(session_id) + if current_session: + append_chat_exchange( + current_session, + question, + INSUFFICIENT_CONTEXT_MESSAGE, + [], + mode, + ) + _mark_session_dirty(session_id) - def _refuse_stream(): - yield _sse_frame(INSUFFICIENT_CONTEXT_MESSAGE) - yield _sse_done() + def _refuse_stream(): + yield _sse_frame(INSUFFICIENT_CONTEXT_MESSAGE) + yield _sse_done() - return StreamingResponse(_refuse_stream(), media_type="text/event-stream; charset=utf-8") + return StreamingResponse(_refuse_stream(), media_type="text/event-stream; charset=utf-8") context = format_context(docs) diff --git a/rag-service/requirements.txt b/rag-service/requirements.txt index c301862..19df181 100644 --- a/rag-service/requirements.txt +++ b/rag-service/requirements.txt @@ -16,4 +16,5 @@ numpy rank-bm25==0.2.2 python-multipart pypdf>=4.0.0 -flake8>=7,<8 \ No newline at end of file +flake8>=7,<8 +duckduckgo-search>=5.0.0 \ No newline at end of file From 984f1995a86af3088d153639e3a83c0fdef0c397 Mon Sep 17 00:00:00 2001 From: tejask011 Date: Fri, 12 Jun 2026 12:00:28 +0530 Subject: [PATCH 2/5] Update frontend/src/components/ChatPanel/MessageBubble.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- frontend/src/components/ChatPanel/MessageBubble.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ChatPanel/MessageBubble.jsx b/frontend/src/components/ChatPanel/MessageBubble.jsx index 961e711..0746bf2 100644 --- a/frontend/src/components/ChatPanel/MessageBubble.jsx +++ b/frontend/src/components/ChatPanel/MessageBubble.jsx @@ -365,7 +365,18 @@ const MessageBubble = ({ key={`${source.document_id || sourceLabel}-${source.page || "unknown"}-${index}`} onClick={() => { if (isWebSource && source.url) { - window.open(source.url, '_blank'); + try { + const url = new URL(source.url); + if (!["http:", "https:"].includes(url.protocol)) return; + const opened = window.open( + url.toString(), + "_blank", + "noopener,noreferrer", + ); + if (opened) opened.opener = null; + } catch { + return; + } } else if (canOpenPage) { onOpenSource?.(source); } From be85d267982b795e7df1a010845799ffe7d6a206 Mon Sep 17 00:00:00 2001 From: tejask011 Date: Sat, 13 Jun 2026 14:19:47 +0530 Subject: [PATCH 3/5] fix: resolve CodeQL clear-text logging of sensitive information alerts --- frontend/package-lock.json | 33 +-------------------------------- rag-service/main.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 38 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cd3619b..3b3bb4c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -80,7 +80,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -752,7 +751,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1636,7 +1634,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", @@ -2479,7 +2476,6 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2523,7 +2519,6 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -3132,7 +3127,6 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.11.tgz", "integrity": "sha512-yq8bPc3LxOwKRWpcjRgDkYFmpM6aKlARfESTmOQcvLYFeJwtHte2tw6hJDrb8sk8wcvpDprHEHVaoUU0MslIkw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.6", "@mui/core-downloads-tracker": "^7.3.11", @@ -3703,7 +3697,6 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -4235,7 +4228,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4647,7 +4639,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4783,7 +4774,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -4837,7 +4827,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5207,7 +5196,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5306,7 +5294,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6269,7 +6256,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -7419,8 +7405,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -8254,7 +8239,6 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -11227,7 +11211,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -12113,7 +12096,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -14165,7 +14147,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -15300,7 +15281,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15701,7 +15681,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -15888,7 +15867,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -15993,7 +15971,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16575,7 +16552,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -16821,7 +16797,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18273,7 +18248,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18462,7 +18436,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -19044,7 +19017,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -19115,7 +19087,6 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "license": "MIT", - "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -19537,7 +19508,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -19876,7 +19846,6 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/rag-service/main.py b/rag-service/main.py index d5a6bb1..9a9ffd3 100644 --- a/rag-service/main.py +++ b/rag-service/main.py @@ -3476,7 +3476,8 @@ def ask_question(data: Question): best_score = scored_candidates[0][1] if scored_candidates else None if not passes_evidence_gate(question, docs, best_score, intent): - logger.info("Evidence gate refused answer session_id=%s. Attempting web search fallback...", session_id) + sid_hash = hashlib.sha256(str(session_id).encode("utf-8")).hexdigest()[:8] + logger.info("Evidence gate refused answer sid=%s. Attempting web search fallback...", sid_hash) web_docs = perform_web_search(question) if web_docs: docs = web_docs @@ -3484,8 +3485,8 @@ def ask_question(data: Question): logger.info("Web search fallback successful, retrieved %s documents", len(docs)) else: logger.info( - "Evidence gate refused answer session_id=%s intent=%s best_score=%s retrieved_chunks=%s", - session_id, + "Evidence gate refused answer sid=%s intent=%s best_score=%s retrieved_chunks=%s", + sid_hash, intent, best_score, len(docs), @@ -3926,7 +3927,8 @@ def _sse_done() -> str: best_score = scored_candidates[0][1] if scored_candidates else None if not passes_evidence_gate(question, docs, best_score, intent): - logger.info("Stream evidence gate refused session_id=%s. Attempting web search fallback...", session_id) + sid_hash = hashlib.sha256(str(session_id).encode("utf-8")).hexdigest()[:8] + logger.info("Stream evidence gate refused sid=%s. Attempting web search fallback...", sid_hash) web_docs = perform_web_search(question) if web_docs: docs = web_docs @@ -3934,8 +3936,8 @@ def _sse_done() -> str: logger.info("Web search fallback successful, retrieved %s documents", len(docs)) else: logger.info( - "Stream evidence gate refused session_id=%s intent=%s best_score=%s", - session_id, + "Stream evidence gate refused sid=%s intent=%s best_score=%s", + sid_hash, intent, best_score, ) From 303a221ce99a38e672aad2f159232de0f17ba0ad Mon Sep 17 00:00:00 2001 From: tejask011 Date: Sat, 13 Jun 2026 22:39:56 +0530 Subject: [PATCH 4/5] fix: resolve route shadowing of /ask and /summarize endpoints --- rag-service/main.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rag-service/main.py b/rag-service/main.py index cd6da75..61de338 100644 --- a/rag-service/main.py +++ b/rag-service/main.py @@ -3410,9 +3410,6 @@ def processing_status( @app.post("/ask") def ask_question(data: Question, _ready: None = Depends(require_models_ready)): cleanup_expired_sessions() - - -def ask_question(data: Question): question = (data.question or "").strip() if not question: @@ -4211,7 +4208,6 @@ def _run_generation_locked(model, generate_kwargs): @app.post("/summarize") def summarize_pdf(data: SummarizeRequest, _ready: None = Depends(require_models_ready)): cleanup_expired_sessions() -def summarize_pdf(data: SummarizeRequest): session_id = str(data.session_id) with sessions_lock: session = _touch_session_unlocked(session_id) From 547b8c37e831680d2d4389854c18b52659a1f7de Mon Sep 17 00:00:00 2001 From: tejask011 Date: Sat, 13 Jun 2026 22:42:33 +0530 Subject: [PATCH 5/5] fix: remove inline cleanup_expired_sessions() from ask and summarize handlers --- rag-service/main.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rag-service/main.py b/rag-service/main.py index 61de338..1b06f2d 100644 --- a/rag-service/main.py +++ b/rag-service/main.py @@ -3409,7 +3409,6 @@ def processing_status( @app.post("/ask") def ask_question(data: Question, _ready: None = Depends(require_models_ready)): - cleanup_expired_sessions() question = (data.question or "").strip() if not question: @@ -4207,7 +4206,6 @@ def _run_generation_locked(model, generate_kwargs): @app.post("/summarize") def summarize_pdf(data: SummarizeRequest, _ready: None = Depends(require_models_ready)): - cleanup_expired_sessions() session_id = str(data.session_id) with sessions_lock: session = _touch_session_unlocked(session_id)