惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V2EX - 技术
V2EX - 技术
P
Privacy International News Feed
Security Latest
Security Latest
H
Hacker News: Front Page
T
Tenable Blog
The Hacker News
The Hacker News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Security @ Cisco Blogs
Project Zero
Project Zero
O
OpenAI News
AI
AI
Spread Privacy
Spread Privacy
C
CERT Recently Published Vulnerability Notes
The Last Watchdog
The Last Watchdog
G
GRAHAM CLULEY
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Scott Helme
Scott Helme
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
CXSECURITY Database RSS Feed - CXSecurity.com
NISL@THU
NISL@THU
A
Arctic Wolf
T
Threat Research - Cisco Blogs
PCI Perspectives
PCI Perspectives
N
News and Events Feed by Topic
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Cybersecurity and Infrastructure Security Agency CISA
Simon Willison's Weblog
Simon Willison's Weblog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Know Your Adversary
Know Your Adversary
Google Online Security Blog
Google Online Security Blog
罗磊的独立博客
L
LINUX DO - 最新话题
U
Unit 42
S
Security Affairs
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
博客园 - 【当耐特】
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
月光博客
月光博客
Engineering at Meta
Engineering at Meta
腾讯CDC
F
Full Disclosure
Cyberwarzone
Cyberwarzone
S
SegmentFault 最新的问题
Recorded Future
Recorded Future
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
The Cloudflare Blog

博客园 - 哥本哈士奇(aspnetx)

