stevafernandes commited on
Commit
9586c06
·
verified ·
1 Parent(s): 6e19de5

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +5 -2
  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 source document "
166
- "name(s) shown in the context (e.g. \"According to the NCCN patient guideline ...\").\n"
 
 
 
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 numbered, source-attributed context block."""
 
317
  parts = []
318
- for i, r in enumerate(retrieved, 1):
319
- parts.append(f"[Source {i}: {r['source']}]\n{r['text']}")
 
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)