
先说个结论LangChain本身不是模型也不是某种“大模型能力外挂”它是一个工程框架。它的价值在于把大模型应用开发里那些重复造的轮子——提示词管理、模型调用、外部工具接入、记忆状态、文档检索——做成了一套相对统一的抽象接口。我个人做AI应用落地一年多最直观的体会是没有LangChain一套带检索和工具调用的聊天机器人也能写但代码会越写越乱到处是待兼容的模型API、拼字符串的Prompt、手写的JSON解析用LangChain串起来之后虽然偶尔也会被框架的封装绕得头痛但长期维护和迭代的效率确实高出一大截。这篇文章不是写“LangChain零基础入门教程”那种官方文档复读而是按我实际做过的项目来讲组件选型为什么这么选、RAG和Agent两条主线的完整落地流程、常见坑怎么排以及LangGraph继承者登场之后LangChain的定位怎么变。适合有一定Python基础、准备把大模型接进真实业务的后端开发者也适合面试前突击LangChain核心考点的同学。1. LangChain到底解决什么问题为什么说它是工程框架1.1 大模型应用开发的三道坎这几年来接入大模型API本身已经不难了难的是“从单次对话到能解决实际问题的应用”。第一道坎是模型API五花八门。OpenAI、Anthropic、国内的各家模型接口规范互不相同请求参数和返回结构各有差异。你今天用这家明天要换那家如果所有代码都直接调SDK换模型等于重写一部分业务逻辑。第二道坎是提示词的工程化。简单场景下直接往API里塞字符串没问题可一旦是业务系统提示词里要动态嵌套用户提问、历史会话、检索回来的文档片段、工具返回结果。如果你用f-string手工拼要不了几次就会遇到转义、截断、格式混乱的问题。第三道坎是应用不止“一问一答”。回答问题前要先查知识库查完要组装上下文需要算数时得调计算器API需要查天气时得调天气服务。这就涉及流程编排、状态管理、工具调用循环。这些逻辑如果全部手写稳定性很难保证。1.2 LangChain的解题思路抽象标准加组件化LangChain对上面三道坎的回应本质上是两件事。第一件事是抽象标准。它对“模型”“提示词”“文档”“记忆”“工具”“检索器”都定义了通用接口。哪怕是不同厂商的模型包装后接口基本一致不同格式的文档加载后都变成标准的Document对象。第二件事是组件化拼装。LangChain用Chain和Agent的概念把组件串起来。Chain是“按固定顺序走”的流程Agent是“模型自主决定调用哪个工具、走哪个分支”的流程。刚开始接触LangChain的人容易被它的类名吓到其实背后就是一套“输入到输出”的数据流。所有组件定义好输入输出的数据类型然后你用管道一样的方式把它们接起来。这种设计降低了替换和扩展的成本但也带来了一个代价——框架本身有学习成本很多“简单问题”被抽象层包裹后反而显得绕。所以下面我会把核心组件逐个拆开讲搞清楚每一种抽象解决的是什么具体问题。2. 最快上手的核心组件模型、提示词、链、记忆2.1 模型接口的统一封装在LangChain里模型被封装成两类接口生成文本的LLM类和面向对话的ChatModel类。一个比较典型的ChatModel使用方式是这样的from langchain_openai import ChatOpenAI llm ChatOpenAI( modelgpt-4o-mini, temperature0.2, )对于本地模型用法也同样简单from langchain_ollama import ChatOllama llm ChatOllama( modelqwen2.5:7b, temperature0.1, )换模型时只要接口兼容业务代码几乎不用动。这就是抽象标准的直接收益。我自己踩过的坑是temperature这个参数。代码生成、数据抽取类任务建议调到0.1或0.2创造性写作再考虑提高在Agent流程里温度太高会导致工具调用格式不稳定模型会“自由发挥”出不存在的参数名。2.2 提示词模板与其重要性PromptTemplate把提示词里变化的和不变的部分拆开。例如一个信息抽取模板from langchain_core.prompts import ChatPromptTemplate prompt ChatPromptTemplate.from_messages([ (system, 你是一个严谨的信息抽取助手。只提取文本中提到的实体不要推断。), (human, 文本内容如下\n{text}\n\n请提取所有实体并返回JSON。), ])模板里定义的变量text在调用时再传入。它在代码里把“规则”和“动态数据”隔离开也方便以后做多版本提示词的AB测试。实际项目中我最小化模板数量和变量粒度一个模板只服务于一个明确任务变量越少越不容易乱。2.3 链的串接与输出解析链是LangChain的核心概念最简单的链就是把模板、模型、输出解析器按顺序执行from langchain_core.output_parsers import StrOutputParser chain prompt | llm | StrOutputParser() result chain.invoke({text: 张三在北京上班。})这里的|管道符是LangChain的LCEL语法左边组件输出格式满足右边组件输入要求即可。STROutputParser把模型返回的内容转成纯字符串后面还会讲到更复杂的JSON输出解析器。链式写法的好处是每个环节都可以单独替换或插入新处理。比如你想在交给模型前对文本做长度截断插一个自定义函数就能实现。2.4 记忆与状态管理带对话历史的应用会用到记忆。LangChain早期版本提供ConversationBufferMemory这类组件但那套记忆和LCEL链整合得一般运行时状态管理经常出问题。后来的LangGraph把状态机制重新设计了一遍把这个痛点解决得更好。如果你的项目只是简单会话并且用的模型本身就支持多轮上下文我反而建议先自行把历史消息列表传给模型而不是一上来就套记忆组件。框架的记忆组件适合有复杂状态的场景普通场景手动维护messages数组更直观也更省心。3. RAG落地Ollama加Chroma搭建本地知识库3.1 整体流程与架构选择RAG检索增强生成是目前把大模型用进企业内部知识库最务实的一条路。它不微调模型而是把外部文档切碎、向量化、存进向量数据库用户提问后先检索相关片段再把这些片段连同问题一起交给模型组织答案。我推荐这套本地方案的原因很直接数据不出内网、模型推理成本可控、部署依赖简单。架构选型可以这样定模型用Ollama跑Qwen系列做检索侧的Embedding也用Ollama里的开源嵌入模型向量库用Chroma它是嵌入式库一个目录就能跑适合中小规模知识库。3.2 文档加载与切块细节原始文档五花八门PDF、Markdown、Word、HTML先用文档加载器统一转成LangChain的Document然后再切块。from langchain_community.document_loaders import TextLoader, PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader TextLoader(docs/产品手册.txt) docs loader.load() splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap100, separators[\n\n, \n, 。, , , , ], ) chunks splitter.split_documents(docs)这里有两个参数特别关键。第一个是chunk_size。切块太小召回的内容缺乏上下文答案经常是“碎片拼凑”切块过大向量表示被稀释检索精确度会下降输入模型的Token也会变多。800到1000个字符是我在通用文档上的常用起调值。第二个是chunk_overlap。相邻块之间保留少量重叠可以避免关键句子正好被切断导致谁都搜不到。一般取块大小的十分之一。separators列表代表切分优先级中文文档把中文标点放进来否则切块会硬生生把句子掰断。按这个顺序递归地去匹配分隔符能尽量保持语义完整性。3.3 Embedding的加载与向量入库Chroma这里要处理好持久化目录。一个容易踩的坑是不清空旧的数据目录就重复执行入库脚本每次都会追加重复向量。做试验阶段建议每次跑脚本前先删掉向量库目录保证检索测试在一个干净的环境里做。from langchain_chroma import Chroma from langchain_ollama import OllamaEmbeddings embeddings OllamaEmbeddings(modelbge-m3) vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db, )选Embedding模型时中英文混合场景可以优先看bge-m3这类多语言模型。语言不匹配会直接影响检索效果这个比向量库选型的影响还要大。3.4 检索增强方法详解基础检索是用相似度搜索找回TopK个块retriever vectorstore.as_retriever(search_kwargs{k: 4})但实践里很快会发现一个问题适合向量检索的块不一定适合直接作为参考答案。块太碎时召回命中率高但内容不完整。于是就有了MultiVectorRetriever这类“摘要映射”思路。MultiVectorRetriever的核心是“每个原始文档对应多个向量表示”。比较实用的做法是每个文档生成一段概要存入索引原始全文存到文档存储区检索时用概要向量去做召回命中了概要再把完整文档拿出来送给模型。from langchain.retrievers.multi_vector import MultiVectorRetriever from langchain.storage import InMemoryByteStore docstore InMemoryByteStore() retriever MultiVectorRetriever( vectorstorevectorstore, byte_storedocstore, id_keydoc_id, ) # 每条文档生成概要后把概要做向量索引docstore里放summary和代码块对应的原始文本这个方案解决了“检索粒度与语义覆盖冲突”的问题。代价是离线索引逻辑变复杂我一般在文档质量参差不齐、单块能独立回答的信息比较少时才启用它。还有一个检索增强技巧是ParentDocumentRetriever它把文档切成更小的子块用于检索命中的子块再映射回上一级的父文档。这个和MultiVectorRetriever思路相反应用在“小块负责命中大块负责提供完整答案”的场景。3.5 RAG流程的问答闭环搭建完毕之后具体的问答流程可以这样组装from langchain_core.runnables import RunnablePassthrough def format_docs(docs): return \n\n---\n\n.join(doc.page_content for doc in docs) rag_chain ( {context: retriever | format_docs, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() )实际跑通这套流程后还需要做一轮检索质量测评。我给自己定的及格线是二十个真实业务问题中前四个召回结果里至少有一个能覆盖问题核心。如果大量问题都召不回有效信息优先检查Embedding模型和切块策略先不要怀疑模型参数不够。4. Agent实战让LangChain自动把测试用例转成UI自动化脚本4.1 目标拆解与工具设计这是我最近比较完整的一个实战项目。需求描述很简单输入一份测试用例Agent自动产出可执行的UI自动化测试脚本。第一刀先拆需求。测试用例常见的字段包括前置条件、操作步骤、期望结果。UI自动化的流程包括启动浏览器、定位元素、点击输入、断言结果。真正要做的就是让Agent完成从“自然语言步骤”到“可执行代码”的翻译。拆完之后我开始想这个Agent到底需要哪些工具读取测试用例文件的工具查询被测系统页面元素定位信息的工具执行代码片段或查询已有脚本模板的工具生成代码文件并落盘的工具。4.2 Agent中间件的正确打开方式LangChain把Agent可以调用的外部能力封装成Tool也就是常说的中间件。定义工具的方式有两种用tool装饰器直接装饰函数或者继承BaseTool做更复杂的自定义。from langchain_core.tools import tool tool def get_page_element_info(selector_hint: str) - str: 根据元素提示返回页面元素定位信息返回格式为JSON。 参数selector_hint可能是按钮文字、输入框placeholder等。 # 这里可以查元素仓库、读页面快照或者反过来调用一次Playwright探查 return element_store.lookup(selector_hint)工具描述特别重要。模型的工具选择能力很大程度上取决于你为每个工具写的文档字符串是否清晰、参数说明是否准确。工具名要像函数名一样明确描述里写明“什么情况下调用”“参数格式是什么”“返回格式是什么”。实际调试阶段最复杂的场景是“模型来回选错工具”。现象是某个简单问题模型非要绕三个步骤才完成或者选了个无关工具。我的处理方式之一是加一个“路由工具”的提示词模板强调工具选择规则。另一个方式是给高频工具“让路”——把常用工具的描述写得更靠前、更详细让模型更容易命中。4.3 用Playwright能力生成脚本的落地路径要使生成的脚本真正可执行我让Agent基于两个输入工作测试用例的自然语言步骤以及被测系统的页面录制快照。具体思路是先用Playwright自带代码生成能力记录一遍手工操作把元素定位器、页面路径信息整理出来作为“页面事实”存进工具查询仓库。模型生成脚本时查询的是现有定位信息而不是“猜”定位符这样生成结果可靠性高得多。from langgraph.prebuilt import create_react_agent agent create_react_agent(modelllm, tools[read_case_file, get_page_element_info, execute_playwright_snippet, save_script_file]) result agent.invoke({messages: [{role: user, content: 请根据tests/login_case.json生成测试脚本}]})这里我用LangGraph而不是传统LangChain AgentExecutor原因是整个流程有循环Agent可能先读用例、再查元素、生成代码后尝试执行、执行失败后又回到查元素重试。这种动态循环用Graph表达更自然下面第5节专门讲LangChain和LangGraph的关系。生成的代码要真正跑得稳最后还要追一层“脚本自检”的机制让Agent把生成的脚本放到沙箱运行如果失败把报错信息回传给它自己修改。这一步能显著提升从“看起来像代码”到“实际能跑”的转化率。4.4 这类的通用架构沉淀做完这个项目后我总结出“用例转工具调用”类Agent的通用架构业务输入解析成结构化参数、领域知识做成可查询工具、生成结果可执行可自检。这种架构不仅适用于测试脚本生成做数据报表生成、接口契约生成、审批流配置生成套路基本都是这一套。5. LangChain与LangGraph怎么选5.1 两者的定位差异LangChain和LangGraph是两个不同时代的产物。LangChain核心是链式抽象适合“线性流程”和简单的工具调用LangGraph的核心是状态图节点之间有明确的边与条件分支。可以这样理解两者的区别Chain像是流水线产品按固定工序往前走Graph像是带交通信号和绕行路线的路网车状态根据路况选择不同路线走甚至可能绕回上一个路口重新走一遍。复杂Agent的每一步都需要知道前面发生了什么、下一步有哪些合法选择这些信息要集中管理。LangGraph把状态对象显式化每一步执行的都是图的节点节点内部可以调用LangChain的组件所以两者不是替代关系而是互补关系。5.2 从Chain到Graph的迁移什么时候有必要我的判断标准很简单流程里出现“根据执行结果决定下一步走哪个分支”时就值得考虑LangGraph。比如客服Agent问用户问题、判断回答是否包含必要信息、包含则继续、不包含则重新追问。这种循环用Chain写会很别扭但用Graph就是一个带条件边的回环。from langgraph.graph import StateGraph, START, END def ask_node(state): ... def judge_node(state): return ask or next builder StateGraph(AgentState) builder.add_node(ask, ask_node) builder.add_node(next, next_node) builder.add_edge(START, ask) builder.add_conditional_edges(ask, judge_node, {ask: ask, next: next}) builder.add_edge(next, END)这个例子里状态AgentState是LangGraph的命脉。设计状态字段时一定想清楚哪些信息需要跨步骤共享哪些只是局部临时变量。状态设计不合理后续排错会非常痛苦我在最早一次迁移时把整段对话记录都塞进了状态结果每个节点都在反复处理超大对象性能很差。5.3 基于LangChain与LangGraph的面试考点实践中被问过很多次的问题主要有这么几个Agent的ReAct循环是什么Answer 模型推理出一个“下一步该做什么”调用工具拿回观察结果把这个结果再喂给模型继续推理直到得到最终回答。LangChain支持的SQL查询链、文档问答链、摘要链核心区别在Prompt模板和前后处理逻辑不同底层都是LCEL的组合。LangGraph的StateGraph与LangChain的AgentExecutor相比优势在于显式状态管理与循环控制调试时可观测性更好。LangChain的abroad能力在不熟悉的时候不要乱讲直接从状态图、节点、边的角度讲自己的理解就足够。模型内部如果不支持带循环的流程编排就必须交给Graph层来处理。这也正是LangGraph存在的价值。5.4 关于DeepAgents这类新配置的观察LangChain生态里出现了一些更高层面的封装比如DeepAgents这类预设Agent配置。它把常用的智能体框架整合成可直接使用的结构目标是把构建Agent的流程再简化一步。我的看法是这类封装适合快速原型验证但生产环境中框架预设的提示词和行为策略未必匹配你的业务模型与工具风格。很多情况下还是要回到LangGraph亲手搭建流程。“框架降低的是75%的重复工作剩下25%的业务定制反而是决定成败的地方。”如果把LangChain比作标准件的世界LangGraph就是你可以自己画流程图的白板。两者结合才是大多数真实系统的最终形态。6. 实战排错与效率提升笔记6.1 Chat模型的输出格式化问题结构化输出是最常翻车的位置。模型直接返回JSON、套一个输出解析器、用函数调用机制强制结构化是三层不同强度的方案。对绝大多数场景我推荐第三层只要求模型必须以JSON格式返回并用JSON输出解析器处理结果。如果模型偶尔输出带解释文字的JSON可以启用LangChain的with_structured_output在接口层强制模型按指定Schema输出。这种方式要求你先把Schema定义清楚from pydantic import BaseModel, Field class Entity(BaseModel): name: str Field(description实体名称) entity_type: str Field(description实体类型) structured_llm llm.with_structured_output(Entity) result structured_llm.invoke(张三在北京上班)底层依赖模型的工具调用能力如果你的模型不支持严格结构化输出调用时会报错或退回普通输出。出现这种情况时回到“提示词加解析器”方案并做好解析兜底。6.2 工具调用的失败排查工具调用出问题时先区分是模型没有选择工具还是工具返回错误被模型“误解”了。模型不调用工具的常见原因有三个工具描述写得像绕口令、工具数量过多导致选择困难、当前模型能力本身较弱。处理方式是精简工具并强化描述必要时升级模型。工具返回错误被误解常见现象是模型把异常信息当成答案回复给了用户。解决方法是让工具在异常时返回结构化的错误信息并在系统提示词里注明错误处理策略。6.3 Token与成本控制的经验值长链路Agent的Token消耗经常超出预期。一次真实的测试用例生成任务模型从读取文件到调用工具最终产出脚本消耗的Token可能是最终脚本文本量的五倍。控制成本不能只盯着单次问答。我的三个做法是缓存重复工具结果尤其是页面元素查询这种高频高耗的调用控制历史消息的保留范围只保留与当前任务相关的关键状态在生成脚本阶段优先使用便宜的快速模型只把最终审核阶段交给更强模型。6.4 本地模型场景的响应延迟问题用Ollama跑本地模型时响应延迟往往是最大的痛点。除了换更好的显卡一些实际有效的优化包括量化模型、减小输入上下文的冗余度、以及用流式输出提升体验。真正压测时并发请求一多本地单卡推理的队列延迟会很明显这时可以考虑做推理服务化与负载均衡。7. 最后再分享一点我的体会LangChain生态变化快API版本变动频繁这一直被吐槽也确实是事实。我的应对策略是尽量使用langchain_core中的稳定接口Prompt、ChatModel、Retriever等社区组件部分减少依赖把环境锁在一个固定的版本组合里升级前先看变更日志。写到这里回头看LangChain这门“实战课”真正重要的其实不是记住每个类的名字而是理解它的抽象思想接口统一、组件可拼装、流程可编排。框架会迭代类名会变但这套抽象方法论会一直有用。也不必焦虑是不是每一个组件都要精通。我和团队的实际经验是先趟通一条主流程比如RAG或一个简单Agent再在真实需求驱动下逐步扩展。等你有了一条完整跑通的主流程再来回看LangChain的大小概念会觉得清晰很多。这套经验分享到这里希望能给你接下来的LangChain项目一个相对清爽的起点。