Raw Data Vault vs Business Data Vault Neo4j 多跳查询实战:从社交网络场景理解图遍历 Git 本地版本管理学习笔记 看完《低智商犯罪》,学习Cypher构建知识图谱 LangChain DeepAgents 学习笔记 LangChain Deepagent 版本0.6.1中间件一个bug Neo4j笔记(五):查询的处理流程 微软 SQL Server 版本演进史:从诞生到 SQL Server 2025 提升 Text2SQL 准确率 SQLServer Management Studio(SSMS) 22 Copilot 最佳实践学习笔记 SQLServer RAG笔记4:从服务层到前端交互 SQLServer RAG笔记5:为SQLServer 2025配置Ollama SQL Server RAG 笔记3:SQLServer2025 向量数据库 详解传统RAG、Text2SQL、Graph RAG:适用场景与问题示例汇总 SQL Server RAG 笔记2:图数据库服务层与前端可视化构建 SQL Server RAG 笔记1:图数据库构建 Neo4j 笔记(四):一篇文章如何入库的 数据仓库笔记 第六篇:PSA 层 SCD2 处理方式 数据仓库笔记 第五篇:Data Mart 层(数据集市) 数据仓库笔记 第四篇:Star Schema 层(维度建模) 数据仓库笔记 第三篇:常用缓慢变化维处理方式介绍 数据仓库笔记 第二篇:PSA 层(持久化暂存区)详解 数据仓库笔记 第一篇:数据仓库的定义、历史与意义 Neo4j 笔记(三):数据建模与程序连接实战 Neo4j 基础教程(二):Cypher CRUD 完全指南 Neo4j 基础教程(一):安装与快速入门 Power BI学习笔记第20篇:面试题汇总 · 第三篇:高级应用与最佳实践篇 Power BI学习笔记第19篇:面试题汇总 · 第二篇:数据建模与 DAX 篇 Power BI学习笔记第18篇:面试题汇总 · 第一篇:基础概念篇 Power BI学习笔记第15篇:企业级报表开发与最佳实践 Power BI学习笔记第13篇:高级可视化与自定义图表 Power BI学习笔记第14篇:Power Query 高级数据处理 Power BI学习笔记第12篇:DAX 高级计算与性能优化 Power BI学习笔记第11篇:高级数据建模与关系设计 Power BI学习笔记第10篇:实战案例 — 销售数据分析仪表板 Power BI学习笔记第08篇:报表交互与筛选器 Power BI学习笔记第09篇:Power BI Service 发布与共享 Power BI学习笔记第07篇:可视化图表设计与最佳实践 Power BI学习笔记第06篇:DAX 进阶:时间智能与复杂计算 Power BI学习笔记第05篇:DAX 基础:计算列与度量值 Power BI学习笔记第04篇:数据建模与关系管理 Power BI学习笔记第03篇:Power Query 数据清洗与转换 Power BI学习笔记第02篇:数据获取与连接数据源 Power BI学习笔记第01篇:Power BI 简介与环境搭建 Power BI Dashboard 常用布局方案推荐 Power BI 示例一览 OpenClaw 核心八大 MD 文件 Microsoft Agent Framework 智能体调用工具 Microsoft Agent Framework 创建智能体 一个简单案例理解OpenClaw的做梦过程 SQLAlchemy 学习笔记 LangChain RAG索引与查询 - 学习笔记 LangChain使用deep agent并且加载SKILL 本地系统对接大模型智能体的若干尝试 财务分析报告 - 常用Power BI DAX公式详解 用SKILL实现请假流程信息收集 使用小龙虾来操作猿编程的遥控车 使用OpenClaw的Skills对接本地系统 OpenClaw必装Skill全指南 Dify Agentic RAG 笔记 # 简单的神经网络计算过程 - 正负判断 使用Gradio构建AI前端 - RAG的QA模块 使用Gradio构建AI前端 - RAG召回测试 Streamlit + LangChain 1.0 简单实现智能问答前后端 利用AI Agent,辅助销售团队制定培训和考试内容 实现AI和BI整合的初步思路和探索-Part3 实现AI和BI整合的初步思路和探索-Part2 实现AI和BI整合的初步思路和探索 以《出师表》作为例子,对比通用分块和父子分块的区别 记录无法修改OneDrive文件的解决方法 Power BI制作指标达成跟踪器 Power BI学习笔记-周报销售数据分析
LangChain RAG 学习笔记:从文档加载到问答服务
哥本哈士奇(aspnet · 2025-10-29 · via 博客园 - 哥本哈士奇(aspnetx)

LangChain RAG 学习笔记:从文档加载到问答服务

我在先前的随笔中分享过用Dify低代码平台来实现问答系统,也有几篇随笔是通过不同的方式来访问大模型。本篇将使用LangChain来做对应的实现。相关代码主要是通过Trae,它可以帮助你快速的了解了基本使用 LangChain 构建 RAG的方法,包括从文档加载、向量存储到问答接口实现,整个过程涉及多个关键环节。
虽然借助大模型以及Trae,给我们提供了另外一种生成代码和学习代码的方式,但其目前还是需要人工来参与的,尤其是版本的变化导致引入的包和接口的调用方式都发生了很多变化,所以这就需要一个根据生成的代码不断的去调试和修正。本文里贴出的代码也是经历过这个过程之后总结下来的。

RAG 系统整体架构

首先回忆一下RAG 系统的核心思想,是将用户查询与知识库中的相关信息进行匹配,再结合大语言模型生成准确回答。
这里我将一套 RAG 系统通分成以下几个模块:

  1. 文档加载与处理
  2. 文本分割与嵌入
  3. 向量存储管理
  4. 检索功能实现
  5. 问答生成服务
  6. 接口部署

这几个模块完成了后端模块的建立。实际项目中会考虑更多的模块,比如大模型的选择和部署,向量数据库的选择,知识库的准备,前端页面的搭建等,这些将不作为本文描述的重点。

本文代码,关于大模型的选择,我们将基于 DashScope 提供的嵌入模型和大语言模型,结合 LangChain 和 Chroma 向量数据库来实现整个系统。

这里我历经过一些莫名其妙的磨难,比如刚开始我选择本地的Ollama部署,包括向量模型都是在本地。但是在测试的过程中,发现召回的结果很离谱。比如我投喂了劳动法和交通法的内容,然后问一个劳动法相关的问题,比如哪些节假日应该安排休假,结果召回的结果中有好多是交通法的内容。刚开始我以为是向量模型的问题,于是在CherryStudio里,构建同样的知识库,使用同样的向量嵌入模型,召回测试的结果很符合预期。后来在LangChain里又尝试过更换向量数据库,以及更改距离算法,召回的结果都达不到预期。直到有一天,本地部署的嵌入模型突然不工作了(真的好奇怪,同样的模型在windows和macos都有部署,突然间就都不能访问了,至今原因不明。),于是尝试更换到在线的Qwen的大模型,召回测试终于复合预期了。

吐槽完毕,接下来进入正题:

1. 文档加载与向量库构建

文档加载是 RAG 系统的基础,需要处理不同格式的文档并将其转换为向量存储。这里我检索的是所有txt和docx文件。
所有的知识库文件都放在knowledge_base文件夹下,向量数据库存储在chroma_db下。
知识库为了测试召回方便,我投喂了法律相关的内容,主要有劳动法和道路安全法,同时也投喂了一些自己造的文档。
向量数据库这里用到的是chroma,其调用方法相对简单,不需要额外安装配置什么。同时也可以选择比如FAISS,Milvus甚至PostgreSQL,但这些向量库需要单独的部署和配置,过程稍微复杂一点。所以这篇文章的向量库选择了Chroma。

核心代码实现

def load_documents_to_vectorstore(
    document_dir: str = "./RAG/knowledge_base",
    vectorstore_dir: str = "./RAG/chroma_db",
    embedding_model: str = "text-embedding-v1",
    dashscope_api_key: Optional[str] = None,
    chunk_size: int = 1000,
    chunk_overlap: int = 200,
    collection_name: str = "my_collection",
) -> bool:
    # 文档目录检查
    if not os.path.exists(document_dir):
        logger.error(f"文档目录不存在: {document_dir}")
        return False

    # 加载不同格式文档
    documents = []
    # 加载 txt
    txt_loader = DirectoryLoader(document_dir, glob="**/*.txt", loader_cls=TextLoader)
    documents.extend(txt_loader.load())
    # 加载 docx
    docx_loader = DirectoryLoader(document_dir, glob="**/*.docx", loader_cls=Docx2txtLoader)
    documents.extend(docx_loader.load())

    # 文本分割
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        separators=["\n\n", "\n", " ", ""],
    )
    splits = text_splitter.split_documents(documents)

    # 初始化嵌入模型
    embeddings = DashScopeEmbeddings(model=embedding_model, dashscope_api_key=dashscope_api_key)
    
    # 探测嵌入维度,避免维度冲突
    probe_vec = embeddings.embed_query("dimension probe")
    emb_dim = len(probe_vec)
    collection_name = f"{collection_name}_dim{emb_dim}"
    
    # 创建向量存储
    vectorstore = Chroma.from_documents(
        documents=splits,
        embedding=embeddings,
        collection_name=collection_name,
        persist_directory=persist_dir,
    )
    vectorstore.persist()
    return True

