6. Embedding
Embedding(嵌入向量)是一种把文字、图像、音频等“非数值信息”转换成可计算的数字向量的技术。
在自然语言处理中,它最常见的用途是把句子、段落或单词转换成一串浮点数列表(通常是几百或几千维的向量),以便计算机能理解和比较它们之间的语义关系。
举个例子
假设有三句话:
- “我喜欢苹果”
- “我爱吃水果”
- “我今天去跑步了”
模型会将它们转换成如下向量(示意):
[0.12, -0.34, 0.56, ...] # 我喜欢苹果 [0.10, -0.32, 0.60, ...] # 我爱吃水果 [-0.45, 0.22, 0.11, ...] # 我今天去跑步了前两条的向量在空间上“更接近”,说明它们语义相似(都与“吃水果”相关),
而第三条则比较远,语义差别大。
Embedding 常见用途
| 场景 | 说明 |
|---|---|
| 🔎 文本相似度 | 判断两句话或文档的语义是否接近(比如检索问答) |
| 📚 向量数据库 | 将大量文本嵌入向量后存入数据库,用于 RAG 检索增强生成 |
| 🧠 聚类与分类 | 用向量表示样本,再进行聚类或分类 |
| 🗣️ 语义搜索 | 用户输入一句话,系统找出语义最相近的文本 |
| 🤖 推荐系统 | 计算物品或用户的向量相似度进行推荐 |
文本向量化工具代码实例
通过环境变量动态选择 OpenAI 或国产免费 API(如硅基流动),将文本转换为机器可理解的数字向量,以便进行后续的语义搜索或文档匹配。
注:该代码需要去https://cloud.siliconflow.cn/注意账号获取密钥,这次用他的向量模型,免费的。
.env配置如下
DEEPSEEK_API_KEY = "yourkey" DEEPSEEK_BASE_URL = "<https://api.deepseek.com>" OPENAI_API_KEY = "yourkey" OPENAI_BASE_URL = "<https://api.aigc369.com/v1>" # 向量嵌入用 硅基流动 (免费) EMBEDDING_API_KEY=yourkey EMBEDDING_BASE_URL=https://api.siliconflow.cn/v1 EMBEDDING_MODEL=BAAI/bge-m3具体代码如下
import os from dotenv import load_dotenv from langchain_openai import OpenAIEmbeddings # 1. 加载环境变量 load_dotenv() # 2. 逻辑开关:True 用 OpenAI 官方,False 用硅基流动(或其他兼容接口) use_openai = os.getenv("USE_OPENAI", "false").lower() == "true" if use_openai: config = { "api_key": os.getenv("OPENAI_API_KEY"), "base_url": os.getenv("OPENAI_BASE_URL"), # 默认是 <https://api.openai.com/v1> "model": "text-embedding-3-large" } else: # 硅基流动配置 (免费使用 BAAI 系列模型) config = { "api_key": os.getenv("EMBEDDING_API_KEY"), "base_url": os.getenv("EMBEDDING_BASE_URL"), # 例如 <https://api.siliconflow.cn/v1> "model": "BAAI/bge-m3" # 推荐模型,中文支持极好 } # 3. 初始化嵌入模型 embeddings = OpenAIEmbeddings( model=config["model"], openai_api_key=config["api_key"], openai_api_base=config["base_url"] ) # 4. 要嵌入的文本 texts = ["狗", "猫", "你好", "你好啊", "你叫什么名字?", "我叫王大锤", "很高兴认识你,大锤"] # 5. 计算并输出 try: results = embeddings.embed_documents(texts) print(f"--- 当前模型: {config['model']} ---") print(f"向量条数: {len(results)}") print(f"维度: {len(results[0])}") print(f"前 5 个数值: {results[0][:5]}") except Exception as e: print(f"运行失败,请检查 API Key 或 Base URL。错误信息: {e}")6.1 检索增强生成系统
代码实例
它先利用向量数据库从本地文档中“捞出”相关线索,再交给 DeepSeek 模型进行逻辑筛选和总结,从而确保即使检索结果存在细微偏差,也能给出精准、不被干扰的最终答案。
注:这里由于向量模型质量不好,采用了模型纠偏,把检索结果给了模型让他来帮我选择。
具体代码
import os from dotenv import load_dotenv from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.docstore.document import Document from langchain_community.vectorstores import FAISS from langchain.prompts import ChatPromptTemplate # 1. 加载环境变量 load_dotenv() # --- 配置:向量模型使用硅基流动,对话模型使用 DeepSeek --- # 向量配置 emb_config = { "api_key": os.getenv("EMBEDDING_API_KEY"), "base_url": os.getenv("EMBEDDING_BASE_URL"), "model": "BAAI/bge-m3" } # 对话配置 llm_config = { "api_key": os.getenv("DEEPSEEK_API_KEY"), "base_url": os.getenv("DEEPSEEK_BASE_URL"), "model": "deepseek-chat" } # 2. 初始化模型 embeddings = OpenAIEmbeddings( model=emb_config["model"], openai_api_key=emb_config["api_key"], openai_api_base=emb_config["base_url"] ) llm = ChatOpenAI( model=llm_config["model"], openai_api_key=llm_config["api_key"], openai_api_base=llm_config["base_url"] ) # 3. 原始文本库 texts = [ "我的个人爱好是去看电影。", "《带我飞往月球》是我最喜欢的歌曲之一。", "凯尔特人队是我最喜欢的球队。", "这是一篇关于波士顿凯尔特人的文件资料。", "篮球是一项伟大的竞技活动。", "波士顿凯尔特人队以20分的优势赢得了比赛", "《艾尔登之环》是过去15年最好的游戏之一。", "拉里·伯德是一位标志性的NBA球员。" ] documents = [Document(page_content=text) for text in texts] db = FAISS.from_documents(documents, embeddings) # 4. 核心 RAG 流程 def rag_answer(query): print(f"\\n[用户提问]: {query}") # 第一步:检索(为了确保搜到,我们取前 3 名) # BGE 模型建议加上指令前缀 processed_query = f"为这个句子生成表示以用于检索相关文章:{query}" retrieved_docs = db.similarity_search(processed_query, k=3) # 打印一下捞上来的鱼 print(f"[检索到的线索]: {[d.page_content for d in retrieved_docs]}") # 第二步:构造 Prompt 将线索喂给 DeepSeek context = "\\n".join([f"- {d.page_content}" for d in retrieved_docs]) prompt = ChatPromptTemplate.from_template(""" 你是一个严谨的助手。请仅基于以下提供的【参考资料】来回答用户的问题。 如果资料中没有相关信息,请直接回答“不知道”。 【参考资料】: {context} 【用户问题】: {question} """) # 第三步:LLM 生成答案 chain = prompt | llm response = chain.invoke({"context": context, "question": query}) print(f"[DeepSeek 回答]: {response.content}") print("-" * 60) # 5. 运行测试 if __name__ == "__main__": rag_answer("关于我的爱好你知道些什么?") rag_answer("我最喜欢的歌曲是什么?")6.2 重写问题(Rephrase)
rephrase_prompt = hub.pull("langchain-ai/chat-langchain-rephrase")是LangChain Hub的用法。它的作用是:
从官方或社区的「提示词模板仓库(LangChain Hub)」中下载一个标准 Prompt 模板。
具体解释
LangChain Hub 是一个集中存放常用 Prompt 的平台,类似于“Prompt 代码仓库”。
上面那行代码:
rephrase_prompt = hub.pull("langchain-ai/chat-langchain-rephrase")等价于做了这件事:
从 langchain-ai 的仓库里拉取一个名为 "chat-langchain-rephrase" 的提示模板,用于 重写用户输入的问题。
1.为什么要重写问题?
在RAG 对话系统中,我们希望模型能结合上下文理解用户提问。
举个例子:
用户:RAG之父是谁? (上文问过“中国大模型RAG之父是谁”)如果你直接把这个问题送去检索,Retriever 不知道“RAG之父”指谁。
所以我们要用一个 Prompt 让模型改写:
把用户的问题改写成包含上下文的独立问题: "RAG之父是谁?" ➜ "中国大模型RAG之父是谁?"这就是chat-langchain-rephrasePrompt 的功能。
代码实例
具体代码
import os from dotenv import load_dotenv from langchain_community.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain_community.vectorstores import FAISS from langchain import hub from langchain.chains import create_history_aware_retriever, create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain.prompts import ChatPromptTemplate # 1. 加载环境变量 load_dotenv() # --- 配置:DeepSeek 对话 + 硅基流动向量 --- emb_config = { "api_key": os.getenv("EMBEDDING_API_KEY"), "base_url": os.getenv("EMBEDDING_BASE_URL"), "model": "BAAI/bge-m3" } llm_config = { "api_key": os.getenv("DEEPSEEK_API_KEY"), "base_url": os.getenv("DEEPSEEK_BASE_URL"), "model": "deepseek-chat" } # 2. 加载与分块 loader = TextLoader(r"D:\\python object\\DeepSeek\\LangChain\\RAG\\demo.txt", encoding='utf-8') docs = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=40) texts = text_splitter.split_documents(docs) print(f"✅ 文档加载完成,共分割 {len(texts)} 段") # 3. 初始化 Embedding (兼容 OpenAI 格式的硅基流动) embeddings = OpenAIEmbeddings( model=emb_config["model"], openai_api_key=emb_config["api_key"], openai_api_base=emb_config["base_url"] ) db = FAISS.from_documents(texts, embeddings) # 4. 检索器 (k=3 表示检索最相关的3个片段) retriever = db.as_retriever(search_kwargs={"k": 3}) # 5. 初始化 DeepSeek 作为 LLM llm = ChatOpenAI( model=llm_config["model"], openai_api_key=llm_config["api_key"], openai_api_base=llm_config["base_url"] ) # 6. 加载重写问题提示模板 (从历史对话中提炼独立问题) rephrase_prompt = hub.pull("langchain-ai/chat-langchain-rephrase") # 7. 创建具备“历史感知”的检索器 # 注意:这里由 DeepSeek 负责将“他毕业于哪?”改写为“XX(人名)毕业于哪?” history_aware_retriever = create_history_aware_retriever( llm=llm, retriever=retriever, prompt=rephrase_prompt ) # 8. 回答用 Prompt 模板 qa_prompt = ChatPromptTemplate.from_template(""" 你是一个专业的知识问答助手。请基于以下【参考资料】回答用户问题。 如果你在资料中找不到答案,请诚实回答不知道,不要胡乱猜测。 【参考资料】: {context} 【当前对话历史】: {chat_history} 【用户问题】: {input} """) # 9. 构建文档处理链 document_chain = create_stuff_documents_chain(llm, qa_prompt) # 10. 构建最终 RAG 对话链 rag_chain = create_retrieval_chain(history_aware_retriever, document_chain) # 11. 对话循环测试 chat_history = [] def ask(question, chat_history): # 调用对话链 response = rag_chain.invoke({ "input": question, "chat_history": chat_history }) answer = response.get("answer", "") context_docs = response.get("context", []) print(f"\\n🧠 问题:{question}") print(f"💬 答案:{answer}") # 输出引用来源 if context_docs: print("📚 参考片段:") for i, doc in enumerate(context_docs, 1): print(f" [{i}] {doc.page_content.strip()[:100]}...") # 这一步至关重要:手动更新对话历史到列表中 chat_history.append({"role": "user", "content": question}) chat_history.append({"role": "assistant", "content": answer}) if __name__ == "__main__": ask("中国大模型RAG之父是谁?", chat_history) ask("他毕业于哪所学校?", chat_history)6.3 chain_type
在 LangChain 中,chain_type是构建文档问答(RAG)时的关键参数之一。它决定了检索到的文档如何与 LLM 结合使用。下面是一张完整的对比表格,帮你清楚理解常见的chain_type类型及其差异
1.LangChain 中常见的chain_type类型对比表
| chain_type 名称 | 核心逻辑 | 工作方式 | 适用场景 | 优点 | 缺点 | 是否支持多文档 |
|---|---|---|---|---|---|---|
stuff | 直接“塞入”所有检索结果 | 将所有文档拼接成一个大字符串,一次性喂给 LLM | 小规模文档、短上下文 | 简单高效、实现最容易 | 超出上下文窗口容易截断;无法处理长文档 | ✅ 是 |
map_reduce | 分段总结再汇总 | “map”阶段每个文档单独生成回答;“reduce”阶段合并结果 | 长文本、需要汇总总结的任务 | 可处理超长内容,节省上下文 | 结构复杂、速度慢 | ✅ 是 |
refine | 逐步优化答案 | 先用第1篇文档生成初稿,之后每篇文档依次优化答案 | 文档之间互相关联、需要累积推理 | 输出更完整连贯 | 耗时长,对Prompt设计要求高 | ✅ 是 |
map_rerank | 并行生成并评分 | 每个文档单独回答并附带分数,最后选出最高分答案 | 需要最可靠答案、检索结果差异大 | 精确、可控性高 | 计算量大、慢 | ✅ 是 |
self_query | 模型自生成检索条件 | 模型先生成检索查询再检索文档 | 智能RAG(自动选择检索内容) | 语义匹配强 | 依赖模型能力 | ✅ 是 |
contextual_compression | 压缩冗余上下文 | 先用LLM压缩冗余信息,再进入问答链 | 冗长文档或重复内容较多 | 降低token消耗 | 需要额外LLM调用 | ✅ 是 |
retrieval_qa | 通用封装 | 自动将检索器与问答链拼接 | 快速构建RAG原型 | 一行构建、简单易用 | 细节控制少 | ✅ 是 |
2.举个例子:
from langchain.chains import RetrievalQA qa_chain = RetrievalQA.from_chain_type( llm=ChatOpenAI(), retriever=my_retriever, chain_type="stuff" # 👈 可改为 map_reduce、refine、map_rerank 等 )| 示例 | 说明 |
|---|---|
stuff | 把检索到的3段文字拼接成一个输入直接问LLM |
map_reduce | 每段文字分别回答后再总结最终答案 |
refine | 先看第1段答一遍,然后依次用后面几段改进答案 |
map_rerank | 每段文字答一遍并评分,取分数最高的那个答案 |
3.推荐选择建议:
| 使用场景 | 推荐 chain_type | 理由 |
|---|---|---|
| 简单问答、短文档 | stuff | 最快、最稳 |
| 文档很长 | map_reduce | 能处理超长文本 |
| 逻辑性强、答案需完善 | refine | 输出连贯、逐步优化 |
| 检索质量参差不齐 | map_rerank | 通过评分自动筛选最优答案 |
6.4 RAGFromPDF
运行代码:
python -m streamlit run "D:\\python object\\DeepSeek\\LangChain\\RAG\\RAGFromPDF\\main.py"解析PDF项目代码,如有需求请私信。