
前言
大模型越来越强大,但它们依旧有一个致命短板:知识更新慢。如果直接问 ChatGPT 之类的模型一个近期事件的问题,它很可能答不上来。这就是为什么 RAG(检索增强生成) 变得重要 —— 在回答问题之前,先去找相关资料,再让模型结合这些资料生成答案。
不过,RAG 并不是“一刀切”的方案:有些问题根本不需要检索(比如定义类问题),有些问题需要一次检索就能解决,而另一些则需要多次尝试(比如先改写问题,再检索)。这就是 自适应RAG 的核心:根据问题的不同,动态选择最合适的策略。
本文我们将用 LangGraph + 本地 LLM(Ollama + Mistral) 搭建一个 Adaptive RAG 系统,能在 Web 搜索 和 向量库检索 之间灵活切换,还能自我纠错。
注意:我们的 Adaptive RAG 系统有两个主要分支:
**Web Search:**处理最近事件相关的问题(因为向量库的数据是历史快照,不会包含最新信息)。借助 Tavily 搜索 API 获取网页结果,再交给 LLM 组织答案。
Self-Corrective RAG:针对我们自己构建的知识库(这里我们抓取了 Lilian Weng 的几篇经典博客:Agent、Prompt Engineering、Adversarial Attack)。向量库用 Chroma 搭建,文本向量用 Nomic 本地 Embedding 生成。如果第一次检索结果不相关,会尝试改写问题,再次检索。同时会过滤掉“答非所问”的文档,避免垃圾结果。
1. 环境准备
%capture --no-stderr%pip install -U langchain-nomic langchain_community tiktoken langchainhub chromadb langchain langgraph tavily-python nomic[local]
设置 API Key(Tavily 搜索 + Nomic embedding)。
import getpass, os
def _set_env(var: str): if not os.environ.get(var): os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("TAVILY_API_KEY")_set_env("NOMIC_API_KEY")
2. 本地模型和向量库
我们将要构建了一个 向量数据库,内容是 Lilian Weng 的三篇博客。以后凡是涉及 Agent/Prompt Engineering/Adversarial Attack 的问题,就走这里。
# Ollama 模型local_llm = "mistral"
# 文本切分 & 向量化from langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain_community.document_loaders import WebBaseLoaderfrom langchain_community.vectorstores import Chromafrom langchain_nomic.embeddings import NomicEmbeddings
urls = [ "https://lilianweng.github.io/posts/2023-06-23-agent/", "https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/", "https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/",]docs = [WebBaseLoader(url).load() for url in urls]docs_list = [item for sublist in docs for item in sublist]
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( chunk_size=250, chunk_overlap=0)doc_splits = text_splitter.split_documents(docs_list)
vectorstore = Chroma.from_documents( documents=doc_splits, collection_name="rag-chroma", embedding=NomicEmbeddings(model="nomic-embed-text-v1.5", inference_mode="local"),)retriever = vectorstore.as_retriever()
3. 问题路由器(Router)
假如这个问题和 Agent 相关,所以走向量库。
from langchain.prompts import PromptTemplatefrom langchain_community.chat_models import ChatOllamafrom langchain_core.output_parsers import JsonOutputParser
llm = ChatOllama(model=local_llm, format="json", temperature=0)
prompt = PromptTemplate( template="""You are an expert at routing a user question to a vectorstore or web search... Question to route: {question}""", input_variables=["question"],)question_router = prompt | llm | JsonOutputParser()
question = "llm agent memory"print(question_router.invoke({"question": question}))
执行结果
{'datasource': 'vectorstore'}
4. 检索质量评估(Retrieval Grader)
如果检索到的文档与问题相关。
retrieval_grader = prompt | llm | JsonOutputParser()question = "agent memory"docs = retriever.get_relevant_documents(question)doc_txt = docs[1].page_contentprint(retrieval_grader.invoke({"question": question, "document": doc_txt}))
执行结果
{'score': 'yes'}
5. 答案生成(RAG Generate)
成了一段关于 “Agent Memory” 的解释。
from langchain import hubfrom langchain_core.output_parsers import StrOutputParser
prompt = hub.pull("rlm/rag-prompt")llm = ChatOllama(model=local_llm, temperature=0)
rag_chain = prompt | llm | StrOutputParser()
question = "agent memory"generation = rag_chain.invoke({"context": docs, "question": question})print(generation)
执行结果
In an LLM-powered autonomous agent system, the Large Language Model (LLM) functions as the agent's brain...
6. 幻觉检测(Hallucination Grader)
如果答案确实是基于文档生成的,没有瞎编。如果答案不靠谱,就让系统重新检索或改写问题。
hallucination_grader = prompt | llm | JsonOutputParser()hallucination_grader.invoke({"documents": docs, "generation": generation})
执行结果
{'score': 'yes'}
7. 答案有用性评估(Answer Grader)
如果这个答案对用户有用。
answer_grader.invoke({"question": question, "generation": generation})
执行结果
{'score': 'yes'}
8. 问题重写器(Question Rewriter)
改成了更适合检索的问法。
question_rewriter.invoke({"question": question})
执行结果
'What is agent memory and how can it be effectively utilized in vector database retrieval?'
9. Web 搜索工具
当问题和近期事件有关时,就会走 Tavily 搜索,而不是本地库。
from langchain_community.tools.tavily_search import TavilySearchResultsweb_search_tool = TavilySearchResults(k=3)
执行结果日志
---ROUTE QUESTION---What is the AlphaCodium paper about?{'datasource': 'web_search'}---ROUTE QUESTION TO WEB SEARCH------WEB SEARCH---"Node 'web_search':"'---'---GENERATE------CHECK HALLUCINATIONS------DECISION: GENERATION IS GROUNDED IN DOCUMENTS------GRADE GENERATION vs QUESTION------DECISION: GENERATION ADDRESSES QUESTION---"Node 'generate':"'---'('The AlphaCodium paper introduces a new approach for code generation...')
10.工作流(LangGraph 具体实现)
我们用 LangGraph 把这些步骤连起来,形成一个有条件分支的工作流:
-
开始 → 判断走 Web Search 还是 Vectorstore
-
如果走 Vectorstore:检索 → 文档过滤 →
-
- 如果靠谱 → 返回结果
- 如果不靠谱 → 改写问题 → 再检索
-
- 如果没文档:改写问题 → 再检索
- 如果有文档:生成答案 → 检查是否靠谱
-
如果走 Web Search:直接搜 → 生成答案 → 检查 → 返回结果
最终,系统能在不同类型的问题上灵活切换,而不是死板地“一问一搜”。
from typing import Listfrom typing_extensions import TypedDictclass GraphState(TypedDict): """ Represents the state of our graph. Attributes: question: question generation: LLM generation documents: list of documents """ question: str generation: str documents: List[str] ### Nodesfrom langchain.schema import Documentdef retrieve(state): """ Retrieve documents Args: state (dict): The current graph state Returns: state (dict): New key added to state, documents, that contains retrieved documents """ print("---RETRIEVE---") question = state["question"] # Retrieval documents = retriever.get_relevant_documents(question) return {"documents": documents, "question": question}def generate(state): """ Generate answer Args: state (dict): The current graph state Returns: state (dict): New key added to state, generation, that contains LLM generation """ print("---GENERATE---") question = state["question"] documents = state["documents"] # RAG generation generation = rag_chain.invoke({"context": documents, "question": question}) return {"documents": documents, "question": question, "generation": generation}def grade_documents(state): """ Determines whether the retrieved documents are relevant to the question. Args: state (dict): The current graph state Returns: state (dict): Updates documents key with only filtered relevant documents """ print("---CHECK DOCUMENT RELEVANCE TO QUESTION---") question = state["question"] documents = state["documents"] # Score each doc filtered_docs = [] for d in documents: score = retrieval_grader.invoke( {"question": question, "document": d.page_content} ) grade = score["score"] if grade == "yes": print("---GRADE: DOCUMENT RELEVANT---") filtered_docs.append(d) else: print("---GRADE: DOCUMENT NOT RELEVANT---") continue return {"documents": filtered_docs, "question": question}def transform_query(state): """ Transform the query to produce a better question. Args: state (dict): The current graph state Returns: state (dict): Updates question key with a re-phrased question """ print("---TRANSFORM QUERY---") question = state["question"] documents = state["documents"] # Re-write question better_question = question_rewriter.invoke({"question": question}) return {"documents": documents, "question": better_question}def web_search(state): """ Web search based on the re-phrased question. Args: state (dict): The current graph state Returns: state (dict): Updates documents key with appended web results """ print("---WEB SEARCH---") question = state["question"] # Web search docs = web_search_tool.invoke({"query": question}) web_results = "\n".join([d["content"] for d in docs]) web_results = Document(page_content=web_results) return {"documents": web_results, "question": question}### Edges ###def route_question(state): """ Route question to web search or RAG. Args: state (dict): The current graph state Returns: str: Next node to call """ print("---ROUTE QUESTION---") question = state["question"] print(question) source = question_router.invoke({"question": question}) print(source) print(source["datasource"]) if source["datasource"] == "web_search": print("---ROUTE QUESTION TO WEB SEARCH---") return "web_search" elif source["datasource"] == "vectorstore": print("---ROUTE QUESTION TO RAG---") return "vectorstore"def decide_to_generate(state): """ Determines whether to generate an answer, or re-generate a question. Args: state (dict): The current graph state Returns: str: Binary decision for next node to call """ print("---ASSESS GRADED DOCUMENTS---") state["question"] filtered_documents = state["documents"] if not filtered_documents: # All documents have been filtered check_relevance # We will re-generate a new query print( "---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---" ) return "transform_query" else: # We have relevant documents, so generate answer print("---DECISION: GENERATE---") return "generate"def grade_generation_v_documents_and_question(state): """ Determines whether the generation is grounded in the document and answers question. Args: state (dict): The current graph state Returns: str: Decision for next node to call """ print("---CHECK HALLUCINATIONS---") question = state["question"] documents = state["documents"] generation = state["generation"] score = hallucination_grader.invoke( {"documents": documents, "generation": generation} ) grade = score["score"] # Check hallucination if grade == "yes": print("---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---") # Check question-answering print("---GRADE GENERATION vs QUESTION---") score = answer_grader.invoke({"question": question, "generation": generation}) grade = score["score"] if grade == "yes": print("---DECISION: GENERATION ADDRESSES QUESTION---") return "useful" else: print("---DECISION: GENERATION DOES NOT ADDRESS QUESTION---") return "not useful" else: pprint("---DECISION: GENERATION IS NOT GROUNDED IN DOCUMENTS, RE-TRY---")
from langgraph.graph import END, StateGraph, STARTworkflow = StateGraph(GraphState)# Define the nodesworkflow.add_node("web_search", web_search) # web searchworkflow.add_node("retrieve", retrieve) # retrieveworkflow.add_node("grade_documents", grade_documents) # grade documentsworkflow.add_node("generate", generate) # generateworkflow.add_node("transform_query", transform_query) # transform_query# Build graphworkflow.add_conditional_edges( START, route_question, { "web_search": "web_search", "vectorstore": "retrieve", },)workflow.add_edge("web_search", "generate")workflow.add_edge("retrieve", "grade_documents")workflow.add_conditional_edges( "grade_documents", decide_to_generate, { "transform_query": "transform_query", "generate": "generate", },)workflow.add_edge("transform_query", "retrieve")workflow.add_conditional_edges( "generate", grade_generation_v_documents_and_question, { "not supported": "generate", "useful": END, "not useful": "transform_query", },)# Compileapp = workflow.compile()
inputs = {"question": "What is the AlphaCodium paper about?"}for output in app.stream(inputs): for key, value in output.items(): pprint(f"Node '{key}':") pprint("\n---\n")pprint(value["generation"])
执行结果
---ROUTE QUESTION---What is the AlphaCodium paper about?{'datasource': 'web_search'}---ROUTE QUESTION TO WEB SEARCH------WEB SEARCH---"Node 'web_search':"'---'---GENERATE------CHECK HALLUCINATIONS------DECISION: GENERATION IS GROUNDED IN DOCUMENTS------GRADE GENERATION vs QUESTION------DECISION: GENERATION ADDRESSES QUESTION---"Node 'generate':"'---'('The AlphaCodium paper introduces a new approach for code generation...')
我们写的这套 自适应 RAG 系统展示了几个关键点:
灵活路由:不同问题走不同管道(Web / Vectorstore)。
自我纠错:检索结果不相关时,自动改写问题再试。
质量把控:通过“幻觉检测 + 答案有用性判断”,尽量避免胡编乱造。
本地化:Embedding 和 LLM 都可以跑在本地(隐私友好,节省成本)。
未来可以扩展的方向包括:增加“多步推理”路线(先子问题分解,再检索)。更细的路由分类(比如结构化查询 vs 自然语言查询)。融合图数据库或知识图谱,增强事实性。
最后
为什么要学AI大模型
当下,⼈⼯智能市场迎来了爆发期,并逐渐进⼊以⼈⼯通⽤智能(AGI)为主导的新时代。企业纷纷官宣“ AI+ ”战略,为新兴技术⼈才创造丰富的就业机会,⼈才缺⼝将达 400 万!
DeepSeek问世以来,生成式AI和大模型技术爆发式增长,让很多岗位重新成了炙手可热的新星,岗位薪资远超很多后端岗位,在程序员中稳居前列。

