6. Embedding

Embedding(嵌入向量) 是一种把文字、图像、音频等“非数值信息”转换成 可计算的数字向量 的技术。

在自然语言处理中,它最常见的用途是把 句子、段落或单词 转换成一串浮点数列表(通常是几百或几千维的向量),以便计算机能理解和比较它们之间的语义关系。


举个例子

假设有三句话:

  1. “我喜欢苹果”
  2. “我爱吃水果”
  3. “我今天去跑步了”

模型会将它们转换成如下向量(示意):

[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-rephrase Prompt 的功能。


代码实例

具体代码

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项目代码,如有需求请私信。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