本地全私有化RAG搭建:使用自定义本地Embedding构建FAISS向量库报错求助
本地全私有化RAG搭建:使用自定义本地Embedding构建FAISS向量库报错求助
我太懂你这种想全本地搭建RAG的执念了——跟Hugging Face模型下载的哈希错误死磕真的会让人崩溃!你的思路完全没问题:用LM Studio本地接口生成embeddings,不依赖任何外部服务,现在卡在FAISS这一步,咱们来一步步把问题解决掉。
首先看你的报错:
TypeError: FAISS.from_texts() missing 1 required positional argument: 'embedding'
虽然你贴的代码里写的是FAISS.from_embeddings(),但报错信息显示你实际调用的是from_texts()——大概率是你运行时的代码和贴的版本有出入。不过不管怎样,核心问题是:LangChain的FAISS方法对参数格式有严格要求,而且你还缺了一个关键的「Embeddings接口对象」,用来处理后续查询时的embedding生成。
问题根源拆解
LangChain的向量库(包括FAISS)依赖统一的Embeddings抽象类来处理embedding生成逻辑——哪怕你已经提前生成了文档的embeddings,FAISS也需要这个类来把用户的查询文本转换成embedding,才能进行相似性检索。你之前只手动生成了文档的embeddings,但没给FAISS提供这个接口对象,而且参数传递也不符合要求。
解决方案:自定义Embeddings和LLM类适配本地服务
我们需要做两个关键自定义:一是包装LM Studio的embedding接口成LangChain认可的Embeddings类,二是把LM Studio的LLM接口包装成LangChain的LLM类(你后面的RetrievalQA也会用到这个)。
1. 完整修正后的代码
import requests from langchain_community.document_loaders import PyPDFLoader from langchain.chains import RetrievalQA from langchain_community.vectorstores.faiss import FAISS from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_core.embeddings import Embeddings from langchain_core.llms import LLM from typing import Optional, List, Mapping, Any lm_studio_endpoint = "http://127.0.0.1:1234" # 自定义Embeddings类:适配LM Studio的embedding接口 class LMStudioEmbeddings(Embeddings): def __init__(self, endpoint: str): self.endpoint = endpoint self.model_name = "text-embedding-granite-embedding-278m-multilingual" # 对应你LM Studio加载的embedding模型 def embed_documents(self, texts: list[str]) -> list[list[float]]: print(f"Generating embeddings for {len(texts)} chunks...") try: response = requests.post(f"{self.endpoint}/v1/embeddings", json={ "input": texts, "model": self.model_name }) response.raise_for_status() return [item['embedding'] for item in response.json()['data']] except requests.exceptions.RequestException as e: print(f"Embedding request failed: {e}") return [] def embed_query(self, text: str) -> list[float]: # 复用批量embedding接口处理单查询 return self.embed_documents([text])[0] # 自定义LLM类:适配LM Studio的completion接口 class LMStudioLLM(LLM): def __init__(self, endpoint: str): self.endpoint = endpoint self.model_name = "你的LM Studio加载的LLM模型名" # 比如:llama-3.2-3b-instruct-q4_K_M self.max_tokens = 150 @property def _llm_type(self) -> str: return "lm_studio_local" def _call( self, prompt: str, stop: Optional[List[str]] = None, **kwargs: Any, ) -> str: try: response = requests.post(f"{self.endpoint}/v1/completions", json={ "prompt": prompt, "max_tokens": self.max_tokens, "model": self.model_name, **kwargs }) response.raise_for_status() return response.json()['choices'][0]['text'].strip() except requests.exceptions.RequestException as e: print(f"LLM request failed: {e}") return "" @property def _identifying_params(self) -> Mapping[str, Any]: return {"endpoint": self.endpoint, "model_name": self.model_name} def setup_qa_system(file_path): # 加载并分割PDF try: loader = PyPDFLoader(file_path) docs = loader.load_and_split() except Exception as e: print(f"PDF加载失败: {e}") return None # 文本分块 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=50) chunks = text_splitter.split_documents(docs) # 初始化自定义Embeddings local_embeddings = LMStudioEmbeddings(lm_studio_endpoint) # 方法1:用from_documents自动生成embeddings(推荐,代码更简洁,保留元数据) vector_store = FAISS.from_documents(chunks, local_embeddings) # 如果你想手动用已生成的embeddings,用下面的代码替换上面一行: # texts = [chunk.page_content for chunk in chunks] # embeddings = local_embeddings.embed_documents(texts) # vector_store = FAISS.from_embeddings(list(zip(texts, embeddings)), local_embeddings) retriever = vector_store.as_retriever() # 初始化自定义LLM local_llm = LMStudioLLM(lm_studio_endpoint) # 构建RetrievalQA链 qa_chain = RetrievalQA.from_chain_type( llm=local_llm, chain_type="stuff", # 根据需求选择chain_type:stuff/map_reduce/refine等 retriever=retriever, return_source_documents=True # 可选:返回检索到的源文档 ) return qa_chain if __name__ == '__main__': qa_chain = setup_qa_system('Documents/OfMiceAndMen.pdf') if qa_chain: query = "What is the main theme of 'Of Mice and Men'?" result = qa_chain.invoke({"query": query}) print("=== 回答 ===") print(result['result']) print("\n=== 参考源文档 ===") for doc in result['source_documents']: print(f"- 页码: {doc.metadata['page']}, 内容片段: {doc.page_content[:200]}...")
2. 关键修改点说明
- 自定义Embeddings类:实现LangChain的
Embeddings抽象方法,把LM Studio的接口封装起来,FAISS会用它来生成文档embeddings和查询embeddings,完美适配LangChain的生态。 - 自定义LLM类:同样实现LangChain的
LLM抽象方法,这样RetrievalQA就能正确调用你的本地LLM,而不是像之前那样传一个普通函数(这也是你后续会踩的坑)。 - FAISS初始化:推荐用
from_documents(),LangChain会自动处理分块、生成embeddings的流程,代码更简洁,还能保留PDF的页码等元数据;如果坚持手动生成embeddings,用from_embeddings()时第二个参数必须传自定义的Embeddings对象,而不是embeddings列表。 - RetrievalQA调用:用
invoke()方法统一调用,而不是手动去获取retriever再调用LLM,这是LangChain的标准用法,更稳定。
额外注意事项
- 确保LM Studio里加载的embedding模型和LLM模型名和代码里的一致,端口
1234没错。 - 如果分块后embedding请求失败,可能是LM Studio的embedding模型不支持批量请求,可以在
embed_documents里把文本分批处理。 - 调试时可以在自定义类里加日志,查看请求的参数和返回的结果,方便定位问题。
备注:内容来源于stack exchange,提问作者aceofjohnonlone




