ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于RAG与大模型的医疗问答系统:从文档加载到流式输出

基于RAG与大模型的医疗问答系统:从文档加载到流式输出 简介一份基于RAG与大模型技术的医疗问答系统毕业设计资源包面向计算机、人工智能、通信等专业学生与从业者既可用于小白入门大模型应用开发也可作为课程设计、大作业及毕设的综合参考。资源共75个文件压缩包约84.65MB文件类型涵盖Python源码、Jupyter Notebook、JSON/TXT/CSV数据、YAML配置与Markdown文档等。py脚本涉及Web界面、实体识别模型、图数据库构建等核心模块ipynb记录数据解析、微调运行与NER结果分析PNG/JPG截图及流程图可直观对照界面与系统结构。整套资源已经过调试测试能直接运行结合文档说明与配套数据可支撑从数据预处理、知识图谱搭建、实体识别到RAG问答与模型微调的完整研发链路。目前已有378人学习浏览具备较强学习借鉴价值基础较好的读者还能在此基础上修改扩展实现个性化医疗问答场景。1. 医疗问答系统不是 Chatbot 套壳RAG 才是这个毕设的硬骨头如果你以为“医疗问答系统”就是在百川或者 ChatGLM 的 API 外面包一层网页那这个毕设拿不到高分。真正拉开差距的是 RAG检索增强生成链路用户问“感冒了能不能吃阿莫西林”系统不是靠大模型硬记而是先从医学知识库里检索出相关条目再把它拼进 Prompt 让大模型“看着资料回答”。这套基于 RAG 与大模型技术的医疗问答系统源码把文档加载、文本切分、向量化、检索、重排、Prompt 拼接、流式输出全部串成了一条可运行管线并且附带了完整文档与答辩资料。适合正在做毕设、想把“能用”做到“能讲清楚原理”的 Python 方向学生也适合想快速搭一个领域问答 Demo 的从业者。下面我从落地顺序讲先把链路拆开看每个环节怎么调再集中说医疗场景特有的坑。2. RAG 链路拆解加载、切分、Embedding、检索与重排的实现顺序很多同学拿到源码第一步就去看大模型接口调用这是本末倒置。RAG 系统的地基是“怎么把知识库变成可检索的向量索引”这个环节的代码量不大但每一步的参数都直接影响最终问答质量。这一章按我习惯的实现顺序讲先做文档加载与切分再做 Embedding 与向量库落库最后做检索与重排。顺序不能乱——切分粒度决定了 Embedding 的语义单元检索策略决定了 Token 怎么花。2.1 文档加载与切分为什么先按结构切比按字数切省一半调参功夫我拆过不少 RAG 项目常见翻车点是上来就用CharacterTextSplitter按固定字符数切结果一段医学知识被拦腰截断比如“成人一次 0.25g每 6 小时一次”被切到两段里检索时怎么都召回不全。这个毕设源码里推荐的做法是先按文档结构切再按语义块合并。from langchain_community.document_loaders import PyPDFLoader, TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # 第一步按文件类型加载 loader PyPDFLoader(data/药品说明书.pdf) documents loader.load() # 第二步按标题/段落级别先切一次保留结构信息 splitter RecursiveCharacterTextSplitter( chunk_size512, chunk_overlap80, separators[\n\n, \n, 。, , . ], keep_separatorTrue, ) chunks splitter.split_documents(documents) # 第三步给每个 chunk 打印前 50 字人工抽检切分边界 for i, chunk in enumerate(chunks[:5]): print(f--- chunk {i} ---) print(chunk.page_content[:50])这里chunk_size512是字符数还是 Token 数取决于你用的 splitterRecursiveCharacterTextSplitter统计的是字符。对中文医疗文本512 字大概能覆盖一段完整医嘱。chunk_overlap80保留相邻片段的重叠区避免“阿莫西林胶囊”这类词组恰好被切成“阿莫西林”和“胶囊”。separators里我把中文句号“。”和分号“”加进去了这样切分点更符合中文表达习惯。做完切分后一定要做检查而不是直接丢进向量库。我一般会写一个临时脚本统计 chunk 数量、平均长度并随机抽 10 个 chunk 看有没有语义断裂。常见问题是药品说明书的“【适应症】”和“【用法用量】”被并到同一个 chunk导致用户问“每天吃几次”时召回了“适应症”内容。解决方式是让加载器保留文档自带的结构元数据。你可以在PyPDFLoader之后把 PDF 的标题层级写入metadata再按metadata里的结构来切。如果知识库里混着 PDF、Word、Markdown最好先统一转成 Markdown 或纯文本再切。源码里提供了一个convert_to_text.py它的逻辑是先判断扩展名再分别调用pdfplumber和python-docx提取文字最后统一编码成 UTF-8 写入中间目录。这一步不做的话后面 Embedding 会出现一堆乱码尤其是扫描版 PDF 缺 OCR 层提取出来是空白。2.2 Embedding 选型与向量库落库维度、距离函数与索引参数切分完成后的核心决策是选 Embedding 模型。源码默认用的是text2vec-large-chinese或者bge-large-zh-v1.5这两个都是中文语料预训练输出维度分别是 1024 和 1024。注意不要用 OpenAI 的text-embedding-ada-002做中文医疗文本除非你做了大量数据增强否则医疗实体词不在它的分布里。from sentence_transformers import SentenceTransformer from langchain_community.vectorstores import FAISS model_name BAAI/bge-large-zh-v1.5 encoder SentenceTransformer(model_name) # 对每个 chunk 生成向量并写入 FAISS 索引 vector_store FAISS.from_documents( chunks, embeddingencoder, distance_strategyCOSINE, # 或 EUCLIDEAN ) vector_store.save_local(storage/faiss_index)distance_strategy我建议用COSINE。医疗问答里用户输入“头疼”和知识库里的“头痛”在字面上不同但在向量空间里余弦相似度仍然会很高。而欧氏距离对向量模长敏感不同长度文本的模长差异会干扰相似度排序。源码里默认也是COSINE如果你的版本里默认是L2记得改成COSINE。落库之后有一个容易被忽略的参数是FAISS的索引类型。FAISS.from_documents默认构建的是IndexFlatIP它是暴力精确检索数据量在几万条以内完全够用。如果知识库超过 10 万条才需要切到IndexIVFFlat或HNSW。对毕设场景几万条以内建议就用精确索引别为了追求“高级”引入IVF它需要训练索引调不好召回率反而下降。落库完成后我强烈建议你做一次“检索自检”拿 10 个种子问题去查向量库看返回的 Top-5 是不是真的包含答案。这一步能直接暴露 Embedding 模型和知识库是否匹配。如果你发现“感冒了能吃什么药”召回的却是“高血压用药注意事项”那说明你的知识库里感冒相关内容太少或者切分时把感冒和高血压混在同一个文档块里了。2.3 检索召回与重排Top-K 怎么定重排器要不要上这是 RAG 链路里最“玄学”的环节。Top-K 设小了答案可能不在召回结果里设大了Prompt 里塞进一堆无关内容大模型容易被带偏。源码里的默认值是 K5但我在实际调试中很少直接用默认值。我的习惯是先跑 20 个测试问题统计“答案所在 chunk 在召回结果中的排名”如果答案经常出现在第 4 到第 5 位就说明 K 设小了或者切分粒度太粗。retriever vector_store.as_retriever( search_typesimilarity, search_kwargs{k: 8} ) docs retriever.invoke(成人阿莫西林一次吃多少) for i, doc in enumerate(docs): print(i, doc.metadata.get(source), doc.page_content[:30])search_type除了similarity还可以用mmr。mmr会在相关性和多样性之间做平衡避免返回的 8 个 chunk 全是同一篇文章的邻近段落。对医疗问答我偏向先用similarity因为领域知识库里同一话题的邻近段落往往正是需要的完整上下文用mmr反而可能丢掉关键细节。如果你的知识库是多个来源混合的比如既有药品说明书又有临床指南可以考虑mmr让答案覆盖不同来源。重排器Reranker是很多人纠结的点。我的判断是如果总文档量在 2 万条以内且你的 Embedding 模型本身质量不错重排器带来的增益不明显但会引入额外的推理延迟。源码里留了bge-reranker-v2-m3的接口但默认不启用。当你发现 Top-5 里前排结果经常“相关但不对症”比如用户问“儿童用量”却召回“成人用量”这时候再开重排器。用法是先用向量检索拿回 Top-20再用重排器精排取 Top-5。from transformers import AutoModelForSequenceClassification, AutoTokenizer reranker_model AutoModelForSequenceClassification.from_pretrained(BAAI/bge-reranker-v2-m3) tokenizer AutoTokenizer.from_pretrained(BAAI/bge-reranker-v2-m3) pairs [[query, doc.page_content] for doc in docs[:20]] inputs tokenizer(pairs, paddingTrue, truncationTrue, return_tensorspt) scores reranker_model(**inputs).logits.view(-1).float().tolist() reranked sorted(zip(docs, scores), keylambda x: x[1], reverseTrue)注意bge-reranker-v2-m3是交叉编码器它把问题和文档拼成一对做精细打分速度比向量检索慢一个数量级所以只对 Top-20 做精排是性价比最高的做法。毕设答辩时哪怕你的系统最终没启用重排器也要在 PPT 里把“为什么用/不用”讲清楚这属于加分项。3. 大模型生成环节Prompt 模板、上下文拼接与流式输出的落地写法检索拿到资料后下一步是把它们拼进 Prompt 交给大模型。这个环节别看代码简单影响回答质量的三个细节分别是Prompt 模板结构、上下文长度控制、输出接口形态。源码里把这一块封装成了rag_chain.py但直接复制它之前你要先理解它为什么这么写。3.1 Prompt 模板设计把系统提示词、检索片段、对话历史拼成一条消息一个常见错误是只有一段“你是医疗助手”的 system prompt然后把所有检索 chunk 一字排开塞进 user。实践下来更稳的结构是三段式先给身份与边界再给检索依据最后给用户问题。源码里的模板是这样的PROMPT_TEMPLATE 你是一名严谨的医疗问答助手。请严格依据以下资料回答用户问题。 如果资料中没有明确信息请直接回答“资料中没有找到相关内容”不要自行猜测。 每条回答末尾用 [来源序号] 标注依据的资料编号。 资料 {context} 对话历史 {history} 用户问题{question} def build_messages(question, context_chunks): context_text \n\n.join( f[{i1}] {chunk.page_content} for i, chunk in enumerate(context_chunks) ) messages [ {role: system, content: PROMPT_TEMPLATE.format( contextcontext_text, history, questionquestion, )}, ] return messages这里的关键是[来源序号]的设计。它不只是为了展示更是给大模型一个“内引用的锚点”模型倾向于在生成时复述这些序号从而降低自由发挥的概率。另一个细节是history字段如果对话历史为空就传空字符串不要省略这个 key否则模板渲染会报错。实际项目里history可能来自前端传递的最近 N 轮对话。太长时要截断否则上下文爆炸。我习惯只保留最近两轮用户和助手消息因为更早的对话对当前回答几乎没有帮助只会让模型注意力分散。3.2 上下文窗口与 Token 控制防止长上下文把生成质量拖垮大模型的上下文窗口是硬约束。虽然现在很多模型宣称 128K 上下文但实际使用时中间部分的注意力会衰减。对医疗问答这种需要精确引用剂量/禁忌的场景上下文越短越容易生成高质量回答。源码里给出的策略是限制最多拼接 5 个 chunk每个 chunk 最多 512 字符加上系统提示和问题总 Token 控制在 2500 以内。MAX_CHUNKS 5 MAX_CHUNK_CHARS 512 def truncate_chunk(chunk_text, max_charsMAX_CHUNK_CHARS): if len(chunk_text) max_chars: return chunk_text return chunk_text[:max_chars] ……MAX_CHUNK_CHARS是字符数不是 Token 数中文一个字大约 1.5 到 2 个 Token512 字对应 800 到 1000 Token。5 个 chunk 加上对话历史与问题总 Token 在 2500 上下既不会超出主流开源模型的 4K 窗口又能给出足够的证据稠密度。若你用的是 32K 上下文模型也可以适当把MAX_CHUNKS提到 8但收益会衰减。调试时有一个很直观的检查方法把拼好的 Prompt 打印出来人眼读一遍。如果读到问题处已经忘了前面资料在说什么说明上下文太长了。这在 API 调试器里不合适但在本地跑源码时可以加一行logger.debug(fPrompt tokens: {len(messages[0][content])})手动估算。3.3 流式输出与接口封装Flask/FastAPI 返回格式与前端对接源码后端用的是 FastAPI/chat接口支持流式返回。流式不是炫技而是因为大模型生成 300 个字的耗时可能超过 10 秒如果不流式前端页面会一直空白用户以为系统挂了。from fastapi import FastAPI from fastapi.responses import StreamingResponse app FastAPI() app.post(/chat) async def chat(request: dict): question request[question] chunks retriever.invoke(question) messages build_messages(question, chunks) def generate(): for token in llm.stream(messages): yield fdata: {token}\n\n yield data: [DONE]\n\n return StreamingResponse(generate(), media_typetext/event-stream)注意StreamingResponse的media_type必须是text/event-stream前端EventSource或fetch的流式解析器才能正常工作。这里有个坑EventSource只支持 GET 请求不支持 POST所以如果你前端用了EventSource要么把接口改成 GET 用 query 传参要么用fetch配合ReadableStream。源码前端用的是后者比较稳妥。对接时前端解析流式数据要注意格式。每一行data: {token}里的 token 可能是不完整的中文前端不要逐字渲染最好缓冲一个字再显示否则会出现半个汉字闪烁。源码里的 Vue 页面就是这么处理的onmessage里先拼进一个 buffer每 50ms 判断一次是否有完整 UTF-8 字符再刷到 DOM 上。4. 医疗问答的领域陷阱药品名、剂量与同义词怎么处理通用 RAG 教程不会告诉你医疗问答里“词汇层面”的问题比“语义层面”更致命。用户不会按标准药典名提问他会说“阿莫仙”但说明书里写的是“阿莫西林”。向量检索对这种字面差异的容忍度有限尤其在短文本场景下必须做实体归一化。4.1 医疗实体归一化为什么“阿莫西林”和“阿莫仙”必须映射到同一 ID归一化不是 RAG 的必备环节但对医疗问答几乎是必选项。不做的表现是用户问“阿莫仙怎么吃”向量检索返回的是“阿莫仙”在词典条目里的定义而“阿莫西林”的用法用量在另一个 chunk 里两者向量距离较远导致检索召回错误。解决思路是构造一个别名表在查询改写阶段把别名替换成标准名。ALIAS_MAP { 阿莫仙: 阿莫西林, 安必仙: 氨苄西林, 泰诺: 对乙酰氨基酚, 扑热息痛: 对乙酰氨基酚, } def normalize_query(query: str) - str: for alias, std_name in ALIAS_MAP.items(): if alias in query: query query.replace(alias, std_name) return queryALIAS_MAP的维护是体力活我一般先把药典里的通用名和商品名整理成表再手工补几十条高频别名。不要指望用大模型自动做映射它会造出不存在的关系。源码的data/alias.csv里预置了约 300 条常见药品别名修改它比改代码更安全。注意映射方向只能是“别名 → 标准名”不能反过来否则用户输入标准名会被替换成商品名反而丢失语义。4.2 剂量与单位解析正则抽取“一次 0.25g一日三次”这类医嘱剂量问答是医疗问答系统的高频测试点也是最容易出错的地方。原因是剂量描述充满变体“0.25g”“250mg”“每次一粒”“一日 23 次”。源码里内置了一个dosage_parser用正则做粗抽取再用单位换算做归一化。import re DOSAGE_PATTERN re.compile( r(一次|每次|顿服)?\s* r(\d(?:\.\d)?|半|一|两|三)?\s* r(mg|g|ml|粒|片|袋|支)? ) def parse_dosage(text: str): matches DOSAGE_PATTERN.findall(text) result [] for dose, num, unit in matches: if not num or not unit: continue result.append({ dose: dose, num: num, unit: unit, }) return result这个解析器很简陋但它能处理“一次 0.25g”“一日三次”这类常见句式。真正的坑在单位换算如果知识库里写“250mg”用户问“0.25g”检索召回后直接拼接原文也能答对但如果你想做结构化答案展示就需要mg和g的换算。源码里定义了一个UNIT_RATE {mg: 0.001, g: 1, ml: 1}乘以系数统一换算成g或ml再比较这能避免“一次 500mg”和“一次 0.5g”被判定为不同剂量。另一个细节是“一日三次”里的“次”和“一次 0.25g”里的“次”冲突。正则里(一次|每次|顿服)是前置修饰符而“一日三次”中的“次”是频率词两者要分开解析。我建议把频率解析拆成独立正则匹配“一日 N 次”“每日 N 次”“每 X 小时一次”等模式结果存成frequency字段。如果用户直接问“一天吃几次”系统优先从frequency字段匹配再查向量库速度更快也更稳。4.3 知识库与结构化知识结合何时用 RAG、何时直接查规则表RAG 不是万能的。像“青霉素过敏者禁用”这类禁忌如果知识库里有明确条目RAG 可以召回。但如果用户问“孕妇能不能吃布洛芬”说明书里可能写了“孕妇慎用”或“禁用”不同版本说明书措辞不同。这时候更适合维护一张结构化的禁忌表用规则引擎直接命中而不是靠向量相似度。源码里做了一个双路查询先用规则表精确匹配实体与属性匹配不到再走 RAG。这个设计在答辩时非常加分因为评委能看到你理解了两种检索范式的边界。实现上很简单def hybrid_query(question: str): # 先查结构化规则表 rule_result rule_engine.search(question) if rule_result: return rule_result # 再走 RAG chunks retriever.invoke(question) return build_rag_answer(question, chunks)rule_engine的底层是一张 SQLite 表字段包括drug_name,attribute,value,source。例如(布洛芬, 孕妇, 禁用),(布洛芬, 儿童, 慎用)。当用户问题同时包含药品名和人群词时用正则抽取这两个槽位然后精确查表。查不到再走 RAG既保证了高频规则的高确定性又能保留 RAG 的开放性。注意规则表的准确率必须是 100%否则宁可让 RAG 给出带引用的模糊答案也不要从规则表给出错误结论。毕设演示时规则表里的数据量不用大但每条一定要准。5. 避坑指南四个让 RAG 问答翻车的常见问题与排查思路这一章是重头戏。下面四条都是我在实际跑这个源码时真实踩过的坑每一条都按“现象 → 原因 → 解决”写你可以直接对照排查。5.1 检索结果相关但答案错误双层召回与重排的补偿现象用户问“儿童阿莫西林用量”检索返回的 chunk 确实都是阿莫西林相关内容但答出来的剂量是成人剂量。这说明向量检索没有区分“儿童”和“成人”这两个细粒度实体。原因有两种可能一是知识库里儿童剂量和成人剂量在同一个 chunk 里切分时没分开二是 Embedding 模型对“儿童”这个修饰词的敏感度不够。解决方案是先检查切分边界如果同一 chunk 有两条剂量改成按段落切如果已经切分再考虑对查询做实体扩充把“儿童”映射成“小儿”“儿童用药”等同义词多路召回后再合并。我在这个项目里最常用的是先把知识库里的所有[儿童]与[成人]段落做了标签切分再在检索时做一次用户年龄意图分类这一步能显著提升正确率。5.2 回答幻觉在 Prompt 里做“无据不答”约束并加引用标记现象大模型在资料不充分时编造了“每次 500mg”但原作者说明书里根本没有这个数据。原因是单纯把资料拼进 Prompt没有对模型做强约束。解决方法是修改 Prompt 中的指令词把“如果没有相关资料请直接回答不了解”改成更强制性的表达“以下每条回答必须能在资料中找到直接依据若没有依据请只输出‘资料中未找到相关内容’。”同时要求模型在每个关键数值后加 [来源序号]。我做过的实验里加引用标记后幻觉率大约下降四成。另一个辅助手段是对回答做后校验用正则把回答中的剂量数值抽出来和检索结果里的数值做比对不一致就发警告。这个后校验不是必选项但能在演示时体现你的工程严谨度。5.3 性能瓶颈向量索引构建慢、并发查询超时的调优顺序现象本地跑源码时第一次构建 FAISS 索引花了十几分钟并发 5 个查询时接口响应超过 30 秒。原因一般是三个Embedding 模型在 CPU 上跑太慢、FAISS 索引类型不适合并发、FastAPI 没有配置线程池。调优顺序不要乱。先看 Embedding 是否为 CPU 推理如果是换onnxruntime版本或用 GPU再看热启动时是否每次请求都重新加载模型如果是把它提到全局变量最后看 FAISS 索引IndexFlatIP在单线程下检索本身很快瓶颈更多是 Embedding 推理。源码里有一个--preload启动参数会在服务启动前构建索引并且把模型常驻内存这个参数在演示前一定要用。5.4 中文医疗分词差异自定义词典与实体链接的配合现象用户输入“咽炎能吃头孢么”系统把“头孢”识别成了“头”和“孢”导致召回不准确。原因是向量检索虽然不需要分词但如果你用了关键词命中做前置过滤或者用了jieba做查询改写默认词典里没有“头孢”这个词。解决方式是在jieba里加载自定义医疗词典把常用药品名、疾病名、症状名加进去。要注意自定义词典的词频设置设得太高会影响分词泛化。源码的data/medical_dict.txt里每行一个词用jieba.load_userdict加载。这个文件不是越大越好把高频 500 词维护好就够了。另外一个更稳的方案是直接跳过 jieba用向量检索为主、规则匹配为辅不依赖分词正确性。对毕设而言两条路都可用但如果你要答辩讲技术细节建议把“为什么不做分词”也说清楚因为 RAG 的向量模型天然支持整句匹配分词反而会丢失上下文。6. 把毕设做成能演示的系统验证指标、示例问答与本地部署技巧最后这一章不讲原理只讲怎么在答辩前把系统调到“看起来可靠”的状态。能用和能演示是两码事演示版需要更稳定的指标、更快的响应和更不容易出错的交互路径。6.1 用 20 个黄金问题做回归测试准确率、命中率与引用率怎么算我在跑这类项目时习惯准备 20 个覆盖典型场景的黄金问题比如“成人阿莫西林一次吃多少”“孕妇能不能吃布洛芬”“感冒和流感的区别”“头孢和青霉素过敏怎么办”。每个问题预先把正确答案写进测试集然后跑脚本统计三个指标。golden_questions [ {question: 成人阿莫西林一次吃多少, expected_answer_contains: [0.25g, 250mg, 一次一片]}, {question: 孕妇能不能吃布洛芬, expected_answer_contains: [禁用, 慎用, 医生说]}, ] hits 0 count 0 for item in golden_questions: answer chat_sync(item[question]) count 1 if any(kw in answer for kw in item[expected_answer_contains]): hits 1 accuracy hits / count这个脚本评估的是“答案是否包含关键事实”。不要要求模型逐字和标准答案一致医疗问答的表述本来就有多种。除了准确率还要统计“引用率”看回答是否都带上了[1]、[2]这样的来源标记。如果某些回答没有引用说明模型跳过了检索依据那可能是 Prompt 约束失效。黄金问题不要只选简单的至少包含 3 个边界问题比如“查不到的”“跨科室的”“同义词替换的”这样能暴露系统的真实短板。6.2 环境复现Python 虚拟环境、依赖冻结与一键启动脚本毕设源码能不能在答辩电脑上顺利跑起来80% 取决于环境是否干净。源码目录里有requirements.txt但我拿到后第一件事是把所有包升到兼容版本并重新冻结。推荐用venv而不是conda因为 conda 在演示电脑上可能没有装而python3 -m venv是标准库自带。python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt python -c import torch; print(torch.__version__)安装时最容易出问题的是sentence-transformers需要的 torch 版本。源码的requirements.txt里没有锁死 torch 版本如果你本地是 CPU 环境建议安装 CPU 版 torch体积小一半。加载模型时把所有模型文件缓存到本地目录避免演示现场联网下载。源码里默认会从 HuggingFace 拉取模型如果你在答辩现场没有外网环境提前跑一遍脚本让模型缓存进~/.cache/huggingface或者用--model_path指向本地目录。这一步不做好现场红温就晚了。6.3 演示前必做的三件事预构建索引、缓存答案、关闭调试模式第一件事是预构建索引。不要等到应用启动时再构建 FAISS如果文档有几万条构建需要几分钟。源码里有build_index.py跑一次生成storage/faiss_index之后应用启动直接读取索引文件就能秒开。第二件事是热点问答缓存。把黄金问题里最常用的 10 个问题提前跑一遍把问题和答案存进 Redis 或简单的字典缓存。演示时无论网络怎么抖动这 10 个问题都能秒回。第三件事是关闭调试模式。FastAPI 的--reload和 Flask 的debugTrue在演示时一定要关否则每次请求都会触发文件监视和额外的堆栈采集速度明显变慢。if __name__ __main__: import uvicorn uvicorn.run(app:app, host0.0.0.0, port8000, reloadFalse)把reloadFalse写死是一种习惯。我见过太多现场演示因为开着 reload改了一行无关紧要的 CSS 导致服务自动重启然后所有内存里的向量索引全部重新加载等了三分钟页面还没起来。从那以后我做毕设答辩前一定花 10 分钟完整走一遍演示脚本启动服务、问黄金问题、检查引用标记、测试一个边界问题再截一张成功回答的图存到本地备用。这套习惯不复杂但它能避免九成以上的演示翻车希望帮到你。本文还有配套的精品资源点击获取
返回列表