news 2026/5/7 21:34:52

基于RAG与LangChain构建多PDF智能问答系统:从原理到实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于RAG与LangChain构建多PDF智能问答系统:从原理到实践

1. 项目概述:一个能与多份PDF“对话”的智能助手

如果你经常需要从一堆PDF报告、论文或手册里找信息,肯定体会过那种“大海捞针”的烦躁。一页页翻,用Ctrl+F搜索关键词,结果要么是搜不到,要么是搜出一堆不相关的内容,真正需要的那段话可能藏在某个复杂的句子里,传统搜索根本无能为力。这个项目,Multi-PDFs ChatApp AI Agent,就是为了解决这个痛点而生的。

简单来说,它让你能用“说话”的方式和你的PDF文档库互动。你不再需要记住精确的关键词,只需要像问同事一样,用自然语言提问,比如“总结一下第三季度财报的要点”、“对比A方案和B方案在成本上的差异”,或者“根据这份技术手册,设备X的日常维护步骤是什么?”。这个基于Streamlit构建的Web应用,会利用LangChain框架、Google Gemini Pro大语言模型以及FAISS向量数据库,从你上传的所有PDF中理解问题、找到最相关的信息片段,并生成一个准确、连贯的答案。

它本质上是一个检索增强生成(RAG)系统的完整实现。RAG是当前让大模型“落地”、克服其“幻觉”和知识滞后问题的关键技术。这个项目把RAG的整个流水线——从文档加载、文本切分、向量化存储,到语义检索和智能生成——都封装成了一个开箱即用的应用。无论是学生研究文献、分析师处理市场报告,还是开发者查阅技术文档,都能通过这个工具极大提升信息获取效率。接下来,我会带你深入这个项目的内部,拆解它的每一处设计,并分享我在搭建和优化类似系统时踩过的坑和总结的经验。

2. 核心架构与组件选型解析

一个能“读懂”多份PDF并回答问题的系统,背后是一套精密的协作流程。理解这个架构,不仅能帮你用好这个应用,更能让你在需要定制或排查问题时,知道从何下手。

2.1 整体工作流程:从PDF到答案的旅程

整个系统的运作可以看作一个五步流水线,下图清晰地展示了这个过程:

  1. 文档摄入与解析:用户通过Web界面(Streamlit)上传一个或多个PDF文件。后端使用PyPDF2库打开这些文件,逐页提取出纯文本内容。这里第一个注意事项就来了:PyPDF2对某些复杂排版或扫描版PDF的文本提取能力有限,如果遇到提取乱码或大量空白,可能需要换用pdfplumberpymupdf(又名fitz)库,它们对格式的兼容性更好。

  2. 文本分块与向量化:直接处理整本PDF是不现实的,因为大模型有上下文长度限制。因此,需要把提取的长文本切割成大小合适的“块”。这个项目采用了滑动窗口分块技术。简单说,就是像用一个固定长度的窗口在文本上滑动,每次截取一段,并且相邻的窗口之间会有部分重叠。这样做的好处是,即使一个问题相关的信息恰好被标准分块切在了两个块的边界,重叠部分也能保证上下文信息的连贯性,提高检索的准确性。分块后的文本,通过langchain_google_genai调用Google的嵌入模型,将每一段文字转换成一个高维度的向量(可以理解为一串代表语义的数字指纹)。

  3. 向量存储与索引:生成的海量向量需要被高效地存储和检索。这里选用了FAISS(Facebook AI Similarity Search)库。FAISS就像一个为向量特化的超级搜索引擎,它会把所有文本块的向量构建成一个索引。当用户提问时,系统能在这个索引中快速找到与问题语义最相似的几个文本块,这个过程比在原始文本中逐字匹配要快得多、也准得多。

  4. 语义检索与上下文组装:用户输入问题后,问题文本同样被转换成向量。FAISS接收这个“问题向量”,并在索引中执行相似度搜索,找出前k个(比如前4个)最相似的文本块。这些块就是系统认为与问题最相关的原始材料。它们被从数据库中取出,拼接在一起,形成提供给大模型的“参考上下文”。

  5. 答案生成与呈现:最后,拼接好的上下文和用户的问题,被一起构造成一个提示词(Prompt),发送给Google Gemini Pro模型。Prompt通常会这样设计:“请基于以下上下文信息回答问题:{上下文}。问题是:{用户问题}。如果上下文不包含答案,请直接说‘根据提供的信息无法回答’。” 模型基于这个指令和上下文,生成最终的自然语言答案,并通过Streamlit界面流畅地(Streaming)展示给用户。