关键技术点解析

1.** 文档加载 **:使用 DirectoryLoader 批量加载目录中的 TXT 和 DOCX 文档,可根据需求扩展支持 PDF 等其他格式

2.** 文本分割 **:采用 RecursiveCharacterTextSplitter 进行文本分割,关键参数:

  • chunk_size:文本块大小
  • chunk_overlap:文本块重叠部分,确保上下文连贯性
  • separators:分割符列表,优先使用段落分隔

3.** 嵌入处理 **:

  • 使用 DashScope 提供的嵌入模型生成文本向量
  • 自动探测嵌入维度,避免不同模型间的维度冲突
  • 为不同模型创建独立的存储目录,确保向量库兼容性

4.** 数据写入 ** 使用的是from_documents方法。这里如果嵌入模型不可用的话,会卡死在这里。

2. 向量库构建与检索功能

向量库是 RAG 系统的核心组件,负责高效存储和检索文本向量。

向量库构建函数

def build_vectorstore(
    vectorstore_dir: str = "./RAG/chroma_db",
    embedding_model: str = "text-embedding-v4",
    dashscope_api_key: Optional[str] = None,
    collection_name_base: str = "my_collection",
) -> Tuple[Chroma, DashScopeEmbeddings, int, str]:
    # 获取API密钥
    if dashscope_api_key is None:
        dashscope_api_key = os.getenv("DASHSCOPE_API_KEY")
    
    # 初始化嵌入模型
    embeddings = DashScopeEmbeddings(model=embedding_model, dashscope_api_key=dashscope_api_key)

    # 探测嵌入维度与持久化目录
    probe_vec = embeddings.embed_query("dimension probe")
    emb_dim = len(probe_vec)
    collection_name = f"{collection_name_base}_dim{emb_dim}"
    model_dir_tag = embedding_model.replace(":", "_").replace("/", "_")
    persist_dir = os.path.join(vectorstore_dir, model_dir_tag)

    # 加载向量库
    vs = Chroma(
        persist_directory=persist_dir,
        embedding_function=embeddings,
        collection_name=collection_name,
    )
    return vs, embeddings, emb_dim, persist_dir

