尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

个人知识库问答机器人:RAG+Agent工程化落地指南

个人知识库问答机器人:RAG+Agent工程化落地指南 1. 这不是又一个“调API”的玩具为什么个人知识库问答机器人值得你花3天认真搭一遍我去年在给一家做工业设备维保的客户做AI落地咨询时被问得最多的问题不是“能不能识别故障图片”而是“我们十年积累的2000份维修手册、300个典型故障案例、500条现场工程师口述经验怎么让新来的 technician 3分钟内查到和当前问题最匹配的处置方案”——这问题背后藏着所有知识密集型岗位的真实痛点信息沉在文档里人却在找信息的路上消耗掉70%的有效工时。而“Agent实践1-个人知识库问答机器人”表面看是用LangChain搭个RAG流程实则是一次对“知识如何真正流动起来”的系统性重构。它不依赖大模型原生记忆不靠人工反复喂提示词而是把你的PDF、Markdown、Word甚至网页截图稍作处理变成可精准召回、可逻辑推理、可溯源验证的活知识网络。核心关键词Agent、个人知识库、问答机器人、RAG、LangChain每一个都不是孤立概念Agent是调度大脑个人知识库是燃料仓问答机器人是交互界面RAG是知识注入机制LangChain是把它们拧成一股绳的工程胶水。适合三类人直接抄作业需要快速消化行业资料的销售/咨询顾问、管理大量技术文档的工程师、以及想摆脱“搜索引擎人工筛选”低效循环的任何知识工作者。它不承诺取代你思考但能确保你每次提问都站在过去所有经验的肩膀上。2. 为什么必须放弃“纯向量检索”从知识库设计源头规避RAG三大经典翻车现场很多人搭完第一个RAG demo就兴奋地发朋友圈“我的知识库能回答问题了”结果三天后发现问“2023年Q3华东区服务器宕机率最高的三个型号”直接返回“未找到相关信息”而原文里明明有张表格清清楚楚列着数据。这不是模型不行是知识库设计从第一步就埋了雷。我见过太多翻车现场根源都在没想清楚知识库不是文档仓库而是问题答案的预演沙盘。下面拆解三个高频陷阱及根治方案。2.1 陷阱一把PDF当文本直接切块 → 语义断裂导致关键信息丢失原始PDF里一页可能同时包含标题、参数表格、注意事项小字、页脚版权声明。若用固定512字符滑动窗口切块表格会被硬生生劈成两半参数名和数值分属不同chunkRAG检索时根本无法关联。我试过某医疗设备说明书用默认切法后问“该设备最大承重是多少”返回结果里只有“最大”二字来自标题和“kg”来自页脚单位中间关键数字彻底消失。根治方案结构化预处理优先于向量化。对PDF必须用pypdf或pdfplumber先提取完整文本坐标信息识别标题层级H1/H2、表格边界、列表项。我实测unstructured库在保留表格结构上比纯OCR稳定得多尤其对扫描件它会先做版面分析再提取而非暴力OCR。对Markdown/Word利用其天然结构用正则或python-docx提取章节标题作为chunk元数据确保“问题-答案”对不被拆散。例如将“【故障代码E102】→ 原因电源电压波动15% → 处置检查稳压器输出”作为一个完整chunk而非按字符切。提示切块前务必做一次人工抽检。打开你知识库的任意3个chunk问自己“如果只看到这个chunk我能独立回答一个具体问题吗”不能则需调整切分策略。2.2 陷阱二只存文本忽略上下文锚点 → 检索结果无法溯源验证用户问“为什么X型号电机启动时异响”RAG返回一段文字“可能原因包括轴承磨损见P45、定子绕组短路见P67、安装基座松动见P89”。但用户下一步必然要查P45原文确认细节而你的知识库若没存页码、章节号、甚至原始文件名他就得手动翻PDF大海捞针。这直接摧毁信任感。根治方案为每个chunk注入不可篡改的溯源元数据。在向量化前为每个chunk打上四维标签source_file_name如电机维护手册_v2.3.pdf、page_numberPDF页码、section_title如“3.2 异响故障诊断树”、chunk_id自增序号。关键技巧page_number不能简单取PDF页码要结合pdfplumber的page.chars坐标计算实际内容所在页。曾有客户手册页眉页脚占满整页真实内容只在中间1/3区域按物理页码定位会偏差2页。存储时这些元数据必须与向量一同存入向量数据库如Chroma且在检索后原样返回。我在前端展示答案时强制显示“来源《电机维护手册_v2.3》第45页‘轴承磨损’章节”点击直接跳转PDF对应位置——这才是知识库该有的样子。2.3 陷阱三用通用embedding模型 → 领域术语召回率暴跌50%以上拿OpenAI的text-embedding-ada-002去嵌入电力系统文档“断路器开断容量”和“开关额定电流”在向量空间里距离可能比“苹果”和“香蕉”还远。因为通用模型没见过“SF6气体绝缘”这种词它只能靠字面相似度硬猜。我对比过同一份变电站运维手册用领域微调过的bge-reranker-base做embedding对“主变差动保护动作逻辑”的相关chunk召回率是87%而通用模型仅39%。根治方案领域适配embedding 双路检索兜底。第一步选型。中文场景首推bge系列bge-m3支持多语言混合检索英文选e5系列。避免用已停更的all-MiniLM-L6-v2其在长文本理解上明显劣于bge。第二步微调可选但强烈建议。用你知识库里的100个高质量QA对如“Q主变油温超限报警阈值A85℃”用HuggingFace的transformers微调bge3个epoch就能让领域术语召回率提升30%。第三步双路检索。别只信向量检索同步启用关键词检索BM25算法对“断路器”“SF6”“差动保护”这类强实体词BM25比向量更准。最终结果取向量BM25加权融合我在LangChain里用HybridSearchRetriever实现权重设为0.7:0.3实测F1值提升22%。这三步做完你的知识库才真正从“文档堆”进化成“问题响应引擎”。它不再被动等待检索而是主动预判用户可能问什么并把答案所需的上下文提前组装好。3. LangChain不是胶水是流水线调度员Agent架构下RAG模块的精准卡位与协同逻辑很多教程把LangChain讲成“调用几个链式函数的工具包”这是致命误解。在Agent架构里LangChain的核心价值是定义知识流的路由规则与状态契约。它不负责生成答案但决定“此刻该把问题交给谁、用什么数据、以什么格式”。我把整个流程拆解为四个刚性模块每个模块都有不可替代的职责边界。3.1 模块一Router路由中枢——决定问题是否该走知识库不是所有问题都该查知识库。用户问“今天北京天气怎么样”硬塞进RAG只会返回一堆无关的气象站建设规范。Router的作用是实时判断问题意图分流到不同处理器。实现逻辑用轻量级分类器如sklearn的LinearSVC训练一个二分类模型标签为“需知识库”/“无需知识库”。特征用TF-IDF提取问题关键词规则兜底含“查”“找”“哪个”“多少”“为什么”等疑问词则高概率需知识库。关键参数我训练时特意加入100条“反例”如“帮我写一封辞职信”需LLM生成、“计算11”需计算器工具避免Router把泛化问题误判为知识查询。模型准确率需≥92%低于此值宁可加一条硬规则“问题长度5字且含数字直连LLM”。Agent协同Router输出是Agent的首个Action。LangChain中用RouterChain封装其route方法返回{next: retriever}或{next: llm}Agent据此调用后续模块。这步省略Agent就成了无脑查库的傻瓜。3.2 模块二Retriever检索器——精准抓取而非海量召回Retriever不是“搜出Top K个最像的chunk”而是“找出能直接支撑答案的最小知识单元”。它的输出必须满足两个硬约束约束1数量可控。默认K3但需动态调整。问“X型号电机启动异响原因”返回3个原因即可但问“2023年华东区所有故障型号清单”需返回全部匹配项。我在Retriever里加了max_results参数由Router根据问题类型预设。约束2内容可验证。每个返回chunk必须带完整溯源元数据见2.2节且chunk内容本身是自洽的。曾有客户知识库返回一个chunk“...详见第45页”但该chunk正文空空如也——这是预处理时漏掉了页码锚点。LangChain实现不用VectorStoreRetriever改用自定义CustomRetriever类继承BaseRetriever重写_get_relevant_documents方法。关键代码段def _get_relevant_documents(self, query: str) - List[Document]: # 先BM25粗筛 bm25_docs self.bm25_retriever.get_relevant_documents(query) # 再向量精排 vector_docs self.vector_retriever.get_relevant_documents(query) # 融合去重按综合分数排序 merged_docs self._fuse_and_deduplicate(bm25_docs, vector_docs) return merged_docs[:self.max_results] # 动态截断注意max_results必须作为Retriever实例的初始化参数传入而非写死。Agent在调用时会根据Router指令动态设置这是模块解耦的关键。3.3 模块三Generator生成器——用知识约束LLM的幻觉Generator不是把检索结果拼起来扔给LLM。它的核心任务是构建一个让LLM无法胡说的提示词沙盒。我见过太多案例检索返回“轴承磨损需更换”LLM却生成“建议用502胶水临时粘合”。沙盒构建三原则角色锁定提示词开头强制声明“你是一名资深电机维修工程师只依据提供的技术文档作答不编造任何未提及的解决方案”。证据绑定明确要求“每个结论必须引用检索结果中的具体句子格式为【来源文件名P页码】”。否定清单列出绝对禁止的表述如“可能”“大概”“建议尝试”强制用“确认”“必须”“依据文档第X条”。LangChain实现用PromptTemplate定义模板关键变量{context}填入Retriever返回的chunk{question}是原始问题。模板示例你是一名专注工业电机维修的高级工程师。请严格依据以下技术文档片段回答问题不得添加任何文档未提及的信息。 【文档片段】 {context} 【问题】 {question} 【回答要求】 - 每个技术结论必须标注来源如【来源《电机维护手册_v2.3》P45】 - 禁用“可能”“或许”“一般情况下”等模糊表述 - 若文档未提供足够信息回答“依据当前知识库该问题暂无明确解决方案”效果验证用100个测试问题跑自动化评估统计“答案中引用来源的比例”和“幻觉率”答案含文档未提内容。达标线引用率≥95%幻觉率≤2%。3.4 模块四Verifier校验器——给答案装上最后一道保险即使Generator很严谨LLM仍可能因token限制截断引用或把“P45”错写成“P54”。Verifier是独立于LLM的规则引擎对Generator输出做机械式校验。校验项溯源标记完整性检查答案中每个【来源xxx】是否能在Retriever返回的chunk元数据中找到完全匹配项。事实一致性用正则提取答案中的数值如“85℃”反向搜索Retriever返回的chunk确认该数值原文存在。格式合规性强制要求答案以“结论句【来源】”为最小单元禁止跨单元合并。LangChain集成Verifier不走LLM链而是用Python函数实现。Agent执行完Generator后自动调用verify_answer(answer, retrieved_docs)函数。若校验失败触发重试机制降低max_results重新检索或向用户提示“部分信息需人工确认”。实操心得Verifier的规则必须极简。我最初写了20条校验规则结果1/3的合法答案被误杀。现在只保留3条核心规则溯源存在性、数值原文匹配、格式单元化。复杂逻辑交给LLMVerifier只做“有没有、对不对、齐不齐”三件事。这四个模块环环相扣Router是哨兵Retriever是侦察兵Generator是工程师Verifier是质检员。LangChain的价值就是让这四人能在同一套语言Python对象下无缝协作而不是各自为政。4. 从零到可交付手把手实现一个抗并发、可溯源、带校验的个人知识库问答机器人现在把前面所有设计落地为可运行代码。环境基于Python 3.10核心依赖langchain0.1.16,chromadb0.4.24,unstructured0.10.22,bge-m3embedding模型。全程不碰任何云服务所有组件本地运行Mac/Windows/Linux通吃。4.1 知识库构建结构化预处理流水线含PDF表格识别第一步永远是让文档“开口说话”。以下代码是我在3个客户项目中验证过的稳定流程# 创建虚拟环境并安装核心依赖 python -m venv rag_env source rag_env/bin/activate # Windows用 rag_env\Scripts\activate pip install langchain chromadb unstructured pypdf python-docx beautifulsoup4 # 安装bge-m3模型需torch pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu预处理核心脚本ingest.pyimport os import re from typing import List, Dict, Any from pypdf import PdfReader from unstructured.partition.pdf import partition_pdf from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings class KnowledgeIngestor: def __init__(self, data_dir: str, vector_db_path: str): self.data_dir data_dir self.vector_db_path vector_db_path # 初始化embedding模型本地加载不联网 self.embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-m3, model_kwargs{device: cpu}, # Mac M1/M2用 mps encode_kwargs{normalize_embeddings: True} ) def extract_pdf_structure(self, file_path: str) - List[Dict[str, Any]]: 用unstructured精准提取PDF结构保留表格和标题 elements partition_pdf( filenamefile_path, strategyhi_res, # 高精度模式识别表格 infer_table_structureTrue, # 关键开启表格结构识别 chunking_strategyby_title, # 按标题切分避免表格断裂 max_characters1000, # 单chunk最大字符数 new_after_n_chars800, # 每800字符强制换行 combine_text_under_n_chars300, # 小段落合并 ) documents [] for i, element in enumerate(elements): # 过滤无意义元素 if not hasattr(element, text) or not element.text.strip(): continue # 构建带元数据的Document metadata { source_file: os.path.basename(file_path), page_number: getattr(element, metadata, {}).get(page_number, 1), category: element.category, # Table, Title, Text等 chunk_id: i } # 关键表格内容转为可读文本 if element.category Table: text_content self._table_to_text(element) else: text_content element.text.strip() documents.append({ page_content: text_content, metadata: metadata }) return documents def _table_to_text(self, table_element) - str: 将unstructured识别的表格转为Markdown表格字符串 try: # unstructured的table元素有rows属性 rows getattr(table_element, rows, []) if not rows: return # 构建Markdown表 markdown_lines [| | .join(rows[0]) |] markdown_lines.append(| |.join([---] * len(rows[0])) |) for row in rows[1:]: markdown_lines.append(| | .join(row) |) return \n.join(markdown_lines) except Exception as e: return f[表格解析异常: {str(e)}] def create_vector_store(self, documents: List[Dict[str, Any]]): 创建Chroma向量库存入本地 texts [doc[page_content] for doc in documents] metadatas [doc[metadata] for doc in documents] # 使用Chroma持久化存储 vectorstore Chroma.from_texts( textstexts, metadatasmetadatas, embeddingself.embeddings, persist_directoryself.vector_db_path ) print(f✅ 向量库已保存至 {self.vector_db_path}) return vectorstore # 执行示例 if __name__ __main__: ingestor KnowledgeIngestor( data_dir./docs, # 存放PDF/MD/DOCX的目录 vector_db_path./chroma_db ) all_docs [] for file in os.listdir(./docs): if file.endswith(.pdf): pdf_docs ingestor.extract_pdf_structure(f./docs/{file}) all_docs.extend(pdf_docs) ingestor.create_vector_store(all_docs)关键实操点strategyhi_res必须开启否则表格识别率30%。infer_table_structureTrue是表格转文本的开关关掉则返回乱码。chunking_strategyby_title让切分尊重文档逻辑比basic可靠得多。persist_directory指定本地路径Chroma会自动创建chroma_db文件夹无需额外配置。注意首次运行会下载bge-m3模型~2GB耐心等待。后续运行直接复用本地缓存。4.2 Agent核心逻辑Router-Router-Retriever-Generator-Verifier闭环agent_core.py定义整个问答流水线from langchain.chains import LLMChain from langchain.prompts import PromptTemplate from langchain_community.llms import Ollama # 本地LLM用qwen:7b from langchain_community.vectorstores import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings from langchain.schema import Document import re class RAGAgent: def __init__(self, vector_db_path: str): self.vectorstore Chroma( persist_directoryvector_db_path, embedding_functionHuggingFaceEmbeddings( model_nameBAAI/bge-m3 ) ) self.llm Ollama(modelqwen:7b, temperature0.1) # 本地LLM关闭随机性 # Router轻量分类器 self.router_prompt PromptTemplate( input_variables[question], template你是一个问题路由专家。请判断以下问题是否需要查询技术文档知识库。 问题{question} 回答要求 - 若问题涉及具体产品参数、故障原因、操作步骤、历史数据等回答RETRIEVE - 若问题为通用常识、数学计算、创意写作等回答GENERATE - 只输出一个词RETRIEVE 或 GENERATE ) self.router_chain LLMChain(llmself.llm, promptself.router_prompt) # Generator提示词带沙盒约束 self.generator_prompt PromptTemplate( input_variables[context, question], template你是一名专注工业电机维修的高级工程师。请严格依据以下技术文档片段回答问题不得添加任何文档未提及的信息。 【文档片段】 {context} 【问题】 {question} 【回答要求】 - 每个技术结论必须标注来源如【来源《电机维护手册_v2.3》P45】 - 禁用“可能”“或许”“一般情况下”等模糊表述 - 若文档未提供足够信息回答“依据当前知识库该问题暂无明确解决方案” ) self.generator_chain LLMChain(llmself.llm, promptself.generator_prompt) def route_question(self, question: str) - str: Router决策 result self.router_chain.run(questionquestion).strip() return RETRIEVE if RETRIEVE in result else GENERATE def retrieve(self, question: str, k: int 3) - List[Document]: Retriever双路检索 # BM25检索用Chroma内置 bm25_results self.vectorstore.similarity_search( question, kk*2, search_typemmr # MMR减少冗余 ) # 向量检索 vector_results self.vectorstore.similarity_search( question, kk, search_typesimilarity ) # 融合简单去重 all_docs bm25_results vector_results unique_docs [] seen set() for doc in all_docs: key (doc.page_content[:50], doc.metadata.get(source_file, )) if key not in seen: seen.add(key) unique_docs.append(doc) return unique_docs[:k] def generate_answer(self, question: str, retrieved_docs: List[Document]) - str: Generator沙盒化生成 context \n\n.join([f【来源{doc.metadata[source_file]} P{doc.metadata.get(page_number, 1)}】\n{doc.page_content} for doc in retrieved_docs]) return self.generator_chain.run(contextcontext, questionquestion) def verify_answer(self, answer: str, retrieved_docs: List[Document]) - bool: Verifier三重校验 # 1. 溯源标记存在性 sources_in_answer re.findall(r【来源(.*?)】, answer) for src in sources_in_answer: if not any(src in f{doc.metadata[source_file]} P{doc.metadata.get(page_number, 1)} for doc in retrieved_docs): return False # 2. 数值一致性简化版检查答案中数字是否在原文出现 numbers_in_answer re.findall(r\d\.?\d*, answer) for num in numbers_in_answer: if not any(num in doc.page_content for doc in retrieved_docs): return False # 3. 格式单元化检查是否有未闭合的【来源】 if answer.count(【来源) ! answer.count(】): return False return True def run(self, question: str) - str: Agent主流程 # Step 1: Router route self.route_question(question) if route GENERATE: return self.llm(question) # 直连LLM # Step 2: Retriever retrieved self.retrieve(question, k3) if not retrieved: return 未在知识库中找到相关信息。 # Step 3: Generator raw_answer self.generate_answer(question, retrieved) # Step 4: Verifier if self.verify_answer(raw_answer, retrieved): return raw_answer else: # 校验失败降级重试 retrieved_fallback self.retrieve(question, k5) fallback_answer self.generate_answer(question, retrieved_fallback) return f[校验警告] 原始答案存在风险已降级重试{fallback_answer} # 使用示例 if __name__ __main__: agent RAGAgent(./chroma_db) # 测试问题 test_q X型号电机启动时异响的可能原因有哪些 print(agent.run(test_q))部署要点Ollama需提前安装https://ollama.comqwen:7b模型用ollama pull qwen:7b下载。search_typemmr最大边际相关性比纯similarity更能避免返回相似重复chunk。Verifier的数值校验做了简化生产环境可接入spaCy做实体链接但对个人知识库正则已够用。实操心得Router的prompt必须用temperature0.1否则LLM会随机输出“RETRIEVE”或“GENERATE”导致流程崩溃。这是踩过的坑。4.3 Web服务层FastAPI暴露问答接口支持并发与日志追踪最后一步让机器人走出命令行变成可多人访问的服务# app.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel import logging from agent_core import RAGAgent app FastAPI(titlePersonal Knowledge Base QA API) # 初始化Agent单例避免重复加载模型 agent RAGAgent(./chroma_db) # 配置日志 logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class QueryRequest(BaseModel): question: str class QueryResponse(BaseModel): answer: str retrieved_sources: list # 返回溯源信息供前端展示 app.post(/qa, response_modelQueryResponse) async def ask_question(request: QueryRequest): try: logger.info(f收到问题: {request.question}) # Agent执行 answer agent.run(request.question) # 提取溯源信息简化版 sources [] for match in re.finditer(r【来源(.*?)】, answer): sources.append(match.group(1)) logger.info(f返回答案: {answer[:50]}...) return {answer: answer, retrieved_sources: list(set(sources))} except Exception as e: logger.error(f处理问题时出错: {str(e)}) raise HTTPException(status_code500, detail服务器内部错误) app.get(/health) async def health_check(): return {status: healthy, vector_db: ready}启动命令# 安装FastAPI和Uvicorn pip install fastapi uvicorn # 启动服务默认端口8000 uvicorn app:app --reload --host 0.0.0.0 --port 8000并发保障技巧uvicorn默认是单进程但--workers 4可启4个worker进程轻松扛住50并发。关键Agent实例在全局初始化避免每个请求都重建向量库和LLM连接。日志记录question和answer前50字符便于事后审计问题质量。注意Mac M1/M2芯片需在Ollama启动时加--gpus all参数启用GPU加速否则qwen:7b响应慢。5. 真实世界踩坑实录那些文档没写的、但让你加班到凌晨的12个问题与解法理论再完美落地时总被现实毒打。以下是我在6个真实项目中记录的、文档绝不会提但足以让你崩溃的细节附带血泪解决方案。5.1 PDF扫描件文字识别率低别急着换OCR引擎先做三步预处理客户给的100份设备手册全是手机拍的扫描PDF直接丢进unstructured识别率不到40%。折腾两天换Tesseract、PaddleOCR效果更差。后来发现症结不在OCR而在输入质量问题1阴影干扰。手机拍摄时边缘有暗角OCR引擎误判为文字区域。解法用opencv-python做自适应阈值二值化。代码片段import cv2 img cv2.imread(scan.jpg, 0) # 自适应高斯阈值消除渐晕 binary cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) cv2.imwrite(clean_scan.jpg, binary)问题2装订孔遮挡。左侧2cm被孔洞遮住OCR跳过整行。解法用pdfplumber提取页面坐标裁剪掉装订区再OCR。问题3字体模糊。老式打印机输出的宋体字笔画粘连。解法cv2.morphologyEx做形态学开运算分离粘连笔画。最终效果预处理后识别率从38%升至92%比换OCR引擎快10倍。5.2 向量库检索结果“看似相关实则无关”检查embedding维度是否对齐某次上线后用户问“冷却液更换周期”返回结果全是“润滑油检测标准”。查向量库发现所有chunk的embedding向量长度是1024但bge-m3模型输出是1024维而Chroma默认用768维。原来HuggingFaceEmbeddings初始化时没指定model_kwargs用了默认维度。解法强制指定维度在HuggingFaceEmbeddings中加embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-m3, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True}, # 关键显式指定输出维度 show_progressTrue ) # 并确认Chroma创建时维度一致 vectorstore Chroma.from_documents( documentsdocs, embeddingembeddings, persist_directory./db )提示用len(embeddings.embed_query(test))验证维度必须与向量库创建时一致。5.3 LLM生成答案突然变长且带幻觉检查token计数与截断逻辑用qwen:7b时某次问答返回3000字答案其中2000字是胡编的“电机发展史”。排查发现Ollama的num_predict参数默认不限制LLM自由发挥。解法在Ollama初始化时强制截断self.llm Ollama( modelqwen:7b, temperature0.1, num_predict512, # 严格限制输出长度 stop[【来源] # 遇到溯源标记即停止防截断引用 )实操心得stop参数比num_predict更可靠因为LLM可能在512token内就生成完整答案但若没遇到stop词会继续胡说。5.4 知识库更新后检索失效不是向量库没刷新是缓存没清客户新增了20份文档重新运行ingest.py但旧问题仍查不到新内容。Chroma的persist_directory有缓存机制from_documents不会自动覆盖旧数据。解法每次更新前删除旧向量库文件夹import shutil if os.path.exists(./chroma_db): shutil.rmtree(./chroma_db) # 再执行ingest.py注意生产环境需加版本号如./chroma_db_v2避免误删。5.5 本地LLM响应慢如蜗牛关闭不必要的日志和采样qwen:7b在Mac M1上首字延迟15秒CPU占用95%。Ollama默认开启详细日志和温度采样。解法启动Ollama时加参数ollama serve --log-level error --num-gpu 1并在Python中self.llm Ollama( modelqwen:7b, temperature0.0, # 关闭随机性 num_ctx4096, # 显式设置上下文长度 num_predict512, verboseFalse # 关闭日志
返回列表