2.2 关键技术栈选型背后的考量

为什么是LangChain + Gemini + FAISS + Streamlit这个组合?这背后每一环都有其道理。

  • LangChain: 编排框架,而非必选项。LangChain的核心价值在于它提供了一套高级抽象(如DocumentLoaderTextSplitterVectorStore),让开发者能像搭积木一样快速构建RAG应用,无需从零处理每个细节。它封装了与多种模型、数据库交互的复杂性。但要注意,LangChain有时会带来额外的复杂性和性能开销。对于追求极致控制或性能的场景,直接调用各组件API也是完全可行的路径。这个项目使用LangChain,极大地加速了原型开发和代码可读性。

  • Google Gemini Pro: 性能与成本的平衡。在众多大模型中,选择Gemini Pro有几个原因:一是其API易于获取且稳定;二是在长文本理解和推理任务上表现优异,适合处理从PDF中提取的复杂信息;三是相较于某些按Token精细计费的模型,其定价模式可能对中等使用量的场景更友好。项目也提到了兼容OpenAI GPT、Claude等,这体现了LangChain的优势——通过更换ChatModel的配置,就能轻松切换模型供应商,提供了灵活性。

  • FAISS: 本地向量检索的效率之选。FAISS是Meta开源的库,专为稠密向量相似性搜索优化,尤其擅长处理百万级甚至更大规模的向量集。它支持CPU和GPU加速,并且可以持久化到磁盘。在这个项目中,使用faiss-cpu版本意味着无需昂贵的GPU也能运行,降低了部署门槛。相比其他向量数据库(如Pinecone、Weaviate等云服务),FAISS是本地部署、零成本的解决方案,适合对数据隐私敏感或希望离线运行的项目。

  • Streamlit: 快速构建原型的利器。对于数据科学家和算法工程师来说,Streamlit能以极少的代码将Python脚本转化为交互式Web应用。它内置了文件上传、按钮、聊天框、状态显示等组件,非常适合快速搭建AI demo界面。其“代码即UI”的理念,让开发者能专注于核心逻辑,而非前端细节。这个项目的UI,包括侧边栏上传、聊天历史展示,都是用Streamlit简洁的API实现的。

实操心得:组件选型的替代方案这个技术栈是一个优秀的起点,但并非唯一解。例如,文本分块可以用更精细的RecursiveCharacterTextSplitter,它尝试按段落、句子等自然边界分割,可能比固定窗口保留更多语义完整性。向量数据库可以换成ChromaDB,它更轻量且自带持久化和元数据管理。前端也可以用Gradio,它同样简单,且在自定义复杂布局时可能更灵活。了解这些备选方案,能帮助你在项目需求变化时快速调整。

3. 环境搭建与核心配置详解

纸上得来终觉浅,绝知此事要躬行。让我们一步步把这个项目跑起来,并深入每个配置项的细节。

3.1 从零开始的环境准备

首先,你需要一个Python环境(建议3.8以上)。我强烈推荐使用condavenv创建独立的虚拟环境,避免包版本冲突。

# 克隆项目代码 git clone https://github.com/GURPREETKAURJETHRA/Multi-PDFs_ChatApp_AI-Agent.git cd Multi-PDFs_ChatApp_AI-Agent # 创建并激活虚拟环境 (以conda为例) conda create -n pdf-chatbot python=3.10 conda activate pdf-chatbot # 安装依赖 pip install -r requirements.txt

安装过程可能会因网络问题卡住,特别是安装faiss-cpu时。如果遇到超时,可以尝试使用国内镜像源,例如:

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

3.2 获取并配置Google API密钥

这是整个项目能运行起来的最关键一步。没有有效的API密钥,模型调用将全部失败。

  1. 访问 Google AI Studio (原MakerSuite)。你需要一个Google账号。
  2. 点击“Create API Key”按钮。系统可能会提示你创建一个项目,按指引操作即可。
  3. 成功创建后,你会获得一个以AIza开头的长字符串,这就是你的API密钥。请像保护密码一样保护它,不要上传到任何公开仓库。

接下来,在项目的根目录下(与app.py同级),创建一个名为.env的文件。这个文件用于存储环境变量,确保敏感信息不写入代码。

# .env 文件内容 GOOGLE_API_KEY=你的API密钥粘贴在这里

例如:GOOGLE_API_KEY=AIzaSyB_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