检索功能实现

def retrieve_context(
    question: str,
    k: int,
    vectorstore: Chroma,
) -> List[str]:
    """使用向量库检索 top-k 文档内容,返回文本片段列表"""
    docs = vectorstore.similarity_search(question, k=k)
    chunks: List[str] = []
    for d in docs:
        src = d.metadata.get("source", "<unknown>")
        text = d.page_content.strip().replace("\n", " ")
        chunks.append(f"[source: {src}]\n{text}")
    return chunks

技术要点说明

1.** 向量库兼容性处理 **:

  • 为不同嵌入模型创建独立目录
  • 集合名包含维度信息,避免维度冲突
  • 自动探测嵌入维度,确保兼容性

2.** 检索实现 **:

  • 使用 similarity_search 进行向量相似度检索
  • 返回包含来源信息的文本片段
  • 可通过调整 k 值控制返回结果数量,CherryStudio默认是5,所以在这里我也用这个值。

注:similarity_search不返回相似度信息,如果需要这个信息,需要使用similarity_search_with_relevance_scores。

3. 问答功能实现

问答功能是 RAG 系统的核心应用,大体的流程就是结合检索到的上下文和大语言模型生成回答。如果你已经知道了如何在Dify中进行类似操作,那么这部分代码理解上就会容易些,尤其是在用户提示词部分,思路都是一样的。

问答核心函数

def answer_question(
    question: str,
    top_k: int = 5,
    embedding_model: str = "text-embedding-v4",
    chat_model: str = os.getenv("CHAT_MODEL", "qwen-turbo"),
    dashscope_api_key: Optional[str] = None,
    vectorstore_dir: str = "./RAG/chroma_db",
    temperature: float = 0.2,
    max_tokens: int = 1024,
) -> Tuple[str, List[str]]:
    # 构建向量库
    vs, embeddings, emb_dim, persist_dir = build_vectorstore(
        vectorstore_dir=vectorstore_dir,
        embedding_model=embedding_model,
        dashscope_api_key=dashscope_api_key,
    )

    # 检索上下文
    context_chunks = retrieve_context(question, k=top_k, vectorstore=vs)
    sources = []
    for c in context_chunks:
        # 提取来源信息
        if c.startswith("[source: "):
            end = c.find("]\n")
            if end != -1:
                sources.append(c[len("[source: "):end])
    context_str = "\n\n".join(context_chunks)

    # 构造提示词
    system_prompt = (
        "你是一个严谨的问答助手。请基于提供的检索上下文进行回答,"
        "不要编造信息,若上下文无答案请回答:我不知道。"
    )
    user_prompt = (
        f"问题: {question}\n\n"
        f"检索到的上下文(可能不完整,仅供参考):\n{context_str}\n\n"
        "请给出简洁、准确的中文回答,并在需要时引用关键点。"
    )

    # 调用大语言模型生成答案
    dashscope.api_key = dashscope_api_key
    gen_kwargs = {
        "model": chat_model,
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt},
        ],
        "result_format": "message",
        "temperature": temperature,
        "max_tokens": max_tokens,
    }

    resp = Generation.call(**gen_kwargs)
    answer = _extract_answer_from_generation_response(resp)
    return answer.strip(), sources

关键技术点

1.** 提示词设计 **:

  • 系统提示词明确回答约束(基于上下文、不编造信息)
  • 用户提示词包含问题和检索到的上下文
  • 明确要求简洁准确的中文回答

2.** 模型调用参数 **:

  • temperature:控制输出随机性,低温度值生成更确定的结果,对于问答系统这个值推荐接近0。如果是生成诗词类应用则推荐接近1.
  • max_tokens:限制回答长度
  • result_format:指定输出格式,便于解析

3.** 结果处理 **:

  • 从模型响应中提取答案文本
  • 收集并返回来源信息,提高回答可信度

4. 构建 HTTP 服务接口

为了方便使用,我们可以将问答功能封装为 HTTP 服务,这样更方便将服务集成到其它应用环境中。

