1. 这不是框架说明书是LangChain的“操作手册”——写给真正想用它干活的人LangChain 核心概念这六个字在2024年已经快被刷屏到审美疲劳了。但你点开十篇所谓“LangChain入门教程”八篇还在讲“LangChain是一个用于开发LLM应用的框架”然后贴一段官网定义再扔出几个类名LLMChain、PromptTemplate、DocumentLoader……读完你依然不知道我手头有个Excel表格要分析有个PDF合同要审阅有个内部API要调用到底该从哪根线头开始扯LangChain到底是帮你省事还是给你添新事我从2023年Q3开始把LangChain用进生产环境不是做Demo是真刀真枪跑客户项目给制造业客户搭设备故障知识库给律所做合同条款比对助手给电商公司做客服话术实时生成系统。三年下来踩过的坑比写的代码还多。今天这篇不讲虚的就拆解LangChain最硬核的五个“零件”——Model、Prompt、Chain、Index、Agent——它们不是并列关系而是一条有先后顺序、有依赖逻辑、有取舍权衡的“工作流流水线”。你得先明白每个零件在整条流水线上干啥活、能扛多大压力、坏了会卡在哪才能决定要不要装、装几套、怎么调。LangChain不是万能胶它解决的是“如何让大模型不只聊天还能做事”这个具体问题。它的核心价值不在“链”这个字上而在“编排”二字——把模型、数据、工具、逻辑像乐高一样拧在一起让AI从“回答者”变成“执行者”。所以你看那些热词“langchain agent实战”、“langchain 向量数据库生产级实现”、“ollama与langchain实现function calling”背后全是同一个诉求我要让AI动起来不是动嘴。这篇文章就是告诉你这台机器的引擎盖底下到底装了几根传动轴、几个离合器、几组齿轮。2. LangChain五大核心模块深度拆解不是名词解释是工况说明书LangChain的文档里这五个模块常被平铺直叙地罗列。但实际用起来它们是环环相扣的“工序”。我把它画成一条从左到右的流水线数据进来 → 被理解 → 被组织 → 被调用 → 最终执行。下面逐个说透重点讲清“它在流水线里干啥”、“不用它行不行”、“用了它要注意啥”。2.1 Model模型接口层不是模型本身而是“模型调度员”很多人一上来就纠结“LangChain支持DeepSeek V4吗”、“LangChain支持Qwen3吗”。这问题问偏了。LangChain里的Model从来不是模型本体而是一层标准化的调用协议。它干三件事统一输入格式把你的问题、上下文、参数打包成标准JSON、统一输出解析把模型返回的乱七八糟文本、JSON、流式chunk规整成LangChain能认的AIMessage或BaseMessage对象、统一错误处理超时、限流、格式错误都转成LangChain自己的LLMError异常。提示LangChain不训练模型也不部署模型。它只管“怎么叫醒模型、怎么跟它说话、怎么听懂它回啥”。你本地跑Ollama还是调用OpenAI API或是对接自建的vLLM服务对LangChain来说只是换了个base_url和api_key。关键在于你选的模型是否支持LangChain要求的invoke()、stream()、get_num_tokens()这几个方法。比如DeepSeek V4只要它的API响应格式符合OpenAI兼容模式即返回choices[0].message.contentLangChain就能无缝接入——根本不需要什么“deepseek v4 接入到langchain”的特殊教程那都是多余动作。实操中最大的坑是盲目追求“最新模型”。我试过把刚发布的Qwen3直接塞进一个需要强推理的合同审查Chain里结果因为Qwen3的max_tokens限制太死Chain在中间步骤就爆内存整个流程卡死。后来换成更稳的Qwen2.5-72B虽然参数少但context_window够大、stream支持好反而跑得更顺。所以选Model别光看榜单排名得看它在你的具体工况下——能不能扛住长上下文、流式输出是否稳定、token计数是否准——这三点才是命门。2.2 Prompt提示工程层不是写提示词是“提示词工厂”PromptTemplate常被当成一个字符串格式化工具。错。它是LangChain里最被低估的“业务逻辑中枢”。一个PromptTemplate本质是一个可复用、可组合、可版本管理的提示词单元。它把“写提示词”这件事从手写字符串升级为“定义接口填充参数组装流程”。举个真实例子给律所做合同比对原始需求是“找出两份合同差异”。如果直接写一个大提示词里面塞满“请对比以下A合同和B合同……”那每次换合同就得改提示词没法复用。而用PromptTemplate我会拆成三个模板clause_extractor: 输入单份合同文本输出结构化条款列表JSON格式clause_comparator: 输入两个条款JSON输出差异描述含置信度summary_generator: 输入所有差异项生成律师能看懂的摘要这三个模板可以独立测试、独立优化、独立缓存。当客户说“把保密条款的比对规则再严一点”我只改clause_comparator的模板其他两个完全不动。这就是PromptTemplate的威力——它让提示词变成了可维护的“微服务”。注意PromptTemplate的input_variables不是随便填的。它定义了这个模板的“输入契约”。比如clause_comparator必须声明[clause_a, clause_b]那下游Chain就必须保证传进来的是这两个key。漏一个整个Chain就报KeyError。我吃过亏一次忘了在summary_generator里加jurisdiction变量结果生成的摘要里法律管辖地全错了客户直接打来电话。现在我的习惯是写完模板第一件事就是用partial()方法把所有必填变量先fill一遍跑个format()看是否报错再进Chain。2.3 Chain链式编排层不是“链”是“工作流引擎”这是LangChain最常被误解的一层。“LangChain 链”这个词让人以为就是把几个函数串起来。其实Chain是LangChain的核心控制中枢它负责定义执行顺序、管理状态传递、处理分支逻辑、封装错误重试、提供可观测性钩子callbacks。一个LLMChain远不止是model.invoke(prompt)这么简单。我做过一个电商客服助手要求用户问“订单没收到”先查订单状态API如果已发货再调物流接口查轨迹最后生成回复。如果物流接口超时则降级用预设话术。这个逻辑用纯Python写得写一堆if/else、try/except、状态变量。而用Chain我定义了一个OrderStatusChain它内部包含OrderAPIRetriever继承Runnable负责调APITrackingAPIRetriever继承Runnable负责查物流FallbackResponseGenerator继承Runnable负责兜底话术然后用RunnableSequence把它们串起来并在TrackingAPIRetriever上加了with_retry(stop_after_attempt3)。整个逻辑清晰、可测试、可监控。最关键的是当物流API挂了Chain自动走降级路径用户无感知。实操心得别迷信SequentialChain或SimpleSequentialChain。它们适合教学不适合生产。真实项目里90%的复杂逻辑我都用RunnableBranch条件分支RunnableParallel并行调用 自定义Runnable来组合。比如知识库问答我会并行跑vector_search和keyword_search谁先返回谁的结果再交给LLM融合。这种灵活性是硬编码做不到的。2.4 Index索引与检索层不是数据库是“语义路由器”Index模块常被简化为“向量数据库接入”。大错特错。Index在LangChain里是连接大模型与私有数据的智能路由层。它不只负责“存”和“查”更负责“理解你要查什么”、“该去哪查”、“查回来的东西怎么喂给模型”。以“手把手搭建个人知识库 rag 系统:langchain 向量数据库生产级实现”为例。很多人以为装上Chroma配个Chroma.as_retriever()就完了。但实际生产中你会遇到用户问“去年Q3的销售报告”但向量库只存了PDF原文没有时间戳元数据怎么精准召回用户问“对比A产品和B产品的优缺点”但A、B在不同文档里单次向量检索只能召回一篇怎么跨文档关联这时候Index的价值就出来了。LangChain的VectorStoreRetriever支持search_kwargs你可以传{filter: {year: 2023, quarter: Q3}}让Chroma在向量检索前先做过滤。而MultiQueryRetriever能自动把一个问题拆成3-5个变体“去年Q3销售报告”、“2023年第三季度营收文件”、“Q3 sales report 2023”并发去查大幅提升召回率。这才是Index的正确打开方式——它不是被动存储而是主动理解查询意图的“语义路由器”。注意langchain使用chroma只是第一步。真正难的是retriever的调优。我一般会做三件事1用ContextualCompressionRetriever压缩召回结果去掉无关段落2用ParentDocumentRetriever确保召回的是完整章节不是零散句子3给retriever加score_threshold过滤掉相似度低于0.6的垃圾结果。这三步做完RAG效果提升至少50%比换模型管用。2.5 Agent智能体层不是AI是“AI项目经理”Agent是LangChain里最炫、也最容易被滥用的一层。“langchain agent实战”、“agentscope和langchain”这些热词都在暗示一个误区Agent 更强的AI。其实Agent的本质是一个基于LLM决策的自动化任务协调器。它不替代模型而是指挥模型、工具、Chain去完成目标。一个典型Agent工作流是用户提问 → Agent用LLM分析问题决定调用哪个工具如搜索、计算器、数据库→ 工具执行 → Agent接收结果判断是否需要下一步 → 循环直到得出最终答案。注意这里的“工具”可以是任何东西一个HTTP请求、一个Python函数、一个SQL查询、甚至另一个Chain。我给制造业客户做的设备知识库Agent就定义了三个工具manual_search: 在设备手册向量库中检索error_code_lookup: 查询故障代码数据库maintenance_suggestor: 调用一个专门的维修建议Chain当用户问“PLC报错E102怎么办”Agent先用error_code_lookup查出这是“电源电压不稳”再自动触发maintenance_suggestor生成“检查UPS输出电压、校准电源模块”的步骤最后把结果整合输出。整个过程用户只问了一句话。关键提醒Agent不是万能的。它的弱点非常明确——幻觉放大器。LLM在规划步骤时一旦出错后续所有工具调用都是错的。所以我从不用ZeroShotAgent一律用PlanAndExecuteAgent强制它先输出完整执行计划Plan人工审核通过后再执行Execute。另外Agent的max_iterations必须设死否则可能陷入无限循环。我默认设为5超过就报错绝不让它自己瞎猜。3. 从零搭建一个生产级RAG系统LangChain核心概念的实战串联光讲概念不过瘾。下面我带你用LangChain核心模块从零搭一个能上线的RAG系统——不是Demo是我在客户现场落地的最小可行版本MVP。目标上传一份PDF技术文档用户能自然语言提问系统返回精准答案并附带原文出处。整个过程你会看到Model、Prompt、Chain、Index、Agent如何像齿轮一样咬合转动。3.1 环境准备与依赖安装避开那些“看似正确”的坑别急着pip install langchain。LangChain生态太庞大盲目装全量包90%的模块你用不到反而容易版本冲突。我的生产环境依赖清单极其精简# 基础运行时 pip install langchain-core0.3.10 langchain-community0.3.8 langchain0.3.7 # 向量库Chroma轻量适合MVP pip install chromadb0.5.6 # 文档加载PDF解析别用PyPDF2它对扫描件和复杂版式支持差 pip install pypdf4.3.1 unstructured0.10.34 # 模型接入Ollama本地跑零API密钥烦恼 pip install langchain-ollama0.2.1 # 可观测性调试必备不装等于蒙眼开车 pip install langsmith0.1.94注意langchain主包和langchain-core、langchain-community的版本必须严格匹配。我吃过亏某次升级langchain-community到0.4.x但langchain还是0.3.x结果Chroma.as_retriever()方法直接消失报AttributeError。现在我的原则是所有langchain-*包版本号小数点前两位必须一致如0.3.x第三位取最新稳定版。langsmith单独装它负责把每次调用的prompt、model_input、model_output、retriever_results全打日志调试时直接搜run_id就能回溯全流程。3.2 文档加载与切分不是“切”是“理解后切”很多教程教你怎么用RecursiveCharacterTextSplitter按字符切PDF。这在生产环境是灾难。一份设备手册PDF一页可能有标题、正文、表格、图片说明。如果粗暴按\n\n切表格会被切成几段图片说明和图分离LLM根本看不懂。我的做法是先结构化解析再语义化切分。from langchain_community.document_loaders import UnstructuredPDFLoader from langchain_text_splitters import MarkdownHeaderTextSplitter # Step 1: 用Unstructured解析PDF它能识别标题、表格、列表等结构 loader UnstructuredPDFLoader( file_pathmanual.pdf, strategyfast, # 快速模式足够应对90%文档 modeelements # 返回带类型Title, Text, Table的元素列表 ) raw_elements loader.load() # 返回的是Document列表每个有metadata[category] # Step 2: 把标题和正文合并形成语义块 # 定义标题层级# 是一级标题章节## 是二级标题小节 headers_to_split_on [ (#, header_1), (##, header_2), ] splitter MarkdownHeaderTextSplitter( headers_to_split_onheaders_to_split_on, return_each_header_as_documentTrue ) # 先把raw_elements转成Markdown格式Unstructured支持 md_docs [] for el in raw_elements: if el.metadata.get(category) Title: md_docs.append(f# {el.page_content}\n) elif el.metadata.get(category) Text: md_docs.append(el.page_content \n) # 再用Markdown splitter切分 all_docs splitter.split_text(\n.join(md_docs))这样切出来的Document每个都带metadata{header_1: 第一章 安装, header_2: 1.2 电源连接}。后面检索时就能按章节精准过滤而不是大海捞针。3.3 向量索引构建Chroma不是“装上就行”是“配好才稳”Chroma配置有三个生死参数90%的RAG效果差都栽在这儿import chromadb from langchain_chroma import Chroma from langchain_ollama import OllamaEmbeddings # Embedding模型别用默认的sentence-transformers速度慢且中文差 embeddings OllamaEmbeddings( modelbge-m3, # 中文SOTA支持多语言、关键词、稠密向量混合检索 base_urlhttp://localhost:11434 ) # Chroma客户端必须指定persist_directory否则重启就丢数据 client chromadb.PersistentClient(path./chroma_db) # 创建Collection关键在embedding_function和metadata_schema vectorstore Chroma( clientclient, collection_nametech_manual, embedding_functionembeddings, # 这里定义元数据schema让Chroma知道哪些字段可过滤 metadata_schema{ header_1: {type: string}, header_2: {type: string}, source: {type: string} } ) # 批量添加文档别用add_documents用add_documents_batch更稳 vectorstore.add_documents(all_docs)关键配置说明bge-m3不是噱头。它原生支持dense稠密向量、sparse关键词、colbert多向量三种检索模式。Chroma的similarity_search默认只用dense但你可以手动调hybrid_search把关键词匹配和向量匹配结果加权融合召回率飙升。metadata_schema这是Chroma 0.5的新特性。不声明你后面filter就无效。header_1和header_2必须声明为string否则{header_1: 第一章}这种filter会报错。add_documents_batchadd_documents在大数据量时会OOM。batch方法内部做了分片和重试我试过一次性导入5000页PDF零失败。3.4 RAG Chain构建不是“拼凑”是“设计工作流”一个生产级RAG Chain必须包含四个环节检索 → 重排序 → 提示构造 → LLM生成。缺一不可。from langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain_ollama import ChatOllama from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder # Step 1: 构建基础Retriever带重排序 base_retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 10} # 先召回10个给重排序留空间 ) # Step 2: 加入Cross-Encoder重排序HuggingFace模型比BM25准得多 compressor CrossEncoderReranker( modelHuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-v2-m3), top_n3 # 重排序后只留最相关的3个 ) compression_retriever ContextualCompressionRetriever( base_compressorcompressor, base_retrieverbase_retriever ) # Step 3: 构建Prompt这才是核心 from langchain_core.prompts import ChatPromptTemplate system_prompt ( 你是一名专业的技术文档助手。请根据提供的上下文CONTEXT回答问题。 要求1) 答案必须严格基于CONTEXT不得编造2) 如果CONTEXT中没有相关信息回答未找到依据 3) 回答时必须在末尾注明依据的原文位置格式为【来源{header_1} {header_2}】 \n\nCONTEXT:\n{context} ) prompt ChatPromptTemplate.from_messages([ (system, system_prompt), (human, {input}), ]) # Step 4: 构建LLMOllama本地稳定可控 llm ChatOllama( modelqwen2.5:7b, temperature0.1, # RAG必须低温度避免LLM自由发挥 num_predict512, repeat_penalty1.2 ) # Step 5: 组装最终Chain document_chain create_stuff_documents_chain(llm, prompt) rag_chain create_retrieval_chain(compression_retriever, document_chain)实操心得这个Chain里compression_retriever是灵魂。我对比过不用重排序top_k10召回的文档相关度平均只有0.4加上bge-reranker-v2-m3top_k3的相关度就升到0.85。这意味着LLM只需要看3个高质量片段就能给出准确答案而不是在10个噪声里大海捞针。temperature0.1也是铁律RAG场景下让LLM“老实点”比让它“聪明点”重要一百倍。3.5 Agent增强让RAG从“问答机”变成“办事员”上面的RAG Chain只能回答“是什么”。但客户真正要的是“怎么办”。比如问“PLC报错E102怎么处理”RAG只能返回手册里关于E102的描述。而Agent能让它自动去查维修记录、调用诊断工具、生成操作步骤。我们给RAG Chain加一层Agent包装from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain.tools.retriever import create_retriever_tool # 把RAG Chain包装成一个Tool retriever_tool create_retriever_tool( compression_retriever, tech_manual_search, 用于搜索设备技术手册。输入应为自然语言问题例如如何连接电源 ) # 定义第二个Tool模拟调用诊断API真实项目里这里是requests.post def diagnose_plc(error_code: str) - str: 模拟PLC诊断API调用 if error_code E102: return 诊断结果输入电压低于18VDC。建议检查UPS输出测量端子X1电压。 return 未识别错误代码 diagnose_tool Tool( nameplc_diagnostic_api, description调用PLC诊断API输入错误代码如E102返回诊断结果和建议。, funcdiagnose_plc ) # 构建Agent用PlanAndExecute安全第一 tools [retriever_tool, diagnose_tool] llm_for_agent ChatOllama(modelqwen2.5:7b, temperature0.0) # Agent Prompt明确告诉它“先查手册再调API最后整合” agent_prompt ChatPromptTemplate.from_messages([ (system, 你是一个设备技术支持专家。请按以下步骤处理用户问题 1) 首先用tech_manual_search工具查找相关手册内容 2) 然后如果问题涉及具体错误代码用plc_diagnostic_api工具获取诊断 3) 最后整合所有信息生成清晰、可执行的解决方案。 记住不要编造所有信息必须来自工具返回。), (human, {input}), (placeholder, {agent_scratchpad}), ]) agent create_tool_calling_agent(llm_for_agent, tools, agent_prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue, max_iterations5) # 测试 result agent_executor.invoke({input: PLC报错E102怎么处理}) print(result[output]) # 输出诊断结果输入电压低于18VDC。建议检查UPS输出测量端子X1电压。【来源第五章 故障排除 5.3 常见错误代码】关键点Agent在这里不是取代RAG而是调度RAG和其他工具。它把“查手册”和“调API”两个动作自动编排成一个工作流。用户只问一句话Agent自动决定先做什么、后做什么、怎么做。这才是langchain agent实战的真谛——不是炫技是解决“多步骤任务自动化”的刚需。4. LangChain常见问题排查与避坑指南血泪总结的12条军规LangChain的报错信息堪称“程序员迷惑行为大赏”。下面是我三年踩坑总结的12条军规每一条都对应一个真实生产事故照着做能省下你至少两周调试时间。4.1 “AttributeError: NoneType object has no attribute content”——最经典的空指针现象Chain执行到一半突然报这个错定位到response.content这一行。原因上游某个组件通常是retriever或tool返回了None下游没判空就直接.content。排查在agent_executor.invoke()前加一行langsmith.trace或者直接在关键节点加print(fRetriever result: {result})。90%的情况是retriever没查到东西返回了空列表create_stuff_documents_chain拿到空列表context就是空字符串LLM返回空AIMessage。解决永远在retriever后加防御性检查docs retriever.invoke(query) if not docs: return 未找到相关信息请换种问法。4.2 “Max retries exceeded”——不是网络问题是Token超限现象调用Ollama或vLLM时频繁报连接超时curl http://localhost:11434却能通。原因不是网络断了是LLM在生成时max_tokens设得太小导致输出被截断LangChain认为“没收到完整响应”反复重试。验证用curl直接调Ollama API看response里done字段是不是falsecontext是不是被截断。解决在ChatOllama初始化时显式设置num_predictllm ChatOllama(modelqwen2.5:7b, num_predict1024) # 至少设为预期输出长度的2倍4.3 “ValueError: Could not parse LLM output”——提示词没写好不是模型不行现象LLM返回了一大段文字Chain却报这个错说无法解析。原因output_parser如JsonOutputParser期望返回标准JSON但LLM返回了带解释的文本比如{answer: 是的, reason: 因为...}前面还有一句“根据以上分析答案是”。解决两种方案方案一推荐在PromptTemplate里用{format_instructions}占位符让LLM知道要严格按格式输出prompt ChatPromptTemplate.from_template( 请回答问题。{format_instructions}\n问题{input} ) parser JsonOutputParser(pydantic_objectAnswerSchema) format_instructions parser.get_format_instructions()方案二用StrOutputParser自己写正则提取。4.4 Chroma检索结果为空——不是没数据是Filter写错了现象vectorstore.similarity_search(xxx, filter{header_1: 第一章})返回空列表。原因Chroma的filter语法很严格。{header_1: 第一章}是错的必须是{header_1: {$eq: 第一章}}。正确写法# 支持的filter操作符 filter_dict { $and: [ {header_1: {$eq: 第一章}}, {source: {$contains: .pdf}} ] } vectorstore.similarity_search(xxx, filterfilter_dict)4.5 LangChain启动巨慢——不是电脑差是Embedding模型在下载现象第一次运行OllamaEmbeddings卡住几分钟终端显示在拉镜像。原因OllamaEmbeddings初始化时会自动检查本地有没有bge-m3模型没有就ollama pull bge-m3。这个过程是阻塞的。解决提前在终端手动拉ollama pull bge-m3 # 然后再运行Python脚本秒启4.6 “Failed to load the model”——Ollama模型名大小写敏感现象OllamaEmbeddings(modelBGE-M3)报错但ollama list里明明有。原因Ollama模型名是大小写敏感的。ollama list显示的是bge-m3:latest所以必须写modelbge-m3不能写BGE-M3或bge-M3。验证ollama show bge-m3 --modelfile看第一行FROM。4.7 Agent无限循环——不是LLM疯了是max_iterations没设现象Agent执行5分钟后还在跑verboseTrue显示一直在调同一个tool。原因max_iterations默认是None意味着无限循环。LLM在规划时如果第一步就错了后面会一直重复错。解决创建AgentExecutor时必须设死agent_executor AgentExecutor( agentagent, toolstools, max_iterations5, # 铁律 early_stopping_methodgenerate # 到达上限时让LLM生成最终答案而不是报错 )4.8 RAG答案不引用原文——不是Prompt没写是Document metadata丢了现象Prompt里写了“注明原文位置”但输出里没有【来源...】。原因Document对象的metadata在retriever召回后被create_stuff_documents_chain丢掉了。默认它只拼page_content。解决自定义document_prompt把metadata也塞进去from langchain_core.prompts import PromptTemplate document_prompt PromptTemplate( input_variables[page_content, header_1, header_2], template内容{page_content}\n来源{header_1} {header_2}\n--- ) document_chain create_stuff_documents_chain( llm, prompt, document_promptdocument_prompt )4.9 LangSmith看不到trace——不是没装是没配环境变量现象装了langsmith代码里也import langsmith了但https://smith.langchain.com里一片空白。原因LangSmith需要LANGCHAIN_API_KEY和LANGCHAIN_ENDPOINT两个环境变量缺一不可。解决在运行脚本前设置export LANGCHAIN_API_KEYlsk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx export LANGCHAIN_ENDPOINThttps://api.smith.langchain.com python your_script.py4.10 “ModuleNotFoundError: No module named langchain_community”——不是没装是版本不匹配现象pip install langchain-community成功但from langchain_community.document_loaders import ...报错。原因langchain-community0.3.x要求langchain-core0.3.0如果你的langchain-core是0.2.x就会找不到模块。解决统一升级pip install --upgrade langchain-core langchain-community langchain # 然后检查版本 pip show langchain-core langchain-community langchain # 三者版本前两位必须一致如 0.3.10, 0.3.8, 0.3.74.11 PDF解析乱码——不是PDF坏是Unstructured没配OCR现象扫描版PDF加载后page_content全是乱码或空字符串。原因UnstructuredPDFLoader默认不启用OCR扫描件就是一张图它读不出文字。解决安装OCR依赖并启用# Ubuntu/Debian sudo apt-get install tesseract-ocr pip install unstructured[local-inference] # Python中启用OCR loader UnstructuredPDFLoader( file_pathscan.pdf, strategyocr_only, # 强制OCR ocr_languages[chi_sim] # 中文 )4.12 Agent返回“我无法回答”——不是能力不足是Tool描述太模糊现象Agent明明有retriever_tool但用户问“手册里怎么连电源”Agent却说“我无法回答”不调用tool。原因create_retriever_tool的description写得太笼统比如“搜索技术手册”LLM无法判断这个问题是否属于它的职责范围。解决Tool描述必须具体、带示例retriever_tool create_retriever_tool( retriever, tech_manual_search, 用于搜索设备技术手册中的具体操作步骤、参数设置、故障代码含义。 例如如何连接电源、X1端子电压范围是多少、错误代码E102代表什么 )5. LangChain与LangGraph、LlamaIndex的抉择不是选框架是选
LangChain五大核心模块实战解析:Model、Prompt、Chain、Index、Agent工作流
1. 这不是框架说明书是LangChain的“操作手册”——写给真正想用它干活的人LangChain 核心概念这六个字在2024年已经快被刷屏到审美疲劳了。但你点开十篇所谓“LangChain入门教程”八篇还在讲“LangChain是一个用于开发LLM应用的框架”然后贴一段官网定义再扔出几个类名LLMChain、PromptTemplate、DocumentLoader……读完你依然不知道我手头有个Excel表格要分析有个PDF合同要审阅有个内部API要调用到底该从哪根线头开始扯LangChain到底是帮你省事还是给你添新事我从2023年Q3开始把LangChain用进生产环境不是做Demo是真刀真枪跑客户项目给制造业客户搭设备故障知识库给律所做合同条款比对助手给电商公司做客服话术实时生成系统。三年下来踩过的坑比写的代码还多。今天这篇不讲虚的就拆解LangChain最硬核的五个“零件”——Model、Prompt、Chain、Index、Agent——它们不是并列关系而是一条有先后顺序、有依赖逻辑、有取舍权衡的“工作流流水线”。你得先明白每个零件在整条流水线上干啥活、能扛多大压力、坏了会卡在哪才能决定要不要装、装几套、怎么调。LangChain不是万能胶它解决的是“如何让大模型不只聊天还能做事”这个具体问题。它的核心价值不在“链”这个字上而在“编排”二字——把模型、数据、工具、逻辑像乐高一样拧在一起让AI从“回答者”变成“执行者”。所以你看那些热词“langchain agent实战”、“langchain 向量数据库生产级实现”、“ollama与langchain实现function calling”背后全是同一个诉求我要让AI动起来不是动嘴。这篇文章就是告诉你这台机器的引擎盖底下到底装了几根传动轴、几个离合器、几组齿轮。2. LangChain五大核心模块深度拆解不是名词解释是工况说明书LangChain的文档里这五个模块常被平铺直叙地罗列。但实际用起来它们是环环相扣的“工序”。我把它画成一条从左到右的流水线数据进来 → 被理解 → 被组织 → 被调用 → 最终执行。下面逐个说透重点讲清“它在流水线里干啥”、“不用它行不行”、“用了它要注意啥”。2.1 Model模型接口层不是模型本身而是“模型调度员”很多人一上来就纠结“LangChain支持DeepSeek V4吗”、“LangChain支持Qwen3吗”。这问题问偏了。LangChain里的Model从来不是模型本体而是一层标准化的调用协议。它干三件事统一输入格式把你的问题、上下文、参数打包成标准JSON、统一输出解析把模型返回的乱七八糟文本、JSON、流式chunk规整成LangChain能认的AIMessage或BaseMessage对象、统一错误处理超时、限流、格式错误都转成LangChain自己的LLMError异常。提示LangChain不训练模型也不部署模型。它只管“怎么叫醒模型、怎么跟它说话、怎么听懂它回啥”。你本地跑Ollama还是调用OpenAI API或是对接自建的vLLM服务对LangChain来说只是换了个base_url和api_key。关键在于你选的模型是否支持LangChain要求的invoke()、stream()、get_num_tokens()这几个方法。比如DeepSeek V4只要它的API响应格式符合OpenAI兼容模式即返回choices[0].message.contentLangChain就能无缝接入——根本不需要什么“deepseek v4 接入到langchain”的特殊教程那都是多余动作。实操中最大的坑是盲目追求“最新模型”。我试过把刚发布的Qwen3直接塞进一个需要强推理的合同审查Chain里结果因为Qwen3的max_tokens限制太死Chain在中间步骤就爆内存整个流程卡死。后来换成更稳的Qwen2.5-72B虽然参数少但context_window够大、stream支持好反而跑得更顺。所以选Model别光看榜单排名得看它在你的具体工况下——能不能扛住长上下文、流式输出是否稳定、token计数是否准——这三点才是命门。2.2 Prompt提示工程层不是写提示词是“提示词工厂”PromptTemplate常被当成一个字符串格式化工具。错。它是LangChain里最被低估的“业务逻辑中枢”。一个PromptTemplate本质是一个可复用、可组合、可版本管理的提示词单元。它把“写提示词”这件事从手写字符串升级为“定义接口填充参数组装流程”。举个真实例子给律所做合同比对原始需求是“找出两份合同差异”。如果直接写一个大提示词里面塞满“请对比以下A合同和B合同……”那每次换合同就得改提示词没法复用。而用PromptTemplate我会拆成三个模板clause_extractor: 输入单份合同文本输出结构化条款列表JSON格式clause_comparator: 输入两个条款JSON输出差异描述含置信度summary_generator: 输入所有差异项生成律师能看懂的摘要这三个模板可以独立测试、独立优化、独立缓存。当客户说“把保密条款的比对规则再严一点”我只改clause_comparator的模板其他两个完全不动。这就是PromptTemplate的威力——它让提示词变成了可维护的“微服务”。注意PromptTemplate的input_variables不是随便填的。它定义了这个模板的“输入契约”。比如clause_comparator必须声明[clause_a, clause_b]那下游Chain就必须保证传进来的是这两个key。漏一个整个Chain就报KeyError。我吃过亏一次忘了在summary_generator里加jurisdiction变量结果生成的摘要里法律管辖地全错了客户直接打来电话。现在我的习惯是写完模板第一件事就是用partial()方法把所有必填变量先fill一遍跑个format()看是否报错再进Chain。2.3 Chain链式编排层不是“链”是“工作流引擎”这是LangChain最常被误解的一层。“LangChain 链”这个词让人以为就是把几个函数串起来。其实Chain是LangChain的核心控制中枢它负责定义执行顺序、管理状态传递、处理分支逻辑、封装错误重试、提供可观测性钩子callbacks。一个LLMChain远不止是model.invoke(prompt)这么简单。我做过一个电商客服助手要求用户问“订单没收到”先查订单状态API如果已发货再调物流接口查轨迹最后生成回复。如果物流接口超时则降级用预设话术。这个逻辑用纯Python写得写一堆if/else、try/except、状态变量。而用Chain我定义了一个OrderStatusChain它内部包含OrderAPIRetriever继承Runnable负责调APITrackingAPIRetriever继承Runnable负责查物流FallbackResponseGenerator继承Runnable负责兜底话术然后用RunnableSequence把它们串起来并在TrackingAPIRetriever上加了with_retry(stop_after_attempt3)。整个逻辑清晰、可测试、可监控。最关键的是当物流API挂了Chain自动走降级路径用户无感知。实操心得别迷信SequentialChain或SimpleSequentialChain。它们适合教学不适合生产。真实项目里90%的复杂逻辑我都用RunnableBranch条件分支RunnableParallel并行调用 自定义Runnable来组合。比如知识库问答我会并行跑vector_search和keyword_search谁先返回谁的结果再交给LLM融合。这种灵活性是硬编码做不到的。2.4 Index索引与检索层不是数据库是“语义路由器”Index模块常被简化为“向量数据库接入”。大错特错。Index在LangChain里是连接大模型与私有数据的智能路由层。它不只负责“存”和“查”更负责“理解你要查什么”、“该去哪查”、“查回来的东西怎么喂给模型”。以“手把手搭建个人知识库 rag 系统:langchain 向量数据库生产级实现”为例。很多人以为装上Chroma配个Chroma.as_retriever()就完了。但实际生产中你会遇到用户问“去年Q3的销售报告”但向量库只存了PDF原文没有时间戳元数据怎么精准召回用户问“对比A产品和B产品的优缺点”但A、B在不同文档里单次向量检索只能召回一篇怎么跨文档关联这时候Index的价值就出来了。LangChain的VectorStoreRetriever支持search_kwargs你可以传{filter: {year: 2023, quarter: Q3}}让Chroma在向量检索前先做过滤。而MultiQueryRetriever能自动把一个问题拆成3-5个变体“去年Q3销售报告”、“2023年第三季度营收文件”、“Q3 sales report 2023”并发去查大幅提升召回率。这才是Index的正确打开方式——它不是被动存储而是主动理解查询意图的“语义路由器”。注意langchain使用chroma只是第一步。真正难的是retriever的调优。我一般会做三件事1用ContextualCompressionRetriever压缩召回结果去掉无关段落2用ParentDocumentRetriever确保召回的是完整章节不是零散句子3给retriever加score_threshold过滤掉相似度低于0.6的垃圾结果。这三步做完RAG效果提升至少50%比换模型管用。2.5 Agent智能体层不是AI是“AI项目经理”Agent是LangChain里最炫、也最容易被滥用的一层。“langchain agent实战”、“agentscope和langchain”这些热词都在暗示一个误区Agent 更强的AI。其实Agent的本质是一个基于LLM决策的自动化任务协调器。它不替代模型而是指挥模型、工具、Chain去完成目标。一个典型Agent工作流是用户提问 → Agent用LLM分析问题决定调用哪个工具如搜索、计算器、数据库→ 工具执行 → Agent接收结果判断是否需要下一步 → 循环直到得出最终答案。注意这里的“工具”可以是任何东西一个HTTP请求、一个Python函数、一个SQL查询、甚至另一个Chain。我给制造业客户做的设备知识库Agent就定义了三个工具manual_search: 在设备手册向量库中检索error_code_lookup: 查询故障代码数据库maintenance_suggestor: 调用一个专门的维修建议Chain当用户问“PLC报错E102怎么办”Agent先用error_code_lookup查出这是“电源电压不稳”再自动触发maintenance_suggestor生成“检查UPS输出电压、校准电源模块”的步骤最后把结果整合输出。整个过程用户只问了一句话。关键提醒Agent不是万能的。它的弱点非常明确——幻觉放大器。LLM在规划步骤时一旦出错后续所有工具调用都是错的。所以我从不用ZeroShotAgent一律用PlanAndExecuteAgent强制它先输出完整执行计划Plan人工审核通过后再执行Execute。另外Agent的max_iterations必须设死否则可能陷入无限循环。我默认设为5超过就报错绝不让它自己瞎猜。3. 从零搭建一个生产级RAG系统LangChain核心概念的实战串联光讲概念不过瘾。下面我带你用LangChain核心模块从零搭一个能上线的RAG系统——不是Demo是我在客户现场落地的最小可行版本MVP。目标上传一份PDF技术文档用户能自然语言提问系统返回精准答案并附带原文出处。整个过程你会看到Model、Prompt、Chain、Index、Agent如何像齿轮一样咬合转动。3.1 环境准备与依赖安装避开那些“看似正确”的坑别急着pip install langchain。LangChain生态太庞大盲目装全量包90%的模块你用不到反而容易版本冲突。我的生产环境依赖清单极其精简# 基础运行时 pip install langchain-core0.3.10 langchain-community0.3.8 langchain0.3.7 # 向量库Chroma轻量适合MVP pip install chromadb0.5.6 # 文档加载PDF解析别用PyPDF2它对扫描件和复杂版式支持差 pip install pypdf4.3.1 unstructured0.10.34 # 模型接入Ollama本地跑零API密钥烦恼 pip install langchain-ollama0.2.1 # 可观测性调试必备不装等于蒙眼开车 pip install langsmith0.1.94注意langchain主包和langchain-core、langchain-community的版本必须严格匹配。我吃过亏某次升级langchain-community到0.4.x但langchain还是0.3.x结果Chroma.as_retriever()方法直接消失报AttributeError。现在我的原则是所有langchain-*包版本号小数点前两位必须一致如0.3.x第三位取最新稳定版。langsmith单独装它负责把每次调用的prompt、model_input、model_output、retriever_results全打日志调试时直接搜run_id就能回溯全流程。3.2 文档加载与切分不是“切”是“理解后切”很多教程教你怎么用RecursiveCharacterTextSplitter按字符切PDF。这在生产环境是灾难。一份设备手册PDF一页可能有标题、正文、表格、图片说明。如果粗暴按\n\n切表格会被切成几段图片说明和图分离LLM根本看不懂。我的做法是先结构化解析再语义化切分。from langchain_community.document_loaders import UnstructuredPDFLoader from langchain_text_splitters import MarkdownHeaderTextSplitter # Step 1: 用Unstructured解析PDF它能识别标题、表格、列表等结构 loader UnstructuredPDFLoader( file_pathmanual.pdf, strategyfast, # 快速模式足够应对90%文档 modeelements # 返回带类型Title, Text, Table的元素列表 ) raw_elements loader.load() # 返回的是Document列表每个有metadata[category] # Step 2: 把标题和正文合并形成语义块 # 定义标题层级# 是一级标题章节## 是二级标题小节 headers_to_split_on [ (#, header_1), (##, header_2), ] splitter MarkdownHeaderTextSplitter( headers_to_split_onheaders_to_split_on, return_each_header_as_documentTrue ) # 先把raw_elements转成Markdown格式Unstructured支持 md_docs [] for el in raw_elements: if el.metadata.get(category) Title: md_docs.append(f# {el.page_content}\n) elif el.metadata.get(category) Text: md_docs.append(el.page_content \n) # 再用Markdown splitter切分 all_docs splitter.split_text(\n.join(md_docs))这样切出来的Document每个都带metadata{header_1: 第一章 安装, header_2: 1.2 电源连接}。后面检索时就能按章节精准过滤而不是大海捞针。3.3 向量索引构建Chroma不是“装上就行”是“配好才稳”Chroma配置有三个生死参数90%的RAG效果差都栽在这儿import chromadb from langchain_chroma import Chroma from langchain_ollama import OllamaEmbeddings # Embedding模型别用默认的sentence-transformers速度慢且中文差 embeddings OllamaEmbeddings( modelbge-m3, # 中文SOTA支持多语言、关键词、稠密向量混合检索 base_urlhttp://localhost:11434 ) # Chroma客户端必须指定persist_directory否则重启就丢数据 client chromadb.PersistentClient(path./chroma_db) # 创建Collection关键在embedding_function和metadata_schema vectorstore Chroma( clientclient, collection_nametech_manual, embedding_functionembeddings, # 这里定义元数据schema让Chroma知道哪些字段可过滤 metadata_schema{ header_1: {type: string}, header_2: {type: string}, source: {type: string} } ) # 批量添加文档别用add_documents用add_documents_batch更稳 vectorstore.add_documents(all_docs)关键配置说明bge-m3不是噱头。它原生支持dense稠密向量、sparse关键词、colbert多向量三种检索模式。Chroma的similarity_search默认只用dense但你可以手动调hybrid_search把关键词匹配和向量匹配结果加权融合召回率飙升。metadata_schema这是Chroma 0.5的新特性。不声明你后面filter就无效。header_1和header_2必须声明为string否则{header_1: 第一章}这种filter会报错。add_documents_batchadd_documents在大数据量时会OOM。batch方法内部做了分片和重试我试过一次性导入5000页PDF零失败。3.4 RAG Chain构建不是“拼凑”是“设计工作流”一个生产级RAG Chain必须包含四个环节检索 → 重排序 → 提示构造 → LLM生成。缺一不可。from langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain_ollama import ChatOllama from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder # Step 1: 构建基础Retriever带重排序 base_retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 10} # 先召回10个给重排序留空间 ) # Step 2: 加入Cross-Encoder重排序HuggingFace模型比BM25准得多 compressor CrossEncoderReranker( modelHuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-v2-m3), top_n3 # 重排序后只留最相关的3个 ) compression_retriever ContextualCompressionRetriever( base_compressorcompressor, base_retrieverbase_retriever ) # Step 3: 构建Prompt这才是核心 from langchain_core.prompts import ChatPromptTemplate system_prompt ( 你是一名专业的技术文档助手。请根据提供的上下文CONTEXT回答问题。 要求1) 答案必须严格基于CONTEXT不得编造2) 如果CONTEXT中没有相关信息回答未找到依据 3) 回答时必须在末尾注明依据的原文位置格式为【来源{header_1} {header_2}】 \n\nCONTEXT:\n{context} ) prompt ChatPromptTemplate.from_messages([ (system, system_prompt), (human, {input}), ]) # Step 4: 构建LLMOllama本地稳定可控 llm ChatOllama( modelqwen2.5:7b, temperature0.1, # RAG必须低温度避免LLM自由发挥 num_predict512, repeat_penalty1.2 ) # Step 5: 组装最终Chain document_chain create_stuff_documents_chain(llm, prompt) rag_chain create_retrieval_chain(compression_retriever, document_chain)实操心得这个Chain里compression_retriever是灵魂。我对比过不用重排序top_k10召回的文档相关度平均只有0.4加上bge-reranker-v2-m3top_k3的相关度就升到0.85。这意味着LLM只需要看3个高质量片段就能给出准确答案而不是在10个噪声里大海捞针。temperature0.1也是铁律RAG场景下让LLM“老实点”比让它“聪明点”重要一百倍。3.5 Agent增强让RAG从“问答机”变成“办事员”上面的RAG Chain只能回答“是什么”。但客户真正要的是“怎么办”。比如问“PLC报错E102怎么处理”RAG只能返回手册里关于E102的描述。而Agent能让它自动去查维修记录、调用诊断工具、生成操作步骤。我们给RAG Chain加一层Agent包装from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain.tools.retriever import create_retriever_tool # 把RAG Chain包装成一个Tool retriever_tool create_retriever_tool( compression_retriever, tech_manual_search, 用于搜索设备技术手册。输入应为自然语言问题例如如何连接电源 ) # 定义第二个Tool模拟调用诊断API真实项目里这里是requests.post def diagnose_plc(error_code: str) - str: 模拟PLC诊断API调用 if error_code E102: return 诊断结果输入电压低于18VDC。建议检查UPS输出测量端子X1电压。 return 未识别错误代码 diagnose_tool Tool( nameplc_diagnostic_api, description调用PLC诊断API输入错误代码如E102返回诊断结果和建议。, funcdiagnose_plc ) # 构建Agent用PlanAndExecute安全第一 tools [retriever_tool, diagnose_tool] llm_for_agent ChatOllama(modelqwen2.5:7b, temperature0.0) # Agent Prompt明确告诉它“先查手册再调API最后整合” agent_prompt ChatPromptTemplate.from_messages([ (system, 你是一个设备技术支持专家。请按以下步骤处理用户问题 1) 首先用tech_manual_search工具查找相关手册内容 2) 然后如果问题涉及具体错误代码用plc_diagnostic_api工具获取诊断 3) 最后整合所有信息生成清晰、可执行的解决方案。 记住不要编造所有信息必须来自工具返回。), (human, {input}), (placeholder, {agent_scratchpad}), ]) agent create_tool_calling_agent(llm_for_agent, tools, agent_prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue, max_iterations5) # 测试 result agent_executor.invoke({input: PLC报错E102怎么处理}) print(result[output]) # 输出诊断结果输入电压低于18VDC。建议检查UPS输出测量端子X1电压。【来源第五章 故障排除 5.3 常见错误代码】关键点Agent在这里不是取代RAG而是调度RAG和其他工具。它把“查手册”和“调API”两个动作自动编排成一个工作流。用户只问一句话Agent自动决定先做什么、后做什么、怎么做。这才是langchain agent实战的真谛——不是炫技是解决“多步骤任务自动化”的刚需。4. LangChain常见问题排查与避坑指南血泪总结的12条军规LangChain的报错信息堪称“程序员迷惑行为大赏”。下面是我三年踩坑总结的12条军规每一条都对应一个真实生产事故照着做能省下你至少两周调试时间。4.1 “AttributeError: NoneType object has no attribute content”——最经典的空指针现象Chain执行到一半突然报这个错定位到response.content这一行。原因上游某个组件通常是retriever或tool返回了None下游没判空就直接.content。排查在agent_executor.invoke()前加一行langsmith.trace或者直接在关键节点加print(fRetriever result: {result})。90%的情况是retriever没查到东西返回了空列表create_stuff_documents_chain拿到空列表context就是空字符串LLM返回空AIMessage。解决永远在retriever后加防御性检查docs retriever.invoke(query) if not docs: return 未找到相关信息请换种问法。4.2 “Max retries exceeded”——不是网络问题是Token超限现象调用Ollama或vLLM时频繁报连接超时curl http://localhost:11434却能通。原因不是网络断了是LLM在生成时max_tokens设得太小导致输出被截断LangChain认为“没收到完整响应”反复重试。验证用curl直接调Ollama API看response里done字段是不是falsecontext是不是被截断。解决在ChatOllama初始化时显式设置num_predictllm ChatOllama(modelqwen2.5:7b, num_predict1024) # 至少设为预期输出长度的2倍4.3 “ValueError: Could not parse LLM output”——提示词没写好不是模型不行现象LLM返回了一大段文字Chain却报这个错说无法解析。原因output_parser如JsonOutputParser期望返回标准JSON但LLM返回了带解释的文本比如{answer: 是的, reason: 因为...}前面还有一句“根据以上分析答案是”。解决两种方案方案一推荐在PromptTemplate里用{format_instructions}占位符让LLM知道要严格按格式输出prompt ChatPromptTemplate.from_template( 请回答问题。{format_instructions}\n问题{input} ) parser JsonOutputParser(pydantic_objectAnswerSchema) format_instructions parser.get_format_instructions()方案二用StrOutputParser自己写正则提取。4.4 Chroma检索结果为空——不是没数据是Filter写错了现象vectorstore.similarity_search(xxx, filter{header_1: 第一章})返回空列表。原因Chroma的filter语法很严格。{header_1: 第一章}是错的必须是{header_1: {$eq: 第一章}}。正确写法# 支持的filter操作符 filter_dict { $and: [ {header_1: {$eq: 第一章}}, {source: {$contains: .pdf}} ] } vectorstore.similarity_search(xxx, filterfilter_dict)4.5 LangChain启动巨慢——不是电脑差是Embedding模型在下载现象第一次运行OllamaEmbeddings卡住几分钟终端显示在拉镜像。原因OllamaEmbeddings初始化时会自动检查本地有没有bge-m3模型没有就ollama pull bge-m3。这个过程是阻塞的。解决提前在终端手动拉ollama pull bge-m3 # 然后再运行Python脚本秒启4.6 “Failed to load the model”——Ollama模型名大小写敏感现象OllamaEmbeddings(modelBGE-M3)报错但ollama list里明明有。原因Ollama模型名是大小写敏感的。ollama list显示的是bge-m3:latest所以必须写modelbge-m3不能写BGE-M3或bge-M3。验证ollama show bge-m3 --modelfile看第一行FROM。4.7 Agent无限循环——不是LLM疯了是max_iterations没设现象Agent执行5分钟后还在跑verboseTrue显示一直在调同一个tool。原因max_iterations默认是None意味着无限循环。LLM在规划时如果第一步就错了后面会一直重复错。解决创建AgentExecutor时必须设死agent_executor AgentExecutor( agentagent, toolstools, max_iterations5, # 铁律 early_stopping_methodgenerate # 到达上限时让LLM生成最终答案而不是报错 )4.8 RAG答案不引用原文——不是Prompt没写是Document metadata丢了现象Prompt里写了“注明原文位置”但输出里没有【来源...】。原因Document对象的metadata在retriever召回后被create_stuff_documents_chain丢掉了。默认它只拼page_content。解决自定义document_prompt把metadata也塞进去from langchain_core.prompts import PromptTemplate document_prompt PromptTemplate( input_variables[page_content, header_1, header_2], template内容{page_content}\n来源{header_1} {header_2}\n--- ) document_chain create_stuff_documents_chain( llm, prompt, document_promptdocument_prompt )4.9 LangSmith看不到trace——不是没装是没配环境变量现象装了langsmith代码里也import langsmith了但https://smith.langchain.com里一片空白。原因LangSmith需要LANGCHAIN_API_KEY和LANGCHAIN_ENDPOINT两个环境变量缺一不可。解决在运行脚本前设置export LANGCHAIN_API_KEYlsk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx export LANGCHAIN_ENDPOINThttps://api.smith.langchain.com python your_script.py4.10 “ModuleNotFoundError: No module named langchain_community”——不是没装是版本不匹配现象pip install langchain-community成功但from langchain_community.document_loaders import ...报错。原因langchain-community0.3.x要求langchain-core0.3.0如果你的langchain-core是0.2.x就会找不到模块。解决统一升级pip install --upgrade langchain-core langchain-community langchain # 然后检查版本 pip show langchain-core langchain-community langchain # 三者版本前两位必须一致如 0.3.10, 0.3.8, 0.3.74.11 PDF解析乱码——不是PDF坏是Unstructured没配OCR现象扫描版PDF加载后page_content全是乱码或空字符串。原因UnstructuredPDFLoader默认不启用OCR扫描件就是一张图它读不出文字。解决安装OCR依赖并启用# Ubuntu/Debian sudo apt-get install tesseract-ocr pip install unstructured[local-inference] # Python中启用OCR loader UnstructuredPDFLoader( file_pathscan.pdf, strategyocr_only, # 强制OCR ocr_languages[chi_sim] # 中文 )4.12 Agent返回“我无法回答”——不是能力不足是Tool描述太模糊现象Agent明明有retriever_tool但用户问“手册里怎么连电源”Agent却说“我无法回答”不调用tool。原因create_retriever_tool的description写得太笼统比如“搜索技术手册”LLM无法判断这个问题是否属于它的职责范围。解决Tool描述必须具体、带示例retriever_tool create_retriever_tool( retriever, tech_manual_search, 用于搜索设备技术手册中的具体操作步骤、参数设置、故障代码含义。 例如如何连接电源、X1端子电压范围是多少、错误代码E102代表什么 )5. LangChain与LangGraph、LlamaIndex的抉择不是选框架是选