重要安全警告:务必确保.env文件被添加到.gitignore中,防止误提交。在app.py中,项目使用python-dotenv库的load_dotenv()函数来读取这个密钥,然后通过os.getenv("GOOGLE_API_KEY")调用。这是管理配置的标准安全实践。

3.3 首次运行与界面初探

配置完成后,在终端运行以下命令启动应用:

streamlit run app.py

几秒钟后,你的默认浏览器会自动打开一个标签页,地址通常是http://localhost:8501。你将看到类似下图的界面:

应用界面主要分为两部分:

  • 左侧边栏:这里是控制中心。包含PDF文件上传区域、“Submit & Process”按钮,以及一些可能的配置选项(如选择分块大小、重叠长度等)。
  • 主区域:聊天交互区。上方会显示聊天历史,下方有一个输入框供你提问。

首次运行时,侧边栏会显示文件上传器。你可以拖放或点击选择你的PDF文件。选择后,务必记得点击“Submit & Process”按钮。这个动作会触发后台的文档处理流水线(加载、分块、向量化、存储)。你会在界面上看到处理进度提示。

处理完成后,你就可以在主聊天框输入问题了。试试问一些基于PDF内容的问题,比如“这份文档主要讲了什么?”或者更具体的内容。

4. 核心代码模块深度拆解

理解了流程和界面,我们深入到代码层面,看看每个核心功能是如何实现的。我们以app.py为主要分析对象。

4.1 文档加载与智能分块策略

app.py中,处理上传文件的函数(通常命名为process_documents或类似)会执行以下操作:

# 伪代码,示意核心逻辑 from langchain.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter def process_uploaded_files(uploaded_files): all_docs = [] for uploaded_file in uploaded_files: # 1. 临时保存上传的文件 with open(temp_file_path, "wb") as f: f.write(uploaded_file.getbuffer()) # 2. 使用LangChain的PDF加载器 loader = PyPDFLoader(temp_file_path) documents = loader.load() # 返回一个Document对象列表,每个对象包含一页文本和元数据 all_docs.extend(documents) # 3. 文本分块 - 这是影响效果的关键! text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, # 每个块的最大字符数 chunk_overlap=200, # 块与块之间的重叠字符数 length_function=len, separators=["\n\n", "\n", " ", ""] # 优先按段落,再按句子,再按单词分割 ) chunks = text_splitter.split_documents(all_docs) return chunks

参数调优经验

  • chunk_size:太小会导致信息碎片化,模型缺乏足够上下文;太大会使检索精度下降,且可能超出模型上下文窗口。1000-1500字符是一个常见的起始点。对于技术文档,可以稍大;对于对话或小说,可以稍小。
  • chunk_overlap:这是保证上下文连贯性的“安全边际”。通常设置为chunk_size的10%-20%。200-300字符的重叠能有效防止关键信息被割裂。
  • separatorsRecursiveCharacterTextSplitter会按这个列表的顺序尝试分割,这比简单的滑动窗口更能尊重文档的自然结构(段落->句子)。

4.2 向量化与FAISS索引构建

分块后的文本需要变成向量并存入FAISS。

# 伪代码,示意核心逻辑 from langchain_google_genai import GoogleGenerativeAIEmbeddings from langchain.vectorstores import FAISS def create_vector_store(text_chunks): # 1. 初始化嵌入模型 # 注意:这里需要你的GOOGLE_API_KEY已通过环境变量设置 embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001") # 指定嵌入模型 # 2. 从文本块创建向量存储 # 这一步会调用Google的嵌入API,将每个chunk转换为向量,然后由FAISS在内存中创建索引 vector_store = FAISS.from_documents(documents=text_chunks, embedding=embeddings) # 3. (可选)将索引保存到本地磁盘,下次无需重新处理 vector_store.save_local("faiss_index") return vector_store

关键点

  • GoogleGenerativeAIEmbeddings是LangChain提供的包装器,它负责调用Google的嵌入API。每次调用都会产生费用和网络延迟。
  • FAISS.from_documents是一个同步操作,如果文档很多(比如上千页),这个过程可能会耗时较长。在Streamlit应用中,你需要用st.spinner或进度条给用户反馈。
  • 保存索引到本地(save_local)是一个最佳实践。这样,用户再次打开应用时,如果上传相同的文档,可以直接加载已有索引,无需重新调用API和计算,速度极快。

4.3 检索链与对话记忆的实现

这是应用的大脑,负责理解问题、查找资料、组织对话。

