用LangGraph写一个小RAG
最小RAG的链式写法大致这样用户问题 - 检索资料 - 拼Prompt - 调模型回答用LangGraph把最小RAG拆成一张小图START ↓ retrieve_docs ├─ 有足够相关资料 - build_context - answer - END └─ 无足够相关资料 - fallback - END图结构接下里主要是看下graph里一般怎么接入rag实现不复杂。准备知识库和测试问题先准备一份很小的客服知识库包含退款、发票、会员续费、商品质量问题等内容然后测试三个问题1、未发货订单能退款吗 2、电子发票在哪里下载 3、积分可以提现吗预期效果应该是前两个问题应该能检索到资料并回答第三个问题不在知识库范围内应该走fallback。定义State和Runtime ContextState保存这次问答过程中产生的数据class RagDocument(TypedDict): source: str chunk_index: int start_index: int content: str similarity: float class RagState(TypedDict, totalFalse): question: str retrieved_docs: list[RagDocument] context: str route: Literal[answer, fallback] answer: str稍微解释下question是用户问题retrieved_docs是结构化检索结果context是给模型看的上下文这里后续会包含从向量哭里检索出来的文档片段route记录走了正常回答还是fallbackanswer保存最终回答。Runtime Context保存运行配置和外部依赖内容包括class RagContext(TypedDict): knowledge_base_path: str top_k: int min_similarity: float embedding_model: str answer_model: str ollama_base_url: str vectorstore: InMemoryVectorStoretop_k是每次检索最多返回几个相关片段min_similarity是最低相似度阈值低于这个分数的资料不进入回答流程embedding_model使用的Embedding模型vectorstore已经构建好的向量库对象用它根据用户问题检索相关资料两份存储的区别1、问答过程中产生的东西放State2、运行时需要的配置和依赖放Runtime Context启动时先建向量库启动时建一次向量库后面多个问题都可以复用写入到内存暂时不用本地存储def build_vectorstore( knowledge_base_path: str, embedding_model: str, ollama_base_url: str, ) - InMemoryVectorStore: docs load_support_docs(knowledge_base_path) embeddings OllamaEmbeddings( modelembedding_model, base_urlollama_base_url, ) return InMemoryVectorStore.from_documents(docs, embeddings)文本切分用RecursiveCharacterTextSplittersplitter RecursiveCharacterTextSplitter( chunk_size160, chunk_overlap30, separators[\n\n, \n, 。, , , ], add_start_indexTrue, ) docs splitter.split_documents(raw_docs)add_start_indexTrue能保留chunk在原文里的起始位置后面展示参考资料时有用。写检索节点retrieve_docs检索节点做的四件事读取问题、查询向量库、过滤低相关资料、写回State。def retrieve_docs(state: RagState, runtime: Runtime[RagContext]) - RagState: context runtime.context question get_question(state) results context[vectorstore].similarity_search_with_score( question, kcontext[top_k], ) retrieved_docs: list[RagDocument] [] for doc, similarity in results: if similarity context[min_similarity]: continue retrieved_docs.append( { source: Path(str(doc.metadata.get(source, unknown))).name, chunk_index: int(doc.metadata.get(chunk_index, -1)), start_index: int(doc.metadata.get(start_index, -1)), content: doc.page_content, similarity: similarity, } ) return {retrieved_docs: retrieved_docs}这里的min_similarity0.60只是当前知识库和embedding模型下的实验阈值不是通用标准。换知识库或模型后要重新观察和选择你要使用啥分数写条件边有资料才回答检索后不要直接回答先判断有没有足够相关资料def route_after_retrieve(state): if state.get(retrieved_docs): return build_context return fallback有资料才回答没资料就fallback这里也体现了langchain与langgraph环境下使用rag的实现区别。写build_context、answer和fallbackbuild_context把结构化检索结果整理成模型可读的上下文def build_context(state: RagState) - RagState: context_parts [] for index, doc in enumerate(state.get(retrieved_docs, []), start1): context_parts.append( f资料 {index}{doc[source]}#chunk-{doc[chunk_index]}\n f相似度{doc[similarity]:.3f}\n f起始位置{doc[start_index]}\n f正文{doc[content]} ) return {context: \n\n.join(context_parts), route: answer}answer只根据用户问题和检索上下文回答model ChatOllama( modelcontext[answer_model], base_urlcontext[ollama_base_url], temperature0, )但是Prompt里要写清楚只根据给定资料回答不要编造资料中没有的信息这个约束还是挺重要的不然模型会放飞自己。fallback不调用模型直接兜底就行。def fallback(state: RagState) - RagState: return { route: fallback, context: , answer: 当前知识库里没有找到足够相关的资料建议转人工客服或补充更多问题信息。, }RAG的底线没有依据时不要不懂装懂。把节点连成图图本身不大graph_builder StateGraph(RagState, context_schemaRagContext) graph_builder.add_node(retrieve_docs, retrieve_docs) graph_builder.add_node(build_context, build_context) graph_builder.add_node(answer, answer) graph_builder.add_node(fallback, fallback) graph_builder.add_edge(START, retrieve_docs) graph_builder.add_conditional_edges( retrieve_docs, route_after_retrieve, { build_context: build_context, fallback: fallback, }, ) graph_builder.add_edge(build_context, answer) graph_builder.add_edge(answer, END) graph_builder.add_edge(fallback, END) graph graph_builder.compile()到这里一个最小LangGraph RAG就搭好了。运行并观察三条路径启动时配置知识库、模型和向量库knowledge_base_path support_policy_retriever.txt embedding_model qwen3-embedding:latest answer_model qwen3-coder:30b ollama_base_url http://localhost:11434 vectorstore build_vectorstore( knowledge_base_pathknowledge_base_path, embedding_modelembedding_model, ollama_base_urlollama_base_url, ) runtime_context { knowledge_base_path: knowledge_base_path, top_k: 2, min_similarity: 0.60, embedding_model: embedding_model, answer_model: answer_model, ollama_base_url: ollama_base_url, vectorstore: vectorstore, }每次调用时初始State只放问题initial_state {”question”: question} final_state graph.invoke(initial_state, contextruntime_context)看下关键输出的效果问题 1未发货订单能退款吗 路径answer 最终回答未发货订单可以申请退款系统会自动拦截发货流程退款通常在1-3个工作日内原路退回。 参考资料[1] support_policy_retriever.txt#chunk-10(0.730); [2] support_policy_retriever.txt#chunk-2109(0.626) 问题 2电子发票在哪里下载 路径answer 最终回答电子发票可在订单详情页下载。进入”我的订单 - 订单详情 - 发票信息”查找发票入口。 参考资料[1] support_policy_retriever.txt#chunk-3208(0.732) 问题 3积分可以提现吗 路径fallback 最终回答当前知识库里没有找到足够相关的资料建议转人工客服或补充更多问题信息。 参考资料无基本符合预期。‘9. 五条核心边界1、State保存用户问题、检索结果、上下文和最终回答。2、Runtime Context保存知识库、模型、检索参数和向量库。3、向量库启动时构建一次不要每个问题重复建索引。4、检索节点只负责找资料。5、没有足够相关资料就fallback不让模型硬答。实验细节GitHub 仓库 https://github.com/yauld/ai-forge 完整实验文章 labs/langgraph/foundations/24 | LangGraph RAG把最小问答链路接入图.md 实验代码 labs/langgraph/foundations/experiments/24_minimal_rag_graph/学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】