Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- app.py +5 -2
- knowledge_base.py +37 -3
app.py
CHANGED
|
@@ -162,8 +162,11 @@ def system_instruction():
|
|
| 162 |
"B. If the RETRIEVED CONTEXT does not contain enough information to answer, say so plainly: "
|
| 163 |
"\"I couldn't find that in the reference documents provided for this tool. Please ask your care "
|
| 164 |
"team.\" Do not guess or fill gaps from general knowledge.\n"
|
| 165 |
-
"C. When you use information from the context, attribute it naturally to the
|
| 166 |
-
"
|
|
|
|
|
|
|
|
|
|
| 167 |
"D. Never invent drug names, doses, statistics, or study results that are not in the context.\n\n"
|
| 168 |
"CONVERSATION RULES:\n"
|
| 169 |
"1. When you ask a follow-up question, ask exactly ONE question per turn, 1-3 sentences, "
|
|
|
|
| 162 |
"B. If the RETRIEVED CONTEXT does not contain enough information to answer, say so plainly: "
|
| 163 |
"\"I couldn't find that in the reference documents provided for this tool. Please ask your care "
|
| 164 |
"team.\" Do not guess or fill gaps from general knowledge.\n"
|
| 165 |
+
"C. When you use information from the context, attribute it naturally to the document "
|
| 166 |
+
"TITLE(S) shown in each passage's [Document: ...] label — use that title, not a number, and "
|
| 167 |
+
"never say \"Source 1/2/3\" (e.g. \"According to the NCCN Guideline ...\" or \"The CAR T cell "
|
| 168 |
+
"Therapy — International Myeloma Foundation page notes ...\"). Use the title exactly as shown; "
|
| 169 |
+
"do not invent or embellish document names.\n"
|
| 170 |
"D. Never invent drug names, doses, statistics, or study results that are not in the context.\n\n"
|
| 171 |
"CONVERSATION RULES:\n"
|
| 172 |
"1. When you ask a follow-up question, ask exactly ONE question per turn, 1-3 sentences, "
|
knowledge_base.py
CHANGED
|
@@ -177,6 +177,38 @@ def chunk_text(text, source, target_chars=TARGET_CHARS, overlap=OVERLAP):
|
|
| 177 |
return chunks
|
| 178 |
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
# ----------------------------------------------------------------------
|
| 181 |
# Embedding (Gemini) with batching + disk cache
|
| 182 |
# ----------------------------------------------------------------------
|
|
@@ -313,8 +345,10 @@ class KnowledgeBase:
|
|
| 313 |
"score": float(sims[i])} for i in idx]
|
| 314 |
|
| 315 |
def context_block(self, retrieved):
|
| 316 |
-
"""Format retrieved chunks as a
|
|
|
|
| 317 |
parts = []
|
| 318 |
-
for
|
| 319 |
-
|
|
|
|
| 320 |
return "\n\n---\n\n".join(parts)
|
|
|
|
| 177 |
return chunks
|
| 178 |
|
| 179 |
|
| 180 |
+
# ----------------------------------------------------------------------
|
| 181 |
+
# Source title formatting (filename -> human-readable document title)
|
| 182 |
+
# ----------------------------------------------------------------------
|
| 183 |
+
def source_title(filename):
|
| 184 |
+
"""Human-readable title from a source filename.
|
| 185 |
+
|
| 186 |
+
Only removes file extensions and export-timestamp suffixes and tidies
|
| 187 |
+
separators — it never invents words, so it cannot hallucinate a title. If
|
| 188 |
+
the cleaned result is a single token with no spaces (an accession/ID code
|
| 189 |
+
such as 's41467-022-31430-0' or 'nihms385546'), it is prefixed with
|
| 190 |
+
'Document ' so the model refers to a document rather than a bare code.
|
| 191 |
+
Always returns a non-empty string.
|
| 192 |
+
"""
|
| 193 |
+
if not filename:
|
| 194 |
+
return "Reference document"
|
| 195 |
+
original = os.path.basename(str(filename))
|
| 196 |
+
name = re.sub(r"\.(pdf|png|jpe?g|txt|md)$", "", original, flags=re.IGNORECASE)
|
| 197 |
+
# strip trailing export/tracking timestamp suffix, e.g. "-07-13-2026_04_20_PM"
|
| 198 |
+
name = re.sub(r"[-_]\d{1,2}[-_]\d{1,2}[-_]\d{2,4}[_-]\d{1,2}[_-]\d{2}[_-][AP]M$", "", name)
|
| 199 |
+
# tidy separators
|
| 200 |
+
name = name.replace(" _ ", " — ") # site/section separator used by several files
|
| 201 |
+
name = name.replace("+", " ").replace("_", " ")
|
| 202 |
+
# hyphens joining two letters -> spaces (leave digit-joining hyphens in IDs alone)
|
| 203 |
+
name = re.sub(r"(?<=[A-Za-z])-(?=[A-Za-z])", " ", name)
|
| 204 |
+
name = re.sub(r"\s{2,}", " ", name).strip(" -—")
|
| 205 |
+
if not name:
|
| 206 |
+
return original
|
| 207 |
+
if " " not in name: # single token => identifier/code
|
| 208 |
+
return f"Document {name}"
|
| 209 |
+
return name
|
| 210 |
+
|
| 211 |
+
|
| 212 |
# ----------------------------------------------------------------------
|
| 213 |
# Embedding (Gemini) with batching + disk cache
|
| 214 |
# ----------------------------------------------------------------------
|
|
|
|
| 345 |
"score": float(sims[i])} for i in idx]
|
| 346 |
|
| 347 |
def context_block(self, retrieved):
|
| 348 |
+
"""Format retrieved chunks as a source-attributed context block, labeling
|
| 349 |
+
each passage with the document's readable title (not 'Source N')."""
|
| 350 |
parts = []
|
| 351 |
+
for r in retrieved:
|
| 352 |
+
title = source_title(r["source"])
|
| 353 |
+
parts.append(f"[Document: {title}]\n{r['text']}")
|
| 354 |
return "\n\n---\n\n".join(parts)
|