【LangGraph】本文主要内容: LangGraph真正上手,以及做一些简单的案例
-
- 前言:
- [1. 项目概述](#1. 项目概述)
-
-
- [1.1. 系统组件](#1.1. 系统组件)
-
- [2. 系统实现步骤](#2. 系统实现步骤)
-
-
- [2.1 数据加载与处理](#2.1 数据加载与处理)
-
- 接下来,将文本转为向量并存入向量数据库:
-
-
- [2.2. 创建检索工具](#2.2. 创建检索工具)
- [2.3. 构建工作流程](#2.3. 构建工作流程)
-
- [2.3.1. 决策节点](#2.3.1. 决策节点)
- [2.3.2. 重写问题节点](#2.3.2. 重写问题节点)
- [2.3.3. 答案生成节点](#2.3.3. 答案生成节点)
- [2.4. 定义工作流图](#2.4. 定义工作流图)
-
- [3. 流程执行与调试](#3. 流程执行与调试)
- [4. 总结与展望](#4. 总结与展望)
上一章内容在这-> 【LangGraph】实战:支持搜索的智能代理系统(Agent + Tool)
前言:
在现代人工智能应用中,问答系统已经成为一个非常重要的方向。传统的生成式模型虽然具备一定的知识能力,但其知识来源受限于训练数据,存在"过时"或"幻觉"的问题
为了解决这一问题,检索增强生成(Retrieval-Augmented Generation,RAG)技术逐渐成为主流方案。通过将"检索"与"生成"结合,模型可以在回答问题时动态访问外部知识库,从而显著提升答案的准确性与可靠性
本文将基于 LangGraph,结合 LangChain 生态,构建一个代理式 RAG 文档问答系统,实现从文档检索到答案生成的完整闭环流程
1. 项目概述
本项目基于 LangGraph 框架构建
采用代理式 RAG(检索增强生成)架构
引入"决策 + 检索 + 重写 + 生成"的多节点流程
系统的核心思路是:
- 用户输入问题
- 系统判断是否需要检索知识库
- 若需要,则检索相关文档片段
- 对问题进行必要优化(Rewrite)
- 最终结合上下文生成答案
1.1. 系统组件
这个智能文档问答系统由以下几个核心组件组成:
- 模型(ChatOpenai/ChatTongyi):用于生成回答的语言模型
- 嵌入模型(OllamaEmbeddings):用于将文档内容转换为向量表示
- 文档加载与处理(UnstructuredMarkdownLoader):用于加载和处理 Markdown 格式的文档
- 向量存储(InMemoryVectorStore):用于存储和管理文档的向量表示,支持快速检索
- 文本分割器(RecursiveCharacterTextSplitter):用于将文档内容切分为适合模型处理的小块
- LangGraph:用于构建和管理工作流,包括节点和条件边
从整体来看,这是一套典型的:
数据层(文档) → 向量层(Embedding) → 检索层(Retriever) → 决策层(Graph Agent)
2. 系统实现步骤
2.1 数据加载与处理
首先,定义数据加载流程,将原始文档转换为模型可处理的格式
本项目的数据源为 Markdown 文件,使用 UnstructuredMarkdownLoader 加载,并用 RecursiveCharacterTextSplitter 进行切分
python
# 加载所有 .md 文件
docs = [UnstructuredMarkdownLoader(path).load() for path in paths]
#展平多层列表
docs_list = [item for sublist in docs for item in sublist]
# 定义文本分割器
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", chunk_size=1000, chunk_overlap=50
)
# 切分文档
doc_splits = text_splitter.split_documents(docs_list)
接下来,使用 OllamaEmbeddings 将文档片段转化为向量,并存储在内存向量库中,以便后续快速检索。
# 使用内存向量存储
vectorstore = InMemoryVectorStore.from_documents(
documents=doc_splits,
embedding=embeddings,
)
这里的关键点在于:
- chunk_size:控制每段文本长度
- chunk_overlap:保证上下文连续性
接下来,将文本转为向量并存入向量数据库:
2.2. 创建检索工具
在构建问答系统时,检索工具的作用非常关键。系统需要从知识库中检索与用户问题相关的文档片段
我们通过 LangChain 的 create_retriever_tool 函数,结合 InMemoryVectorStore 来创建一个检索工具
python
# 创建检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
创建检索工具
```python
retriever_tool = create_retriever_tool(
retriever,
"own_retrieve",
"搜索并返回项目信息."
)
这里的意义在于:
- 将检索能力"工具化"
- 允许 LLM 自主决定是否调用
- 为后续 Agent 决策提供基础
2.3. 构建工作流程
相比传统 LangChain Chain,LangGraph 更强调:
状态 + 节点 + 条件流转
整个系统可以抽象为一个"有向状态图"。
2.3.1. 决策节点
决策节点的作用是根据当前状态决定是否需要使用检索工具。若用户的问题需要更详细的上下文信息,则触发检索;否则,直接回答用户的问题
python
def generate_query_or_respond(state: MessagesState):
"""根据当前状态决定使用检索工具或直接响应用户"""
result = model.bind_tools([retriever_tool]).invoke(state["messages"])
return {
"messages": [result]
}
该节点的作用:
- 判断是否需要调用检索工具
- 本质是一个"工具选择器"
2.3.2. 重写问题节点
有时候,用户提出的问题可能不够清晰或者有歧义
为了提高问答质量,我们可以添加一个重写问题的步骤,尝试优化用户的问题,明确其意图
python
def rewrite_question(state: MessagesState):
"""重写原始问题,优化表达"""
question = state["messages"][0]
prompt = REWRITE_PROMPT.format(question=question)
result = model.invoke([HumanMessage(content=prompt)])
return {
"messages": [HumanMessage(content=result.content)]
}
2.3.3. 答案生成节点
在重写问题之后,系统会调用生成答案的节点,结合检索到的上下文信息来生成最终的回答
python
def generate_answer(state: MessagesState):
"""生成最终的答案"""
question = state["messages"][0].content
context = state["messages"][-1].content
prompt = GENERATE_PROMPT.format(question=question, context=context)
result = model.invoke([HumanMessage(content=prompt)])
return {
"messages": [result]
}
2.4. 定义工作流图
完成各个节点的定义后,我们将它们组合成一个完整的工作流图
图中的边表示不同节点之间的流转关系,条件边则表示在某些条件下节点的跳转
python
# 定义工作流图
agent_builder = StateGraph(MessagesState)
# 添加节点
agent_builder.add_node(generate_query_or_respond)
agent_builder.add_node("retrieve", retriever_node)
agent_builder.add_node(rewrite_question)
agent_builder.add_node(generate_answer)
# 添加普通边
agent_builder.add_edge(START, "generate_query_or_respond")
# 添加条件边
agent_builder.add_conditional_edges(
"generate_query_or_respond", tools_condition, {"tools": "retrieve", "__end__": END}
)
agent_builder.add_conditional_edges(
"retrieve", grade_documents, ["generate_answer", "rewrite_question"]
)
# 编译工作流图
graph = agent_builder.compile()
整个流程可以总结为:
用户问题 → 是否检索 → 检索 → 质量判断 →(通过/重写)→ 生成答案
3. 流程执行与调试
完成图的定义后,系统可以流式执行,并根据用户输入生成对应的回答
我们可以模拟一个用户输入并查看系统的响应
css
# 流式输出
for chunk in graph.stream(
{"messages": [HumanMessage(content="技术栈都有什么")]}
):
print(chunk)
LangGraph 支持流式执行,可以清晰看到:
- 每一步节点执行情况
- 状态如何变化
- Agent 决策路径
4. 总结与展望
本文基于 LangGraph 实现了一个完整的代理式 RAG 文档问答系统,相比传统 RAG,具备以下优势:
- 引入"决策能力",不再固定流程
- 支持问题重写,提高检索质量
- 增加结果评分机制,提升可靠性
- 使用图结构,使流程更加清晰可控
从本质上看,该系统已经从"简单的问答系统"演进为"具备感知、决策与执行能力的轻量级智能体(Agent)
ok今天的分享先到这了,下期再见~