HTTP 服务实现

class QAHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        if parsed.path != "/qa":
            self.send_response(HTTPStatus.NOT_FOUND)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": "Not Found"}).encode("utf-8"))
            return

        qs = urllib.parse.parse_qs(parsed.query)
        question = (qs.get("question") or [None])[0]
        top_k = int((qs.get("top_k") or [5])[0])
        embedding_model = (qs.get("embedding_model") or [os.getenv("EMBEDDING_MODEL", "text-embedding-v4")])[0]
        chat_model = (qs.get("chat_model") or [os.getenv("CHAT_MODEL", "qwen-turbo")])[0]

        if not question:
            self.send_response(HTTPStatus.BAD_REQUEST)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": "Missing 'question' parameter"}).encode("utf-8"))
            return

        try:
            answer, sources = answer_question(
                question=question,
                top_k=top_k,
                embedding_model=embedding_model,
                chat_model=chat_model,
                dashscope_api_key=os.getenv("DASHSCOPE_API_KEY"),
                vectorstore_dir=os.getenv("VECTORSTORE_DIR", "./RAG/chroma_db"),
            )
            payload = {
                "question": question,
                "answer": answer,
                "sources": sources,
                "top_k": top_k,
                "embedding_model": embedding_model,
                "chat_model": chat_model,
                "status": "ok",
            }
            self.send_response(HTTPStatus.OK)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(payload, ensure_ascii=False).encode("utf-8"))
        except Exception as e:
            logger.error(f"请求处理失败: {e}")
            self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": "internal_error", "message": str(e)}).encode("utf-8"))

def run_server(host: str = "0.0.0.0", port: int = int(os.getenv("PORT", "8000"))):
    httpd = HTTPServer((host, port), QAHandler)
    logger.info(f"QA 服务已启动: http://localhost:{port}/qa?question=...")
    httpd.serve_forever()

通过这个http接口,就可以供其它应用进行调用,比如如下我用Trae生成的前端:

img

服务特点

1.** 接口设计 :提供 /qa 端点,支持通过 URL 参数指定问题和模型参数
2.
错误处理 :对缺失参数、服务错误等情况返回适当的 HTTP 状态码
3.
灵活性 :支持动态指定 top_k、嵌入模型和聊天模型
4.
易用性 **:返回包含问题、答案、来源和模型信息的 JSON 响应

5. 系统测试与验证

为确保检索的结果复合预期,建议单独实现召回测试功能,验证检索效果:

def recall(
    query: str,
    top_k: int = 5,
    vectorstore_dir: str = "./RAG/chroma_db",
    embedding_model: str = "text-embedding-v4",
    dashscope_api_key: Optional[str] = None,
) -> None:
    vs = build_vectorstore(
        vectorstore_dir=vectorstore_dir,
        embedding_model=embedding_model,
        dashscope_api_key=dashscope_api_key,
    )

    logger.info(f"执行相似度检索: k={top_k}, query='{query}'")
    docs = vs.similarity_search(query, k=top_k)

    print("\n=== Recall Results ===")
    for i, d in enumerate(docs, start=1):
        src = d.metadata.get("source", "<unknown>")
        snippet = d.page_content.strip().replace("\n", " ")
        if len(snippet) > 500:
            snippet = snippet[:500] + "..."
        print(f"[{i}] source={src}\n    {snippet}\n")

通过召回测试,可以直观地查看检索到的文本片段,评估检索质量,为调整文本分割参数和检索参数提供依据。
当然召回测试,除了能在调用大模型前提前看到准确度,也能在测试过程中,节省大模型调用的成本消耗。

总结与展望

本文汇总了基于LangChain 构建 RAG 系统的简单实现,从文档加载、向量存储到问答服务实现。后续可以从以下几个方面进行改进:

  1. 支持更多文档格式(PDF、Markdown 等)
  2. 实现更高级的检索策略(混合检索、重排序等)
  3. 替换向量数据库
  4. 更改相似度算法
  5. 增加缓存机制,提高服务响应速度
  6. 实现批量处理和增量更新功能
  7. 增加用户认证和权限管理

本文所有代码可以在以下地址找到:
https://github.com/microsoftbi/Langchain_DEMO/tree/main/RAG