❝本文系统介绍静态 RAG 与动态 RAG 的核心原理、技术对比、主流实现方案及代码实践,适合技术选型和深入学习参考。❞
RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合信息检索与文本生成的技术架构,通过从外部知识库检索相关信息来增强大语言模型的生成能力。
问题 | RAG 解决方案 |
|---|---|
LLM 知识截止日期 | 检索最新的外部知识 |
幻觉问题 | 基于检索到的事实生成 |
领域知识不足 | 接入专业知识库 |
私有数据访问 | 检索企业内部文档 |
用户问题 → 向量化 → 相似度检索 → 获取相关文档 → 构建 Prompt → LLM 生成 → 返回答案
静态 RAG 是传统的检索增强生成方法,采用「一次检索、一次生成」的线性流程:
┌─────────────────────────────────────────────────────────────┐
│ 静态 RAG 流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 用户 │ → │ 向量化│ → │ 检索 │ → │ 拼接 │ → │ 生成 │ │
│ │ 问题 │ │ Query │ │ Top-K│ │Prompt│ │ 答案 │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ 特点:流程固定,检索一次,上下文不再更新 │
└─────────────────────────────────────────────────────────────┘
「核心特点:」
虽然是静态流程,但可以通过多种技术优化检索和生成质量:
技术 | 描述 | 适用场景 |
|---|---|---|
「HyDE」 | 先让 LLM 生成假设答案,用假设答案去检索 | 问题表述模糊时 |
「Query Expansion」 | 扩展用户查询为多个变体,提升召回率 | 提高召回率 |
「Reranker」 | 检索后用交叉编码器重排序 | 提高精确度 |
「Sentence Window」 | 检索小块,返回时扩展上下文窗口 | 需要更多上下文 |
「Parent Document」 | 检索小块,返回其父文档 | 保持文档完整性 |
「Fusion RAG」 | 多路检索结果融合(RRF 算法) | 多维度召回 |
「Hybrid Search」 | 向量检索 + BM25 关键词检索混合 | 兼顾语义和关键词 |
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 1. 加载文档
loader = WebBaseLoader("https://example.com/document")
documents = loader.load()
# 2. 分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
splits = text_splitter.split_documents(documents)
# 3. 创建向量库
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./chroma_db"
)
# 4. 创建检索器
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 4}
)
# 5. 定义 Prompt
prompt = ChatPromptTemplate.from_messages([
("system", "基于以下上下文回答问题。如果不知道答案,请说不知道。\n\n上下文:{context}"),
("human", "{question}")
])
# 6. 构建 Chain
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
def format_docs(docs):
return"\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 7. 查询
answer = rag_chain.invoke("你的问题")
print(answer)
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
# 创建 Reranker
model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base")
compressor = CrossEncoderReranker(model=model, top_n=3)
# 包装检索器
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=retriever
)
# 使用带重排序的检索器
rag_chain = (
{"context": compression_retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
动态 RAG(也称 Agentic RAG)是更智能的检索增强方法,核心特点是「按需检索、迭代优化」:
┌─────────────────────────────────────────────────────────────────┐
│ 动态 RAG 流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────┐ ┌──────────┐ ┌──────────┐ │
│ │ 用户 │ → │ LLM 分析 │ → │ 需要检索?│ │
│ │ 问题 │ │ 问题复杂度│ │ │ │
│ └──────┘ └──────────┘ └────┬─────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ↓ 否 ↓ 是 ↓ 复杂 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 直接生成 │ │ 单次检索 │ │ 多轮迭代 │ │
│ └──────────┘ └────┬─────┘ └────┬─────┘ │
│ ↓ ↓ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 评估相关性│ │ 分解子问题│ │
│ └────┬─────┘ └────┬─────┘ │
│ ↓ ↓ │
│ ┌────────────┴────────────┐ │
│ │ 不满意?改写查询重新检索 │ │
│ └────────────┬────────────┘ │
│ ↓ │
│ ┌──────────┐ │
│ │ 生成答案 │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
「核心特点:」
方案 | 类型 | 核心特点 | 是否需要专门训练 |
|---|---|---|---|
「Self-RAG」 | 端到端 | 模型内置反思令牌,自动决定是否检索 | ✅ 需要 |
「CRAG」 | 工作流 | 检索后评估文档质量,支持网络搜索兜底 | ❌ 不需要 |
「Adaptive RAG」 | 工作流 | 根据问题复杂度动态选择策略 | ❌ 不需要 |
「IRCoT」 | 工作流 | 交错检索与思维链推理 | ❌ 不需要 |
Self-RAG(Self-Reflective RAG)的核心是「训练模型本身具备"反思"能力」,通过特殊的 Reflection Tokens(反思令牌)让模型自己决定检索和评估。
┌─────────────────────────────────────────────────────────────────┐
│ Self-RAG 工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户问题 │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Self-RAG 模型(内置反思能力) │ │
│ │ │ │
│ │ Step 1: 输出 [Retrieval] 或 [No Retrieval] │ │
│ │ → 模型自己决定是否需要检索 │ │
│ │ │ │
│ │ Step 2: 如果检索,输出 [Relevant] 或 [Irrelevant] │ │
│ │ → 模型自己评估文档相关性 │ │
│ │ │ │
│ │ Step 3: 生成答案,输出 [Fully Supported] 等 │ │
│ │ → 模型自己评估答案是否有依据 │ │
│ │ │ │
│ │ Step 4: 输出 [Utility:1-5] │ │
│ │ → 模型自己评估整体质量 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 最终答案 │
└─────────────────────────────────────────────────────────────────┘
Self-RAG 定义了四种反思令牌:
令牌类型 | 作用 | 可能的值 |
|---|---|---|
「Retrieve」 | 判断是否需要检索 | [Retrieval], [No Retrieval] |
「ISREL」 | 评估检索文档是否相关 | [Relevant], [Irrelevant] |
「ISSUP」 | 评估生成内容是否有文档支持 | [Fully Supported], [Partially Supported], [No Support] |
「ISUSE」 | 评估整体回答质量 | [Utility:5] 到 [Utility:1] |
「关键优势」:这些判断都是模型在训练时学会的,推理时自动输出,不需要额外的评估 prompt。
# 1. 克隆仓库
git clone https://github.com/AkariAsai/self-rag.git
cd self-rag
# 2. 创建环境
conda create -n selfrag python=3.10
conda activate selfrag
# 3. 安装依赖
pip install vllm transformers torch accelerate
# 4. 模型会从 HuggingFace 自动下载
# 可用模型:
# - selfrag/selfrag_llama2_7b (7B 版本,需要 ~16GB 显存)
# - selfrag/selfrag_llama2_13b (13B 版本,需要 ~32GB 显存)
from vllm import LLM, SamplingParams
# 初始化模型
model = LLM(
"selfrag/selfrag_llama2_7b",
dtype="half",
tensor_parallel_size=1, # GPU 数量
)
sampling_params = SamplingParams(
temperature=0.0,
top_p=1.0,
max_tokens=200,
skip_special_tokens=False# 重要:保留反思令牌以便观察
)
def format_prompt(question, paragraph=None):
"""格式化输入提示"""
prompt = f"### Instruction:\n{question}\n\n### Response:\n"
if paragraph:
prompt += f"[Retrieval]<paragraph>{paragraph}</paragraph>"
return prompt
# 示例:不带检索的生成
question = "法国的首都是哪里?"
prompt = format_prompt(question)
output = model.generate([prompt], sampling_params)
print(output[0].outputs[0].text)
# 可能输出: [No Retrieval]法国的首都是巴黎。[Utility:5]
# 示例:带检索的生成
paragraph = "巴黎是法国的首都和最大城市,位于法国北部塞纳河畔..."
prompt_with_retrieval = format_prompt(question, paragraph)
output = model.generate([prompt_with_retrieval], sampling_params)
print(output[0].outputs[0].text)
# 可能输出: [Relevant]法国的首都是巴黎...[Fully Supported][Utility:5]
from vllm import LLM, SamplingParams
from typing import List, Optional
import re
class SelfRAGSystem:
"""完整的 Self-RAG 系统实现"""
def __init__(self, model_name: str = "selfrag/selfrag_llama2_7b"):
self.model = LLM(model_name, dtype="half")
self.sampling_params = SamplingParams(
temperature=0.0,
top_p=1.0,
max_tokens=300,
skip_special_tokens=False
)
# 反思令牌定义
self.RETRIEVAL_TOKEN = "[Retrieval]"
self.NO_RETRIEVAL_TOKEN = "[No Retrieval]"
self.RELEVANT_TOKEN = "[Relevant]"
self.IRRELEVANT_TOKEN = "[Irrelevant]"
def format_prompt(self, question: str, context: Optional[str] = None) -> str:
"""格式化提示"""
prompt = f"### Instruction:\n{question}\n\n### Response:\n"
if context:
prompt += f"[Retrieval]<paragraph>{context}</paragraph>"
return prompt
def should_retrieve(self, question: str) -> bool:
"""让模型决定是否需要检索"""
prompt = self.format_prompt(question)
output = self.model.generate([prompt], self.sampling_params)[0]
text = output.outputs[0].text
# 模型会输出 [Retrieval] 或 [No Retrieval]
return self.RETRIEVAL_TOKEN in text
def evaluate_relevance(self, question: str, document: str) -> bool:
"""让模型评估文档相关性"""
prompt = self.format_prompt(question, document)
output = self.model.generate([prompt], self.sampling_params)[0]
text = output.outputs[0].text
# 模型会输出 [Relevant] 或 [Irrelevant]
return self.RELEVANT_TOKEN in text
def retrieve_documents(self, query: str, top_k: int = 3) -> List[str]:
"""
检索文档(需要接入你的向量库)
这里是示例接口,实际使用时替换为你的检索逻辑
"""
# 示例:使用 Chroma
# from langchain_community.vectorstores import Chroma
# vectorstore = Chroma(persist_directory="./chroma_db", ...)
# docs = vectorstore.similarity_search(query, k=top_k)
# return [doc.page_content for doc in docs]
# 占位返回
return []
def generate_answer(self, question: str, documents: List[str]) -> str:
"""基于文档生成答案"""
# 筛选相关文档
relevant_docs = [
doc for doc in documents
if self.evaluate_relevance(question, doc)
]
if relevant_docs:
context = "\n\n".join(relevant_docs)
prompt = self.format_prompt(question, context)
else:
prompt = self.format_prompt(question)
output = self.model.generate([prompt], self.sampling_params)[0]
return self.clean_output(output.outputs[0].text)
def clean_output(self, text: str) -> str:
"""清理输出中的反思令牌"""
tokens_to_remove = [
"[Retrieval]", "[No Retrieval]",
"[Relevant]", "[Irrelevant]",
"[Fully supported]", "[Partially supported]", "[No support]",
r"\[Utility:\d\]"
]
for token in tokens_to_remove:
text = re.sub(token, "", text)
# 移除 paragraph 标签
text = re.sub(r"<paragraph>.*?</paragraph>", "", text, flags=re.DOTALL)
return text.strip()
def run(self, question: str) -> dict:
"""
完整的 Self-RAG 流程
返回包含决策过程的详细结果
"""
result = {
"question": question,
"retrieval_needed": False,
"documents_retrieved": [],
"relevant_documents": [],
"answer": ""
}
# Step 1: 模型决定是否需要检索
if self.should_retrieve(question):
result["retrieval_needed"] = True
print(f"[Self-RAG] 模型决定:需要检索")
# Step 2: 执行检索
documents = self.retrieve_documents(question)
result["documents_retrieved"] = documents
if documents:
# Step 3: 模型评估文档相关性
for doc in documents:
if self.evaluate_relevance(question, doc):
result["relevant_documents"].append(doc)
print(f"[Self-RAG] 文档相关: {doc[:50]}...")
else:
print(f"[Self-RAG] 文档不相关: {doc[:50]}...")
# Step 4: 生成答案
result["answer"] = self.generate_answer(question, documents)
else:
print(f"[Self-RAG] 模型决定:不需要检索,直接回答")
prompt = self.format_prompt(question)
output = self.model.generate([prompt], self.sampling_params)[0]
result["answer"] = self.clean_output(output.outputs[0].text)
return result
# 使用示例
if __name__ == "__main__":
selfrag = SelfRAGSystem()
# 简单问题(可能不需要检索)
result1 = selfrag.run("1 + 1 等于多少?")
print(f"答案: {result1['answer']}")
print(f"是否检索: {result1['retrieval_needed']}")
# 需要外部知识的问题
result2 = selfrag.run("2024年诺贝尔物理学奖得主是谁?")
print(f"答案: {result2['answer']}")
print(f"是否检索: {result2['retrieval_needed']}")
CRAG(Corrective RAG,纠正式 RAG)的核心是「在工作流层面增加纠错机制」,不需要专门训练模型:
┌─────────────────────────────────────────────────────────────────┐
│ CRAG 工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户问题 → 检索文档 → 评估文档质量 │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ↓ ↓ ↓ │
│ [Correct] [Ambiguous] [Incorrect] │
│ 文档正确 文档模糊 文档错误 │
│ ↓ ↓ ↓ │
│ 直接使用 知识精炼后使用 丢弃,网络搜索 │
│ ↓ ↓ ↓ │
│ └─────────────────┼─────────────────┘ │
│ ↓ │
│ 生成最终回答 │
└─────────────────────────────────────────────────────────────────┘
「三个核心组件:」
组件 | 作用 |
|---|---|
「Retrieval Evaluator」 | 评估检索文档的质量(Correct/Ambiguous/Incorrect) |
「Knowledge Refinement」 | 对模糊文档进行知识提炼,去除无关信息 |
「Web Search Fallback」 | 当本地检索失败时,使用网络搜索兜底 |
「与 Self-RAG 的核心区别:」
┌─────────────────────────────────────────────────────────────────┐
│ CRAG │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 你定义 │ → │ 你定义 │ → │ 你定义 │ │
│ │ 检索逻辑 │ │ 评估Prompt│ │ 分支逻辑 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ LLM 只是执行你定义的评估 prompt,流程由你控制 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Self-RAG │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 专门训练的模型 │ │
│ │ 内置: 是否检索? → 文档相关吗? → 回答有依据吗? │ │
│ │ [Retrieval] [Relevant] [Fully Supported] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ 模型本身会输出判断令牌,你只需解析这些令牌 │
└─────────────────────────────────────────────────────────────────┘
# 创建环境
conda create -n crag python=3.11
conda activate crag
# 安装依赖
pip install langgraph langchain langchain-openai langchain-community
pip install chromadb tiktoken tavily-python
pip install python-dotenv
# 可选:使用本地模型
pip install langchain-ollama
「环境变量配置(.env 文件):」
OPENAI_API_KEY=your_openai_api_key
TAVILY_API_KEY=your_tavily_api_key # 用于网络搜索
"""
CRAG (Corrective RAG) 完整实现
基于 LangGraph 的工作流编排
"""
import os
from typing import List, Literal
from typing_extensions import TypedDict
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langgraph.graph import StateGraph, START, END
load_dotenv()
# ============================================================
# 1. 定义状态
# ============================================================
class GraphState(TypedDict):
"""工作流状态定义"""
question: str # 用户问题
documents: List[Document] # 检索到的文档
generation: str # 生成的回答
web_search_needed: bool # 是否需要网络搜索
relevance_scores: List[str] # 文档相关性评分
# ============================================================
# 2. 初始化组件
# ============================================================
# LLM(可替换为本地模型)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 向量存储
embeddings = OpenAIEmbeddings()
# 网络搜索工具
web_search_tool = TavilySearchResults(max_results=3)
# ============================================================
# 3. 定义评估器和生成器
# ============================================================
# 文档相关性评估 Prompt
relevance_prompt = ChatPromptTemplate.from_messages([
("system", """你是一个文档相关性评估专家。
评估给定的文档是否与用户问题相关。
只回答 'yes' 或 'no'。
- 'yes': 文档包含与问题相关的关键信息
- 'no': 文档与问题无关或信息不足
"""),
("human", """问题: {question}
文档内容:
{document}
该文档是否与问题相关?"""),
])
relevance_chain = relevance_prompt | llm | StrOutputParser()
# 知识精炼 Prompt
refinement_prompt = ChatPromptTemplate.from_messages([
("system", """你是一个知识提炼专家。
从给定的文档中提取与用户问题最相关的关键信息。
去除无关内容,只保留核心知识点。
如果文档完全无关,返回 "无相关信息"。
"""),
("human", """问题: {question}
文档内容:
{document}
请提炼关键信息:"""),
])
refinement_chain = refinement_prompt | llm | StrOutputParser()
# 生成回答 Prompt
generation_prompt = ChatPromptTemplate.from_messages([
("system", """你是一个知识问答助手。
基于提供的上下文信息回答用户问题。
如果上下文信息不足,请诚实说明。
回答要准确、简洁、有条理。
"""),
("human", """上下文信息:
{context}
问题: {question}
请回答:"""),
])
generation_chain = generation_prompt | llm | StrOutputParser()
# ============================================================
# 4. 定义节点函数
# ============================================================
def retrieve(state: GraphState) -> GraphState:
"""
检索节点:从向量库检索相关文档
"""
print("---[节点] RETRIEVE: 执行检索---")
question = state["question"]
try:
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
documents = vectorstore.similarity_search(question, k=4)
print(f" 检索到 {len(documents)} 个文档")
except Exception as e:
print(f" 检索失败: {e}")
documents = []
return {
**state,
"documents": documents,
"relevance_scores": []
}
def grade_documents(state: GraphState) -> GraphState:
"""
评估节点:评估每个文档的相关性
这是 CRAG 的核心 - Retrieval Evaluator
"""
print("---[节点] GRADE: 评估文档相关性---")
question = state["question"]
documents = state["documents"]
relevant_docs = []
relevance_scores = []
for i, doc in enumerate(documents):
score = relevance_chain.invoke({
"question": question,
"document": doc.page_content
}).strip().lower()
relevance_scores.append(score)
if score == "yes":
relevant_docs.append(doc)
print(f" ✓ 文档 {i+1} 相关")
else:
print(f" ✗ 文档 {i+1} 不相关")
# 判断是否需要网络搜索(没有相关文档时触发)
web_search_needed = len(relevant_docs) == 0
if web_search_needed:
print(" → 没有相关文档,需要网络搜索")
else:
print(f" → 找到 {len(relevant_docs)} 个相关文档")
return {
**state,
"documents": relevant_docs,
"relevance_scores": relevance_scores,
"web_search_needed": web_search_needed
}
def refine_knowledge(state: GraphState) -> GraphState:
"""
知识精炼节点:提炼文档中的关键信息
这是 CRAG 的 Knowledge Refinement 组件
"""
print("---[节点] REFINE: 精炼知识---")
question = state["question"]
documents = state["documents"]
refined_docs = []
for i, doc in enumerate(documents):
refined_content = refinement_chain.invoke({
"question": question,
"document": doc.page_content
})
if refined_content != "无相关信息":
refined_docs.append(Document(page_content=refined_content))
print(f" 文档 {i+1} 精炼完成")
else:
print(f" 文档 {i+1} 无有效信息,跳过")
return {
**state,
"documents": refined_docs
}
def web_search(state: GraphState) -> GraphState:
"""
网络搜索节点:当本地检索失败时使用
这是 CRAG 的 Fallback 机制
"""
print("---[节点] WEB SEARCH: 执行网络搜索---")
question = state["question"]
try:
search_results = web_search_tool.invoke({"query": question})
web_docs = [
Document(page_content=result["content"])
for result in search_results
]
print(f" 网络搜索返回 {len(web_docs)} 个结果")
except Exception as e:
print(f" 网络搜索失败: {e}")
web_docs = []
return {
**state,
"documents": web_docs
}
def generate(state: GraphState) -> GraphState:
"""
生成节点:基于文档生成最终回答
"""
print("---[节点] GENERATE: 生成回答---")
question = state["question"]
documents = state["documents"]
# 合并文档内容
if documents:
context = "\n\n".join([doc.page_content for doc in documents])
else:
context = "没有找到相关信息。"
# 生成回答
generation = generation_chain.invoke({
"question": question,
"context": context
})
return {
**state,
"generation": generation
}
# ============================================================
# 5. 定义条件边
# ============================================================
def decide_to_search(state: GraphState) -> Literal["web_search", "refine"]:
"""
决策函数:根据文档评估结果决定下一步
- 如果没有相关文档 → 网络搜索
- 如果有相关文档 → 知识精炼
"""
if state.get("web_search_needed", False):
print("---[决策] 转向网络搜索---")
return"web_search"
else:
print("---[决策] 转向知识精炼---")
return"refine"
# ============================================================
# 6. 构建工作流图
# ============================================================
def build_crag_graph():
"""构建 CRAG 工作流图"""
workflow = StateGraph(GraphState)
# 添加节点
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade_documents", grade_documents)
workflow.add_node("refine", refine_knowledge)
workflow.add_node("web_search", web_search)
workflow.add_node("generate", generate)
# 定义边
workflow.add_edge(START, "retrieve")
workflow.add_edge("retrieve", "grade_documents")
# 条件边:根据评估结果决定下一步
workflow.add_conditional_edges(
"grade_documents",
decide_to_search,
{
"web_search": "web_search",
"refine": "refine"
}
)
workflow.add_edge("refine", "generate")
workflow.add_edge("web_search", "generate")
workflow.add_edge("generate", END)
return workflow.compile()
# ============================================================
# 7. 辅助函数:创建向量库
# ============================================================
def create_vectorstore(texts: List[str] = None, urls: List[str] = None):
"""
创建或更新向量库
Args:
texts: 文本列表
urls: URL 列表(会自动抓取内容)
"""
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
documents = []
# 从 URL 加载
if urls:
for url in urls:
try:
loader = WebBaseLoader(url)
documents.extend(loader.load())
print(f"已加载: {url}")
except Exception as e:
print(f"加载失败 {url}: {e}")
# 从文本加载
if texts:
for text in texts:
documents.append(Document(page_content=text))
ifnot documents:
print("没有文档可加载")
returnNone
# 分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
splits = text_splitter.split_documents(documents)
# 创建向量库
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./chroma_db"
)
print(f"向量库创建完成,包含 {len(splits)} 个文档块")
return vectorstore
# ============================================================
# 8. 主函数
# ============================================================
def main():
"""CRAG 使用示例"""
# 构建工作流
app = build_crag_graph()
# 可视化工作流(可选)
try:
from IPython.display import Image, display
display(Image(app.get_graph().draw_mermaid_png()))
except:
pass
# 运行查询
print("\n" + "="*60)
print("CRAG 系统启动")
print("="*60 + "\n")
result = app.invoke({
"question": "什么是量子计算?它有哪些应用?",
"documents": [],
"generation": "",
"web_search_needed": False,
"relevance_scores": []
})
print("\n" + "="*60)
print("最终回答:")
print("="*60)
print(result["generation"])
if __name__ == "__main__":
# 首次运行时,可以先创建向量库
# create_vectorstore(urls=["https://example.com/doc1", "https://example.com/doc2"])
main()
from langchain_ollama import ChatOllama
# 替换 LLM 为本地模型
llm = ChatOllama(
model="qwen2.5:7b", # 或 llama3.1:8b, deepseek-r1:8b 等
temperature=0
)
# 其余代码保持不变
RAGFlow 是一个「支持静态和动态两种模式的 RAG 平台」,不是纯粹的动态 RAG:
模式 | 说明 |
|---|---|
「Chat 助手」 | 静态 RAG,单次检索 + 生成 |
「Agent 工作流」 | 动态 RAG,通过 Graph 编排实现多步骤迭代 |
RAGFlow 的动态能力通过 「Graph 工作流编排」 实现:
用户输入 → Graph 工作流引擎 → 节点1(判断) → 节点2(检索) → 节点3(评估)
↓ 不满意
节点4(改写查询) → 重新检索 → ...
↓ 满意
节点5(生成回答)
「关键点:」
pip install ragflow-sdk
from ragflow_sdk import RAGFlow
# 1. 初始化客户端
rag = RAGFlow(
api_key="<YOUR_API_KEY>",
base_url="http://<YOUR_HOST>:9380"
)
# 2. 创建数据集
dataset = rag.create_dataset(
name="my_knowledge_base",
embedding_model="BAAI/bge-large-zh-v1.5@BAAI",
chunk_method="naive"
)
# 3. 上传文档
dataset.upload_documents([
{"display_name": "doc.pdf", "blob": open("doc.pdf", "rb").read()}
])
# 4. 解析文档
dataset.async_parse_documents(["doc_id"])
# 5. 创建对话助手
assistant = rag.create_chat(
name="my_assistant",
dataset_ids=[dataset.id],
llm={"model_name": "gpt-3.5-turbo"},
prompt={"similarity_threshold": 0.2}
)
# 6. 对话
session = assistant.create_session()
for msg in session.ask("你的问题", stream=True):
print(msg.content, end="")
# 创建 Agent(需要先配置工作流 DSL)
agent = rag.create_agent(
title="智能助手",
description="支持多轮检索的助手",
dsl={
"canvas": {
"nodes": [...], # 节点定义
"edges": [...] # 边定义
}
}
)
# 列出已有 Agent
agents = rag.list_agents()
维度 | 静态 RAG | 动态 RAG |
|---|---|---|
「检索次数」 | 1 次 | 多次(按需) |
「决策能力」 | 无 | LLM 自主决策或规则驱动 |
「适用场景」 | 简单问答、FAQ | 复杂推理、多跳问题 |
「延迟」 | 低 | 较高 |
「实现难度」 | 简单 | 复杂 |
「成本」 | 低(单次 LLM 调用) | 高(多次 LLM 调用) |
维度 | Self-RAG | CRAG (LangGraph) |
|---|---|---|
「实现方式」 | 专门微调的模型 | 工作流编排 + 普通 LLM |
「"是否检索"判断」 | 模型内部自动决定 | 你写 prompt 让 LLM 判断 |
「模型要求」 | 必须用 selfrag 模型 | 任意 LLM(OpenAI、本地等) |
「GPU 需求」 | 必须(16GB+) | 可选(用 API 则不需要) |
「安装复杂度」 | 高(vLLM + 大模型) | 低(pip install) |
「可定制性」 | 低(逻辑固化在模型中) | 高(可自定义任何逻辑) |
「推理延迟」 | 较低(单次推理含判断) | 较高(多次 LLM 调用) |
「适合场景」 | 对事实性要求极高、研究 | 快速原型、业务迭代 |
┌─────────────────────────────────────────────────────────────────┐
│ 选型决策树 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 问题复杂度如何? │
│ │ │
│ ├── 简单(单跳问答)→ 静态 RAG + Reranker │
│ │ │
│ └── 复杂(多跳推理)→ 动态 RAG │
│ │ │
│ ├── 有 GPU 资源? │
│ │ │ │
│ │ ├── 是 → Self-RAG(效果最好) │
│ │ │ │
│ │ └── 否 → CRAG + API(灵活易用) │
│ │ │
│ └── 需要高度定制? │
│ │ │
│ ├── 是 → CRAG / LangGraph │
│ │ │
│ └── 否 → RAGFlow Agent │
│ │
└─────────────────────────────────────────────────────────────────┘
「具体建议:」
场景 | 推荐方案 |
|---|---|
快速原型验证 | CRAG (LangGraph) + OpenAI API |
生产环境简单问答 | 静态 RAG + Reranker + 混合检索 |
生产环境复杂问答 | CRAG + 本地模型 或 RAGFlow Agent |
研究/对事实性要求极高 | Self-RAG |
企业级知识库平台 | RAGFlow / Dify / FastGPT |
论文 | 链接 |
|---|---|
Self-RAG: Learning to Retrieve, Generate and Critique | arXiv:2310.11511 |
Corrective Retrieval Augmented Generation | arXiv:2401.15884 |
Adaptive-RAG | arXiv:2403.14403 |
项目 | 链接 | 说明 |
|---|---|---|
Self-RAG | GitHub | 官方实现 |
LangGraph | GitHub | 工作流编排框架 |
RAGFlow | GitHub | RAG 平台 |
LlamaIndex | GitHub | RAG 框架 |
LangChain | GitHub | LLM 应用框架 |
模型 | HuggingFace |
|---|---|
Self-RAG Llama2-7B | selfrag/selfrag_llama2_7b |
Self-RAG Llama2-13B | selfrag/selfrag_llama2_13b |
BGE Reranker | BAAI/bge-reranker-base |