ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于LangGraph与Streamlit构建本地化RAG与Agent应用

基于LangGraph与Streamlit构建本地化RAG与Agent应用 简介本资源是一套面向AI应用开发者的实战项目代码包聚焦基于本地大模型的RAG增强问答与智能体Agent系统构建适用于具备Python基础、希望掌握LangGraph图编排与Streamlit轻量Web部署的中级开发者。项目完整实现检索增强生成流程与多角色Agent协同逻辑可快速应用于客服助手、知识库问答、教育辅导等场景。压缩包共12个文件含10个核心Python模块如naive_rag.py实现检索链路、agent_chat_page.py封装Agent交互页、st_main.py为Streamlit主入口、1个依赖清单requirements.txt及1个LICENSE文件整体仅12KB结构精简、开箱即用。已有226人学习下载提供从零搭建RAGAgent双模式对话系统的完整代码骨架、清晰模块划分webui/与utils/分层、本地模型调用占位设计及可扩展的工具集成接口助读者深入理解LangGraph状态流转与Streamlit动态UI联动机制。1. 项目缘起为什么选择这个技术栈最近在社区里看到不少朋友在讨论如何把大模型的能力真正“落地”尤其是想摆脱对云端API的依赖搞一个完全在自己电脑或内网服务器上跑起来的智能应用。需求很明确既要能回答基于特定文档的问题RAG又要能让模型自己规划步骤、调用工具去完成任务Agent还得有个像样的界面让非技术人员也能用。听起来是个大工程对吧但实际折腾下来我发现用LangGraph和Streamlit这套组合拳完全可以从零搭建一个功能完整、完全基于本地模型的RAG与Agent应用。先说为什么是它们。LangGraph是 LangChain 团队出的一个新框架它核心解决的是复杂工作流的编排问题。传统的链式调用Chain是线性的A做完做BB做完做C。但Agent任务往往不是线性的它可能需要根据中间结果决定下一步是查资料、写代码还是直接给出答案这种带“循环”和“分支”的图状结构正是LangGraph的拿手好戏。它让你能用清晰的代码定义出Agent的“大脑”运作逻辑。而Streamlit简直是快速构建数据应用的原型神器。你几乎不用写前端代码用Python脚本就能生成一个交互式Web界面特别适合我们这种更关注后端逻辑的开发者能快速把能力暴露给用户。至于模型我们坚持“本地化”。这不仅仅是出于数据隐私的考虑更重要的是可控性和成本。你不需要为每一次API调用付费也不担心服务突然不可用。我们将使用Ollama这个工具来在本地运行开源大模型比如Llama 3、Qwen或Mistral等。它把模型拉取、加载和推理服务都封装好了我们通过一个简单的HTTP接口就能调用省去了部署深度学习框架的麻烦。所以这个项目的目标就是串联起Ollama提供的本地模型能力、LangGraph编排的智能体工作流以及Streamlit打造的交互界面实现一个私有化、可定制、具备记忆和规划能力的AI助手。下面我就把从环境准备到最终实现的完整路径包括我踩过的坑和总结的技巧毫无保留地分享出来。2. 核心工具链详解与选型考量工欲善其事必先利其器。在动手写代码之前我们需要对核心工具链的每一个环节有清晰的认识并理解为什么它们是当前场景下的较优解。2.1 模型服务层Ollama的轻量化哲学为什么是Ollama在本地运行大模型有几个绕不开的选项直接使用transformers库加载、使用vLLM或TGI部署高性能推理服务。但对于我们这个旨在快速集成和原型验证的项目来说Ollama的优势非常突出开箱即用一条命令ollama run llama3就能把模型跑起来自动处理模型下载和加载。统一的API它提供了一个类OpenAI的API接口/api/chat这意味着我们可以直接使用LangChain或ChatOpenAI的兼容库来调用切换模型几乎不用改代码。资源友好它针对消费级硬件比如带显卡的笔记本电脑做了优化并且支持量化模型让我们在有限的显存下也能运行较大的模型。实操要点安装直接去Ollama官网下载安装包安装后命令行就能用。拉取模型不是所有模型都默认就有。你需要用ollama pull命令拉取例如ollama pull qwen2:7b。建议从7B参数量的模型开始尝试对硬件要求较低。验证服务运行ollama run llama3后其实它已经在后台启动了一个服务默认端口11434。你可以通过curl http://localhost:11434/api/generate -d {model: llama3, prompt:Hello}来测试。注意首次拉取模型可能需要较长时间取决于你的网络和模型大小。务必确保磁盘有足够空间一个7B模型大约4-8GB。2.2 智能体编排层LangGraph的图状态机LangGraph可以理解为LangChain的“升级版”专为复杂、有状态的智能体工作流设计。它的核心概念是“图”Graph和“状态”State。状态State这是一个字典或Pydantic模型定义了工作流中需要流转和更新的所有数据。比如一定会有的messages列表记录对话历史还可能有intermediate_steps记录工具调用结果、next决定下一步做什么等。节点Node图中的一个功能单元。一个节点就是一个函数它接收当前“状态”执行一些操作比如调用模型、执行工具然后返回更新后的“状态”。关键的是节点可以修改状态中的任何字段。边Edge决定工作流的走向。通常我们会定义一个“路由逻辑”根据状态中的某个字段比如next的值来决定下一个执行哪个节点。这就实现了循环比如反复调用工具直到满足条件和条件分支。为什么它比单纯的Chain更适合Agent想象一个客服Agent用户问“帮我查一下订单12345的状态然后告诉我预计送达时间”。一个简单的Chain可能先调用“查询订单”工具再把结果塞给模型生成回复。但如果查询结果说“订单不存在”呢Agent应该有能力决定是反问用户订单号是否正确还是直接结束对话。这个“决策”环节在LangGraph里可以通过一个专门的“路由节点”来优雅处理让工作流动态适应不同情况。2.3 应用界面层Streamlit的极速开发Streamlit的理念是“将脚本变成Web应用”。对于AI应用原型来说它解决了前端展示、用户输入和结果渲染的痛点。快速迭代你保存代码网页自动刷新所见即所得。丰富的组件st.chat_input用于聊天框st.chat_message用于渲染对话气泡st.sidebar做侧边栏配置st.expander折叠复杂信息基本满足一个对话应用的UI需求。会话状态管理st.session_state可以很方便地在多次交互间保持数据比如保存整个对话历史这对实现多轮对话的Agent至关重要。它的缺点也很明显不适合构建复杂、高定制化的前端当应用逻辑非常复杂时脚本式的开发可能显得有些混乱。但对于我们这个项目的目标——快速验证和交付一个可用的本地AI助手——来说它是完美的选择。3. 项目架构设计与核心模块拆解在开始写代码之前我们先在纸上或脑海里把整个应用的架构画出来。一个清晰的架构能避免后期代码纠缠不清。我们的系统主要分为四个层次数据持久层负责处理用户上传的文档进行文本分割、向量化并存储到向量数据库。这是RAG能力的基石。核心引擎层这是大脑。包含两个核心部分RAG检索链当用户问题需要参考文档时从此处触发。它负责从向量库检索相关片段并组装成增强的提示词Prompt给模型。LangGraph智能体定义Agent的思考和工作流程。它内部会判断何时调用RAG检索链何时调用其他工具如计算器、网络搜索模拟并管理多轮对话的记忆。模型服务层即本地运行的Ollama服务为引擎层提供模型推理能力。应用界面层Streamlit构建的Web界面负责收集用户输入、调用引擎层处理、并展示模型回复和中间过程。模块之间的数据流大致是用户从界面输入问题 - Streamlit调用LangGraph智能体 - 智能体根据状态决定行动 - 若需知识调用RAG检索链获取上下文 - RAG链查询向量数据库 - 智能体将问题上下文发给Ollama模型 - 模型生成回复或工具调用请求 - 智能体执行工具可能循环- 最终回复返回给Streamlit界面展示。接下来我们深入到每一个核心模块的构建细节中。4. 基础环境搭建与依赖管理任何项目的第一步都是准备好战场。这里我强烈建议使用conda或venv创建独立的Python环境避免包版本冲突。步骤1创建并激活环境conda create -n local_agent python3.10 conda activate local_agent步骤2安装核心Python库创建一个requirements.txt文件内容如下streamlit1.28.0 langgraph0.0.20 langchain0.1.0 langchain-community0.0.10 chromadb0.4.0 sentence-transformers2.2.0 pydantic2.0.0 httpx然后安装pip install -r requirements.txt版本选择的心得LangGraph和LangChain目前迭代很快API变动较大。建议锁定一个相对较新且稳定的次要版本如示例中的版本以免遇到已废弃的写法。ChromaDB是一个轻量级、嵌入式的向量数据库非常适合本地开发和生产部署。sentence-transformers用于本地生成文本向量我们选一个多语言效果好的模型比如all-MiniLM-L6-v2。步骤3安装并启动Ollama如前所述去官网下载安装。安装后在终端启动模型服务# 拉取一个合适的模型例如小巧高效的Qwen2 ollama pull qwen2:7b # 以API服务模式运行后台运行 ollama serve # 或者直接运行一个交互式对话来测试这会启动服务 ollama run qwen2:7b确保服务在http://localhost:11434正常运行。5. 构建RAG知识库从文档到向量RAG检索增强生成的核心在于“检索”。我们需要把非结构化的文档TXT PDF Word变成结构化的、可快速查询的知识片段。5.1 文档加载与文本分割首先我们需要读取各种格式的文档。LangChain提供了丰富的DocumentLoader。from langchain_community.document_loaders import TextLoader, PyPDFLoader, Docx2txtLoader import os def load_documents(directory_path): documents [] for filename in os.listdir(directory_path): file_path os.path.join(directory_path, filename) if filename.endswith(.txt): loader TextLoader(file_path, encodingutf-8) elif filename.endswith(.pdf): loader PyPDFLoader(file_path) elif filename.endswith(.docx): loader Docx2txtLoader(file_path) else: continue loaded_docs loader.load() documents.extend(loaded_docs) return documents加载后的文档是完整的但直接向量化效果不好。我们需要“切块”。from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个块大约500字符 chunk_overlap50, # 块之间重叠50字符避免语义被硬切断 separators[\n\n, \n, 。, , , , , , ] # 按此优先级分割 ) split_docs text_splitter.split_documents(documents)分割策略的考量chunk_size是关键。太小会丢失上下文太大会引入噪声且检索效率低。500-1000对于通用文档是个不错的起点。重叠是为了保证边界上的信息不丢失。5.2 向量化与存储接下来我们把文本块变成向量嵌入并存入向量数据库。from langchain.embeddings import HuggingFaceEmbeddings from langchain.vectorstores import Chroma # 1. 初始化本地嵌入模型 embed_model HuggingFaceEmbeddings( model_namesentence-transformers/all-MiniLM-L6-v2, model_kwargs{device: cpu}, # 如果显存够可改为 cuda encode_kwargs{normalize_embeddings: True} # 归一化有利于相似度计算 ) # 2. 创建向量数据库 persist_directory ./chroma_db vectordb Chroma.from_documents( documentssplit_docs, embeddingembed_model, persist_directorypersist_directory ) vectordb.persist() # 持久化到磁盘这里有几个要点all-MiniLM-L6-v2模型约80MB效果和速度平衡得很好支持多语言。Chroma持久化后下次启动可以直接加载无需重新计算嵌入节省大量时间。嵌入Embedding的质量直接决定检索质量。如果领域特殊如医学、法律可以考虑用领域数据微调嵌入模型或换用更大的模型。5.3 构建检索链有了向量库我们就可以构建一个检索器并将其集成到一个“链”中这个链能自动完成“检索相关文本 - 组装提示词 - 调用模型”的流程。from langchain.chains import RetrievalQA from langchain_community.llms import Ollama # 连接到本地Ollama服务 llm Ollama(base_urlhttp://localhost:11434, modelqwen2:7b) # 从磁盘加载已有的向量数据库 vectordb Chroma( persist_directorypersist_directory, embedding_functionembed_model ) # 创建检索QA链 retriever vectordb.as_retriever(search_kwargs{k: 3}) # 检索最相关的3个片段 rag_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最简单的方式将所有检索到的文本“塞”进提示词 retrieverretriever, return_source_documentsTrue # 返回源文档便于调试 ) # 测试 result rag_chain.invoke({query: LangGraph是什么}) print(result[result]) print(来源, [doc.metadata.get(source) for doc in result[source_documents]])chain_typestuff是最直接的方式但如果检索到的文本总长度超过模型上下文限制会报错。对于长文档可以考虑map_reduce或refine等更复杂但能处理长文本的链类型。6. 定义LangGraph智能体打造会思考的工作流这是整个项目最核心也最有趣的部分。我们将创建一个具备“思考-行动-观察”循环的智能体。6.1 定义状态State状态就像智能体的工作记忆记录了对话和任务执行的所有信息。from typing import TypedDict, List, Annotated, Union from langgraph.graph.message import add_messages import operator class AgentState(TypedDict): # 对话消息历史LangGraph内置的add_messages函数能帮我们自动追加 messages: Annotated[List, add_messages] # 用户当前输入的问题 user_input: str # 中间步骤记录工具调用和结果 intermediate_steps: Annotated[List[tuple], operator.add] # 下一步该执行哪个节点比如 “call_tool”, “generate_response” next: str我们使用TypedDict和Annotated来定义状态结构。Annotated中的add_messages和operator.add是“归约器”它们定义了当多个节点同时修改同一个字段时如何合并这些修改例如追加到列表。6.2 创建工具Tools工具是Agent延伸的手脚。我们先定义两个简单的工具一个RAG工具调用前面建的链一个计算器。from langchain.tools import Tool from langchain_core.tools import tool # RAG工具 tool def retrieve_knowledge(query: str) - str: 当用户的问题涉及到本地知识库中的内容时使用此工具进行检索。 result rag_chain.invoke({query: query}) return f根据知识库相关信息如下\n{result[result]}\n\n参考来源{result[source_documents]} # 计算器工具示例实际可用python_repl或自定义 tool def calculate(expression: str) - str: 执行数学计算。输入应为一个有效的数学表达式字符串如 3 5 * 2。 try: # 警告直接eval有安全风险仅作演示。生产环境应用ast.literal_eval或专用库。 result eval(expression) return f计算结果为{result} except Exception as e: return f计算错误{e} # 将工具包装成列表供Agent使用 tools [retrieve_knowledge, calculate]6.3 构建模型与工具调用逻辑我们需要让模型学会在需要时选择并调用工具。这里使用bind_tools方法将工具定义“注入”给模型并使用ToolExecutor来实际运行工具。from langchain_community.chat_models import ChatOllama from langchain.agents import ToolExecutor # 1. 创建支持工具调用的模型 llm_with_tools ChatOllama( base_urlhttp://localhost:11434, modelqwen2:7b, temperature0.1 # 低温度使输出更确定更适合工具调用 ).bind_tools(tools) # 关键绑定工具描述 # 2. 创建工具执行器 tool_executor ToolExecutor(tools)6.4 定义图节点Nodes节点是工作流中的具体步骤。我们的智能体至少需要三个节点Agent节点调用模型让模型根据当前对话历史和状态决定下一步是“说话”还是“调用工具”。工具执行节点执行Agent节点选择的工具并将结果记录到状态中。路由判断节点根据模型输出的内容判断下一步该回到Agent节点继续思考还是结束流程生成最终回复。from langgraph.prebuilt import ToolNode from langchain_core.messages import AIMessage, ToolMessage # 节点1: Agent模型思考 def agent_node(state: AgentState): print(f[Agent节点] 正在思考历史消息数{len(state[messages])}) # 调用模型 response llm_with_tools.invoke(state[messages]) # 模型的响应可能是一个普通消息也可能包含工具调用请求 return {messages: [response]} # 节点2: 工具执行使用LangGraph预构建的ToolNode tool_node ToolNode(toolstools) # 节点3: 路由逻辑判断下一步 def should_continue(state: AgentState) - str: messages state[messages] last_message messages[-1] # 如果模型的最新消息是一个工具调用请求(AIMessage中有tool_calls) if isinstance(last_message, AIMessage) and last_message.tool_calls: return call_tool # 下一步执行工具 else: return end # 下一步结束生成最终回复给用户6.5 组装成图Graph最后我们用StateGraph把这些节点和边连接起来。from langgraph.graph import StateGraph, END # 创建图 workflow StateGraph(AgentState) # 添加节点 workflow.add_node(agent, agent_node) workflow.add_node(tool, tool_node) # 设置入口 workflow.set_entry_point(agent) # 添加边路由 workflow.add_conditional_edges( agent, should_continue, # 路由判断函数 { call_tool: tool, # 如果返回call_tool则前往tool节点 end: END # 如果返回end则结束图执行 } ) # 从工具执行节点回到Agent节点继续思考 workflow.add_edge(tool, agent) # 编译图 app workflow.compile()这个图就定义了一个完整的循环Agent思考 - 判断是否需要工具 - 需要则执行工具 - 带着工具结果回到Agent继续思考 - ... - 直到模型决定直接回复用户。7. 集成Streamlit构建交互界面大脑Agent和记忆RAG都有了现在需要给它一个与外界交互的“嘴巴”和“耳朵”。我们用Streamlit来构建。import streamlit as st from langchain_core.messages import HumanMessage st.set_page_config(page_title本地AI助手, layoutwide) st.title( 基于本地模型的RAG与Agent应用) # 初始化会话状态保存对话历史和编译好的图应用 if messages not in st.session_state: st.session_state.messages [] if agent_app not in st.session_state: # 注意这里需要之前定义好的 app (编译好的LangGraph) # 假设app已经在别处定义并可用这里简化处理。实际中可能需要全局初始化。 st.session_state.agent_app app # 显示历史消息 for msg in st.session_state.messages: with st.chat_message(msg.type): # msg.type 可以是 human, ai, tool st.markdown(msg.content) # 聊天输入框 if prompt : st.chat_input(请输入您的问题): # 添加用户消息到界面和历史 with st.chat_message(human): st.markdown(prompt) st.session_state.messages.append(HumanMessage(contentprompt)) # 准备LangGraph的初始状态 initial_state { messages: st.session_state.messages.copy(), # 传入历史 user_input: prompt, intermediate_steps: [], next: } # 调用编译好的图应用执行智能体工作流 with st.chat_message(ai): with st.spinner(思考中...): # 流式输出可以在这里实现但为简化我们先展示最终结果 final_state st.session_state.agent_app.invoke(initial_state) # 获取最终的AI回复最后一条非工具消息 final_messages final_state[messages] ai_response None for msg in reversed(final_messages): if isinstance(msg, AIMessage) and not msg.tool_calls: ai_response msg.content break if ai_response: st.markdown(ai_response) # 将AI的最终回复也加入会话历史 st.session_state.messages.append(AIMessage(contentai_response)) else: st.warning(未收到有效回复。)这个界面实现了基本的聊天循环。更高级的改进可以包括流式输出使用stream模式调用app实现打字机效果。显示中间过程将intermediate_steps中的工具调用和结果展示在界面上例如用st.expander折叠起来增加可解释性。侧边栏配置添加模型选择、温度调节、知识库管理等控件。8. 调试、优化与避坑指南把代码跑起来只是第一步让它稳定、高效、好用才是挑战。下面是我在开发过程中总结的一些关键问题和解决方案。8.1 常见问题与排查问题现象可能原因排查步骤与解决方案Ollama服务连接失败1. Ollama未启动。2. 端口被占用或防火墙阻止。3. 模型未正确拉取。1. 终端运行ollama serve并确认无报错。2. 用curl http://localhost:11434/api/tags测试API。3. 运行ollama list确认模型存在。LangGraph提示“未找到工具”1. 工具未正确绑定到模型。2. 工具函数签名或描述不符合模型预期。1. 检查llm.bind_tools(tools)是否成功执行。2. 确保tool装饰器的函数有清晰的docstring模型靠它理解工具用途。简化描述。RAG检索结果不相关1. 文本分割策略不当。2. 嵌入模型不匹配或效果差。3. 检索数量k值不合适。1. 调整chunk_size和chunk_overlap尝试300-1000。2. 换用更强的嵌入模型如bge-large-zh-v1.5中文。3. 调整search_kwargs{“k”: 3}尝试2-5。模型回复不调用工具1. 提示词Prompt未明确指示使用工具。2. 模型能力不足不理解工具调用格式。1. 在传入messages时系统提示词System Message要清晰要求“使用可用工具”。2. 尝试更强的模型如llama3:8b或使用json模式等技巧规范输出。Streamlit应用刷新后状态丢失Streamlit脚本每次交互都会从头执行未正确使用st.session_state。将所有需要跨交互保存的数据如消息历史、向量库对象、图应用都存入st.session_state中初始化。处理长文档时提示词超长检索到的文本块总长度超过了模型上下文窗口。1. 换用chain_type“map_reduce”或“refine”。2. 在检索后对文本块进行二次摘要或过滤。3. 使用具有更长上下文窗口的模型。8.2 性能与效果优化心得模型选型是根本7B模型速度快但逻辑能力有限。如果任务复杂优先升级模型如llama3:70b其次才是优化提示词。Ollama支持-numa等参数进行性能调优。给Agent清晰的系统指令在对话开始时通过一个强力的系统消息System Message设定Agent的角色、能力和规则这对输出质量影响巨大。例如“你是一个有帮助的助手可以调用工具来获取知识或进行计算。在回答用户问题时应优先考虑使用工具。”实现短期记忆与摘要当前的messages会无限制增长最终会爆掉模型上下文。一个高级技巧是在对话轮次超过一定数量后让模型自动对早期历史进行摘要然后用摘要替换掉原始长历史只保留最近几条原始消息。这需要在AgentState和节点逻辑中增加处理。为工具调用添加超时和重试网络或模型不稳定可能导致工具调用失败。用try...except包裹工具执行并设计重试逻辑或优雅的降级回复。向量检索的优化除了调整k还可以尝试不同的检索方法。Chroma支持similarity_search_with_score可以过滤掉相似度太低的片段。对于专业领域使用在该领域语料上训练过的嵌入模型是提升效果最有效的方法。8.3 项目扩展方向这个基础框架有巨大的扩展潜力多工具集成轻松添加新的tool比如查询天气、发送邮件、操作数据库等。多智能体协作用LangGraph可以创建多个具有不同角色的Agent如分析员、执行员、校对员让它们通过消息互相协作完成任务。集成外部知识图谱将RAG检索与知识图谱查询结合实现更精准、可推理的知识获取。加入验证与安全层在工具调用前加入权限验证防止恶意提示词导致危险操作。构建这样一个本地AI应用的过程就像在组装一个功能复杂的机器人。Ollama提供了动力源模型LangGraph设计了它的大脑和神经系统工作流与状态Streamlit则赋予了它友好的外表和交互方式。每一步都会遇到问题但每一步的解决都让你对AI应用的工作原理有更深的理解。最重要的是你拥有了一个完全在自己掌控之中、无需担心数据泄露、且可以任意定制和扩展的AI伙伴。希望这份详细的指南能帮你顺利启动自己的项目。本文还有配套的精品资源点击获取
返回列表