[{"data":1,"prerenderedAt":1823},["ShallowReactive",2],{"article-agent":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"tags":11,"body":14,"_type":1817,"_id":1818,"_source":1819,"_file":1820,"_stem":1821,"_extension":1822},"\u002Farticles\u002Fagent","articles",false,"","PharmaLitQA：一个生物医药文献 RAG 智能问答系统的设计与实现","从 0 到 1 搭一个垂直领域的检索增强问答系统：多路召回 + 重排序 + 大模型生成，配合 PubMed 文献自动同步。本文按「技术栈 → 它是什么 → 有哪些功能 → 业务流程 → 每个功能怎么实现」的顺序展开。","2026-06-04",[12,13],"Agent开发","人工智能",{"type":15,"children":16,"toc":1789},"root",[17,26,32,39,196,202,286,291,301,305,311,319,324,357,367,370,376,381,389,430,438,525,533,560,563,569,575,580,588,607,613,621,627,635,638,644,649,655,676,813,832,838,856,901,915,927,933,960,999,1011,1029,1035,1054,1109,1129,1135,1153,1194,1233,1252,1258,1277,1348,1377,1383,1409,1456,1461,1467,1566,1572,1619,1625,1644,1684,1687,1693,1705,1748,1753,1758,1783],{"type":18,"tag":19,"props":20,"children":22},"element","h2",{"id":21},"一技术栈",[23],{"type":24,"value":25},"text","一、技术栈",{"type":18,"tag":27,"props":28,"children":29},"p",{},[30],{"type":24,"value":31},"整个项目是典型的「前后端分离 + AI 中台」结构。",{"type":18,"tag":33,"props":34,"children":36},"h3",{"id":35},"后端python-310",[37],{"type":24,"value":38},"后端（Python 3.10+）",{"type":18,"tag":40,"props":41,"children":42},"table",{},[43,62],{"type":18,"tag":44,"props":45,"children":46},"thead",{},[47],{"type":18,"tag":48,"props":49,"children":50},"tr",{},[51,57],{"type":18,"tag":52,"props":53,"children":54},"th",{},[55],{"type":24,"value":56},"技术",{"type":18,"tag":52,"props":58,"children":59},{},[60],{"type":24,"value":61},"作用",{"type":18,"tag":63,"props":64,"children":65},"tbody",{},[66,84,100,116,132,148,164,180],{"type":18,"tag":48,"props":67,"children":68},{},[69,79],{"type":18,"tag":70,"props":71,"children":72},"td",{},[73],{"type":18,"tag":74,"props":75,"children":76},"strong",{},[77],{"type":24,"value":78},"FastAPI",{"type":18,"tag":70,"props":80,"children":81},{},[82],{"type":24,"value":83},"异步 Web 框架，对外提供 REST API",{"type":18,"tag":48,"props":85,"children":86},{},[87,95],{"type":18,"tag":70,"props":88,"children":89},{},[90],{"type":18,"tag":74,"props":91,"children":92},{},[93],{"type":24,"value":94},"ChromaDB",{"type":18,"tag":70,"props":96,"children":97},{},[98],{"type":24,"value":99},"轻量级向量数据库，做语义检索",{"type":18,"tag":48,"props":101,"children":102},{},[103,111],{"type":18,"tag":70,"props":104,"children":105},{},[106],{"type":18,"tag":74,"props":107,"children":108},{},[109],{"type":24,"value":110},"Sentence-Transformers",{"type":18,"tag":70,"props":112,"children":113},{},[114],{"type":24,"value":115},"本地文本嵌入（BGE 中文模型）+ Cross-Encoder 重排序",{"type":18,"tag":48,"props":117,"children":118},{},[119,127],{"type":18,"tag":70,"props":120,"children":121},{},[122],{"type":18,"tag":74,"props":123,"children":124},{},[125],{"type":24,"value":126},"rank-bm25 \u002F 自研 BM25",{"type":18,"tag":70,"props":128,"children":129},{},[130],{"type":24,"value":131},"关键词检索，与向量检索互补",{"type":18,"tag":48,"props":133,"children":134},{},[135,143],{"type":18,"tag":70,"props":136,"children":137},{},[138],{"type":18,"tag":74,"props":139,"children":140},{},[141],{"type":24,"value":142},"APScheduler",{"type":18,"tag":70,"props":144,"children":145},{},[146],{"type":24,"value":147},"定时任务，自动同步 PubMed 文献",{"type":18,"tag":48,"props":149,"children":150},{},[151,159],{"type":18,"tag":70,"props":152,"children":153},{},[154],{"type":18,"tag":74,"props":155,"children":156},{},[157],{"type":24,"value":158},"Qwen（阿里云 DashScope）",{"type":18,"tag":70,"props":160,"children":161},{},[162],{"type":24,"value":163},"主对话大模型，生成最终答案",{"type":18,"tag":48,"props":165,"children":166},{},[167,175],{"type":18,"tag":70,"props":168,"children":169},{},[170],{"type":18,"tag":74,"props":171,"children":172},{},[173],{"type":24,"value":174},"DeepSeek",{"type":18,"tag":70,"props":176,"children":177},{},[178],{"type":24,"value":179},"备选大模型，主模型失败\u002F限流时自动接管",{"type":18,"tag":48,"props":181,"children":182},{},[183,191],{"type":18,"tag":70,"props":184,"children":185},{},[186],{"type":18,"tag":74,"props":187,"children":188},{},[189],{"type":24,"value":190},"Pydantic \u002F pydantic-settings",{"type":18,"tag":70,"props":192,"children":193},{},[194],{"type":24,"value":195},"配置管理与请求\u002F响应数据校验",{"type":18,"tag":33,"props":197,"children":199},{"id":198},"前端nodejs-18",[200],{"type":24,"value":201},"前端（Node.js 18+）",{"type":18,"tag":40,"props":203,"children":204},{},[205,219],{"type":18,"tag":44,"props":206,"children":207},{},[208],{"type":18,"tag":48,"props":209,"children":210},{},[211,215],{"type":18,"tag":52,"props":212,"children":213},{},[214],{"type":24,"value":56},{"type":18,"tag":52,"props":216,"children":217},{},[218],{"type":24,"value":61},{"type":18,"tag":63,"props":220,"children":221},{},[222,238,254,270],{"type":18,"tag":48,"props":223,"children":224},{},[225,233],{"type":18,"tag":70,"props":226,"children":227},{},[228],{"type":18,"tag":74,"props":229,"children":230},{},[231],{"type":24,"value":232},"Nuxt.js 3 + Vue 3 + TypeScript",{"type":18,"tag":70,"props":234,"children":235},{},[236],{"type":24,"value":237},"SSR 框架与类型安全",{"type":18,"tag":48,"props":239,"children":240},{},[241,249],{"type":18,"tag":70,"props":242,"children":243},{},[244],{"type":18,"tag":74,"props":245,"children":246},{},[247],{"type":24,"value":248},"Element Plus",{"type":18,"tag":70,"props":250,"children":251},{},[252],{"type":24,"value":253},"企业级 UI 组件库",{"type":18,"tag":48,"props":255,"children":256},{},[257,265],{"type":18,"tag":70,"props":258,"children":259},{},[260],{"type":18,"tag":74,"props":261,"children":262},{},[263],{"type":24,"value":264},"Tailwind CSS",{"type":18,"tag":70,"props":266,"children":267},{},[268],{"type":24,"value":269},"原子化样式",{"type":18,"tag":48,"props":271,"children":272},{},[273,281],{"type":18,"tag":70,"props":274,"children":275},{},[276],{"type":18,"tag":74,"props":277,"children":278},{},[279],{"type":24,"value":280},"markdown-it",{"type":18,"tag":70,"props":282,"children":283},{},[284],{"type":24,"value":285},"把大模型返回的 Markdown 渲染成 HTML",{"type":18,"tag":33,"props":287,"children":289},{"id":288},"一张图看懂架构",[290],{"type":24,"value":288},{"type":18,"tag":292,"props":293,"children":295},"pre",{"code":294},"浏览器(3000) → Nuxt 前端 → FastAPI 后端(8000)\n                                 │\n            ┌────────────┬───────┴────────┬──────────────┐\n            ▼            ▼                ▼              ▼\n        ChromaDB     本地 BGE          Qwen \u002F DeepSeek   PubMed API\n       (向量库)      (嵌入\u002F重排)        (答案生成)        (文献来源)\n",[296],{"type":18,"tag":297,"props":298,"children":299},"code",{"__ignoreMap":7},[300],{"type":24,"value":294},{"type":18,"tag":302,"props":303,"children":304},"hr",{},[],{"type":18,"tag":19,"props":306,"children":308},{"id":307},"二它是做什么的",[309],{"type":24,"value":310},"二、它是做什么的",{"type":18,"tag":27,"props":312,"children":313},{},[314],{"type":18,"tag":74,"props":315,"children":316},{},[317],{"type":24,"value":318},"PharmaLitQA 是一个面向生物医药 \u002F 生信领域的文献智能问答系统。",{"type":18,"tag":27,"props":320,"children":321},{},[322],{"type":24,"value":323},"研究人员面对的痛点是：PubMed 上每天新增海量文献，靠关键词检索既费时又容易漏。直接问通用大模型，又会遇到\"幻觉\"——它可能编造一个看起来很专业、实则不存在的结论。",{"type":18,"tag":27,"props":325,"children":326},{},[327,329,334,336,341,343,348,350,355],{"type":24,"value":328},"这个系统的解法是 ",{"type":18,"tag":74,"props":330,"children":331},{},[332],{"type":24,"value":333},"RAG（检索增强生成）",{"type":24,"value":335},"：先从一个",{"type":18,"tag":74,"props":337,"children":338},{},[339],{"type":24,"value":340},"可信的文献知识库",{"type":24,"value":342},"里检索出真正相关的片段，再让大模型",{"type":18,"tag":74,"props":344,"children":345},{},[346],{"type":24,"value":347},"基于这些片段",{"type":24,"value":349},"作答，并附上文献来源（PMID + PubMed 链接）。这样答案既专业，又",{"type":18,"tag":74,"props":351,"children":352},{},[353],{"type":24,"value":354},"可溯源、可核查",{"type":24,"value":356},"，大幅降低幻觉。",{"type":18,"tag":27,"props":358,"children":359},{},[360,362],{"type":24,"value":361},"一句话：",{"type":18,"tag":74,"props":363,"children":364},{},[365],{"type":24,"value":366},"\"带着文献依据回答生物医药问题的 AI 助手\"。",{"type":18,"tag":302,"props":368,"children":369},{},[],{"type":18,"tag":19,"props":371,"children":373},{"id":372},"三有哪些功能",[374],{"type":24,"value":375},"三、有哪些功能",{"type":18,"tag":27,"props":377,"children":378},{},[379],{"type":24,"value":380},"围绕\"问答\"和\"知识库管理\"两条主线：",{"type":18,"tag":27,"props":382,"children":383},{},[384],{"type":18,"tag":74,"props":385,"children":386},{},[387],{"type":24,"value":388},"问答侧",{"type":18,"tag":390,"props":391,"children":392},"ul",{},[393,406,418],{"type":18,"tag":394,"props":395,"children":396},"li",{},[397,399,404],{"type":24,"value":398},"🔍 ",{"type":18,"tag":74,"props":400,"children":401},{},[402],{"type":24,"value":403},"智能问答",{"type":24,"value":405},"：输入中文\u002F英文问题，返回带文献引用的专业答案",{"type":18,"tag":394,"props":407,"children":408},{},[409,411,416],{"type":24,"value":410},"🌐 ",{"type":18,"tag":74,"props":412,"children":413},{},[414],{"type":24,"value":415},"跨语言检索",{"type":24,"value":417},"：英文问题自动翻译为中文领域术语再检索",{"type":18,"tag":394,"props":419,"children":420},{},[421,423,428],{"type":24,"value":422},"📎 ",{"type":18,"tag":74,"props":424,"children":425},{},[426],{"type":24,"value":427},"答案溯源",{"type":24,"value":429},"：每条答案附带来源文献片段、PMID 与 PubMed 原文链接",{"type":18,"tag":27,"props":431,"children":432},{},[433],{"type":18,"tag":74,"props":434,"children":435},{},[436],{"type":24,"value":437},"知识库侧",{"type":18,"tag":390,"props":439,"children":440},{},[441,453,465,477,489,501,513],{"type":18,"tag":394,"props":442,"children":443},{},[444,446,451],{"type":24,"value":445},"📚 ",{"type":18,"tag":74,"props":447,"children":448},{},[449],{"type":24,"value":450},"知识库浏览",{"type":24,"value":452},"：查看库中全部文献",{"type":18,"tag":394,"props":454,"children":455},{},[456,458,463],{"type":24,"value":457},"➕ ",{"type":18,"tag":74,"props":459,"children":460},{},[461],{"type":24,"value":462},"手动录入文献",{"type":24,"value":464},"：按 PMID + 标题 + 摘要添加",{"type":18,"tag":394,"props":466,"children":467},{},[468,470,475],{"type":24,"value":469},"🗑️ ",{"type":18,"tag":74,"props":471,"children":472},{},[473],{"type":24,"value":474},"删除\u002F清空",{"type":24,"value":476},"：按 PMID 删除单篇或一键清空",{"type":18,"tag":394,"props":478,"children":479},{},[480,482,487],{"type":24,"value":481},"🔄 ",{"type":18,"tag":74,"props":483,"children":484},{},[485],{"type":24,"value":486},"PubMed 自动同步",{"type":24,"value":488},"：定时（默认每天凌晨 2 点）拉取最新文献入库",{"type":18,"tag":394,"props":490,"children":491},{},[492,494,499],{"type":24,"value":493},"⏰ ",{"type":18,"tag":74,"props":495,"children":496},{},[497],{"type":24,"value":498},"同步调度管理",{"type":24,"value":500},"：查看同步状态、手动触发、修改定时规则",{"type":18,"tag":394,"props":502,"children":503},{},[504,506,511],{"type":24,"value":505},"🔎 ",{"type":18,"tag":74,"props":507,"children":508},{},[509],{"type":24,"value":510},"PubMed 条件检索",{"type":24,"value":512},"：按关键词 + 日期范围在线检索 PubMed",{"type":18,"tag":394,"props":514,"children":515},{},[516,518,523],{"type":24,"value":517},"💡 ",{"type":18,"tag":74,"props":519,"children":520},{},[521],{"type":24,"value":522},"MeSH 术语联想",{"type":24,"value":524},"：输入时联想标准医学主题词，提升检索准确度",{"type":18,"tag":27,"props":526,"children":527},{},[528],{"type":18,"tag":74,"props":529,"children":530},{},[531],{"type":24,"value":532},"模型可用性",{"type":18,"tag":390,"props":534,"children":535},{},[536,548],{"type":18,"tag":394,"props":537,"children":538},{},[539,541,546],{"type":24,"value":540},"⚡ ",{"type":18,"tag":74,"props":542,"children":543},{},[544],{"type":24,"value":545},"双模型容灾",{"type":24,"value":547},"：Qwen 主、DeepSeek 备，自动故障切换",{"type":18,"tag":394,"props":549,"children":550},{},[551,553,558],{"type":24,"value":552},"💰 ",{"type":18,"tag":74,"props":554,"children":555},{},[556],{"type":24,"value":557},"本地嵌入",{"type":24,"value":559},"：嵌入模型跑在本机，永久免费、离线可用、不耗 API 额度",{"type":18,"tag":302,"props":561,"children":562},{},[],{"type":18,"tag":19,"props":564,"children":566},{"id":565},"四业务流程",[567],{"type":24,"value":568},"四、业务流程",{"type":18,"tag":33,"props":570,"children":572},{"id":571},"_41-问答主流程核心",[573],{"type":24,"value":574},"4.1 问答主流程（核心）",{"type":18,"tag":27,"props":576,"children":577},{},[578],{"type":24,"value":579},"用户从提问到拿到答案，后端经历 6 步：",{"type":18,"tag":292,"props":581,"children":583},{"code":582},"用户问题\n   │\n   ├─① 语言判断与翻译  ──→ 非中文则译为中文领域术语\n   │\n   ├─② 多路召回         ──→ 向量检索(语义) + BM25(关键词)，合并去重，召回 ~20 篇\n   │\n   ├─③ 重排序           ──→ Cross-Encoder 对候选深度打分，精排出 Top-5\n   │\n   ├─④ 构造上下文       ──→ 把 Top-5 片段拼成带 PMID 的提示词\n   │\n   ├─⑤ 大模型生成       ──→ Qwen 作答（失败自动切 DeepSeek）\n   │\n   └─⑥ 组装结果         ──→ 答案 + 来源列表(PMID\u002F片段\u002F相关分) 返回前端\n",[584],{"type":18,"tag":297,"props":585,"children":586},{"__ignoreMap":7},[587],{"type":24,"value":582},{"type":18,"tag":27,"props":589,"children":590},{},[591,593,598,600,605],{"type":24,"value":592},"为什么要\"召回 20 → 精排 5\"两段式？因为",{"type":18,"tag":74,"props":594,"children":595},{},[596],{"type":24,"value":597},"向量检索快但粗",{"type":24,"value":599},"（适合从全库快速捞候选），",{"type":18,"tag":74,"props":601,"children":602},{},[603],{"type":24,"value":604},"Cross-Encoder 准但慢",{"type":24,"value":606},"（适合在小候选集上精挑）。两者结合兼顾速度与精度。",{"type":18,"tag":33,"props":608,"children":610},{"id":609},"_42-文献入库流程",[611],{"type":24,"value":612},"4.2 文献入库流程",{"type":18,"tag":292,"props":614,"children":616},{"code":615},"文献(标题+摘要)\n   │\n   ├─ 分块：短摘要整段成块；长摘要按句子边界滑窗切分(带重叠)\n   ├─ 嵌入：本地 BGE 把每块转成 1024 维向量\n   └─ 入库：向量 + 文本 + PMID 元数据 写入 ChromaDB；重置 BM25 索引\n",[617],{"type":18,"tag":297,"props":618,"children":619},{"__ignoreMap":7},[620],{"type":24,"value":615},{"type":18,"tag":33,"props":622,"children":624},{"id":623},"_43-pubmed-自动同步流程",[625],{"type":24,"value":626},"4.3 PubMed 自动同步流程",{"type":18,"tag":292,"props":628,"children":630},{"code":629},"APScheduler 定时触发(每天 2:00)\n   │\n   ├─ ESearch：按生物医药关键词 + 时间窗检索，拿到 PMID 列表\n   ├─ EFetch ：批量拉取文献标题、摘要、作者、期刊等详情\n   ├─ 去重   ：跳过库中已存在的 PMID\n   └─ 入库   ：走 4.2 的嵌入入库流程\n",[631],{"type":18,"tag":297,"props":632,"children":633},{"__ignoreMap":7},[634],{"type":24,"value":629},{"type":18,"tag":302,"props":636,"children":637},{},[],{"type":18,"tag":19,"props":639,"children":641},{"id":640},"五每个功能的技术实现",[642],{"type":24,"value":643},"五、每个功能的技术实现",{"type":18,"tag":27,"props":645,"children":646},{},[647],{"type":24,"value":648},"下面逐个拆解关键功能背后的代码实现。",{"type":18,"tag":33,"props":650,"children":652},{"id":651},"_51-智能问答rag-编排",[653],{"type":24,"value":654},"5.1 智能问答（RAG 编排）",{"type":18,"tag":27,"props":656,"children":657},{},[658,660,666,668,674],{"type":24,"value":659},"核心在 ",{"type":18,"tag":297,"props":661,"children":663},{"className":662},[],[664],{"type":24,"value":665},"rag\u002Frag_service.py",{"type":24,"value":667}," 的 ",{"type":18,"tag":297,"props":669,"children":671},{"className":670},[],[672],{"type":24,"value":673},"query()",{"type":24,"value":675},"，它是上面 4.1 流程的代码化身：",{"type":18,"tag":292,"props":677,"children":681},{"code":678,"language":679,"meta":7,"className":680,"style":7},"def query(self, question: str) -> Dict[str, Any]:\n    # ① 翻译（仅非中文）\n    translated = get_translator().translate_if_needed(question)\n    # ② 多路召回 ~20 篇\n    candidates = self._multi_retrieve(translated, top_k=20)\n    # ③ Cross-Encoder 精排 Top-5\n    reranked = self._rerank(translated, candidates, top_k=5)\n    # ④ 拼上下文（带 PMID）\n    context = \"\\n\\n\".join(f\"片段{i}（PMID: {r['pmid']}）：{r['text']}\"\n                          for i, r in enumerate(reranked, 1))\n    # ⑤ 大模型生成\n    answer = self.qwen_client.ask_with_context(question, context, pmid=...)\n    # ⑥ 组装来源\n    return {\"answer\": answer, \"source\": [...]}\n","python","language-python shiki shiki-themes github-dark",[682],{"type":18,"tag":297,"props":683,"children":684},{"__ignoreMap":7},[685,696,705,714,723,732,741,750,759,768,777,786,795,804],{"type":18,"tag":686,"props":687,"children":690},"span",{"class":688,"line":689},"line",1,[691],{"type":18,"tag":686,"props":692,"children":693},{},[694],{"type":24,"value":695},"def query(self, question: str) -> Dict[str, Any]:\n",{"type":18,"tag":686,"props":697,"children":699},{"class":688,"line":698},2,[700],{"type":18,"tag":686,"props":701,"children":702},{},[703],{"type":24,"value":704},"    # ① 翻译（仅非中文）\n",{"type":18,"tag":686,"props":706,"children":708},{"class":688,"line":707},3,[709],{"type":18,"tag":686,"props":710,"children":711},{},[712],{"type":24,"value":713},"    translated = get_translator().translate_if_needed(question)\n",{"type":18,"tag":686,"props":715,"children":717},{"class":688,"line":716},4,[718],{"type":18,"tag":686,"props":719,"children":720},{},[721],{"type":24,"value":722},"    # ② 多路召回 ~20 篇\n",{"type":18,"tag":686,"props":724,"children":726},{"class":688,"line":725},5,[727],{"type":18,"tag":686,"props":728,"children":729},{},[730],{"type":24,"value":731},"    candidates = self._multi_retrieve(translated, top_k=20)\n",{"type":18,"tag":686,"props":733,"children":735},{"class":688,"line":734},6,[736],{"type":18,"tag":686,"props":737,"children":738},{},[739],{"type":24,"value":740},"    # ③ Cross-Encoder 精排 Top-5\n",{"type":18,"tag":686,"props":742,"children":744},{"class":688,"line":743},7,[745],{"type":18,"tag":686,"props":746,"children":747},{},[748],{"type":24,"value":749},"    reranked = self._rerank(translated, candidates, top_k=5)\n",{"type":18,"tag":686,"props":751,"children":753},{"class":688,"line":752},8,[754],{"type":18,"tag":686,"props":755,"children":756},{},[757],{"type":24,"value":758},"    # ④ 拼上下文（带 PMID）\n",{"type":18,"tag":686,"props":760,"children":762},{"class":688,"line":761},9,[763],{"type":18,"tag":686,"props":764,"children":765},{},[766],{"type":24,"value":767},"    context = \"\\n\\n\".join(f\"片段{i}（PMID: {r['pmid']}）：{r['text']}\"\n",{"type":18,"tag":686,"props":769,"children":771},{"class":688,"line":770},10,[772],{"type":18,"tag":686,"props":773,"children":774},{},[775],{"type":24,"value":776},"                          for i, r in enumerate(reranked, 1))\n",{"type":18,"tag":686,"props":778,"children":780},{"class":688,"line":779},11,[781],{"type":18,"tag":686,"props":782,"children":783},{},[784],{"type":24,"value":785},"    # ⑤ 大模型生成\n",{"type":18,"tag":686,"props":787,"children":789},{"class":688,"line":788},12,[790],{"type":18,"tag":686,"props":791,"children":792},{},[793],{"type":24,"value":794},"    answer = self.qwen_client.ask_with_context(question, context, pmid=...)\n",{"type":18,"tag":686,"props":796,"children":798},{"class":688,"line":797},13,[799],{"type":18,"tag":686,"props":800,"children":801},{},[802],{"type":24,"value":803},"    # ⑥ 组装来源\n",{"type":18,"tag":686,"props":805,"children":807},{"class":688,"line":806},14,[808],{"type":18,"tag":686,"props":809,"children":810},{},[811],{"type":24,"value":812},"    return {\"answer\": answer, \"source\": [...]}\n",{"type":18,"tag":27,"props":814,"children":815},{},[816,818,823,825,830],{"type":24,"value":817},"设计要点：检索用的是",{"type":18,"tag":74,"props":819,"children":820},{},[821],{"type":24,"value":822},"翻译后",{"type":24,"value":824},"的文本（提升召回），但喂给大模型作答时用",{"type":18,"tag":74,"props":826,"children":827},{},[828],{"type":24,"value":829},"用户原始问题",{"type":24,"value":831},"（保证回答语言\u002F语气贴合用户）。",{"type":18,"tag":33,"props":833,"children":835},{"id":834},"_52-多路召回vector-bm25",[836],{"type":24,"value":837},"5.2 多路召回（Vector + BM25）",{"type":18,"tag":27,"props":839,"children":840},{},[841,847,849,854],{"type":18,"tag":297,"props":842,"children":844},{"className":843},[],[845],{"type":24,"value":846},"_multi_retrieve()",{"type":24,"value":848}," 同时跑两路检索再用 ",{"type":18,"tag":74,"props":850,"children":851},{},[852],{"type":24,"value":853},"PMID 做并集合并",{"type":24,"value":855},"：",{"type":18,"tag":390,"props":857,"children":858},{},[859,876],{"type":18,"tag":394,"props":860,"children":861},{},[862,867,869,874],{"type":18,"tag":74,"props":863,"children":864},{},[865],{"type":24,"value":866},"向量检索",{"type":24,"value":868},"：本地 BGE 把 query 编码成向量，在 ChromaDB 里按 L2 距离找最近邻。擅长",{"type":18,"tag":74,"props":870,"children":871},{},[872],{"type":24,"value":873},"语义相似",{"type":24,"value":875},"（\"OS\"≈\"总生存期\"）。",{"type":18,"tag":394,"props":877,"children":878},{},[879,884,886,891,893,899],{"type":18,"tag":74,"props":880,"children":881},{},[882],{"type":24,"value":883},"BM25 检索",{"type":24,"value":885},"：经典关键词算法，擅长",{"type":18,"tag":74,"props":887,"children":888},{},[889],{"type":24,"value":890},"精确匹配",{"type":24,"value":892},"（基因名、缩写如 \"KRAS G12C\"）。实现见 ",{"type":18,"tag":297,"props":894,"children":896},{"className":895},[],[897],{"type":24,"value":898},"bm25_retriever.py",{"type":24,"value":900},"，核心打分公式：",{"type":18,"tag":292,"props":902,"children":904},{"code":903,"language":679,"meta":7,"className":680,"style":7},"score += idf * (tf * (k1 + 1)) \u002F (tf + k1 * (1 - b + b * doc_len \u002F avgdl))\n",[905],{"type":18,"tag":297,"props":906,"children":907},{"__ignoreMap":7},[908],{"type":18,"tag":686,"props":909,"children":910},{"class":688,"line":689},[911],{"type":18,"tag":686,"props":912,"children":913},{},[914],{"type":24,"value":903},{"type":18,"tag":27,"props":916,"children":917},{},[918,920,925],{"type":24,"value":919},"两路互补：向量召回怕生僻专有名词，BM25 召回怕同义改写，合在一起召回率更高。BM25 索引在每次查询时按知识库最新状态",{"type":18,"tag":74,"props":921,"children":922},{},[923],{"type":24,"value":924},"惰性重建",{"type":24,"value":926},"，保证增删文献后立即生效。",{"type":18,"tag":33,"props":928,"children":930},{"id":929},"_53-重排序cross-encoder-精排",[931],{"type":24,"value":932},"5.3 重排序（Cross-Encoder 精排）",{"type":18,"tag":27,"props":934,"children":935},{},[936,938,944,945,950,952,958],{"type":24,"value":937},"多路召回的 20 篇质量参差，用 ",{"type":18,"tag":297,"props":939,"children":941},{"className":940},[],[942],{"type":24,"value":943},"cross_encoder_reranker.py",{"type":24,"value":667},{"type":18,"tag":74,"props":946,"children":947},{},[948],{"type":24,"value":949},"Cross-Encoder",{"type":24,"value":951},"（",{"type":18,"tag":297,"props":953,"children":955},{"className":954},[],[956],{"type":24,"value":957},"ms-marco-MiniLM-L-6-v2",{"type":24,"value":959},"）做精排：",{"type":18,"tag":292,"props":961,"children":963},{"code":962,"language":679,"meta":7,"className":680,"style":7},"pairs = [(query, doc[\"text\"]) for doc in documents]\nscores = self.model.predict(pairs)          # query-doc 联合编码打分\ndocs.sort(key=lambda d: d[\"rerank_score\"], reverse=True)\nreturn docs[:top_k]\n",[964],{"type":18,"tag":297,"props":965,"children":966},{"__ignoreMap":7},[967,975,983,991],{"type":18,"tag":686,"props":968,"children":969},{"class":688,"line":689},[970],{"type":18,"tag":686,"props":971,"children":972},{},[973],{"type":24,"value":974},"pairs = [(query, doc[\"text\"]) for doc in documents]\n",{"type":18,"tag":686,"props":976,"children":977},{"class":688,"line":698},[978],{"type":18,"tag":686,"props":979,"children":980},{},[981],{"type":24,"value":982},"scores = self.model.predict(pairs)          # query-doc 联合编码打分\n",{"type":18,"tag":686,"props":984,"children":985},{"class":688,"line":707},[986],{"type":18,"tag":686,"props":987,"children":988},{},[989],{"type":24,"value":990},"docs.sort(key=lambda d: d[\"rerank_score\"], reverse=True)\n",{"type":18,"tag":686,"props":992,"children":993},{"class":688,"line":716},[994],{"type":18,"tag":686,"props":995,"children":996},{},[997],{"type":24,"value":998},"return docs[:top_k]\n",{"type":18,"tag":27,"props":1000,"children":1001},{},[1002,1004,1009],{"type":24,"value":1003},"与\"双塔\"向量检索不同，Cross-Encoder 把 query 和 doc ",{"type":18,"tag":74,"props":1005,"children":1006},{},[1007],{"type":24,"value":1008},"拼在一起送进 Transformer",{"type":24,"value":1010},"，做 token 级交互注意力，相关性判断更准。代价是慢，所以只在 20 篇小候选集上用。",{"type":18,"tag":27,"props":1012,"children":1013},{},[1014,1019,1021,1027],{"type":18,"tag":74,"props":1015,"children":1016},{},[1017],{"type":24,"value":1018},"优雅降级",{"type":24,"value":1020},"：模型加载失败时自动回退到 ",{"type":18,"tag":297,"props":1022,"children":1024},{"className":1023},[],[1025],{"type":24,"value":1026},"SimpleReranker",{"type":24,"value":1028},"（向量距离 + 关键词匹配的加权打分），保证服务不中断。",{"type":18,"tag":33,"props":1030,"children":1032},{"id":1031},"_54-本地嵌入bge",[1033],{"type":24,"value":1034},"5.4 本地嵌入（BGE）",{"type":18,"tag":27,"props":1036,"children":1037},{},[1038,1044,1046,1052],{"type":18,"tag":297,"props":1039,"children":1041},{"className":1040},[],[1042],{"type":24,"value":1043},"rag\u002Fembeddings.py",{"type":24,"value":1045}," 用本地 ",{"type":18,"tag":297,"props":1047,"children":1049},{"className":1048},[],[1050],{"type":24,"value":1051},"BAAI\u002Fbge-large-zh-v1.5",{"type":24,"value":1053},"（1024 维）。两个工程细节：",{"type":18,"tag":292,"props":1055,"children":1057},{"code":1056,"language":679,"meta":7,"className":680,"style":7},"# 1) BGE 查询侧需加检索指令，文档侧不加\nBGE_QUERY_INSTRUCTION = \"为这个句子生成表示以用于检索相关文章：\"\ndef embed_query(self, query):\n    text = self.instruction + query if self.use_query_instruction else query\n    return self.model.encode(text, normalize_embeddings=True).tolist()\n# 2) 向量归一化，让 L2 距离与余弦相似度等价排序\n",[1058],{"type":18,"tag":297,"props":1059,"children":1060},{"__ignoreMap":7},[1061,1069,1077,1085,1093,1101],{"type":18,"tag":686,"props":1062,"children":1063},{"class":688,"line":689},[1064],{"type":18,"tag":686,"props":1065,"children":1066},{},[1067],{"type":24,"value":1068},"# 1) BGE 查询侧需加检索指令，文档侧不加\n",{"type":18,"tag":686,"props":1070,"children":1071},{"class":688,"line":698},[1072],{"type":18,"tag":686,"props":1073,"children":1074},{},[1075],{"type":24,"value":1076},"BGE_QUERY_INSTRUCTION = \"为这个句子生成表示以用于检索相关文章：\"\n",{"type":18,"tag":686,"props":1078,"children":1079},{"class":688,"line":707},[1080],{"type":18,"tag":686,"props":1081,"children":1082},{},[1083],{"type":24,"value":1084},"def embed_query(self, query):\n",{"type":18,"tag":686,"props":1086,"children":1087},{"class":688,"line":716},[1088],{"type":18,"tag":686,"props":1089,"children":1090},{},[1091],{"type":24,"value":1092},"    text = self.instruction + query if self.use_query_instruction else query\n",{"type":18,"tag":686,"props":1094,"children":1095},{"class":688,"line":725},[1096],{"type":18,"tag":686,"props":1097,"children":1098},{},[1099],{"type":24,"value":1100},"    return self.model.encode(text, normalize_embeddings=True).tolist()\n",{"type":18,"tag":686,"props":1102,"children":1103},{"class":688,"line":734},[1104],{"type":18,"tag":686,"props":1105,"children":1106},{},[1107],{"type":24,"value":1108},"# 2) 向量归一化，让 L2 距离与余弦相似度等价排序\n",{"type":18,"tag":27,"props":1110,"children":1111},{},[1112,1114,1119,1121,1127],{"type":24,"value":1113},"选本地模型而非云端 Embedding API 的原因：",{"type":18,"tag":74,"props":1115,"children":1116},{},[1117],{"type":24,"value":1118},"免费、离线、不受 API 额度限制",{"type":24,"value":1120},"——这对一个要持续给全库文献做嵌入的系统很关键。模型缓存在 ",{"type":18,"tag":297,"props":1122,"children":1124},{"className":1123},[],[1125],{"type":24,"value":1126},"~\u002F.cache\u002Fhuggingface\u002F",{"type":24,"value":1128},"，全局共享、只下一次。",{"type":18,"tag":33,"props":1130,"children":1132},{"id":1131},"_55-跨语言翻译三层混合策略",[1133],{"type":24,"value":1134},"5.5 跨语言翻译（三层混合策略）",{"type":18,"tag":27,"props":1136,"children":1137},{},[1138,1144,1145,1151],{"type":18,"tag":297,"props":1139,"children":1141},{"className":1140},[],[1142],{"type":24,"value":1143},"rag\u002Ftranslator.py",{"type":24,"value":667},{"type":18,"tag":297,"props":1146,"children":1148},{"className":1147},[],[1149],{"type":24,"value":1150},"HybridTranslator",{"type":24,"value":1152}," 不是简单调翻译 API，而是三层瀑布：",{"type":18,"tag":1154,"props":1155,"children":1156},"ol",{},[1157,1174,1184],{"type":18,"tag":394,"props":1158,"children":1159},{},[1160,1165,1166,1172],{"type":18,"tag":74,"props":1161,"children":1162},{},[1163],{"type":24,"value":1164},"静态词典",{"type":24,"value":951},{"type":18,"tag":297,"props":1167,"children":1169},{"className":1168},[],[1170],{"type":24,"value":1171},"O(1)",{"type":24,"value":1173},"，0 网络开销）：内置数百条高频生物医学术语（癌种、靶点、基因），命中直接返回。",{"type":18,"tag":394,"props":1175,"children":1176},{},[1177,1182],{"type":18,"tag":74,"props":1178,"children":1179},{},[1180],{"type":24,"value":1181},"LLM 兜底",{"type":24,"value":1183},"（~200ms）：词典覆盖不到的，调大模型翻译。",{"type":18,"tag":394,"props":1185,"children":1186},{},[1187,1192],{"type":18,"tag":74,"props":1188,"children":1189},{},[1190],{"type":24,"value":1191},"MeSH 查询扩展",{"type":24,"value":1193},"：调 NCBI MeSH 接口把术语规范化、扩展同义词，提升 PubMed 检索召回。",{"type":18,"tag":292,"props":1195,"children":1197},{"code":1196,"language":679,"meta":7,"className":680,"style":7},"def _llm_fallback(self, text):\n    if not self.enable_llm or not self._llm_api_key:\n        return text                      # 没配 LLM 也能退回词典结果\n    return self.llm_translator.translate(text)\n",[1198],{"type":18,"tag":297,"props":1199,"children":1200},{"__ignoreMap":7},[1201,1209,1217,1225],{"type":18,"tag":686,"props":1202,"children":1203},{"class":688,"line":689},[1204],{"type":18,"tag":686,"props":1205,"children":1206},{},[1207],{"type":24,"value":1208},"def _llm_fallback(self, text):\n",{"type":18,"tag":686,"props":1210,"children":1211},{"class":688,"line":698},[1212],{"type":18,"tag":686,"props":1213,"children":1214},{},[1215],{"type":24,"value":1216},"    if not self.enable_llm or not self._llm_api_key:\n",{"type":18,"tag":686,"props":1218,"children":1219},{"class":688,"line":707},[1220],{"type":18,"tag":686,"props":1221,"children":1222},{},[1223],{"type":24,"value":1224},"        return text                      # 没配 LLM 也能退回词典结果\n",{"type":18,"tag":686,"props":1226,"children":1227},{"class":688,"line":716},[1228],{"type":18,"tag":686,"props":1229,"children":1230},{},[1231],{"type":24,"value":1232},"    return self.llm_translator.translate(text)\n",{"type":18,"tag":27,"props":1234,"children":1235},{},[1236,1238,1243,1244,1250],{"type":24,"value":1237},"带",{"type":18,"tag":74,"props":1239,"children":1240},{},[1241],{"type":24,"value":1242},"持久化缓存",{"type":24,"value":951},{"type":18,"tag":297,"props":1245,"children":1247},{"className":1246},[],[1248],{"type":24,"value":1249},"translator_cache.json",{"type":24,"value":1251},"），同样的词不重复翻译。即使 LLM 那层失败，也会优雅退回词典 + MeSH 结果，不阻断主流程。",{"type":18,"tag":33,"props":1253,"children":1255},{"id":1254},"_56-双模型容灾qwen-主-deepseek-备",[1256],{"type":24,"value":1257},"5.6 双模型容灾（Qwen 主 + DeepSeek 备）",{"type":18,"tag":27,"props":1259,"children":1260},{},[1261,1267,1269,1275],{"type":18,"tag":297,"props":1262,"children":1264},{"className":1263},[],[1265],{"type":24,"value":1266},"rag\u002Fqwen_client.py",{"type":24,"value":1268}," 把\"主备切换\"封装在一个 ",{"type":18,"tag":297,"props":1270,"children":1272},{"className":1271},[],[1273],{"type":24,"value":1274},"call()",{"type":24,"value":1276}," 里，对上层完全透明：",{"type":18,"tag":292,"props":1278,"children":1280},{"code":1279,"language":679,"meta":7,"className":680,"style":7},"def call(self, prompt, ...):\n    try:\n        return self._chat(self.client, self.model, ...)      # ① 先试 Qwen\n    except Exception as e:\n        logger.warning(f\"Qwen 调用失败：{e}\")\n        if self.fallback_client:                             # ② 自动切 DeepSeek\n            return self._chat(self.fallback_client, self.fallback_model, ...)\n        raise\n",[1281],{"type":18,"tag":297,"props":1282,"children":1283},{"__ignoreMap":7},[1284,1292,1300,1308,1316,1324,1332,1340],{"type":18,"tag":686,"props":1285,"children":1286},{"class":688,"line":689},[1287],{"type":18,"tag":686,"props":1288,"children":1289},{},[1290],{"type":24,"value":1291},"def call(self, prompt, ...):\n",{"type":18,"tag":686,"props":1293,"children":1294},{"class":688,"line":698},[1295],{"type":18,"tag":686,"props":1296,"children":1297},{},[1298],{"type":24,"value":1299},"    try:\n",{"type":18,"tag":686,"props":1301,"children":1302},{"class":688,"line":707},[1303],{"type":18,"tag":686,"props":1304,"children":1305},{},[1306],{"type":24,"value":1307},"        return self._chat(self.client, self.model, ...)      # ① 先试 Qwen\n",{"type":18,"tag":686,"props":1309,"children":1310},{"class":688,"line":716},[1311],{"type":18,"tag":686,"props":1312,"children":1313},{},[1314],{"type":24,"value":1315},"    except Exception as e:\n",{"type":18,"tag":686,"props":1317,"children":1318},{"class":688,"line":725},[1319],{"type":18,"tag":686,"props":1320,"children":1321},{},[1322],{"type":24,"value":1323},"        logger.warning(f\"Qwen 调用失败：{e}\")\n",{"type":18,"tag":686,"props":1325,"children":1326},{"class":688,"line":734},[1327],{"type":18,"tag":686,"props":1328,"children":1329},{},[1330],{"type":24,"value":1331},"        if self.fallback_client:                             # ② 自动切 DeepSeek\n",{"type":18,"tag":686,"props":1333,"children":1334},{"class":688,"line":743},[1335],{"type":18,"tag":686,"props":1336,"children":1337},{},[1338],{"type":24,"value":1339},"            return self._chat(self.fallback_client, self.fallback_model, ...)\n",{"type":18,"tag":686,"props":1341,"children":1342},{"class":688,"line":752},[1343],{"type":18,"tag":686,"props":1344,"children":1345},{},[1346],{"type":24,"value":1347},"        raise\n",{"type":18,"tag":27,"props":1349,"children":1350},{},[1351,1353,1359,1361,1367,1369,1375],{"type":24,"value":1352},"只要 Qwen 报任何错（免费额度耗尽 403、限流、网络抖动、模型名错误），立刻用 DeepSeek 重试，前端无感知。两个模型都走 OpenAI 兼容协议，所以同一套 ",{"type":18,"tag":297,"props":1354,"children":1356},{"className":1355},[],[1357],{"type":24,"value":1358},"OpenAI",{"type":24,"value":1360}," SDK 即可调用，只是 ",{"type":18,"tag":297,"props":1362,"children":1364},{"className":1363},[],[1365],{"type":24,"value":1366},"base_url",{"type":24,"value":1368}," 和 ",{"type":18,"tag":297,"props":1370,"children":1372},{"className":1371},[],[1373],{"type":24,"value":1374},"model",{"type":24,"value":1376}," 不同。",{"type":18,"tag":33,"props":1378,"children":1380},{"id":1379},"_57-文献分块入库",[1381],{"type":24,"value":1382},"5.7 文献分块入库",{"type":18,"tag":27,"props":1384,"children":1385},{},[1386,1392,1394,1400,1402,1407],{"type":18,"tag":297,"props":1387,"children":1389},{"className":1388},[],[1390],{"type":24,"value":1391},"rag_service.add_literature()",{"type":24,"value":1393}," + ",{"type":18,"tag":297,"props":1395,"children":1397},{"className":1396},[],[1398],{"type":24,"value":1399},"_chunk_abstract()",{"type":24,"value":1401},"。早期版本按句号切片，导致",{"type":18,"tag":74,"props":1403,"children":1404},{},[1405],{"type":24,"value":1406},"同一句里的关键数据（如\"OS 37.5 个月\"）和上下文被切散",{"type":24,"value":1408},"，检索命中的片段缺数据，模型只能答\"无相关信息\"。改进后的策略：",{"type":18,"tag":292,"props":1410,"children":1412},{"code":1411,"language":679,"meta":7,"className":680,"style":7},"def _chunk_abstract(abstract, max_chars=500, overlap=100):\n    if len(abstract) \u003C= max_chars:\n        return [abstract]                       # 短摘要整段，保留完整上下文\n    # 长摘要：按句子边界聚合成块，块间保留 overlap 重叠\n    ...\n",[1413],{"type":18,"tag":297,"props":1414,"children":1415},{"__ignoreMap":7},[1416,1424,1432,1440,1448],{"type":18,"tag":686,"props":1417,"children":1418},{"class":688,"line":689},[1419],{"type":18,"tag":686,"props":1420,"children":1421},{},[1422],{"type":24,"value":1423},"def _chunk_abstract(abstract, max_chars=500, overlap=100):\n",{"type":18,"tag":686,"props":1425,"children":1426},{"class":688,"line":698},[1427],{"type":18,"tag":686,"props":1428,"children":1429},{},[1430],{"type":24,"value":1431},"    if len(abstract) \u003C= max_chars:\n",{"type":18,"tag":686,"props":1433,"children":1434},{"class":688,"line":707},[1435],{"type":18,"tag":686,"props":1436,"children":1437},{},[1438],{"type":24,"value":1439},"        return [abstract]                       # 短摘要整段，保留完整上下文\n",{"type":18,"tag":686,"props":1441,"children":1442},{"class":688,"line":716},[1443],{"type":18,"tag":686,"props":1444,"children":1445},{},[1446],{"type":24,"value":1447},"    # 长摘要：按句子边界聚合成块，块间保留 overlap 重叠\n",{"type":18,"tag":686,"props":1449,"children":1450},{"class":688,"line":725},[1451],{"type":18,"tag":686,"props":1452,"children":1453},{},[1454],{"type":24,"value":1455},"    ...\n",{"type":18,"tag":27,"props":1457,"children":1458},{},[1459],{"type":24,"value":1460},"短摘要不切，长摘要按句子边界做带重叠的滑窗——既不超模型长度上限，又不丢上下文。配合 BGE 嵌入，问答准确度明显提升。",{"type":18,"tag":33,"props":1462,"children":1464},{"id":1463},"_58-pubmed-同步与调度",[1465],{"type":24,"value":1466},"5.8 PubMed 同步与调度",{"type":18,"tag":390,"props":1468,"children":1469},{},[1470,1497,1537],{"type":18,"tag":394,"props":1471,"children":1472},{},[1473,1479,1481,1487,1489,1495],{"type":18,"tag":297,"props":1474,"children":1476},{"className":1475},[],[1477],{"type":24,"value":1478},"pubmed_sync.py",{"type":24,"value":1480},"：封装 NCBI E-utilities。",{"type":18,"tag":297,"props":1482,"children":1484},{"className":1483},[],[1485],{"type":24,"value":1486},"ESearch",{"type":24,"value":1488}," 拿 PMID 列表，",{"type":18,"tag":297,"props":1490,"children":1492},{"className":1491},[],[1493],{"type":24,"value":1494},"EFetch",{"type":24,"value":1496}," 批量取详情，内置生物医药关键词集做领域过滤。",{"type":18,"tag":394,"props":1498,"children":1499},{},[1500,1506,1508,1512,1514,1520,1522,1528,1530,1535],{"type":18,"tag":297,"props":1501,"children":1503},{"className":1502},[],[1504],{"type":24,"value":1505},"sync_scheduler.py",{"type":24,"value":1507},"：用 ",{"type":18,"tag":74,"props":1509,"children":1510},{},[1511],{"type":24,"value":142},{"type":24,"value":1513}," 注册 cron 任务（默认 ",{"type":18,"tag":297,"props":1515,"children":1517},{"className":1516},[],[1518],{"type":24,"value":1519},"0 2 * * *",{"type":24,"value":1521},"）。同步时先 ",{"type":18,"tag":297,"props":1523,"children":1525},{"className":1524},[],[1526],{"type":24,"value":1527},"get_all_documents()",{"type":24,"value":1529}," 取已有 PMID 做",{"type":18,"tag":74,"props":1531,"children":1532},{},[1533],{"type":24,"value":1534},"去重",{"type":24,"value":1536},"，只对新文献嵌入入库，避免重复与浪费。",{"type":18,"tag":394,"props":1538,"children":1539},{},[1540,1542,1548,1550,1556,1558,1564],{"type":24,"value":1541},"对外暴露 ",{"type":18,"tag":297,"props":1543,"children":1545},{"className":1544},[],[1546],{"type":24,"value":1547},"POST \u002Fapi\u002Fsync\u002Fnow",{"type":24,"value":1549},"（手动触发）、",{"type":18,"tag":297,"props":1551,"children":1553},{"className":1552},[],[1554],{"type":24,"value":1555},"GET \u002Fapi\u002Fsync\u002Fstatus",{"type":24,"value":1557},"（查状态）、",{"type":18,"tag":297,"props":1559,"children":1561},{"className":1560},[],[1562],{"type":24,"value":1563},"POST \u002Fapi\u002Fsync\u002Fschedule",{"type":24,"value":1565},"（改 cron）三个接口，调度策略可运行时调整。",{"type":18,"tag":33,"props":1567,"children":1569},{"id":1568},"_59-mesh-术语联想",[1570],{"type":24,"value":1571},"5.9 MeSH 术语联想",{"type":18,"tag":27,"props":1573,"children":1574},{},[1575,1581,1582,1588,1590,1596,1597,1603,1605,1610,1612,1617],{"type":18,"tag":297,"props":1576,"children":1578},{"className":1577},[],[1579],{"type":24,"value":1580},"GET \u002Fapi\u002Fmesh\u002Fsuggest",{"type":24,"value":951},{"type":18,"tag":297,"props":1583,"children":1585},{"className":1584},[],[1586],{"type":24,"value":1587},"main.py",{"type":24,"value":1589},"）：用户在前端输入时，实时调 NCBI 的 ",{"type":18,"tag":297,"props":1591,"children":1593},{"className":1592},[],[1594],{"type":24,"value":1595},"esearch",{"type":24,"value":1393},{"type":18,"tag":297,"props":1598,"children":1600},{"className":1599},[],[1601],{"type":24,"value":1602},"esummary",{"type":24,"value":1604}," 接口，把输入词映射到标准 ",{"type":18,"tag":74,"props":1606,"children":1607},{},[1608],{"type":24,"value":1609},"MeSH 主题词",{"type":24,"value":1611},"返回下拉建议；无输入时给一批热门术语兜底，网络失败时返回空列表而非报错——",{"type":18,"tag":74,"props":1613,"children":1614},{},[1615],{"type":24,"value":1616},"不让外部依赖影响前端体验",{"type":24,"value":1618},"。",{"type":18,"tag":33,"props":1620,"children":1622},{"id":1621},"_510-前端问答页",[1623],{"type":24,"value":1624},"5.10 前端问答页",{"type":18,"tag":27,"props":1626,"children":1627},{},[1628,1634,1636,1642],{"type":18,"tag":297,"props":1629,"children":1631},{"className":1630},[],[1632],{"type":24,"value":1633},"frontend\u002Fpages\u002Findex.vue",{"type":24,"value":1635},"：Element Plus 输入框 + 提交按钮，调 ",{"type":18,"tag":297,"props":1637,"children":1639},{"className":1638},[],[1640],{"type":24,"value":1641},"POST \u002Fapi\u002Fask",{"type":24,"value":1643},"。拿到结果后：",{"type":18,"tag":390,"props":1645,"children":1646},{},[1647,1658,1671],{"type":18,"tag":394,"props":1648,"children":1649},{},[1650,1652,1656],{"type":24,"value":1651},"用 ",{"type":18,"tag":74,"props":1653,"children":1654},{},[1655],{"type":24,"value":280},{"type":24,"value":1657}," 把答案 Markdown 渲染成 HTML（大模型常用 Markdown 排版）；",{"type":18,"tag":394,"props":1659,"children":1660},{},[1661,1663,1669],{"type":24,"value":1662},"来源用可折叠面板（",{"type":18,"tag":297,"props":1664,"children":1666},{"className":1665},[],[1667],{"type":24,"value":1668},"el-collapse",{"type":24,"value":1670},"）展示，每条带 PMID 和跳转 PubMed 的\"查看原文\"链接；",{"type":18,"tag":394,"props":1672,"children":1673},{},[1674,1676,1682],{"type":24,"value":1675},"支持 ",{"type":18,"tag":297,"props":1677,"children":1679},{"className":1678},[],[1680],{"type":24,"value":1681},"Ctrl\u002FCmd + Enter",{"type":24,"value":1683}," 快捷提交，loading 态防重复提交。",{"type":18,"tag":302,"props":1685,"children":1686},{},[],{"type":18,"tag":19,"props":1688,"children":1690},{"id":1689},"六小结",[1691],{"type":24,"value":1692},"六、小结",{"type":18,"tag":27,"props":1694,"children":1695},{},[1696,1698,1703],{"type":24,"value":1697},"PharmaLitQA 的价值不在某一个\"炫技点\"，而在于把一条",{"type":18,"tag":74,"props":1699,"children":1700},{},[1701],{"type":24,"value":1702},"完整、可靠、可溯源",{"type":24,"value":1704},"的 RAG 链路工程化落地：",{"type":18,"tag":390,"props":1706,"children":1707},{},[1708,1718,1728,1738],{"type":18,"tag":394,"props":1709,"children":1710},{},[1711,1716],{"type":18,"tag":74,"props":1712,"children":1713},{},[1714],{"type":24,"value":1715},"检索质量",{"type":24,"value":1717},"：向量 + BM25 多路召回，再加 Cross-Encoder 精排，召回与精度兼顾；",{"type":18,"tag":394,"props":1719,"children":1720},{},[1721,1726],{"type":18,"tag":74,"props":1722,"children":1723},{},[1724],{"type":24,"value":1725},"可用性",{"type":24,"value":1727},"：本地嵌入永久免费、Qwen\u002FDeepSeek 双模型容灾、翻译三层降级——每个外部依赖都有兜底；",{"type":18,"tag":394,"props":1729,"children":1730},{},[1731,1736],{"type":18,"tag":74,"props":1732,"children":1733},{},[1734],{"type":24,"value":1735},"数据新鲜度",{"type":24,"value":1737},"：APScheduler 定时同步 PubMed，知识库持续更新；",{"type":18,"tag":394,"props":1739,"children":1740},{},[1741,1746],{"type":18,"tag":74,"props":1742,"children":1743},{},[1744],{"type":24,"value":1745},"可信度",{"type":24,"value":1747},"：答案永远附带文献来源，把\"AI 幻觉\"约束在\"有据可查\"的范围内。",{"type":18,"tag":27,"props":1749,"children":1750},{},[1751],{"type":24,"value":1752},"这套架构稍作替换（换领域语料、换嵌入\u002F对话模型），就能迁移到法律、金融、政务等任何需要\"专业 + 可溯源\"问答的垂直场景。",{"type":18,"tag":19,"props":1754,"children":1756},{"id":1755},"项目地址",[1757],{"type":24,"value":1755},{"type":18,"tag":27,"props":1759,"children":1760},{},[1761,1763,1771,1775,1777],{"type":24,"value":1762},"github：",{"type":18,"tag":1764,"props":1765,"children":1769},"a",{"href":1766,"rel":1767},"https:\u002F\u002Fgithub.com\u002Foinsist\u002FPharmaLitQA",[1768],"nofollow",[1770],{"type":24,"value":1766},{"type":18,"tag":1772,"props":1773,"children":1774},"br",{},[],{"type":24,"value":1776},"\ngitee：",{"type":18,"tag":1764,"props":1778,"children":1781},{"href":1779,"rel":1780},"https:\u002F\u002Fgitee.com\u002Fo_insist\u002FPharmaLitQA",[1768],[1782],{"type":24,"value":1779},{"type":18,"tag":1784,"props":1785,"children":1786},"style",{},[1787],{"type":24,"value":1788},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":698,"depth":698,"links":1790},[1791,1796,1797,1798,1803,1815,1816],{"id":21,"depth":698,"text":25,"children":1792},[1793,1794,1795],{"id":35,"depth":707,"text":38},{"id":198,"depth":707,"text":201},{"id":288,"depth":707,"text":288},{"id":307,"depth":698,"text":310},{"id":372,"depth":698,"text":375},{"id":565,"depth":698,"text":568,"children":1799},[1800,1801,1802],{"id":571,"depth":707,"text":574},{"id":609,"depth":707,"text":612},{"id":623,"depth":707,"text":626},{"id":640,"depth":698,"text":643,"children":1804},[1805,1806,1807,1808,1809,1810,1811,1812,1813,1814],{"id":651,"depth":707,"text":654},{"id":834,"depth":707,"text":837},{"id":929,"depth":707,"text":932},{"id":1031,"depth":707,"text":1034},{"id":1131,"depth":707,"text":1134},{"id":1254,"depth":707,"text":1257},{"id":1379,"depth":707,"text":1382},{"id":1463,"depth":707,"text":1466},{"id":1568,"depth":707,"text":1571},{"id":1621,"depth":707,"text":1624},{"id":1689,"depth":698,"text":1692},{"id":1755,"depth":698,"text":1755},"markdown","content:articles:agent:文献智能问答助手.md","content","articles\u002Fagent\u002F文献智能问答助手.md","articles\u002Fagent\u002F文献智能问答助手","md",1780549809999]