# 伪代码,示意核心逻辑 from langchain_google_genai import ChatGoogleGenerativeAI from langchain.chains import ConversationalRetrievalChain from langchain.memory import ConversationBufferMemory def setup_conversation_chain(vector_store): # 1. 初始化大语言模型 llm = ChatGoogleGenerativeAI(model="gemini-pro", temperature=0.3, convert_system_message_to_human=True) # 2. 初始化对话记忆 # Memory用于保存多轮对话的历史,让AI能记住之前的问答上下文。 memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True, output_key='answer') # 3. 构建对话检索链 # 这是LangChain的核心链,它把检索器(retriever)、语言模型(llm)和记忆(memory)串联起来。 retriever = vector_store.as_retriever(search_kwargs={"k": 4}) # 检索最相关的4个块 conversation_chain = ConversationalRetrievalChain.from_llm( llm=llm, retriever=retriever, memory=memory, return_source_documents=True, # 非常重要!返回用于生成答案的源文档片段 verbose=False # 设为True可以看到链的详细执行步骤,用于调试 ) return conversation_chain

参数解析与调优

  • model="gemini-pro": 指定使用Gemini Pro聊天模型。
  • temperature=0.3: 控制模型输出的随机性。0表示最确定、最保守,1表示最有创意、最随机。对于事实性问答,设置为0.1-0.3可以获得更稳定、准确的答案。
  • search_kwargs={"k": 4}: 这是检索器参数,k值决定了每次检索返回多少个文本块。k太小可能信息不全,k太大会引入噪声并增加提示词长度(可能触发模型token限制)。通常从4开始尝试,根据答案质量调整。
  • return_source_documents=True:强烈建议开启。这能让应用在返回答案的同时,也返回它参考了哪些原文片段。这对于验证答案的可靠性、进行溯源至关重要,是构建可信AI应用的基本要求。

4.4 Streamlit前端交互逻辑

Streamlit的代码是声明式的,状态管理需要一些技巧。

# app.py 主逻辑简化示意 import streamlit as st def main(): st.set_page_config(page_title="Chat with PDFs") st.header("Chat with Multiple PDFs 💬") # 1. 初始化会话状态 # Streamlit脚本会从上到下重新执行,用st.session_state来保持状态(如聊天历史、向量库) if "conversation" not in st.session_state: st.session_state.conversation = None if "chat_history" not in st.session_state: st.session_state.chat_history = [] # 2. 侧边栏:文件上传与处理 with st.sidebar: uploaded_files = st.file_uploader("Upload your PDFs", type=['pdf'], accept_multiple_files=True) if st.button("Process"): with st.spinner("Processing..."): # 调用前面定义的函数 raw_text = get_pdf_text(uploaded_files) text_chunks = get_text_chunks(raw_text) vectorstore = get_vectorstore(text_chunks) # 创建对话链并存入会话状态 st.session_state.conversation = get_conversation_chain(vectorstore) st.success("Processing complete! You can now ask questions.") # 3. 主聊天界面 user_question = st.chat_input("Ask a question about your documents:") if user_question: # 显示用户问题 with st.chat_message("user"): st.write(user_question) # 获取并显示AI回答 with st.chat_message("assistant"): with st.spinner("Thinking..."): if st.session_state.conversation is None: st.warning("Please upload and process PDFs first.") else: # 调用对话链 response = st.session_state.conversation({'question': user_question}) answer = response['answer'] source_docs = response.get('source_documents', []) st.write(answer) # 流式输出答案 # 显示来源(可折叠,保持界面简洁) with st.expander("View source passages"): for i, doc in enumerate(source_docs): st.caption(f"**Passage {i+1}** (Page {doc.metadata.get('page', 'N/A')}):") st.text(doc.page_content[:500] + "...") # 只显示前500字符

这个结构清晰地分离了UI、状态管理和业务逻辑,是构建稳定Streamlit应用的常见模式。

5. 高级功能扩展与性能优化

基础功能跑通后,我们可以思考如何让它更强大、更实用。

5.1 支持更多文件格式与复杂文档

当前项目主要处理PDF和TXT。现实中的文档可能是Word、PPT、Excel甚至网页。

  • 扩展加载器:LangChain社区有丰富的DocumentLoader
    from langchain.document_loaders import UnstructuredWordDocumentLoader, UnstructuredPowerPointLoader, CSVLoader # 根据文件后缀选择不同的加载器
  • 处理扫描件(图片PDF):这需要OCR技术。可以使用pymupdf提取页面图片,然后通过pytesseract或Google Cloud Vision API进行文字识别,再将结果交给文本分块流程。这会显著增加处理时间和成本。
  • 处理复杂表格:普通文本提取会破坏表格结构。可以考虑使用专为表格设计的库,如camelottabula,将表格提取为DataFrame,再以结构化文本(如Markdown表格)的形式嵌入到上下文中。

