国外网站模版,红色企业网站,网站开发怎么才能接到私活,wordpress 资源主题本文档基于 src/examples/retrieval/demo_retrieval1.py#xff0c;详细讲解如何利用 LangChain 实现一个简单的 RAG (Retrieval-Augmented Generation) 系统。我们将重点展示每个步骤的代码实现、调试输出#xff08;Logs#xff09;#xff0c;并深度解析每一个关键函数及…本文档基于src/examples/retrieval/demo_retrieval1.py详细讲解如何利用 LangChain 实现一个简单的 RAG (Retrieval-Augmented Generation) 系统。我们将重点展示每个步骤的代码实现、调试输出Logs并深度解析每一个关键函数及其参数。语料背景说明本项目使用的示例文本是《爱比克泰德金言录》 (The Golden Sayings of Epictetus)。作者爱比克泰德 (Epictetus)古希腊著名的斯多葛学派 (Stoicism) 哲学家。内容核心思想是“区分我们能控制的和不能控制的”。他教导人们在面对无法改变的外部环境时如何通过控制自己的判断来获得内心的宁静。用途包含大量哲理段落的非结构化文本适合演示 RAG 如何检索智慧。整体流程图Phase 2: Retrieval Generation (检索与生成)Phase 1: Indexing (构建知识库)FilterTextLoaderSplitterEmbedding ModelIndexEmbedding ModelSimilarity SearchTop K Docs ScoresFormatSendResponseQuery VectorUser QueryContextPromptGemini LLMFinal AnswerCleaned TextRaw Text FileDocumentsChunksVectorsFAISS Vector Store第一部分构建 Vector Store步骤 1: 数据准备 (Data Preparation)获取原始文本过滤无关内容如版权声明并保存为纯净的文本文件。src_docsrc/examples/retrieval/docs/src_golden_hymns_of_epictetus.txtoutput_docsrc/examples/retrieval/docs/output_golden_hymns_of_epictetus_new.txtstart_savingFalsestop_savingFalseline_to_save[]withopen(src_doc,r,encodingutf-8)asf:fori,lineinenumerate(f.readlines()):ifi2000:break# (Demo仅读取前2000行)ifAre these the only works of Providence within us?inline:start_savingTrueif*** END OF THE PROJECT GUTENBERG EBOOK THE GOLDEN SAYINGS OF EPICTETUSinline:stop_savingTrueifstart_savingandnotstop_saving:line_to_save.append(line)logger.info(len of line_to_save:str(len(line_to_save)))withopen(output_doc,w,encodingutf-8)asf:f.writelines(line_to_save)真实调试输出INFO | len of line_to_save:1740深度代码解析这一步主要使用 Python 标准库进行文件处理不涉及 LangChain 特定函数。open(..., encodingutf-8): 确保以 UTF-8 编码读取和写入文件防止处理非 ASCII 字符时出现乱码。过滤逻辑: 通过简单的字符串匹配 (if ... in line) 来确定正文的起止位置。这是 RAG 流程中至关重要的数据清洗 (Data Cleaning)步骤。如果不过滤RAG 可能会检索到版权声明等无用信息干扰回答。步骤 2: 加载数据 (Document Loading)使用TextLoader加载文件。fromlangchain_community.document_loadersimportTextLoader logger.info( load text data to langchain)# 初始化加载器loaderTextLoader(file_pathoutput_doc)# 执行加载golden_saying_contentloader.load()logger.info(type of golden_saying_content: str(type(golden_saying_content)))logger.info(len of golden_saying_content: str(len(golden_saying_content)))真实调试输出INFO | load text data to langchain INFO | type of golden_saying_content: class list INFO | len of golden_saying_content: 1 INFO | type of golden_saying_contens first element:class langchain_core.documents.base.Document深度代码解析TextLoader(file_path):作用: 这是 LangChain 最基础的文档加载器用于处理纯文本文件。参数:file_path指定了要读取的文件路径。loader.load():作用: 执行读取操作返回一个包含Document对象的列表。返回值:List[Document]。对于TextLoader因为它不进行切分所以列表里通常只有1 个Document 对象包含了整个文件的内容。Document 对象: 这是 LangChain 的核心数据结构具有两个属性page_content: 文件的完整文本内容字符串。metadata: 一个字典默认包含{source: 文件路径}。步骤 3: 文本切分 (Text Splitting)使用RecursiveCharacterTextSplitter将长文档切分为片段。fromlangchain_text_splittersimportRecursiveCharacterTextSplitter logger.info( chunking )text_splitterRecursiveCharacterTextSplitter(chunk_size1000,chunk_overlap50,length_functionlen,add_start_indexTrue)textstext_splitter.split_documents(golden_saying_content)logger.info(texts[0])真实调试输出INFO | chunking INFO | page_contentAre these the only works of Providence within us? What words suffice to\npraise or set them forth? ... metadata{source: src/examples/retrieval/docs/output_golden_hymns_of_epictetus_new.txt, start_index: 0}深度代码解析RecursiveCharacterTextSplitter(...): 这是处理通用文本的首选切分器。它按顺序尝试使用分隔符列表[\n\n, \n, , ]进行切分目的是尽量保持段落、句子和单词的完整性。chunk_size1000:目标块大小。分割器会尽量让每个块的字符数接近这个值不超过它除非单个词太长。设置过小会导致上下文丢失设置过大会超出 Embedding 模型的窗口限制。chunk_overlap50:重叠量。相邻的两个块会有 50 个字符的重复内容。这非常重要可以防止重要的关键词如人名、概念被切分在两个块的边界上从而丢失上下文联系。length_functionlen: 用于计算长度的函数。默认是 Python 的len()计算字符数。如果你需要严格控制 Token 数量如 OpenAI 的限制可以使用token_counter函数。add_start_indexTrue: 是否添加起始位置索引。设置为 True 后每个 Chunk 的metadata会增加一个start_index字段记录该片段在原文中的位置这对调试和引用非常有用。text_splitter.split_documents(documents):作用: 接收一个 Document 列表应用上述切分规则返回一个新的、包含更多但更短Document 对象的列表。步骤 4: 向量化与存储 (Embedding Indexing)fromlangchain_community.vectorstoresimportFAISSfromlangchain_google_genaiimportGoogleGenerativeAIEmbeddings logger.info( text embedding )# 1. 初始化 Embedding 模型embedding_modelGoogleGenerativeAIEmbeddings(modelmodels/embedding-001)# 2. 创建向量库vector_storeFAISS.from_documents(documentstexts,embeddingembedding_model)logger.info( embedding done )真实调试输出INFO | text embedding INFO | Checking GOOGLE_API_KEY: True INFO | embedding done 深度代码解析GoogleGenerativeAIEmbeddings(...):作用: LangChain 提供的 Google Gemini Embedding 接口封装。参数modelmodels/embedding-001: 指定使用的具体模型版本。这是一个专门针对语义检索优化的模型。注意: 此类依赖GOOGLE_API_KEY环境变量。FAISS.from_documents(...):这是一个便捷的工厂方法它在后台执行了整个 Indexing 流程Embed: 调用embedding_model.embed_documents()将texts列表中的每个 chunk 文本转化为向量例如 768 维的浮点数数组。Index: 初始化一个 FAISS 索引通常是IndexFlatL2并将这些向量插入其中。Store: 在内存中建立一个映射将向量 ID 映射回原始的 Document 对象以便检索时能返回文本内容。返回值: 一个初始化好的FAISS向量库对象。 深度解析Embedding 模型详解在这一步我们使用了GoogleGenerativeAIEmbeddings基于 LLM 的 Embedding。你可能会问为什么不直接用简单的算法或者直接用 Chat Model1. 为什么要用 LLM (如 Google/OpenAI) 做 Embedding上下文感知 (Contextual): LLM 基于 Transformer 架构能理解“语境”。例如它知道“Apple”在“Apple pie”食物和“Apple Inc”公司中代表完全不同的含义并会生成不同的向量。这是传统方法如 Word2Vec做不到的。语义理解强: 它们不仅仅是匹配关键词而是真正“读懂”了句子。即使两个句子没有共同的词如“手机没电了”和“屏幕黑了”LLM 也能识别出它们在语义上是相关的。2. 为什么不能直接用 Chat Model (如 GPT-4, Gemini Pro) 做 Embedding你可能已经在用强大的 Chat Model对话模型为什么不能直接用它生成向量训练目标不同Chat Model (生成模型)目标是“预测下一个字”它的强项是生成流畅、合逻辑的文本。Embedding Model (表示模型)目标是“将语义压缩成向量”它的强项是计算两个句子在数学空间上的距离相似度。输出格式不同Chat Model 的 API 通常只返回生成的文本字符串。Embedding Model 的 API 返回的是浮点数列表向量。接口限制虽然 Chat Model 内部也有向量Hidden States但绝大多数商业 API如 OpenAI, Google都不开放这个底层数据只开放生成的文本。因此我们需要专门的Embedding Model来完成向量化工作。3. 其他 Embedding 方法对比方法例子优点缺点LLM APIGoogle Gemini, OpenAI效果最好无需维护模型语义理解最强需要付费/联网数据隐私顾虑本地模型HuggingFace (如 all-MiniLM)免费离线可用数据隐私好消耗本地计算资源 (CPU/GPU)大模型跑不动静态词向量Word2Vec, GloVe速度极快资源消耗低不懂上下文无法区分多义词效果一般统计方法TF-IDF, One-Hot简单直观关键词匹配精准稀疏向量完全不懂语义不知道“开心”和“高兴”是近义词总结在 RAG 应用中为了保证检索的准确性尤其是基于自然语言的模糊搜索基于 LLM 或 HuggingFace Transformer 的 Embedding 是目前的标准选择。第二部分基于 Vector Store 的查询步骤 1: 语义检索 (Semantic Search)手动调用检索接口并获取分数。str_queryhow can I practice mindfulness if I am always busy and distractedlogger.info( query from vector store )# 执行检索docs_and_scoresvector_store.similarity_search_with_score(querystr_query,k10)# 打印检索结果fori,(doc,score)inenumerate(docs_and_scores):logger.info(f[Source{i1}] Score:{score:.4f}, Content:{doc.page_content[:50]}...)真实调试输出INFO | query from vector store INFO | type of rs:class list len of rs: 10 INFO | [Source 1] Score: 0.9082, Content: XXX You must know that it is no easy thing for a ... INFO | [Source 2] Score: 0.9529, Content: One who has had fever, even when it has left him, ... INFO | [Source 3] Score: 0.9596, Content: _I move not without Thy knowledge!_ XXIX Conside... ...讲解Score: FAISS 默认通常使用 L2 距离欧氏距离分数越低代表距离越近即越相似。如果是 Cosine Similarity则分数越高越相似。这一点在解读日志时非常重要。注这里的 Score 0.9082 比 0.9529 小说明 Source 1 更相似。Content: 打印前 50 个字符可以快速确认检索到的内容是否真的跟“正念”、“忙碌”有关。步骤 2 3: 构建上下文并生成回答 (Generation)fromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.output_parsersimportStrOutputParserfromsrc.llm.gemini_chat_modelimportget_gemini_llm# 1. 格式化 Contextcontext_parts[]fori,(doc,score)inenumerate(docs_and_scores):context_parts.append(f[Source{i1}] (Score:{score:.4f}):\n{doc.page_content})formatted_context\n\n.join(context_parts)# 2. 定义 PrompttemplateAnswer the question based only on the following context. Please cite the sources you used for your answer (e.g., [Source 1], [Source 2]). Context: {context} Question: {question} promptChatPromptTemplate.from_template(template)# 3. 初始化 LLMllmget_gemini_llm()# 4. 构建 LCEL 链chainprompt|llm|StrOutputParser()# 5. 执行链responsechain.invoke({context:formatted_context,question:str_query})logger.info(fLLM Response:\n{response})真实调试输出 (LLM Response)INFO | query with llm INFO | LLM Response: Based on the context provided, you can practice a form of mindfulness even when busy and distracted through the following methods: * **Reframe your perspective on your situation:** If you are alone, instead of calling it solitude, you should call it Tranquillity and Freedom. When in the company of many, rather than viewing it as a wearisome crowd and tumult, consider it an assembly and a tribunal and accept it with contentment [Source 1]. * **Be self-sufficient and converse with yourself:** A person should be prepared to be sufficient unto himself—to dwell with himself alone. You should be able to converse with yourself, not need others for distraction, and direct your thoughts toward the Divine Administration and your relation to everything else [Source 5]. * **Observe your inner state:** Take time to observe how past and present events have affected you, what things still have the power to hurt you, and how they might be cured or removed [Source 5]. * **Focus on breaking mental habits:** If you have a negative habit like anger, do not feed it or give it anything that helps it increase. A practical step is to keep quiet and count the days you are successful in avoiding the negative habit [Source 2]. * **Maintain your principles daily:** For a principle to become your own, you must maintain it each day and work it out in your life [Source 1]. * **Shift your focus:** Rather than spending all your time calculating and contriving for profit, you are encouraged to learn about the administration of the World, your place in it, and what constitutes your own Good and Evil [Source 4]. **Sources:** * [Source 5] (Score: 0.9677) * [Source 4] (Score: 0.9674) * [Source 3] (Score: 0.9596) * [Source 2] (Score: 0.9529) * [Source 1] (Score: 0.9082)深度代码解析ChatPromptTemplate.from_template(template):作用: 从字符串模板创建一个 Chat Prompt 对象。占位符:{context}和{question}是变量后续invoke时会自动替换。这是 Prompt Engineering 的核心我们显式地告诉 LLM “只基于 context 回答”这是减少幻觉的关键。get_gemini_llm():这是本项目自定义的辅助函数用于初始化GeminiChatModel。它封装了读取 Config、设置 API Key 等繁琐步骤。LCEL (LangChain Expression Language):chain prompt | llm | StrOutputParser()|(Pipe Operator): 这是 LangChain 的特色语法类似于 Unix 管道。数据从左向右流动prompt: 接收输入字典填充模板生成PromptValue。llm: 接收PromptValue调用 Gemini API返回AIMessage对象。StrOutputParser(): 接收AIMessage提取其中的content文本字符串。chain.invoke(...):作用: 触发整个链条的执行。输入: 一个字典{context: ..., question: ...}必须匹配 Prompt 中的占位符。可以看到 LLM 成功地理解了问题。阅读了我们提供的 Context。引用了来源([Source 1])这证明它确实是用我们的数据在回答而不是在瞎编。