RAG的快速上手,可使用以下方式进行RAG的实现:
使用框架
LIamaIndex的五步示例
LangChain的直接实现
LangChain的LCEL链重构
LangGraph的重构
不使用框架
使用可视化工具
使用框架快速上手RAG LIamaIndex 示例如下:
1 2 3 4 5 6 7 8 9 10 from llama_index.core import VectorStoreIndex, SimpleDirectoryReader documents = SimpleDirectoryReader(input_files=["data/黑悟空/设定.txt" ]).load_data() index = VectorStoreIndex.from_documents(documents) query_engine = index.as_query_engine() print (query_engine.query("黑神话悟空中有哪些战斗工具?" ))
以上是简单的五步代码,代码中没有看到使用的大模型和嵌入模型,是LIamaIndex已经内置了Open AI的模型,因此在运行该代码时要在本地的环境变量中设置OpenAI API密钥
1 2 3 4 5 在Linux/Mac系统中,可以通过以下命令设置: export OPENAI_API_KEY='your-api-key' 在Windows系统中,可以通过以下命令设置: set OPENAI_API_KEY=your-api-key
默认的模型是gpt-3.5-turbo
下面我们也可以使用国产大模型DeepSeek进行替换1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 from llama_index.core import VectorStoreIndex, SimpleDirectoryReaderfrom llama_index.embeddings.huggingface import HuggingFaceEmbeddingfrom llama_index.llms.deepseek import DeepSeekfrom dotenv import load_dotenvimport osload_dotenv() embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-zh" ) llm = DeepSeek( model=os.getenv("DEEPSEEK_MODEL_NAME" ), api_key=os.getenv("DEEPSEEK_API_KEY" ), api_base=os.getenv("DEEPSEEK_BASE_URL" ) ) documents = SimpleDirectoryReader(input_files=["../data/黑悟空/设定.txt" ]).load_data() index = VectorStoreIndex.from_documents( documents, embed_model=embed_model ) query_engine = index.as_query_engine( llm=llm ) print (query_engine.query("黑神话悟空中有哪些战斗工具?" ))
执行结果,输出内容
1 《黑神话:悟空》中并未明确提及具体的战斗工具信息。根据设定,游戏融合了中国文化和自然地标,并包含佛教与道教哲学元素,但关于武器或战斗系统的细节在提供的上下文中没有具体说明。
简单说明,RAG系统中需要使用LLM大语言模型、Embedding嵌入模型、向量数据库等……,从以上代码看,已经有使用大语言模型、嵌入模型,没有使用到常用的向量数据库,为什么还可以成功执行呢,从官网有下面一段话
1 2 LlamaIndex provides a in-memory vector database allowing you to run it locally, when you have a large amount of documents vector databases provides more features and better scalability and less memory constraints depending of your hardware. LlamaIndex 提供了一个内存中矢量数据库,允许您在本地运行它,当您有大量文档时,矢量数据库提供了更多的功能和更好的可扩展性,并且根据您的硬件,可以减少内存限制。
从上面介绍,我们了解到,原来LIamaIndex提供了一个内存向量数据库呀。
以上代码中使用到的LIamaIndex介绍:
Vector Database LIamaIndex官方地址:https://docs.llamaindex.ai/en/stable/community/faq/vector_database/
LIamaIndex中存储组件中的向量存储官方地址:https://docs.llamaindex.ai/en/stable/module_guides/storing/vector_stores/#vector-store-options--feature-support
LIamaIndex中查询组件中的Query Engine官方地址:https://docs.llamaindex.ai/en/stable/module_guides/deploying/query_engine/
LangChain 示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import osfrom dotenv import load_dotenvload_dotenv() from langchain_community.document_loaders import WebBaseLoader loader = WebBaseLoader( web_paths=("https://zh.wikipedia.org/wiki/黑神话:悟空" ,) ) docs = loader.load() from langchain_text_splitters import RecursiveCharacterTextSplittertext_splitter = RecursiveCharacterTextSplitter(chunk_size=1000 , chunk_overlap=200 ) all_splits = text_splitter.split_documents(docs) from langchain_huggingface import HuggingFaceEmbeddings embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh" , model_kwargs={'device' : 'cpu' }, encode_kwargs={'normalize_embeddings' : True } ) from langchain_core.vectorstores import InMemoryVectorStorevector_store = InMemoryVectorStore(embeddings) vector_store.add_documents(all_splits) question = "黑悟空有哪些游戏场景?" retrieved_docs = vector_store.similarity_search(question, k=3 ) docs_content = "\n\n" .join(doc.page_content for doc in retrieved_docs) from langchain_core.prompts import ChatPromptTemplateprompt = ChatPromptTemplate.from_template(""" 基于以下上下文,回答问题。如果上下文中没有相关信息, 请说"我无法从提供的上下文中找到相关信息"。 上下文: {context} 问题: {question} 回答:""" ) from langchain_openai import ChatOpenAIllm = ChatOpenAI( model=os.getenv("DEEPSEEK_MODEL_NAME" ), temperature=0.7 , max_tokens=2048 , api_key=os.getenv("DEEPSEEK_API_KEY" ), base_url=os.getenv("DEEPSEEK_BASE_URL" ) ) answer = llm.invoke(prompt.format (question=question, context=docs_content)) print (answer)
调用特点:
会显示大模型调用过程中的详细交互信息
包括token使用情况(prompt tokens/completion tokens)
最终回答存储在content元素中
参数设置:
temperature控制输出随机性(0-1)
max_tokens限制最大输出长度
top_p控制输出多样性(0-1)
presence_penalty防止重复(-2.0到2.0)
frequency_penalty减少重复内容(-2.0到2.0)
执行结果,输出内容
1 content='根据提供的上下文,以下是《黑神话:悟空》中提到的游戏场景及相关取景地信息:\n\n1. **中国古建筑取景地** \n - 游戏中的场景大量借鉴了中国传统古建筑,尤其是山西的古建(36个取景地中有27个位于山西),例如晋祠、悬空寺等(来源:文博日历丨《黑神话:悟空》里的中国古建取景地、36个取景地27个在山西)。\n\n2. **具体场景示例** \n - 游戏中出现的“古刹丛林”“荒原雪岭”等场景,均以实景古建为原型设计,并通过幕后花絮展示了拍摄过程(来源:IGN中国取景地幕后花絮)。\n\n3. **文化符号融入** \n - 场景中融入了石窟造像、壁画等元素,如云冈石窟、永乐宫壁画等(来源:Game8关于中国文化 treasures 的报道)。\n\n若需更具体的场景名称或关卡设计细节,上下文中未明确列出,可能需要参考游戏内的探索攻略或官方完整资料。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 228, 'prompt_tokens': 1331, 'total_tokens': 1559, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'deepseek-ai/DeepSeek-V3', 'system_fingerprint': '', 'id': '019863b18528f5d950a772c8bc3216a0', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None} id='run--5f5f3ff4-d693-4835-b03a-64c291f55f9f-0' usage_metadata={'input_tokens': 1331, 'output_tokens': 228, 'total_tokens': 1559, 'input_token_details': {}, 'output_token_details': {}}
LangChain调用大模型的不同方式
LangChain重构LCEL
组件式设计
管道式(Pipeline)数据流
高度可组合性
兼容异步执行
易于调试和扩展
LCEL链的构建与特点
核心优势:相比传统瀑布式开发,LCEL采用类似Unix命令管道的设计理念,通过”|”符号串联不同处理逻辑组件
典型组件:包含提示模板(ChatPromptTemplate)、语言模型(ChatOpenAI)、输出解析器(StrOutputParser)等模块
重构差异:将原本分步执行的文档加载、分块、嵌入、存储等流程整合为统一管道
组件化设计与管道式数据流
组件式设计:各功能模块如提示模板、LLM、输出解析器等可像乐高积木自由组合
管道式数据流:数据通过chain = (\{"context": retriever | lambda..., "question": RunnablePassthrough()\} | prompt | llm | StrOutputParser())形式流动
开发体验:部分开发者认为比传统分步执行更清晰,但也存在适应成本
高度可组合性与异步执行
组合灵活性:支持任意调整组件位置(但RAG等固定流程需保持顺序)
异步支持:所有chain天然支持异步调用模式,可通过简单切换实现同步/异步转换
调试集成:与LangSmith深度集成,可追踪token消耗、调用次数等指标
LCEL与LangChain的集成
工具兼容:保留原有LangChain的tracing等调试功能
平滑过渡:开发者可从传统LangChain逐步迁移到LCEL
特有优势:主要改进在于提供更直观的管道式编程体验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 from langchain_community.document_loaders import WebBaseLoaderloader = WebBaseLoader( web_paths=("https://zh.wikipedia.org/wiki/黑神话:悟空" ,) ) docs = loader.load() from langchain_text_splitters import RecursiveCharacterTextSplittertext_splitter = RecursiveCharacterTextSplitter(chunk_size=1000 , chunk_overlap=200 ) all_splits = text_splitter.split_documents(docs) from langchain_huggingface import HuggingFaceEmbeddings embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh" , model_kwargs={'device' : 'cpu' }, encode_kwargs={'normalize_embeddings' : True } ) from langchain_core.vectorstores import InMemoryVectorStorevectorstore = InMemoryVectorStore(embeddings) vectorstore.add_documents(all_splits) retriever = vectorstore.as_retriever(search_kwargs={"k" : 3 }) from langchain_core.prompts import ChatPromptTemplateprompt = ChatPromptTemplate.from_template(""" 基于以下上下文,回答问题。如果上下文中没有相关信息, 请说"我无法从提供的上下文中找到相关信息"。 上下文: {context} 问题: {question} 回答:""" )from langchain_openai import ChatOpenAIfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.runnables import RunnablePassthroughimport osfrom dotenv import load_dotenvload_dotenv() llm = ChatOpenAI( model=os.getenv("DEEPSEEK_MODEL_NAME" ), api_key=os.getenv("DEEPSEEK_API_KEY" ), base_url=os.getenv("DEEPSEEK_BASE_URL" ) ) chain = ( { "context" : retriever | (lambda docs: "\n\n" .join(doc.page_content for doc in docs)), "question" : RunnablePassthrough() } | prompt | llm | StrOutputParser() ) question = "黑悟空有哪些游戏场景?" retriever_output = retriever.invoke(question) print ("检索器输出:" , retriever_output)context = "\n\n" .join(doc.page_content for doc in retriever_output) print ("合并文档输出:" , context)prompt_output = prompt.invoke({"context" : context, "question" : question}) print ("提示模板输出:" , prompt_output)llm_output = llm.invoke(prompt_output) print ("LLM输出:" , llm_output)final_output = StrOutputParser().invoke(llm_output) print ("最终输出:" , final_output)question = "黑悟空有哪些游戏场景?" response = chain.invoke(question)
检索阶段:
Lambda处理:
输入:Document列表
输出:合并后的文本字符串
RunnablePassthrough:
提示模板:
输入:字典{“context”:文本, “question”:问题}
输出:格式化后的提示字符串
LLM阶段:
输入:提示模板字符串
输出:ChatMessage对象
输出解析:
输入:ChatMessage对象
输出:纯文本回答
重构之LangGraph
基于状态(State)驱动的执行模型
DAG(有向无环图)任务流
任务模块化
灵活的控制流
并行执行
适用场景:特别适合需要复杂控制流(如循环判断)的RAG系统和Agent开发
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 from langchain_community.document_loaders import WebBaseLoaderloader = WebBaseLoader( web_paths=("https://zh.wikipedia.org/wiki/黑神话:悟空" ,) ) docs = loader.load() from langchain_text_splitters import RecursiveCharacterTextSplittertext_splitter = RecursiveCharacterTextSplitter(chunk_size=1000 , chunk_overlap=200 ) all_splits = text_splitter.split_documents(docs) from langchain_huggingface import HuggingFaceEmbeddingsembeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh" , model_kwargs={'device' : 'cpu' }, encode_kwargs={'normalize_embeddings' : True } ) from langchain_core.vectorstores import InMemoryVectorStorevector_store = InMemoryVectorStore(embeddings) vector_store.add_documents(all_splits) from langchain import hubprompt = hub.pull("rlm/rag-prompt" ) from typing import List from typing_extensions import TypedDictfrom langchain_core.documents import Documentclass State (TypedDict ): question: str context: List [Document] answer: str def retrieve (state: State ): retrieved_docs = vector_store.similarity_search(state["question" ]) return {"context" : retrieved_docs} import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIload_dotenv() llm = ChatOpenAI( model=os.getenv("DEEPSEEK_MODEL_NAME" ), api_key=os.getenv("DEEPSEEK_API_KEY" ), base_url=os.getenv("DEEPSEEK_BASE_URL" ) ) def generate (state: State ): docs_content = "\n\n" .join(doc.page_content for doc in state["context" ]) messages = prompt.invoke({"question" : state["question" ], "context" : docs_content}) response = llm.invoke(messages) return {"answer" : response.content} from langgraph.graph import START, StateGraph graph = ( StateGraph(State) .add_sequence([retrieve, generate]) .add_edge(START, "retrieve" ) .compile () ) question = "黑悟空有哪些游戏场景?" response = graph.invoke({"question" : question}) print (f"\n问题: {question} " )print (f"答案: {response['answer' ]} " )
代码详解:
DAG基本概念与特点
定义:DAG(有向无环图)是由节点和有向边组成的图结构,不能形成循环路径
核心特性:
有向性:边具有明确方向,如A→B表示从A到B的单向流动
无环性:不能出现A→B→C→A这样的循环路径
状态传递:每个节点都有明确的输入输出状态,保证数据流顺序性
应用价值:特别适合Agent工作流排程和复杂RAG流程设计
RAG流程与节点设置
标准流程节点:
Start:流程起始节点
Retrieve:检索相关文档片段
Generate:生成最终回答
状态传递机制:
问题状态(question):用户原始提问
上下文状态(context):检索到的文档片段列表
回答状态(answer):生成的最终答案
应用状态与图构建
状态定义
图构建方法
使用StateGraph初始化图结构
通过add_sequence添加节点执行顺序
用add_edge明确节点间流向
最终调用compile完成编译
复杂查询与多路径检索
扩展性设计:
优势对比:
简单RAG:5行代码即可实现基础功能
复杂RAG:需要图结构处理错误纠正和多路检索
典型案例:游戏《黑神话:悟空》场景查询,涉及重庆大足石刻、山西小西天等多个地点
解决方案选择与需求匹配
LlamaIndex:适合简单检索场景(5行代码实现)
LangChain:适合中等复杂度流程控制
LangGraph:适合需要状态管理和多路径的复杂场景
开发建议:
从简单方案开始,按需升级复杂度
预留扩展接口应对未来需求变化
复杂系统可能需要结合多种框架优势
LangGraph vs. 传统 LangChain
执行方式:
LangChain采用线性执行(pipeline),适合简单任务
LangGraph采用DAG(有向无环图)执行,支持并行和条件分支,适合复杂任务
状态管理:
LangChain需要手动管理变量传递
LangGraph通过统一State对象自动传递状态
任务复用:
LangChain组件化但流程固定
LangGraph各步骤可单独测试和替换
分支逻辑:
LangChain需要手动编写if-else
LangGraph通过add_conditional_edges()方法实现
并行执行:
LangChain需要手动编写async/await
LangGraph通过add_parallel()自动实现并行
适用场景:
LangChain适用于简单任务
LangGraph适用于需要并行检索多数据源(如同时检索关系数据库和向量数据库)的复杂任务
不使用框架,手工构建RAG
手工设置嵌入模型
手工设置向量数据库
手工构建提示词
手工调用LLM API
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import osfrom dotenv import load_dotenvload_dotenv() docs = [ "黑神话悟空的战斗如同武侠小说活过来一般,当金箍棒与妖魔碰撞时,火星四溅,招式行云流水。悟空可随心切换狂猛或灵动的战斗风格,一棒横扫千军,或是腾挪如蝴蝶戏花。" , "72变神通不只是变化形态,更是开启新世界的钥匙。化身飞鼠可以潜入妖魔巢穴打探军情,变作金鱼能够探索深海遗迹的秘密,每一种变化都是一段独特的冒险。" , "每场BOSS战都是一场惊心动魄的较量。或是与身躯庞大的九头蟒激战于瀑布之巅,或是在雷电交织的云海中与雷公电母比拼法术,招招险象环生。" , "驾着筋斗云翱翔在这片神话世界,瑰丽的场景令人屏息。云雾缭绕的仙山若隐若现,古老的妖兽巢穴中藏着千年宝物,月光下的古寺钟声回荡在山谷。" , "这不是你熟悉的西游记。当悟空踏上寻找身世之谜的旅程,他将遇见各路神仙妖魔。有的是旧识,如同样桀骜不驯的哪吒;有的是劲敌,如手持三尖两刃刀的二郎神。" , "作为齐天大圣,悟空的神通不止于金箍棒。火眼金睛可洞察妖魔真身,一个筋斗便是十万八千里。而这些能力还可以通过收集天外陨铁、悟道石等材料来强化升级。" , "世界的每个角落都藏着故事。你可能在山洞中发现上古大能的遗迹,云端天宫里寻得昔日天兵的宝库,或是在凡间集市偶遇卖人参果的狐妖。" , "故事发生在大唐之前的蛮荒世界,那时天庭还未定鼎三界,各路妖王割据称雄。这是一个神魔混战、群雄逐鹿的动荡年代,也是悟空寻找真相的起点。" , "游戏的音乐如同一首跨越千年的史诗。古琴与管弦交织出战斗的激昂,笛萧与木鱼谱写禅意空灵。而当悟空踏入重要场景时,古风配乐更是让人仿佛穿越回那个神话的年代。" ] from sentence_transformers import SentenceTransformermodel = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2' ) doc_embeddings = model.encode(docs) print (f"文档向量维度: {doc_embeddings.shape} " )import faissimport numpy as npdimension = doc_embeddings.shape[1 ] index = faiss.IndexFlatL2(dimension) index.add(doc_embeddings.astype('float32' )) print (f"向量数据库中的文档数量: {index.ntotal} " )question = "黑神话悟空的战斗系统有什么特点?" query_embedding = model.encode([question])[0 ] distances, indices = index.search( np.array([query_embedding]).astype('float32' ), k=3 ) context = [docs[idx] for idx in indices[0 ]] print ("\n检索到的相关文档:" )for i, doc in enumerate (context, 1 ): print (f"[{i} ] {doc} " ) prompt = f"""根据以下参考信息回答问题,并给出信息源编号。 如果无法从参考信息中找到答案,请说明无法回答。 参考信息: {chr (10 ).join(f"[{i + 1 } ] {doc} " for i, doc in enumerate (context))} 问题: {question} 答案:""" from openai import OpenAIclient = OpenAI( api_key=os.getenv("DEEPSEEK_API_KEY" ), base_url=os.getenv("DEEPSEEK_BASE_URL" ) ) response = client.chat.completions.create( model=os.getenv("DEEPSEEK_MODEL_NAME" ), messages=[{ "role" : "user" , "content" : prompt }], max_tokens=1024 ) print (f"\n生成的答案: {response.choices[0 ].message.content} " )
嵌入模型设置:直接调用SentenceTransformer等库,如SentenceTransformer(‘sentence-transformers/all-MiniLM-LG-v2’)
向量数据库构建:
使用FAISS创建索引:faiss.IndexFlatL2(dimension)
添加文档向量:index.add(doc_embeddings.astype(‘float32’))
检索实现:
问题编码:model.encode([question])
相似度搜索:distances, indices = index.search(np.array([query_embedding]).astype(‘float32’), k)
提示词构建:需包含参考信息整合逻辑,如[f”[{i+1}] {doc}” for i, doc in enumerate(context)]
LLM调用:直接调用API如DeepSeek,注意设置max_tokens等参数
适用场景:当项目需求简单明确时,手工构建比使用框架更轻量高效,可避免不必要的框架依赖
核心优势:完全掌控每个环节,适合需要深度定制的场景,如特殊数据处理流程或非标准评估需求