5.2 提升检索质量:超越简单相似度搜索

基础的向量相似度搜索有时会失灵,比如问题“去年的利润是多少?”,而文档中写的是“2023年净利润为1亿元”。由于“去年”和“2023年”的向量可能不接近,导致检索失败。

  • 查询重写/扩展:在检索前,先用LLM对原始问题进行优化。例如,将“去年的利润”重写为“2023年利润 2022年利润 净利润”。
    # 简化的查询扩展示例 from langchain.llms import OpenAI from langchain.chains import LLMChain from langchain.prompts import PromptTemplate rewrite_prompt = PromptTemplate( input_variables=["question"], template="你是一个专业的搜索引擎优化助手。请将以下用户问题扩展成2-3个相关的关键词或短语,用空格分隔:{question}" ) llm = ChatGoogleGenerativeAI(model="gemini-pro", temperature=0) rewrite_chain = LLMChain(llm=llm, prompt=rewrite_prompt) expanded_query = rewrite_chain.run(original_question) # 然后用 expanded_query 去做向量检索
  • 混合搜索:结合稠密向量检索(语义相似)和稀疏向量检索(关键词匹配,如BM25)。LangChain支持通过EnsembleRetriever将多个检索器的结果融合,取长补短。
  • 元数据过滤:在存储向量时,可以为每个块附加元数据,如{“source”: “财报.pdf”, “page”: 5, “section”: “财务摘要”}。检索时,可以要求“只从‘财报.pdf’中检索”或“只检索第5到10页”,这能极大提升精准度。

5.3 优化性能与用户体验

  • 索引持久化与缓存:如前所述,一定要实现vector_store.save_local()FAISS.load_local()。可以为每个上传的文件集计算一个哈希值(如MD5),以哈希值为索引名存储。下次用户上传相同文件时,直接加载已有索引。
  • 异步处理:文档处理(尤其是调用嵌入API)是I/O密集型任务。可以使用asynciolangchain的异步接口,让文件处理在后台进行,不阻塞UI响应。
  • 流式输出:现在的答案是一次性生成的。可以启用LLM的流式响应,让答案一个字一个字地显示出来,提供更即时的反馈感。Gemini API和LangChain都支持流式输出。
  • 设置超时与重试:网络请求可能失败。在调用conversation_chain时,应添加超时和重试逻辑,提高应用的健壮性。

6. 常见问题排查与实战心得

在实际部署和使用中,你肯定会遇到各种问题。这里我总结了一份“避坑指南”。

6.1 安装与依赖问题

  • 问题:安装faiss-cpu失败,提示找不到合适的版本或编译错误。

    • 原因:FAISS的预编译轮子可能不兼容你的操作系统或Python版本。
    • 解决
      1. 首先确保Python版本是3.8-3.11的64位版本。
      2. 尝试使用conda安装:conda install -c conda-forge faiss-cpu。conda的环境管理通常更省心。
      3. 如果必须用pip,可以尝试安装更通用的版本:pip install faiss-cpu --no-binary :all:(这会从源码编译,需要系统有C++编译环境)。
  • 问题:运行时报错ImportError: cannot import name '...' from 'langchain'

    • 原因:LangChain版本更新较快,API可能有变动。项目requirements.txt中指定的版本可能与你安装的最新版不兼容。
    • 解决:查看项目根目录或GitHub页面是否有对LangChain版本的明确要求。可以尝试安装特定版本:pip install langchain==0.0.xxx。或者,根据错误信息去LangChain官方文档查看新版本的导入方式。

6.2 API密钥与网络问题

  • 问题:应用能启动,但一提问就报错,提示API密钥无效或权限不足。

    • 检查1:确认.env文件在正确位置,且密钥格式正确,没有多余空格或换行。
    • 检查2:确认你的Google Cloud项目已启用Gemini API。拥有API密钥不代表服务已启用,需要去 Google Cloud Console 的“API和服务”仪表板中手动启用“Generative Language API”。
    • 检查3:检查账户是否有足够的配额或余额。新项目可能有免费额度,但用完后需要设置账单。
  • 问题:处理文档或回答问题时速度非常慢,甚至超时。

    • 分析:慢可能发生在两个环节:1. 调用Google嵌入API(文档向量化);2. 调用Gemini Pro API(生成答案)。前者耗时与文档总长度成正比,后者与问题复杂度相关。
    • 优化
      • 对于文档处理:实现索引持久化,避免重复处理。
      • 对于问答:检查检索的k值是否过大(比如超过10),减少不必要的上下文长度。
      • 网络问题:考虑应用部署在离Google服务器较近的区域,或者为请求配置合理的超时时间。

