【ollama】基于ollama的lancedb的RAG
- 余弦和L2的区别
- 文本切分然后转成向量入库
- 查询文本转向量ai聚合
余弦和L2的区别
| 余弦相似度 | 余弦距离 dist_cos | L2 距离 dist_l2 | 相关性 |
|---|---|---|---|
| 0.95 | 0.05 | 0.316 | 极高相似 |
| 0.80 | 0.20 | 0.632 | 高度相似 |
| 0.70 | 0.30 | 0.775 | 比较相关 |
| 0.55 | 0.45 | 0.949 | 弱相关 |
| 0.50 | 0.50 | 1.000 | 临界 |
| 0.40 | 0.60 | 1.095 | 基本无关 |
文本切分然后转成向量入库
vectorWrite.py
fromlangchain_text_splittersimportRecursiveCharacterTextSplitterimportrequests# 0.34importlancedbfromdocximportDocumentimportreimporttime# ====================== 配置区 ======================DOCX_PATH=r"D:\OneboxBackup\kedd\allword\Flink on hudi 最佳实践.docx"DB_FOLDER="./lance_kb"TABLE_NAME="document_knowledge"# 这个是要在ollma那边去安装的模型OLLAMA_EMB_MODEL="bgezh:latest"OLLAMA_URL="http://xxxxx:11434/api/embed"VEC_DIM=512CHUNK_SIZE=500CHUNK_OVERLAP=80# ====================================================defclean_text(s:str)->str:ifnotisinstance(s,str):return""s=s.strip()s=s.replace("\n"," ")s=re.sub(r"\s+"," ",s)returnsdefget_embedding(text:str,is_query:bool=False,timeout=30):raw_txt=clean_text(text)ifnotraw_txt:raiseValueError("清洗后文本为空,无法生成向量")ifis_query:prompt=f"为这个句子生成表示以用于检索相关文章:{raw_txt}"else:prompt=raw_txt payload={"model":OLLAMA_EMB_MODEL,"input":prompt}resp=requests.post(OLLAMA_URL,json=payload,timeout=timeout)resp.raise_for_status()res_json=resp.json()emb=res_json.get("embeddings")ifnotemborlen(emb)==0:raiseRuntimeError(f"Ollama返回空embedding!文本片段:{prompt[:100]}...")# 自动兼容二维嵌套 [[向量]]ifisinstance(emb[0],list):emb=emb[0]vec_list=[float(num)fornuminemb]returnvec_listdefload_docx(file_path:str)->str:doc=Document(file_path)paragraphs=[p.text.strip()forpindoc.paragraphsifp.text.strip()]ifnotparagraphs:raiseValueError("文档读取为空,请检查docx文件!")return"\n\n".join(paragraphs)text_splitter=RecursiveCharacterTextSplitter(separators=["\n\n","。","!","?",";","\n",","],chunk_size=CHUNK_SIZE,chunk_overlap=CHUNK_OVERLAP,length_function=len)if__name__=="__main__":db=lancedb.connect(DB_FOLDER)ifTABLE_NAMEindb.list_tables():db.drop_table(TABLE_NAME)print(f"✅ 旧表{TABLE_NAME}已删除\n")full_text=load_docx(DOCX_PATH)print("文档读取完成,开始切分文本……")chunks=text_splitter.split_text(full_text)print(f"一共切分为{len(chunks)}个文本片段\n")data_rows=[]skip_count=0foridx,seg_textinenumerate(chunks):print(f"正在处理片段{idx+1}/{len(chunks)}")try:vec=get_embedding(seg_text,is_query=False)iflen(vec)!=VEC_DIM:print(f"⚠️ 片段{idx+1}向量维度异常{len(vec)},跳过")skip_count+=1continuedata_rows.append({"id":f"seg_{idx}","text":seg_text,"vector":vec})time.sleep(0.15)exceptExceptionase:skip_count+=1print(f"❌ 片段{idx+1}生成向量失败:{str(e)}")continueprint(f"\n有效写入片段:{len(data_rows)},跳过失败片段:{skip_count}")iflen(data_rows)==0:print("没有任何有效向量,终止写入!")else:tbl=db.create_table(TABLE_NAME,data=data_rows,mode="overwrite")print("✅ 创建新表并写入向量数据")# ===== 兼容0.34.0最终检索写法 =====print("\n===== 检索自测【L2距离,越小越相似】 =====")query="Flink 和 Hudi 如何整合实现增量处理?"q_vec=get_embedding(query,is_query=True)results=tbl.search(q_vec).limit(3).to_list()foriteminresults:print(f"L2距离:{item['_distance']:.4f}")print(f"文本:\n{item['text']}\n")查询文本转向量ai聚合
vectorRead.py
importlancedbimportrequestsimportre""" 高度相关:<0.95 弱相关:0.95 ~ 1.05 无关:>1.05 如果你发现很多相关文档被过滤,可以适度把阈值上调到 1.10。 """# ====================== 配置区 ======================DB_FOLDER="./lance_kb"TABLE_NAME="document_knowledge"OLLAMA_BASE_URL="http://xxxx:11434"OLLAMA_EMB_MODEL="bgezh:latest"OLLAMA_CHAT_MODEL="ds05288b:latest"TOP_K=3# 【重点修改】改成L2距离阈值!!DISTANCE_THRESHOLD=0.95# ===================================================defclean_text(s:str)->str:"""统一文本清洗,入库、查询两边保持完全一致"""ifnotisinstance(s,str):return""s=s.strip()s=s.replace("\n"," ")# 多个空白压缩为单个空格s=re.sub(r"\s+"," ",s)returnsdefget_embedding(text:str,is_query:bool=True,timeout=30):""" BGE-ZH 专用Embedding,和入库代码完全对齐 is_query=True:用户提问,带检索指令 is_query=False:知识库原始文本 """text=clean_text(text)ifis_query:prompt=f"为这个句子生成表示以用于检索相关文章:{text}"else:prompt=text url=f"{OLLAMA_BASE_URL}/api/embed"# 和入库统一使用 input 字符串,不再用inputs数组payload={"model":OLLAMA_EMB_MODEL,"input":prompt}try:resp=requests.post(url,json=payload,timeout=timeout)resp.raise_for_status()res_json=resp.json()emb=res_json["embeddings"]# 自动处理嵌套二维 [[向量]]ifisinstance(emb[0],list):emb=emb[0]returnembexceptExceptionase:print(f"【Embedding请求失败】{e}")raisedefsearch_kb(query_text:str,limit=TOP_K):"""向量检索,lancedb 0.34.0 移除metric参数,默认L2距离"""db=lancedb.connect(DB_FOLDER)tbl=db.open_table(TABLE_NAME)query_vec=get_embedding(query_text,is_query=True)# 删除 metric="cosine"results=tbl.search(query_vec).limit(limit).to_list()returnresultsdefollama_answer(user_question:str,context:str,timeout=60):"""基于知识库上下文生成答案,严格禁止幻觉"""sys_prompt=""" 你是知识库问答助手,严格遵守规则: 1. 只允许依据【参考知识库内容】作答,严禁编造任何不存在信息; 2. 如果参考资料不足以回答问题,直接回复:知识库中暂无相关信息; 3. 不要脑补、不要拓展、不要添加原文没有的数据; 4. 回答简洁通顺,不要冗余。 【参考知识库内容】 {context} """url=f"{OLLAMA_BASE_URL}/api/chat"payload={"model":OLLAMA_CHAT_MODEL,"stream":False,"temperature":0.0,"messages":[{"role":"system","content":sys_prompt.format(context=context)},{"role":"user","content":user_question}]}try:resp=requests.post(url,json=payload,timeout=timeout)resp.raise_for_status()res_json=resp.json()returnres_json["message"]["content"]exceptExceptionase:returnf"【请求Ollama对话模型异常】{str(e)}"if__name__=="__main__":print("==== LanceDB RAG查询工具(L2距离检索) ====")print("输入 quit 退出\n")whileTrue:question=input("请输入查询问题:")q=question.strip()ifq.lower()=="quit":breakifnotq:continuetry:res_list=search_kb(q)exceptExceptionase:print(f"检索异常:{e}\n")continueprint("\n----------原始检索结果----------")valid_context=[]foridx,iteminenumerate(res_list,1):dist=item["_distance"]# 打印文字修改为L2距离,防止混淆print(f"\n[{idx}] L2距离:{dist:.4f}")print(f"文本:{item['text']}")ifdist<=DISTANCE_THRESHOLD:valid_context.append(item["text"])print("\n========== AI回答 ==========")ifnotvalid_context:print("知识库中暂无相关信息")else:ctx_str="\n\n".join(valid_context)ans=ollama_answer(q,ctx_str)print(ans)print("\n========================================\n")