Skip to content

请描述使用 LangChain 构建一个文档问答系统的关键技术组件及实现步骤 ​

  • 标签:大模型

🏛 业务架构师 ​

用 LangChain 做文档问答系统,我按"组件—步骤—上线"三段讲:

关键技术组件:DocumentLoader(加载 PDF/Word/Confluence)、TextSplitter(递归切分,300~500 字)、Embeddings+VectorStore(BGE + Milvus/pgvector)、Retriever(混合检索+元数据过滤)、Prompt Template(含"仅依据资料回答"约束与引用编号)、Memory(多轮对话)、LCEL 串装、LangSmith 做追踪评测。

实现步骤:

  1. 知识接入:文档解析清洗,保留来源/部门/时效元数据;
  2. 索引构建:切分→向量化→入库;
  3. 检索链路:查询改写→混合检索 Top 20→Rerank 取 Top 3~5;
  4. 生成链路:retriever | prompt | model | parser 组装,输出带引用的答案;
  5. 评估:建 100+ 条评测集,跑命中率与忠实度;
  6. 运营:badcase 回流→调切块/检索参数→回归测试。

业务落地建议:第一期只做单部门知识库(范围小、数据干净、指标清晰),把评测集和流水线跑成资产,再横向复制到其他部门——知识问答系统的护城河不在代码,在于持续运营的数据闭环。

🏗 系统架构师 ​

用 LangChain 建文档问答系统,我按组件清单 + 实现步骤 + 架构决策讲:

关键技术组件:

  • 加载与解析:DocumentLoader(PDF/Word/Markdown)或 Unstructured;
  • 分块:RecursiveCharacterTextSplitter / MarkdownHeaderTextSplitter(结构感知);
  • 嵌入与存储:Embeddings 模型 + VectorStore(PGVector/Milvus),chunk 附带来源、标题、权限元数据;
  • 检索:Retriever(向量 + BM25 混合,加自查询过滤),必要时 Rerank;
  • 生成:ChatModel + PromptTemplate(含"仅依据上下文回答,无依据则说不知道")+ StrOutputParser;
  • 记忆:ChatMessageHistory(多轮追问);
  • 观测:Callback 接 LangSmith 做 trace。

实现步骤:LCEL 串成 retriever | format_docs | prompt | model | parser,套 create_retrieval_chain 加引用;流式输出透传前端。

三个架构决策(比代码重要):一是权限过滤放在 Retriever 内(pre-filter),防止越权文档进上下文;二是引用溯源——答案携带 chunk ID 映射到原文档页码,可审计;三是评估先行——上线前建 200 条金标集跑 recall/faithfulness,上线后 trace 采样归因。PoC 一天能搭,生产化工作量在数据治理和可观测上。

💻 全栈开发专家 ​

用 LangChain 构建文档问答(RAG)系统,我按组件和步骤过一遍:

技术组件:DocumentLoader → TextSplitter → Embedding → VectorStore → Retriever → Prompt → LLM → 引用组装。生产链路再加 Rerank 和评估。

实现步骤(Python 版):

python
# 1. 离线索引
docs = DirectoryLoader("./docs", loader_cls=TextLoader).load()
chunks = RecursiveCharacterTextSplitter(
    chunk_size=512, chunk_overlap=64).split_documents(docs)   # 保留 metadata
vs = Milvus.from_documents(chunks, OpenAIEmbeddings(),
    connection_args={"uri": "http://milvus:19530"})

# 2. 在线检索+生成
retriever = vs.as_retriever(search_kwargs={"k": 6})
def format_docs(ds): return "\n\n".join(
    f"[{i}] {d.page_content}" for i, d in enumerate(ds, 1))

rag = ({"context": retriever | format_docs, "question": RunnablePassthrough()}
       | ChatPromptTemplate.from_template(
           "仅根据以下编号资料回答,引用 [n]:\n{context}\n\n问题:{question}")
       | llm | StrOutputParser())

上线前的三件事(新手最容易漏):一是 chunk 的 metadata 必须带 source/页码,否则引用溯源做不了;二是建 50~100 条评测集卡 recall@5 和忠实度;三是加 rerank(bge-reranker)和拒答分支(检索分数低于阈值时回「未找到」),这两个动作对用户信任度影响最大。