6.3 应用功能与效果问题

  • 问题:AI的回答完全是胡编乱造,与文档内容无关(“幻觉”严重)。

    • 诊断:这是RAG系统最典型的问题。首先,点击“View source passages”查看AI检索到的源文本。如果源文本本身就不相关,那么问题出在检索阶段
      • 检索阶段优化:尝试减小chunk_size(如从1000降到500),增加chunk_overlap(如从100增到200)。或者尝试使用RecursiveCharacterTextSplitter并调整separators。也可以考虑启用search_type="mmr"(最大边际相关性),在相似性的基础上增加结果的多样性。
    • 如果检索到的文本是相关的,但答案还是胡扯:问题可能出在生成阶段。检查你的Prompt是否明确要求模型“严格基于上下文回答”。可以强化Prompt,例如:“请仅根据提供的上下文信息回答问题。如果上下文没有提供足够信息,请直接说‘根据所给信息无法回答’。不要使用你已有的知识。”
  • 问题:无法进行多轮对话,AI记不住之前说过的话。

    • 检查:确认ConversationBufferMemory已正确初始化并传入ConversationalRetrievalChain。在Streamlit中,确保st.session_state.conversation这个链对象在多次用户输入间被持久化,而不是每次都被重新创建。
    • 注意:记忆的长度是有限的。如果对话轮次非常多,最早的历史可能会被丢弃。你可以使用ConversationSummaryMemoryConversationBufferWindowMemory来管理更长的对话历史。
  • 问题:上传大文件(如200页的PDF)时,应用卡死或无响应。

    • 原因:同步处理大量文本和网络请求阻塞了Streamlit的主线程。
    • 解决
      1. 前端反馈:用st.progressst.status给用户明确的进度提示。
      2. 后台处理:将耗时的文档处理任务放入单独的线程或进程中,避免阻塞UI。Streamlit本身对长时间运行的操作支持有限,可以考虑结合asyncio或像joblib这样的库。
      3. 分步处理:对于极大的文件,可以提示用户分卷上传,或者在后端实现分批处理。

这个项目是一个功能完整且设计良好的RAG应用起点。从理解架构、动手部署,到深入代码、优化排错,整个过程是学习现代AI应用开发的绝佳路径。我最深的体会是,构建一个“能用”的RAG系统不难,但要让其“好用”、“可靠”,需要在每一个环节——从文档预处理的分块策略,到检索的精度与召回平衡,再到Prompt工程和错误处理——都投入精力去细致调优。它不仅仅是一个工具,更是一个需要持续迭代和打磨的产品。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 21:26:05

解密Java字节码:5个关键技巧让你轻松掌握Fernflower反编译工具

解密Java字节码:5个关键技巧让你轻松掌握Fernflower反编译工具 【免费下载链接】fernflower Decompiler from Java bytecode to Java, used in IntelliJ IDEA. 项目地址: https://gitcode.com/gh_mirrors/fe/fernflower 你是否曾经面对一个编译后的Java字节码…

作者头像 李华
网站建设 2026/5/7 21:23:37

Python 爬虫进阶技巧:SSL 证书异常请求处理方案

前言 在 Python 爬虫项目落地过程中,HTTPS 站点已成为互联网主流建站标准,SSL/TLS 证书是保障网络传输加密安全的核心机制。但实际采集场景里,大量网站存在证书过期、域名不匹配、自签名证书、CA 不信任、混合加密协议等异常问题&#xff0c…

作者头像 李华
网站建设 2026/5/7 21:19:50

为什么你的 SPA 网址必须包含 `#`?—— 前端路由 Hash 模式深度解析

为什么你的 SPA 网址必须包含 #?—— 前端路由 Hash 模式深度解析 文章目录为什么你的 SPA 网址必须包含 #?—— 前端路由 Hash 模式深度解析一、一个让开发者困惑的现象二、现象复现:两种 URL,两种命运三、基石:HTTP …

作者头像 李华