先别急着上 RAG我们先把话说清楚传统 RAG 的毛病不在检索而在“没有判断”。用户问什么就检索什么检索到什么就拼进 Prompt结果是啥就生成啥——整个过程是个单行道错了也不知道问了上下文也不接。这次我用 LangGraph 从零搭一个会路由、会改写、会评分、会反思重检索的 Agentic RAG把“检索-生成”这条死链变成“决策-行动-验证-修正”的闭环。代码全部给出思路和踩坑也一并说透。这篇东西适合两类人一类是已经做过 Naive RAG被“检索质量差、多轮对话接不上、答非所问”折腾过的人另一类是只听说过 LangGraph想知道这玩意儿跟 LangChain 到底啥区别、怎么用来做正经事的人。目标是你看完之后能自己把这套架构落到项目里。1. 死板 RAG 死在哪Agentic RAG 到底解决了什么1.1 传统 RAG 的三个硬伤我见过太多把 RAG 当“救世主”的项目上线两周就发现效果跟 demo 差一倍。问题不是 embedding、不是向量库而是流程本身有硬伤。第一无差别检索。不管用户问的是“你叫什么名字”还是“帮我看看 2025 年 Q3 财报里关于现金流的部分”系统都先去向量库捞一遍。可有些问题压根不需要检索本地知识库——比如查天气、查实时新闻本地库里根本没有有些问题只需要常识就能回答检索反而引入噪音。传统 RAG 没有“先判断一下该不该检索”这个环节。第二检索错了没有纠错机制。向量检索的 Top-K 结果里混进一两个不相关的文档太常见了。传统 RAG 的做法是照单全收全部塞给大模型。模型如果被干扰就会答出“看似合理实则错误”的内容。更麻烦的是它不会自己发现问题——没有验证环节没有反思机制。第三多轮对话里一问就懵。用户上一句问“你们公司的退款政策是什么”下一句问“那如果超过 30 天呢”传统 RAG 会把第二句原文拿去检索“超过 30 天”这种指代不明的 query向量检索效果直接崩。你得自己去做 query 改写、上下文拼接而这些工作量在传统流水线里完全没有官方解法。1.2 Agentic RAG 的核心把“检索”变成“决策循环”Agentic RAG 的思路是把 RAG 从一条线性流水线改造成一个“决策循环”。它不再回答“检索→生成”这种固定指令而是让语言模型充当智能体的核心自己决定下一步干什么。具体到我这套方案里循环长这样路由节点先判断这个问题该走 Web 搜索还是该走本地知识库检索如果走本地检索先做 query 改写把多轮上下文、指代词还原成完整的独立问题检索之后不急着生成先让一个“评审员”给每篇文档打分过滤掉不相关的如果过滤完一篇都不剩说明检索失败触发反思逻辑重新改写 query再来一轮检索生成答案之后还可以再加一层质量校验检查答案是否基于证据、是否回答了用户问题全部通过才把答案返回给用户这套东西的学名叫 Corrective RAG / Self-RAG核心就一句话系统对自己输出的质量要有感知感知到不行就自己纠正。1.3 技术选型为什么选 LangGraph 而不是裸 LangChainLangChain 本身也能串 RAG 流程LCEL 用|符号把组件串起来写 demo 非常爽。但它有几个硬伤没有原生循环结构条件分支写起来非常别扭状态管理靠你自己塞进 context。Agentic RAG 的核心恰恰是“循环”和“条件跳转”用 LangChain 的链式写法只能写出 if-else 套 if-else 的屎山。LangGraph 解决的就是这个问题。它是“有状态、可循环、可条件跳转”的图编排框架本质上是个图状态机。你可以把整个流程画成一个图节点是函数或工具边是流转关系条件边是根据状态决定下一步走哪、是否回溯。状态由一个全局 State 对象维护每个节点读状态、改状态然后往下传。我做这个需求的时候对比过 LangChain 和 LangGraph 的代码量同样的“检索评分不过就重写 query 再检索”LangChain 要额外写状态类、循环逻辑、手动管理上下文能跑但代码可读性很差LangGraph 里只需要定义好节点然后用add_conditional_edges连一条回到改写节点的边多级反思循环几行搞定。提示LangGraph 不是 LangChain 的替代品而是它的编排层。LangGraph 的节点内部照样用 LangChain 的 Retriever、Tool、PromptTemplate两者是配合关系不是竞争关系。这也是社区里问“LangChain 和 LangGraph 区别”的标准答案LangChain 管的是“用什么组件”LangGraph 管的是“组件怎么流转”。2. 整体方案设计一个会“反思”的图2.1 状态定义GraphState 是贯穿全流程的“黑板书”LangGraph 里最重要的概念就是 State。你可以把它想象成一块挂在墙上的黑板每个节点在上面读信息、写信息下一个节点接着读。Agentic RAG 的所有决策依据都在这块黑板上。我这套架构的 State 定义如下from typing import TypedDict, Annotated, List, Literal from langgraph.graph.message import add_messages class GraphState(TypedDict): # 多轮对话消息用 add_messages 注解让 LangGraph 自动合并历史 messages: Annotated[list, add_messages] # 当前需要处理的问题可能被改写 question: str # 用户最初输入的原问题用于最终生成时还原语境 original_question: str # 检索到的文档列表 documents: List[str] # 最终生成答案 generation: str # 路由结果web_search 或 retrieve search_type: str # 记录反思/重写次数防止死循环 iteration: int # 记录是否已经重写过一次 rewritten: boolmessages前面加了Annotated[list, add_messages]这是 LangGraph 做状态合并的关键加了之后每次节点返回新消息时不是覆盖而是追加。多轮对话的上下文就是靠这个累积起来的。iteration字段我现在就埋下后面做反思循环的兜底防止节点在“重写-检索-评分不过-再重写”里无限转圈。2.2 节点与条件边画出完整的控制流我先把图的结构画出来不画箭头图了直接文字描述你脑子跟着走一遍入口是route节点输入用户问题输出路由决策条件边判断决策是web_search就走 Web 搜索节点搜完直接进generate生成答案决策是retrieve就走本地知识库路线本地路线先过rewrite_query节点做多轮上下文改写生成完整 queryretrieve节点拿着改写后的 query 去向量库检索取回 Top-K 文档grade_documents节点对每篇文档打分标注 yes/no 是否相关过滤掉 no 的条件边判断过滤后还有文档就进generate生成答案一篇都没剩下就说明检索失败回到rewrite_query重新改写、重新检索generate节点拿着过滤后的合格文档生成最终答案注意第 6 步这就是“反思”和“纠错”落地的关键点。传统 RAG 检索到垃圾就当垃圾吃了这里我们会让系统自己发现“这些文档不行”然后调整策略再来一轮。第二次改写时我会让模型知道“上次检索没找到请换一种表达方式”而不是用同样的话再检索一遍。2.3 这套设计比传统流水线多了哪些“智能”我把关键差异列成表格你感受一下能力维度传统 RAGAgentic RAG问题分类无差别全走向量检索路由节点判断走 Web 还是走本地库多轮上下文需外部拼接历史query 改写节点主动补全指代检索质量Top-K 照单全收文档级评分逐篇过滤错误恢复无答错就错评分全不过时重写 query 再检索可观测性黑盒每个节点状态可查可用graph.get_state()调试可扩展性改流程要改代码加节点、加条件边即可扩展这套设计本质上把大模型从“生成器”升级成了“控制器”。生成答案只是其中一个小节点真正的智能体现在路由判断、质量评审、纠错决策这些环节上——每个决策点都是一个 LLM 调用但每个调用的职责都非常单一所以不会失控。3. 手把手实现核心节点代码逐段拆解3.1 环境准备与依赖老规矩先把依赖装好。Python 3.10 以上建议用 3.11。我自己的项目用 uv 管理依赖你直接用 pip 也行。pip install langgraph langchain langchain-openai langchain-community langchain-chroma python-dotenv版本我建议直接装最新的LangGraph 0.2 的 API 才算稳定。如果你用StateGraph的初始化方式注意 0.2 版本后它的用法有变化我下面代码按新版写。模型配置用环境变量管理import os from dotenv import load_dotenv load_dotenv() from langchain_openai import ChatOpenAI, OpenAIEmbeddings # 主模型负责生成最终答案温度低一点保证事实性 llm ChatOpenAI( modelos.getenv(OPENAI_MODEL, gpt-4o-mini), temperature0.1, ) # 快速模型负责路由、改写、评分这些任务对创造力要求低用快模型省钱省时间 fast_llm ChatOpenAI( modelos.getenv(FAST_MODEL, gpt-4o-mini), temperature0, ) embeddings OpenAIEmbeddings(modeltext-embedding-3-small)提示路由、改写、评分这类“小决策”任务我统一用fast_llm跑只有最终答案用主模型。实测如果全部用 GPT-4o一次反思循环的成本能高一倍多。这个习惯建议你从一开始就养成Agentic 系统里决策点是耗 token 大户。3.2 构建本地知识库先把 RAG 的“弹药”备好为了演示完整性我先快速建一个本地知识库。假设你有一批公司政策文档用 Chroma 当向量库。from langchain_chroma import Chroma from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter # 加载文档这里以 txt 为例pdf/csv 用对应 loader loader TextLoader(./knowledge_base/company_policy.txt, encodingutf-8) docs loader.load() # 切分按段落切chunk_size 取 500 是经验值 # 太小容易丢上下文太大会让评分和生成都变慢 splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, separators[\n\n, \n, 。, , , ], ) chunks splitter.split_documents(docs) vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db, ) retriever vectorstore.as_retriever(search_kwargs{k: 4})k4是我试过的平衡点。k 太大会混入大量噪音评审员压力大k 太小又可能漏掉关键文档。实际项目中你可以根据文档库的规模和评分结果动态调我给新手先用 4。3.3 路由节点让系统先想清楚“该不该检索”路由节点是整个 Agentic RAG 的第一道闸门。它的职责只有一个判断这个 query 应该走 Web 搜索通道还是走本地知识库通道。def route_question(state: GraphState) - dict: question state[question] prompt f你是一个智能路由助手。根据用户的问题判断应该使用哪种检索方式。 用户问题{question} 可选通道 - web_search问题涉及实时信息天气、新闻、股价、最新事件、本地知识库之外的常识性问题 - retrieve问题可以在本地知识库中找到答案比如公司政策、产品文档、内部资料 你只需输出一个词web_search 或 retrieve。不要输出任何其他内容。 response fast_llm.invoke(prompt) search_type response.content.strip().lower() # 做一个格式兜底防止模型输出多余内容 if web_search in search_type: search_type web_search elif retrieve in search_type: search_type retrieve else: search_type retrieve print(f[路由] 判断结果: {search_type}) return {search_type: search_type}这个节点最关键的地方是 Prompt 里给出了明确的判断标准。我见过很多人写路由 Prompt 就一句“请判断用户意图”模型理解不一致输出格式也千奇百怪。把标准写清楚、把输出格式限制死决策质量会高很多。路由之后的条件边是这么连的def decide_route(state: GraphState) - str: return state[search_type] # 在构建图时使用 graph.add_conditional_edges( route, decide_route, { web_search: web_search, retrieve: rewrite_query, }, )条件边的机制decide_route返回一个字符串LangGraph 用这个字符串去匹配第三个参数字典的 key然后决定下一步走哪个节点。这里的retrieve对应本地检索路线但我们不直接去retrieve而是先去rewrite_query做一轮上下文改写。3.4 查询改写解决多轮对话的“指代错乱”多轮对话里最经典的坑是“那退款政策呢” 这种 query 不给上下文谁也检索不明白。改写节点要做的就是把这种指代不清的问题还原成完整表述。def rewrite_query(state: GraphState) - dict: question state[question] messages state.get(messages, []) # 把最近几轮对话拼出来作为上下文 history_text \n.join( f{用户 if i % 2 0 else 助手}: {m.content} for i, m in enumerate(messages[-4:]) # 只看最近两轮太多反而干扰 ) prompt f你是对话理解助手。根据对话历史将用户最新问题改写为可以独立检索的完整问题。 要求 1. 补全指代词他、它、这个、那个等的具体含义 2. 保留原问题所有关键信息 3. 如果原问题已经很完整直接输出原问题 4. 只输出改写后的问题不要解释 对话历史 {history_text} 用户最新问题{question} 改写后的问题 rewritten fast_llm.invoke(prompt).content.strip() print(f[改写] {question} - {rewritten}) return {question: rewritten}我设置只看最近两轮对话这是个经验值。看多了之后历史里的噪音会干扰改写判断而且 token 消耗也会上升。在多数企业知识库场景用户最近一两轮的问题就足够补全上下文了。3.5 检索与 Web 搜索节点本地检索节点本身很朴素就是拿改写后的 query 去打向量库。但注意我把检索结果先放进documents字段而不是直接拼进 Prompt——因为后面还有评分节点要逐篇审。def retrieve(state: GraphState) - dict: question state[question] docs retriever.invoke(question) doc_contents [doc.page_content for doc in docs] print(f[检索] 获取到 {len(doc_contents)} 篇文档) return {documents: doc_contents}Web 搜索节点我用 Tavily Search API它是目前 LangChain 生态里集成最顺手的搜索工具返回结构化结果免费额度够做开发和测试。from langchain_community.tools.tavily_search import TavilySearchResults web_search_tool TavilySearchResults(max_results5) def web_search(state: GraphState) - dict: question state[question] results web_search_tool.invoke({query: question}) # 把搜索结果拼接成文档格式 docs [] for r in results: content r.get(content, ) docs.append(content) print(f[Web搜索] 获取到 {len(docs)} 条结果) return {documents: docs, search_type: web_search}3.6 文档相关性评分给检索结果当“质检员”这是整套系统最核心的节点。检索完不直接生成先让一个评审 LLM 逐篇判断文档和问题的相关性。这一步把传统 RAG 的“静默错误”变成了“显式判断”。def grade_documents(state: GraphState) - dict: question state[question] documents state[documents] filtered_docs [] for doc in documents: prompt f你是文档相关性评审员。判断以下文档是否与用户问题相关。 用户问题{question} 文档内容 {doc[:800]} # 评分不需要看全文档截断省 token 如果文档包含可以回答用户问题的关键信息输出 yes否则输出 no。 只输出一个词。 response fast_llm.invoke(prompt).content.strip().lower() print(f[评分] {相关 if response yes else 不相关}) if response yes: filtered_docs.append(doc) # 如果过滤后一篇不剩把当前轮次加一触发反思条件边 if len(filtered_docs) 0: print([反思] 所有文档均不相关触发重新改写...) return { documents: [], iteration: state.get(iteration, 0) 1, rewritten: True, } return {documents: filtered_docs}注意这里我对原文做了截断.doc[:800]评论任务根本不需要全文看开头部分就足够判断了。这个优化能让评分节点成本降一半。评分之后的条件边是反思循环的开关def decide_after_grade(state: GraphState) - str: if len(state.get(documents, [])) 0: # 已经重写过一轮还是没找到说明知识库可能真没有 if state.get(iteration, 0) 2: return generate # 兜底硬着头皮生成但告诉用户可能没有答案 return rewrite_query # 回去改写换个姿势再检索 return generate # 有文档生成答案这里iteration 2是防死循环的保险丝。反思是好事但无限反思就是灾难。我已经数不清有多少次调 Bug 时看到 agent 在同一个循环里转了几十圈token 烧得哗哗的。给反思加次数限制是 Agentic 系统上线的必备操作。3.7 生成答案节点生成节点用的是主模型把过滤后的文档拼进 Prompt要求模型严格基于文档回答不知道就说不知道。def generate(state: GraphState) - dict: question state[question] documents state.get(documents, []) # 文档可能为空反思次数用完仍没找到这时直接告诉用户没找到 if not documents: response (我尝试检索了知识库但没有找到与你的问题明确相关的内容。 建议你换一种表述方式或者补充更多关键词。) return {generation: response} docs_text \n\n---\n\n.join(documents) prompt f你是一个严谨的问答助手。请仅根据以下参考资料回答用户问题。 规则 1. 如果参考资料中包含答案请准确回答并在答案中标明信息来源文档 2. 如果参考资料中不包含答案请直接说根据现有资料无法回答不要编造 3. 回答要简洁、准确不要重复参考资料中的无关内容 参考资料 {docs_text} 用户问题{question} 你的回答 response llm.invoke(prompt).content print(f[生成] 输出长度 {len(response)} 字) return {generation: response}这里有个细节如果反思轮次用完了还是一篇文档都没有我不会硬编答案而是返回“没有找到相关内容”。对 RAG 系统来说诚实地承认不知道比一本正经地胡说八道要好一百倍。3.8 组装整张图编译可用所有节点定义好后组装成图from langgraph.graph import StateGraph, END def build_agentic_rag_graph(): graph StateGraph(GraphState) # 注册所有节点 graph.add_node(route, route_question) graph.add_node(web_search, web_search) graph.add_node(rewrite_query, rewrite_query) graph.add_node(retrieve, retrieve) graph.add_node(grade_documents, grade_documents) graph.add_node(generate, generate) # 入口路由判断 graph.set_entry_point(route) # 路由条件边 graph.add_conditional_edges( route, decide_route, { web_search: web_search, retrieve: rewrite_query, }, ) # Web 搜索路线搜完直接生成 graph.add_edge(web_search, generate) # 本地检索路线 graph.add_edge(rewrite_query, retrieve) graph.add_edge(retrieve, grade_documents) # 评分后的条件边这是反思循环的核心 graph.add_conditional_edges( grade_documents, decide_after_grade, { rewrite_query: rewrite_query, # 不满意的又一次改写检索 generate: generate, }, ) # 生成完结束 graph.add_edge(generate, END) return graph.compile() agentic_rag build_agentic_rag_graph()到这里一个能自动转圈的 Agentic RAG 已经跑起来了。编译后的对象可以像函数一样调用result agentic_rag.invoke({ question: 公司的退款政策是什么, messages: [], iteration: 0, }) print(result[generation])4. 全流程实测与参数调优心得4.1 三种典型场景实测记录我拿三组不同的问题测了一下把现场输出贴出来供参考。第一组本地知识库问题问面“公司的年假政策是怎么规定的”路由判断是retrieve。改写环节因为问题本身完整没有改动。检索拿到 4 篇文档评分时只有 2 篇判为相关过滤后的内容包括“年假天数按工龄分三档”等关键信息。生成结果直接引用这两篇文档的信息答得干脆。这个案例里最能体现价值的是如果没有评分节点另外 2 篇不相关文档会被强塞进 Prompt答案的可信度会打折。第二组多轮指代问题。用户先说“我想了解弹性工作制”然后接着问“那它的申请流程呢”如果没有改写节点第二句话直接检索效果大概率稀碎。加了改写之后模型输出的改写结果是“弹性工作制度的申请流程是什么”检索结果立刻精准。再往后如果用户说“审批一般多久”前面两轮上下文还在改写节点会把它补全成“弹性工作制的审批一般需要多久”。第三组故意刁难型问题。我拿一个知识库里完全不存在的主题去问“公司的宠物殡葬服务政策是什么”路由正确走了retrieve。检索到的 4 篇文档里评分全员判 no。于是触发反思环系统带着“上次没找到请换一种说法”的指令重新改写结果改成了“宠物丧葬 相关制度”又检索一轮还是全 no。迭代次数到 2兜底逻辑启动生成节点返回“没有找到相关内容”。整个过程自动完成没有死循环没有编造答案行为完全符合预期。4.2 参数调优哪些旋钮决定了系统智商Agentic RAG 的上限取决于模型能力但下限取决于参数调得好不好。我总结几个影响最大的参数。温度。路由和评分节点必须把温度设为 0这两个节点需要的是确定性输出一点随机性都不该有。改写节点可以给 0.1-0.2太低了改写缺乏灵活度太高了容易乱编。生成节点给 0.1 左右既保证事实性又不至于完全机械。迭代上限。反思循环里的max_iteration我建议设成 2 或 3。设 1 等于没反思出一次问题就硬着头皮上。设 5 以上一旦知识库确实没有答案系统就会白白转好几圈烧钱。2 是“给了你一次纠错机会”的合理值。检索 Top-K。K 值直接影响评分节点的负担。K4 是我测下来比较稳的如果知识库很杂、噪音多降到 3 更稳如果知识库很干净、文档切分质量高可以升到 5-6。4.3 知识库切分的隐藏学问很多人调了半天 Prompt 没效果最后发现是文档切分的问题。我再分享一个经验切分器的separators顺序很重要我建议带上中文标点。splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, separators[\n\n, \n, 。, , , , ], )这是因为 RecursiveCharacterTextSplitter 会按顺序尝试分隔符如果你不写中文标点一个段落几百字都会被硬切一刀很可能切断关键语义。加了中文句号后切分点会优先选在语义完整的位置。5. 常见问题与避坑实录5.1 死循环反思的次数失控了这是 Agentic RAG 上线初期最经典的事故。症状是调用链不停地转圈每次调用都是评分不过、回去改写、检索、再评分用户的提问被搁置几分钟token 消耗像流水一样失控。排查方法第一检查decide_after_grade里的兜底条件是否覆盖所有路径。我见过有人写了if iteration 2: return generate但忘记在rewrite_query节点里递增iteration导致条件永远不满足。第二用 LangGraph 自带的调试能力graph.get_state(config)可以查看每个节点执行后的状态直接看iteration字段有没有变化。我的建议是在grade_documents返回的时候无条件递增iteration而不是只在不相关的时候递增。这样即使其他路径出问题保险丝也能起作用。5.2 小模型路由输出格式不稳定路由节点用fast_llm时容易遇到一个问题模型偶尔不老老实实输出一个词而是输出一整句话比如“根据您的询问我认为应该使用 retrieve 通道”。解决办法是在解析时做容错。代码里我用了 substring 匹配的方式而不是精确匹配if web_search in search_type而不是if search_type web_search。这个看似不起眼的处理能吃掉 90% 的格式问题。如果还不行就在 Prompt 里加一句“这是一个极其简单的二选一任务直接输出词不要礼貌不要解释”。语气写得强硬一点模型会更听话。5.3 多轮对话还是接不上你大概率忘了传历史很多人把 LangGraph 代码抄过去了路由、评分、反思都跑通了但多轮对话依然接不上。问题往往出在调用方式上——每次invoke只传了question没传messages。正确用法是把整个对话历史传进 state# 第一次调用 state1 agentic_rag.invoke({question: 什么是弹性工作制, messages: []}) # 第二次调用把上一轮消息追加进 messages messages [ {role: user, content: 什么是弹性工作制}, {role: assistant, content: state1[generation]}, ] state2 agentic_rag.invoke({question: 申请流程是什么, messages: messages})如果你做 Web 应用会话历史一般存在 Redis 或数据库里每次请求时把历史捞出来拼进messages字段即可。5.4 检索评分误杀率太高好文档被当成垃圾评分节点把相关文档判成不相关这也很常见。原因一般有两个一是 Prompt 里的相关标准写得太严二是截断后关键信息恰好被切掉了。解决办法第一评分 Prompt 里把“相关”定义放宽一点改成“文档内容可以作为回答该问题的参考依据”。第二截断长度从 800 增加到 1500牺牲一点 token 成本换取判断准确率。第三实在不行可以把system换成更强、更聪明的模型比如从 min 换成标准版评分准确性会立刻上一个台阶。5.5 扣子Coze是不是用 LangGraph 实现的这个问题在社区里被问得挺多我也被读者私信问过几次。我给个保守的回答扣子这类低代码工作流平台产品形态上确实和 LangGraph 非常像——画布上拖节点、连边、设置条件跳转这本身就是“可视化 Agent 编排”。但你不能说它就是 LangGraph 做的字节跳动自研工作流引擎的概率远大于直接套用开源框架。对开发者的实际意义在于如果你在扣子上玩过工作流理解 LangGraph 的 node/edge/条件边会非常快因为套路完全一样。反过来你在 LangGraph 上趟过的坑死循环、状态管理、条件冲突在扣子上也会以相似的形态出现。概念是通用的工具只是载体。5.6 LangChain 和 LangGraph 到底怎么分工再总结一次因为这个问题我看到太多人搞混了。LangChain 提供的是组件库LLM 封装、Prompt 模板、Retriever、Tool、Output Parser。LangGraph 提供的是编排能力状态机、条件跳转、循环、持久化、人工审核。在实际项目里你不必二选一。我自己的习惯是LangGraph 搭骨架节点内部全用 LangChain 的组件。比如retriever是 LangChain 的llm是 LangChain 的web_search_tool是 LangChain 的但它们的流转关系完全由 LangGraph 控制。这就够了。最后再分享一点经验这套 Agentic RAG 我从 0.1 版本迭代到现在回头看最有价值的设计不是反思循环是“评审节点”的存在。它把 RAG 从“尽全力从噪声里找答案”变成了“先确认什么是噪声再回答问题”。很多看起来是检索质量的问题本质上是行程缺乏质量意识——先把检索结果当成待验证的假设用一次廉价的 LLM 调用快速验证再决定走哪条路这个思路能迁移到很多 Agent 场景里。代码里我用的是最简单的字符串状态和条件边LangGraph 底层还有 Checkpointer、Human-in-the-loop、Message 级别的状态管理等你的场景需要人工审批、断点续跑的时候再升级也不迟。先把手里的模型、向量库、检索器拼出一个能自我纠错的闭环比什么都强。