LangGraph 集成 RAG
一、RAG 是什么?
RAG = Retrieval-Augmented Generation(检索增强生成)
#mermaid-svg-jucQxT5Ypp5NnVsd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jucQxT5Ypp5NnVsd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jucQxT5Ypp5NnVsd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jucQxT5Ypp5NnVsd .error-icon{fill:#552222;}#mermaid-svg-jucQxT5Ypp5NnVsd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jucQxT5Ypp5NnVsd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jucQxT5Ypp5NnVsd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jucQxT5Ypp5NnVsd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jucQxT5Ypp5NnVsd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jucQxT5Ypp5NnVsd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jucQxT5Ypp5NnVsd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jucQxT5Ypp5NnVsd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jucQxT5Ypp5NnVsd .marker.cross{stroke:#333333;}#mermaid-svg-jucQxT5Ypp5NnVsd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jucQxT5Ypp5NnVsd p{margin:0;}#mermaid-svg-jucQxT5Ypp5NnVsd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jucQxT5Ypp5NnVsd .cluster-label text{fill:#333;}#mermaid-svg-jucQxT5Ypp5NnVsd .cluster-label span{color:#333;}#mermaid-svg-jucQxT5Ypp5NnVsd .cluster-label span p{background-color:transparent;}#mermaid-svg-jucQxT5Ypp5NnVsd .label text,#mermaid-svg-jucQxT5Ypp5NnVsd span{fill:#333;color:#333;}#mermaid-svg-jucQxT5Ypp5NnVsd .node rect,#mermaid-svg-jucQxT5Ypp5NnVsd .node circle,#mermaid-svg-jucQxT5Ypp5NnVsd .node ellipse,#mermaid-svg-jucQxT5Ypp5NnVsd .node polygon,#mermaid-svg-jucQxT5Ypp5NnVsd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jucQxT5Ypp5NnVsd .rough-node .label text,#mermaid-svg-jucQxT5Ypp5NnVsd .node .label text,#mermaid-svg-jucQxT5Ypp5NnVsd .image-shape .label,#mermaid-svg-jucQxT5Ypp5NnVsd .icon-shape .label{text-anchor:middle;}#mermaid-svg-jucQxT5Ypp5NnVsd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jucQxT5Ypp5NnVsd .rough-node .label,#mermaid-svg-jucQxT5Ypp5NnVsd .node .label,#mermaid-svg-jucQxT5Ypp5NnVsd .image-shape .label,#mermaid-svg-jucQxT5Ypp5NnVsd .icon-shape .label{text-align:center;}#mermaid-svg-jucQxT5Ypp5NnVsd .node.clickable{cursor:pointer;}#mermaid-svg-jucQxT5Ypp5NnVsd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jucQxT5Ypp5NnVsd .arrowheadPath{fill:#333333;}#mermaid-svg-jucQxT5Ypp5NnVsd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jucQxT5Ypp5NnVsd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jucQxT5Ypp5NnVsd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jucQxT5Ypp5NnVsd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jucQxT5Ypp5NnVsd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jucQxT5Ypp5NnVsd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jucQxT5Ypp5NnVsd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jucQxT5Ypp5NnVsd .cluster text{fill:#333;}#mermaid-svg-jucQxT5Ypp5NnVsd .cluster span{color:#333;}#mermaid-svg-jucQxT5Ypp5NnVsd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jucQxT5Ypp5NnVsd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jucQxT5Ypp5NnVsd rect.text{fill:none;stroke-width:0;}#mermaid-svg-jucQxT5Ypp5NnVsd .icon-shape,#mermaid-svg-jucQxT5Ypp5NnVsd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jucQxT5Ypp5NnVsd .icon-shape p,#mermaid-svg-jucQxT5Ypp5NnVsd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jucQxT5Ypp5NnVsd .icon-shape .label rect,#mermaid-svg-jucQxT5Ypp5NnVsd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jucQxT5Ypp5NnVsd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jucQxT5Ypp5NnVsd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jucQxT5Ypp5NnVsd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户提问
问题向量化
向量库检索 Top-K 片段
拼进 Prompt
LLM 生成回答
你的文档 txt/pdf
切分 Chunk
Embedding 向量化
向量库
为什么需要 RAG?
| 方式 | 能回答什么 | 局限 |
|---|---|---|
| 纯 LLM | 通用知识 | 不知道你的私有文档、公司内部资料 |
| RAG | 基于 你的文档 回答 | 需要先入库;检索质量影响答案 |
RAG :文档切成很多块 → 每块变成向量 → 用户提问时找最相关的几块 → 交给 LLM 作答。
二、安装依赖
cd D:\yunyanshijie\skill_test\python_study
.venv\Scripts\activate
pip install langchain-text-splitters
若需要解析PDF
from langchain_community.document_loaders import PyPDFLoader
三、RAG 四步流水线
加载文档 → 切分 Chunk → 向量化入库 → 检索 + 生成
第 1 步:加载文档(Load)
把文件读成文本。本课主练 txt;PDF 作为加分项(后面说)。
py
from pathlib import Path
text = Path("data/knowledge.txt").read_text(encoding="utf-8")
LangChain 里一条记录叫 Document,通常包含 page_content(正文)和 metadata(来源等):
py
from langchain_core.documents import Document
doc = Document(page_content=text, metadata={"source": "knowledge.txt"})
第 2 步:切分(Split)
整篇文档往往超过模型上下文,也不能整篇 embedding,所以要切成 Chunk(小块)。
| 参数 | 含义 | 建议值 |
|---|---|---|
| chunk_size | 每块最大字符数 | 300~500 |
| chunk_overlap | 相邻块重叠字符数 | 50~100 |
overlap 的作用:避免一句话被拦腰切断,导致检索时语义不完整。
py
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=80,
)
chunks = splitter.split_documents([doc])
print(f"切成 {len(chunks)} 块")
第 3 步:向量化入库(Embed + Store)
学习阶段用用内置的 内存向量库(进程结束即清空):
py
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(
model=embedding_model,
api_key=embedding_api_key,
base_url=embedding_base_url,
tiktoken_enabled=False, # 非 OpenAI 官方 Embedding 模型时必加
)
vectorstore = InMemoryVectorStore.from_documents(chunks, embeddings)
同时需要安装 transformers(LangChain 会用它做分词):
py
pip install transformers
第 4 步:检索 + 生成(Retrieve + Generate)
py
# 检索最相关的 3 块
results = vectorstore.similarity_search("公司的年假有多少天?", k=3)
context = "\n\n".join(doc.page_content for doc in results)
# 拼进 Prompt,再调 LLM
prompt = f"""根据以下资料回答问题。如果资料中没有相关信息,请说「资料中未找到」。
【资料】
{context}
【问题】
{question}
"""
核心:先检索,再生成------LLM 看到的是「问题 + 相关片段」,而不是整本文档。
四、LangGraph 集成 RAG 示例
定义状态时候新增 context
py
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
context: str
定义检索节点
py
def make_retrieve_node(vectorstore: InMemoryVectorStore):
def retrieve_node(state: AgentState):
query = ""
for msg in reversed(state["messages"]):
if isinstance(msg, HumanMessage):
query = msg.content
break
results = vectorstore.similarity_search(query, k=3)
context = "\n\n".join(doc.page_content for doc in results)
return {"context": context}
return retrieve_node
agent 节点动态修改系统提示词
py
def make_agent_node(llm: ChatOpenAI):
llm_with_tools = llm.bind_tools(tools)
def agent_node(state: AgentState):
context = state["context"]
system = SystemMessage(
content=f"""根据以下资料回答问题。如果资料中没有相关信息,请说「资料中未找到」
【参考资料】
{context}
"""
)
response = llm_with_tools.invoke([system, *state["messages"]])
return {"messages": [response]}
return agent_node
构建图
py
def build_graph(llm: ChatOpenAI, vectorstore: InMemoryVectorStore):
builder = StateGraph(AgentState)
builder.add_node("retrieve", make_retrieve_node(vectorstore))
builder.add_node("agent", make_agent_node(llm))
builder.add_node("tools", tool_node)
builder.add_edge(START, "retrieve")
builder.add_edge("retrieve", "agent")
builder.add_conditional_edges(
"agent", tools_condition, {"tools": "tools", "__end__": END}
)
builder.add_edge("tools", "agent")
checkPointer = MemorySaver()
graph = builder.compile(checkpointer=checkPointer)
return graph
rag问答
py
def rag_answer(vectorstore: InMemoryVectorStore, graph: StateGraph):
thread_id = "demo_thread_001"
config = {"configurable": {"thread_id": thread_id}}
while True:
user_input = input("你:").strip()
if user_input.lower() in ["q", "quit", "exit"]:
break
if not user_input:
continue
messages = [HumanMessage(content=user_input)]
results = graph.invoke({"messages": messages, "context": ""}, config=config)
print(results["messages"][-1].content)