与此同时AI与各行各业深度融合,飞速发展,成为炙手可热的新风口,企业非常需要了解AI、懂AI、会用AI的员工,纷纷开出高薪招聘AI大模型相关岗位。

最近很多程序员朋友都已经学习或者准备学习 AI 大模型,后台也经常会有小伙伴咨询学习路线和学习资料,我特别拜托北京清华大学学士和美国加州理工学院博士学位的鲁为民老师给大家这里给大家准备了一份涵盖了AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频 全系列的学习资料,这些学习资料不仅深入浅出,而且非常实用,让大家系统而高效地掌握AI大模型的各个知识点。
这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】
AI大模型系统学习路线
在面对AI大模型开发领域的复杂与深入,精准学习显得尤为重要。一份系统的技术路线图,不仅能够帮助开发者清晰地了解从入门到精通所需掌握的知识点,还能提供一条高效、有序的学习路径。

但知道是一回事,做又是另一回事,初学者最常遇到的问题主要是理论知识缺乏、资源和工具的限制、模型理解和调试的复杂性,在这基础上,找到高质量的学习资源,不浪费时间、不走弯路,又是重中之重。
AI大模型入门到实战的视频教程+项目包
看视频学习是一种高效、直观、灵活且富有吸引力的学习方式,可以更直观地展示过程,能有效提升学习兴趣和理解力,是现在获取知识的重要途径

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

海量AI大模型必读的经典书籍(PDF)
阅读AI大模型经典书籍可以帮助读者提高技术水平,开拓视野,掌握核心技术,提高解决问题的能力,同时也可以借鉴他人的经验。对于想要深入学习AI大模型开发的读者来说,阅读经典书籍是非常有必要的。

600+AI大模型报告(实时更新)
这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

AI大模型面试真题+答案解析
我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下


这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】
1814

被折叠的 条评论
为什么被折叠?



