LangChain核心原理:大模型应用的工程化操作系统
1. 这不是另一个“AI框架”——LangChain 是大模型时代的操作系统内核你第一次听说 LangChain大概率是在某个技术群里看到有人发链接“快看这个能连本地大模型”或者在招聘JD里刷到“熟悉 LangChain 者优先”。但如果你真去翻它的 GitHub 主页、读官方文档第一行会发现它压根没说自己是“框架”——它写的是LangChain is a framework for developing applications powered by large language models.一个为大语言模型驱动的应用而生的框架。注意这个定语“为……而生”不是“用来调用模型”而是“为构建应用而生”。我从 2023 年初就开始用 LangChain 搭第一个内部知识库到现在手上有 7 个生产环境项目跑着不同形态的 LangChain 应用有给法务团队做的合同条款比对助手有嵌入 ERP 系统的采购询价自动回复模块还有给客服坐席用的实时话术推荐弹窗。这三年踩过的坑、重写的链、重构的记忆模块让我越来越确信一件事LangChain 的本质不是让你更快地调用一次 GPT而是帮你把“大模型能力”像水电一样接入你已有的业务系统里。它解决的从来不是“怎么让 AI 说话”而是“怎么让 AI 在你指定的时间、用你指定的方式、处理你指定的数据、再把结果塞进你指定的字段里”。为什么必须学它因为现实中的 AI 项目95% 的工作量根本不在模型本身。我在给一家制造业客户做设备故障问答系统时花 3 天时间调通了 Llama 3 的本地推理但接下来花了 11 天——整整两周——才搞定三件事把他们分散在 SAP、MES 和 PDF 手册里的设备参数表统一清洗成向量让模型在回答时能自动识别用户提问里的设备编号并精准关联到对应产线最后把生成的答案格式化成 JSON直接推送到他们的工单系统 API。这三件事LangChain 提供了现成的轮子DocumentLoader解析各种格式、RetrievalQA做上下文注入、PydanticOutputParser强制结构化输出。没有它我得自己写爬虫、写向量化脚本、写正则提取编号、写 HTTP 客户端——而这些和“大语言模型”本身毫无关系。它解决的是工程落地的“最后一公里”问题。就像当年 jQuery 没有发明 JavaScript但它让前端工程师不用再天天跟 IE6 的事件冒泡死磕LangChain 也没发明大模型但它把所有和“让模型在真实业务里干活”相关的脏活累活打包成可复用、可调试、可监控的组件。你不需要成为大模型专家但你必须懂怎么把它变成你业务流水线上的一个稳定工位。这就是为什么标题问“为啥要学它”答案不是“因为它火”而是“因为你迟早要把它焊进你的系统里晚学一天就多写一天重复代码”。关键词已经非常清晰LangChain、AI应用开发框架、标准化接口、模型集成、大语言模型。这不是一个玩具库而是一套面向生产环境的基础设施协议。下面我会用一个老手带新人的实际视角一层层拆开它的骨架、血肉和神经告诉你它到底在干什么、为什么这么干、以及你第一天上手时最该盯住哪几个关键点。2. 核心设计哲学不是造轮子是定义轮子的尺寸和轴距LangChain 的架构图看起来很复杂一堆 Chain、Agent、Tool、Memory、Retriever 像乐高零件堆在一起。但如果你只记住一句话就能瞬间看懂所有设计LangChain 的一切都是为了把“非结构化”的大模型能力强行塞进“结构化”的软件工程范式里。它不试图改变大模型的混沌本质而是用一套精密的适配器让混沌的输出能被程序可靠地消费。2.1 为什么必须抽象“模型”——告别硬编码的 API 调用想象一下你今天用 OpenAI 的 GPT-4 Turbo明天客户要求切到本地部署的 Qwen2-72B后天又得对接阿里云的百炼平台。如果每换一个模型你都要重写requests.post()的 URL、重调json.dumps()的参数、重改错误码处理逻辑……项目还没上线你已经成了 API 配置工程师。LangChain 的Model组件就是干这个的。它把所有模型调用抽象成两个核心动作输入一段文本返回一段文本或向量。你看它的基类定义class BaseLLM(BaseModel, ABC): abstractmethod def _call(self, prompt: str, stop: Optional[List[str]] None) - str: Run the LLM on the given prompt and return the result. pass就这么简单。_call方法输入prompt字符串输出str字符串。至于背后是调 OpenAI 的 REST API、还是加载 HuggingFace 的.bin权重、还是转发请求到你自建的 vLLM 服务——对上层业务代码完全透明。你只需要初始化不同的子类# OpenAI from langchain.llms import OpenAI llm OpenAI(model_namegpt-4-turbo, temperature0.3) # 本地 Llama.cpp from langchain.llms import LlamaCpp llm LlamaCpp( model_path./models/llama-3-8b.Q4_K_M.gguf, n_ctx4096, n_threads8 ) # 阿里云百炼 from langchain.llms import Tongyi llm Tongyi( dashscope_api_keysk-xxx, model_nameqwen-max )所有这些llm实例你都能用同一行代码调用response llm(请总结以下会议纪要...)这背后的工程价值是什么是解耦。当你把模型当成一个黑盒函数来用你的业务逻辑比如“从合同里抽甲方名称”就和底层技术栈彻底分离了。测试时你可以用便宜的gpt-3.5-turbo快速验证流程上线时再无缝切到gpt-4-turbo客户说要国产化你只要换一个llm初始化其他几百行代码动都不用动。这种抽象不是炫技是应对真实世界技术迭代的生存策略。提示别被LLM和ChatModel两个类搞晕。LLM是纯文本补全适合写文章、生成代码ChatModel是消息序列对话适合聊天机器人。它们的抽象层级相同只是输入格式不同。选哪个取决于你的任务场景而不是模型本身强弱。2.2 为什么必须封装“提示”——把玄学变成可版本管理的配置大模型工程师最常被吐槽的一句话是“我调了 2 小时就为了加一个句号让模型不胡说八道。” 提示工程Prompt Engineering听起来很酷实操起来就是一场和模型随机性的搏斗。而 LangChain 的PromptTemplate就是把这场搏斗变成可复现、可协作、可上线的工程实践。它做了三件关键事变量注入把动态内容用户问题、数据库查询结果和静态模板系统指令、格式要求彻底分开。格式标准化强制所有提示都走format()流程杜绝字符串拼接导致的引号错乱、空格丢失。可复用性一个模板可以被多个链、多个 Agent 共享修改一处全局生效。看一个真实案例。我们给银行做的反洗钱报告生成系统需要模型从交易流水里识别可疑模式。原始提示可能是这样硬编码的# ❌ 危险无法维护无法测试 prompt f你是一名资深反洗钱合规官。请严格按JSON格式输出不要任何解释。 交易流水如下 {transaction_data} 请分析是否存在以下可疑行为1. 分散转入集中转出2. 快进快出3. 与客户身份明显不符。 输出格式{{suspicious: true/false, reason: 简短说明, pattern: 匹配的模式编号}}用 LangChain 改写# ✅ 工程化可测试可版本控制 from langchain.prompts import PromptTemplate PROMPT_TEMPLATE 你是一名资深反洗钱合规官。请严格按JSON格式输出不要任何解释。 交易流水如下 {transaction_data} 请分析是否存在以下可疑行为 1. 分散转入集中转出 2. 快进快出 3. 与客户身份明显不符。 输出格式{{suspicious: true/false, reason: 简短说明, pattern: 匹配的模式编号}} prompt PromptTemplate( templatePROMPT_TEMPLATE, input_variables[transaction_data] ) # 使用时 formatted_prompt prompt.format(transaction_dataraw_data) response llm(formatted_prompt)现在这个PROMPT_TEMPLATE字符串可以放进prompts.yaml配置文件由合规部门审核后发布可以写单元测试固定raw_data输入断言输出 JSON 的suspicious字段是否为true甚至可以 A/B 测试对比两个不同版本的提示模板在 1000 条样本上的准确率。提示从此不再是写在代码注释里的玄学而是产品需求文档的一部分。2.3 为什么必须强加“输出解析”——让自由文本变成可编程的数据这是新手最容易忽略、却最致命的一环。大模型的输出是自由文本而你的业务系统需要的是结构化数据。你不能指望模型永远按你想要的格式输出。我见过太多项目因为没做输出解析在上线后疯狂报错用户问“北京今天天气”模型回答“北京今天晴朗最高气温25度适合户外活动。” —— 你的前端想取temperature字段结果得到None。合同审查系统要求输出{risk_level: high/medium/low}模型却写了“风险等级高”。LangChain 的OutputParser就是专门治这个病的。它不靠祈祷而是靠“引导校验兜底”三板斧引导Prompt Injection在提示里明确告诉模型你要什么格式。校验Schema Validation用 Pydantic 或 JSON Schema 定义合法输出结构。兜底Fallback当模型输出非法时自动重试或返回默认值。继续用上面的反洗钱例子from langchain.output_parsers import PydanticOutputParser from pydantic import BaseModel, Field class AMLReport(BaseModel): suspicious: bool Field(description是否可疑) reason: str Field(description简短说明不超过20字) pattern: int Field(description匹配的模式编号1/2/3) parser PydanticOutputParser(pydantic_objectAMLReport) # 把解析规则注入提示 format_instructions parser.get_format_instructions() # PROMPT_TEMPLATE f\n{format_instructions} # 追加到模板末尾 # 调用模型后强制解析 try: parsed_result parser.parse(response) # parsed_result 是一个真正的 AMLReport 对象有 .suspicious 属性 if parsed_result.suspicious: send_alert_to_compliance_team(parsed_result) except Exception as e: # 解析失败记录日志触发人工审核流程 log_error_and_fallback(raw_data, response)这一步的价值是把 AI 的不确定性关进了软件工程的确定性牢笼里。你的下游系统永远拿到的是一个AMLReport对象而不是一串可能随时变化的字符串。这才是生产环境能接受的 AI。3. 四大核心组件深度拆解从“能用”到“用稳”的实战细节LangChain 的文档喜欢讲概念但真实项目里你每天打交道的是这四个组件Model、PromptTemplate、OutputParser、Retriever。它们不是并列关系而是一个严密的数据流闭环。下面我用一个正在运行的客户项目——“智能采购询价助手”——来逐个拆解告诉你每个组件在真实战场上的作用、陷阱和优化技巧。3.1 Model不只是“调 API”而是“管生命周期”在采购系统里Model组件承担着远超“发请求”的职责。它要处理成本控制GPT-4 Turbo 每千 token 0.01 美元而gpt-3.5-turbo只要 0.0015 美元。一个询价请求平均 500 tokens差价就是 4.25 美元/次。我们必须能根据询价金额、供应商等级动态选择模型。降级策略当 OpenAI API 延迟超过 2 秒自动切到本地 Qwen2-7B保证页面不卡死。Token 管控采购询价单包含大量表格数据必须预估输入长度超长时自动摘要避免context length exceeded错误。实现方案不是写一堆if-else而是利用 LangChain 的BaseLLM扩展机制from langchain.llms import OpenAI, LlamaCpp from langchain.callbacks.manager import CallbackManager from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler class SmartProcurementLLM(BaseLLM): 采购专用LLM内置成本、延迟、降级策略 openai_llm: OpenAI Field(defaultNone) local_llm: LlamaCpp Field(defaultNone) cost_threshold_usd: float Field(default0.005) # 单次询价成本阈值 def _call(self, prompt: str, stop: Optional[List[str]] None) - str: # 1. 预估OpenAI成本 estimated_tokens len(prompt) // 4 # 粗略估算 estimated_cost estimated_tokens * 0.01 / 1000 # 2. 成本低于阈值且延迟OK用OpenAI if (estimated_cost self.cost_threshold_usd and self._is_openai_fast()): return self.openai_llm._call(prompt, stop) # 3. 否则降级到本地模型 return self.local_llm._call(prompt, stop) def _is_openai_fast(self) - bool: # 实际项目中这里会调用Prometheus监控API获取SLA return True # 简化示意 # 初始化时注入两个模型实例 llm SmartProcurementLLM( openai_llmOpenAI(model_namegpt-4-turbo), local_llmLlamaCpp(model_path./qwen2-7b.Q4_K_M.gguf) )实操心得别把Model当成一个静态对象。在生产环境它必须是一个有心跳、有血压、能自主决策的“智能代理”。我建议所有项目都从第一天起就给Model加上CallbackManager记录每次调用的input_tokens、output_tokens、total_time、model_name。这些数据是你后续做成本优化、性能调优的唯一依据。没有监控的 LLM就像没有刹车的汽车。3.2 PromptTemplate从“写提示”到“编排提示工作流”采购询价助手的提示绝不是一句“请分析以下询价单”。它是一个多阶段工作流信息抽取阶段从 PDF 或 Excel 询价单里精准识别物料编码、数量、规格、交期。知识检索阶段拿着物料编码去公司知识库查历史成交价、合格供应商列表、技术协议要点。决策生成阶段综合以上信息生成推荐供应商、预估价格区间、标注风险点。LangChain 的PromptTemplate本身只负责第一阶段。但它的真正威力在于和Chain、Retriever的组合。我们用SequentialChain编排整个流程from langchain.chains import SequentialChain from langchain.chains import LLMChain # 阶段1信息抽取链 extract_prompt PromptTemplate( template你是一个采购数据分析师。请从以下询价单文本中严格提取JSON{text}\n输出, input_variables[text] ) extract_chain LLMChain(llmllm, promptextract_prompt, output_keyextracted_data) # 阶段2知识检索Retriever已预先构建好 retriever vectorstore.as_retriever(search_kwargs{k: 3}) # 阶段3决策生成链使用检索到的上下文 decision_prompt PromptTemplate( template基于以下历史数据{context}\n和本次询价需求{extracted_data}\n请生成JSON格式的采购建议, input_variables[context, extracted_data] ) decision_chain LLMChain(llmllm, promptdecision_prompt, output_keyprocurement_advice) # 组合成工作流 overall_chain SequentialChain( chains[extract_chain, decision_chain], input_variables[text], output_variables[procurement_advice], verboseTrue # 关键开启verbose能看到每一步的输入输出 )注意事项SequentialChain的verboseTrue是调试神器。它会在控制台打印出每一步的input和output让你一眼看出是“信息没抽出来”还是“知识库没检索到”还是“模型在决策阶段胡说八道”。没有这个你就是在黑暗中调试。提示PromptTemplate的partial_variables参数常被低估。它可以预设一些不变的系统指令比如你必须用中文回答且不能编造信息。这样所有使用该模板的链都天然带上这个约束避免在每个地方重复写。3.3 OutputParser结构化输出的“保险丝”与“转换器”采购建议的最终输出必须是下游 ERP 系统能直接消费的 JSON。但模型偶尔会失控。我们的OutputParser不仅要做解析还要做三件事保险丝Circuit Breaker当解析连续失败 3 次自动触发人工审核流程而不是让错误数据流入 ERP。转换器Transformer把模型输出的price_range: ¥500-¥800转换成 ERP 要求的{min_price: 500.0, max_price: 800.0}。审计追踪Audit Trail记录原始模型输出和最终解析结果用于事后追责。我们用自定义OutputParser实现from langchain.output_parsers import BaseOutputParser import json import re class ProcurementOutputParser(BaseOutputParser): 采购专用解析器带保险丝和转换 max_retries: int Field(default3) retry_count: int Field(default0) def parse(self, text: str) - dict: try: # 1. 尝试标准JSON解析 data json.loads(text.strip()) # 2. 转换 price_range 字段 if price_range in data: match re.search(r¥(\d)-¥(\d), data[price_range]) if match: data[min_price] float(match.group(1)) data[max_price] float(match.group(2)) del data[price_range] # 3. 添加审计字段 data[parsed_at] datetime.now().isoformat() data[original_output] text return data except json.JSONDecodeError as e: self.retry_count 1 if self.retry_count self.max_retries: # 触发人工审核 raise ValueError(fParse failed {self.max_retries} times. Original: {text}) # 返回一个空的、但结构合法的默认值让流程继续 return {suggested_supplier: , min_price: 0.0, max_price: 0.0, risk_notes: [解析失败已人工介入]} def get_format_instructions(self) - str: return 请严格按JSON格式输出包含suggested_supplier, min_price, max_price, risk_notes字段。 parser ProcurementOutputParser()常见问题新手常犯的错误是把OutputParser当成万能胶水试图用正则硬扒模型输出。这极其脆弱。正确做法是用PydanticOutputParserget_format_instructions()强力引导模型输出规范格式再用自定义解析器做兜底和转换。引导是主攻兜底是保险。3.4 Retriever不是“搜索”而是“构建可信的知识边界”在采购系统里Retriever是最核心的组件。它决定了模型“知道什么”。我们不用通用搜索引擎而是构建了一个三层知识索引索引层数据源用途更新频率L1 - 实时询价池当日新收询价单保证模型能“看到”最新需求实时L2 - 历史成交库过去3年ERP成交记录提供价格基准、供应商表现每日增量L3 - 技术协议库PDF/Word技术协议、质检标准确保规格描述准确按需Retriever的实现不是简单调Chroma.similarity_search()而是MultiVectorRetriever 自定义retrieverfrom langchain.retrievers import MultiVectorRetriever from langchain.storage import InMemoryStore from langchain.embeddings import OpenAIEmbeddings # 为每一层知识构建独立的向量存储 l1_store Chroma(embedding_functionembeddings, persist_directory./l1) l2_store Chroma(embedding_functionembeddings, persist_directory./l2) l3_store Chroma(embedding_functionembeddings, persist_directory./l3) # 构建多层检索器 retriever MultiVectorRetriever( vectorstorel1_store, # 主检索器 docstoreInMemoryStore(), # 存储原始文档 search_kwargs{k: 2} # 每层最多取2个 ) # 自定义检索逻辑根据询价单特征动态加权各层 def custom_retrieve(query: str) - List[Document]: # 分析query判断是价格询价重L2、还是技术询价重L3 if 价格 in query or cost in query.lower(): # 从L2历史库取更多结果 l2_results l2_store.similarity_search(query, k3) l1_results l1_store.similarity_search(query, k1) return l2_results l1_results else: # 技术相关重L3 l3_results l3_store.similarity_search(query, k3) return l3_results实操心得Retriever的质量直接决定整个应用的可信度。我坚持一个原则绝不让模型“自由发挥”它不知道的事。所有Retriever的search_kwargs都要设置k1或k2宁可漏掉也不要召回无关噪声。在采购这种高风险场景模型说“我不知道”远比它瞎编一个价格要安全一万倍。4. 从零搭建一个生产级 RAG 系统手把手拆解每一步光讲理论没用。现在我带你完整走一遍如何用 LangChain 搭建一个“手把手搭建个人知识库 RAG 系统”的最小可行产品MVP。这个系统要能上传 PDF/Word自动切片、向量化、存储接收自然语言提问返回带来源引用的答案。整个过程我会暴露所有关键决策点、参数计算依据和避坑经验。4.1 环境准备虚拟环境与依赖的精确控制别跳过这一步。我见过太多人因为 Python 版本、依赖冲突在第一步就卡死三天。我的推荐配置2025年实测稳定Python 3.11.9 不是最新的 3.12因为很多底层库如llama-cpp-python还未完全适配pip install --upgrade pip setuptools wheel先升级包管理器核心依赖pip install langchain0.2.11 \ langchain-community0.2.10 \ langchain-openai0.1.22 \ chromadb0.4.24 \ pypdf4.2.0 \ python-docx1.1.2 \ unstructured0.10.30 \ tiktoken0.7.0为什么是这些版本langchain0.2.11是当前最稳定的 0.2.x 版本修复了 0.1.x 中RetrievalQA的内存泄漏chromadb0.4.24修复了 Windows 下的文件锁问题unstructured0.10.30是最后一个支持pdfminer处理扫描版 PDF的版本。这些都不是随便选的是踩过坑后记在笔记里的。注意unstructured依赖libmagic在 macOS 上用brew install libmagic在 Ubuntu 上用apt-get install libmagic1。Windows 用户直接装unstructured会失败必须先装chocolatey再choco install libmagic。4.2 文档加载与切片不是“分段”而是“理解语义边界”很多人以为TextSplitter就是按字数切。错。切片质量直接决定 RAG 效果。我们用RecursiveCharacterTextSplitter但参数必须精心计算from langchain.text_splitter import RecursiveCharacterTextSplitter # 关键参数计算逻辑 # - chunk_size: 模型 context window 的 1/3。GPT-4 Turbo 是 128K tokens所以取 ~40K。 # 但实际文档是中文1个汉字≈2tokens所以 chunk_size 设为 20000 字符约10K tokens。 # - chunk_overlap: 保证语义连贯。设为 chunk_size 的 15%即 3000 字符。 # - separators: 按中文习惯优先按 \n\n段落、\n换行、。、、句号切。 text_splitter RecursiveCharacterTextSplitter( chunk_size20000, chunk_overlap3000, separators[\n\n, \n, 。, , , , , ], keep_separatorTrue, strip_whitespaceTrue )实操步骤加载文档用PyPDFLoaderPDF、Docx2txtLoaderWord、UnstructuredFileLoader通用。预处理删除页眉页脚、OCR 文字纠错对扫描 PDF、统一中英文标点。切片调用text_splitter.split_documents(documents)。后处理过滤掉纯空白、纯数字、少于 50 字的碎片。避坑经验unstructured加载 PDF 时默认会保留图片和表格的占位符如image、table。这对 RAG 是灾难——模型会去“理解”这些占位符。必须在加载时禁用loader UnstructuredPDFLoader( file_pathmanual.pdf, modeelements, # 不要 single 模式 strategyfast, # 不要 hi_res会保留图像 post_processors[lambda x: re.sub(r[^], , x)] # 清除所有HTML标签 )4.3 向量化与存储ChromaDB 的生产级配置ChromaDB是最轻量、最适合个人知识库的向量数据库。但默认配置是玩具级的。生产级配置如下import chromadb from chromadb.config import Settings # 生产级配置 client chromadb.PersistentClient( path./chroma_db, # 指定持久化路径不是内存 settingsSettings( anonymized_telemetryFalse, # 关闭遥测 allow_resetTrue, # 允许重置调试用 is_persistentTrue, ) ) # 创建集合指定 embedding function collection client.create_collection( nameknowledge_base, embedding_functionOpenAIEmbeddings( modeltext-embedding-3-small, # 小模型快且便宜 dimensions512, # 指定维度和后续查询一致 ), metadata{hnsw:space: cosine} # 指定相似度算法 ) # 批量添加文档不是单条add效率差10倍 collection.add( documents[chunk.page_content for chunk in chunks], metadatas[chunk.metadata for chunk in chunks], ids[fdoc_{i} for i in range(len(chunks))], # 必须唯一 )参数详解dimensions512text-embedding-3-small的输出是 512 维向量。如果这里写错插入和查询会失败。hnsw:spacecosineHNSW 索引的相似度算法。cosine最常用l2欧氏距离适合数值型。batch_sizecollection.add()默认批量无需手动分批。性能对比实测方式1000 个 chunk 插入时间内存占用单条add()42 秒1.2 GB批量add()3.8 秒320 MB4.4 RAG 链构建RetrievalQA的深度定制RetrievalQA是 LangChain 为 RAG 封装的最简链。但“最简”不等于“最好用”。我们必须深度定制from langchain.chains import RetrievalQA from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import HumanMessage, SystemMessage # 1. 构建高级提示模板带系统指令和上下文注入 qa_system_prompt 你是一个专业的知识库助手。请基于以下提供的上下文片段准确、简洁地回答用户问题。 - 如果上下文中有明确答案请直接给出不要额外解释。 - 如果上下文不足以回答请说“根据现有知识库无法回答该问题”。 - 答案中必须包含所引用的上下文来源如来源《XX手册》第3章。 - 用中文回答。 context {context} /context qa_prompt ChatPromptTemplate.from_messages([ SystemMessage(contentqa_system_prompt), MessagesPlaceholder(variable_namechat_history), # 为记忆留位置 HumanMessage(content{question}) ]) # 2. 创建检索器带重排序 from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder # 用交叉编码器重排序提升 top-k 准确率 compressor CrossEncoderReranker( modelHuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-base), top_n3 # 重排序后只取前3个 ) retriever ContextualCompressionRetriever( base_compressorcompressor, base_retrievercollection.as_retriever(search_kwargs{k: 10}) # 先取10个粗筛 ) # 3. 构建最终 QA 链 qa_chain RetrievalQA.from_chain_type( llmOpenAI(model_namegpt-4-turbo), chain_typestuff, # 最简单把所有context拼一起喂给模型 retrieverretriever, chain_type_kwargs{ prompt: qa_prompt, verbose: True # 再次强调调试必开 } ) # 4. 执行查询带记忆 result qa_chain.invoke({ question: 如何更换设备的主控板, chat_history: [] # 初始为空 }) print(result[result])关键点解析chain_typestuff适用于小知识库1000 个 chunk。如果知识库很大用refine或map_reduce但会慢很多。ContextualCompressionRetriever先用向量检索粗筛 10 个再用更准但更慢的交叉编码器重排序取前 3 个。这是精度和速度的黄金平衡点。MessagesPlaceholder为后续加入ConversationBufferMemory留下接口现在先空着。4.5 部署为 Web 服务FastAPI LangChain 的最佳实践一个不能被调用的 RAG等于没有。我们用 FastAPI 封装from fastapi import FastAPI, UploadFile, File, HTTPException from fastapi.responses import JSONResponse import uvicorn import os app FastAPI(titlePersonal Knowledge Base API) app.post(/upload) async def upload_document(file: UploadFile File(...)): 上传文档并自动处理 try: # 保存文件 file_path f./uploads/{file.filename} with open(file_path, wb