
大模型应用开发这件事很多人卡在第一步不是不会调API而是不知道怎么把模型调用变成能跑起来的应用。我见过太多人拿着一个API Key写了几十行胶水代码最后发现维护成本比从头写还高。LangChain就是在这个缝隙里长出来的东西——它不解决模型本身的能力问题它解决的是怎么把模型、工具、数据、记忆、流程串成一个可维护的系统。这篇内容适合三类人刚接触大模型应用开发、想找一个能落地的框架入口的开发者已经会用API但代码越写越乱、想找一套工程化组织方式的人以及想搞清楚LangChain和LangGraph到底什么关系、该从哪个开始学的人。我会从实际项目角度出发把LangChain的核心抽象、安装选型、典型链路、常见坑和进阶方向都拆开讲一遍尽量做到看完就能动手。1. 先搞清楚LangChain到底解决什么问题1.1 不用框架也能调模型为什么还要LangChain直接调大模型API本质上就是发一个HTTP请求把prompt塞进去拿回一段文本。这件事用requests库十行代码就能搞定。那为什么还需要LangChain问题出在应用两个字上。一个真实的大模型应用通常不是输入问题→输出答案这么简单。它可能要先从知识库里检索相关文档再把文档和问题拼成prompt然后调用模型拿到结果后判断是否需要调用工具工具返回后再让模型总结最后把对话历史存下来供下一轮使用。这一套流程如果全部手写你会发现代码里充斥着字符串拼接、异常处理、重试逻辑、格式解析而且每换一个模型或换一个向量库就要改一大片。LangChain的价值在于它把这些反复出现的模式抽象成了标准接口。LLM、ChatModel、PromptTemplate、OutputParser、Retriever、Tool、Memory每一个都是可替换的组件。你写业务逻辑的时候面向接口换底层实现的时候只改一行配置。这就是它最核心的贡献——不是让你少写代码而是让你的代码在需求变化时不至于推倒重来。我自己的体会是小demo用不用LangChain差别不大但一旦应用超过三四个步骤、涉及两种以上数据源、需要多轮对话LangChain带来的结构收益就非常明显了。1.2 LangChain的核心抽象用生活化类比理解把LangChain想象成一条流水线工厂。原料从一头进去经过若干工位加工成品从另一头出来。PromptTemplate工位的作业指导书。它规定了给模型的问题长什么样哪些地方是变量需要填。比如请根据以下资料回答问题{context}问题{question}花括号里的就是待填的槽位。Model真正干活的机器。可以是OpenAI的、可以是本地Ollama跑的、可以是任何兼容接口的模型。LangChain把它们统一成同一个调用方式。OutputParser质检员。模型输出的是自然语言但你的程序可能需要JSON、需要列表、需要布尔值。Parser负责把文本转成结构化数据转不了就报错或重试。Retriever仓库管理员。你问它要相关资料它从向量库或数据库里捞出最相关的几条。Tool外挂设备。模型自己算不了实时天气、查不了数据库就通过Tool去调外部函数。Memory记事本。让模型记住之前聊过什么。Chain / Runnable流水线的传送带。把上面这些组件按顺序连起来。理解了这几个角色LangChain的文档看起来就不那么晕了。它所有的API设计基本都是在回答这个组件怎么和下一个组件对接。1.3 LangChain、LangGraph、LangChain4j的关系与选择这是被问得最多的问题之一。简单说名称定位适用场景LangChain组件库 链式编排线性流程、RAG、简单AgentLangGraph有状态的图编排循环、分支、多Agent协作、人工介入LangChain4jJava版LangChainJava/Spring技术栈团队LangChain的Chain是一条道走到黑的线性结构适合RAG这种固定流程。但真实Agent经常需要想一步→做一步→看结果→再想这种循环用Chain表达很别扭LangGraph就是为此生的——它把流程建模成图节点是动作边是跳转条件还自带状态管理。我的建议是入门先学LangChain把Prompt、Model、Retriever、Tool这几个基础件玩熟当你发现流程里出现如果……就回到上一步这种需求时再上LangGraph。不要一上来就啃LangGraph容易劝退。至于LangChain4j如果你团队是Java栈别硬上Python版LangChain4j的API设计更贴合Java习惯集成Spring Boot很顺。2. 环境搭建conda、pip与依赖管理的实际选择2.1 Python环境用conda还是venvLangChain的依赖树不算小尤其是涉及向量库、文档解析的时候。我强烈建议用conda建独立环境而不是全局pip install。原因很实际不同项目对pydantic、numpy的版本要求经常打架全局装迟早出事。conda create -n langchain-demo python3.11 conda activate langchain-demo pip install langchain langchain-community langchain-openaiPython版本选3.10或3.11比较稳3.12有些库的wheel还没跟上3.9则可能遇到类型语法兼容问题。2.2 核心包与周边包的安装边界LangChain从0.1版本开始做了包拆分这点很多人踩坑。langchain-core是核心抽象langchain-community是社区集成langchain-openai是OpenAI官方集成。你不需要一次装全按需装。# 最小可用集 pip install langchain langchain-core langchain-community # 用OpenAI pip install langchain-openai # 用本地Ollama pip install langchain-ollama # 用Chroma向量库 pip install langchain-chroma chromadb注意不要装langchain[all]那会拉一堆你用不上的包还容易版本冲突。按需安装是LangChain生态的基本素养。2.3 API Key与本地模型的环境配置用云端模型把Key放环境变量别写死在代码里export OPENAI_API_KEYyour-key-here用本地模型比如Ollama先确保Ollama服务在跑ollama pull qwen2.5:7b ollama serve然后LangChain这边直接连from langchain_ollama import ChatOllama llm ChatOllama(modelqwen2.5:7b, base_urlhttp://localhost:11434)本地模型的好处是数据不出机器、没有调用成本适合做知识库这类隐私敏感场景。代价是推理速度和效果取决于你的硬件和模型规模。3. 从零跑通第一条链Prompt、Model、Parser三件套3.1 最小可运行示例与逐行解读先看一段能跑的最简代码然后我逐行拆。from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser llm ChatOpenAI(modelgpt-4o-mini, temperature0) prompt ChatPromptTemplate.from_messages([ (system, 你是一个简洁的技术助手回答不超过三句话。), (human, {question}) ]) chain prompt | llm | StrOutputParser() result chain.invoke({question: 什么是向量数据库}) print(result)这里的关键是那个竖线|它是LangChain Expression LanguageLCEL的管道语法。prompt | llm | parser的意思是数据先经过prompt填充输出喂给llmllm的输出再喂给parser。整个chain是一个Runnable有统一的invoke、batch、stream方法。3.2 LCEL管道语法为什么比传统Chain更好老版本LangChain用LLMChain、SequentialChain这些类写法啰嗦而且不支持流式、异步、批处理。LCEL把组件组合变成了类似Unix管道的表达式好处有三个第一统一接口。任何Runnable都能invoke单条、batch批量、stream流式、ainvoke异步。你不用为每个Chain单独学一套调用方式。第二天然支持流式。chain.stream(...)会逐token返回做打字机效果不用额外改造。第三易于调试。可以在管道中间插入一个打印函数看数据流def debug(x): print(中间结果:, x) return x chain prompt | llm | debug | StrOutputParser()这种可插拔的调试方式比在老式Chain里加回调清爽得多。3.3 输出解析让模型返回结构化数据模型返回自然语言程序要的是结构化数据。最常用的是Pydantic解析器from pydantic import BaseModel, Field from langchain_core.output_parsers import PydanticOutputParser class Person(BaseModel): name: str Field(description人物姓名) age: int Field(description年龄) skills: list[str] Field(description技能列表) parser PydanticOutputParser(pydantic_objectPerson) prompt ChatPromptTemplate.from_messages([ (system, 从文本中提取人物信息。\n{format_instructions}), (human, {text}) ]).partial(format_instructionsparser.get_format_instructions()) chain prompt | llm | parser result chain.invoke({text: 张三28岁会Python和Go。}) print(result.name, result.age, result.skills)get_format_instructions()会自动生成一段告诉模型请按这个JSON格式输出的说明省得你手写schema描述。实测下来配合temperature0结构化提取的准确率相当高。提示如果模型偶尔返回带markdown代码块的JSON可以用OutputFixingParser包一层它会自动让模型修正格式错误。4. RAG实战用Ollama Chroma搭本地知识库4.1 RAG流程的四个阶段拆解RAG检索增强生成是大模型应用里最实用的模式没有之一。它的逻辑是模型不知道你的私有知识那就先把相关知识检索出来塞进prompt里让模型基于这些内容回答。完整流程分四步加载把PDF、Markdown、网页等文档读成文本。切分长文档切成小块chunk因为模型上下文有限且检索粒度太粗会不准。向量化并存储每个chunk用embedding模型转成向量存进向量库。检索并生成用户提问时把问题也向量化从库里找最相似的几个chunk拼进prompt让模型回答。4.2 文档切分策略chunk_size和overlap怎么定切分是RAG效果的分水岭。切太大检索出来的块包含太多无关信息干扰模型切太小语义不完整检索不到关键内容。我的经验值chunk_size500~1000字符。中文可以偏小因为同样字符数信息密度更高。chunk_overlapchunk_size的10%~20%。overlap是为了防止一句话被切断导致语义丢失。from langchain_text_splitters import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap150, separators[\n\n, \n, 。, , , , ] ) chunks splitter.split_text(long_text)RecursiveCharacterTextSplitter会按分隔符优先级递归切优先在段落、句子边界切实在不行才硬切。中文场景记得把中文标点加进separators。4.3 向量库选型Chroma为什么适合入门向量库有Chroma、FAISS、Milvus、Qdrant、PGVector等。入门我推荐Chroma理由是纯Python、零配置、支持持久化、API简单。FAISS性能好但不带元数据存储Milvus功能全但要单独部署服务。from langchain_chroma import Chroma from langchain_ollama import OllamaEmbeddings embeddings OllamaEmbeddings(modelnomic-embed-text) vectorstore Chroma.from_texts( textschunks, embeddingembeddings, persist_directory./chroma_db ) retriever vectorstore.as_retriever(search_kwargs{k: 4})k4表示每次检索返回4个最相似的块。这个值不是越大越好太大反而引入噪声。一般3~5起步根据效果调。4.4 把检索器接进链完整RAG代码from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser template 基于以下资料回答问题如果资料中没有相关信息就说资料中未提及。 资料 {context} 问题{question} prompt ChatPromptTemplate.from_template(template) def format_docs(docs): return \n\n.join(doc.page_content for doc in docs) rag_chain ( {context: retriever | format_docs, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() ) answer rag_chain.invoke(文档里提到的部署方式是什么)这段代码的精髓在第一个字典context走检索分支question原样透传两路汇合后填进prompt。RunnablePassthrough就是什么都不做把输入原样传下去的意思。注意prompt里一定要加资料中没有就说不知道这类约束否则模型会一本正经地编造。这是RAG落地最常见的翻车点。5. Agent与工具调用让模型自己决定做什么5.1 Agent和Chain的本质区别Chain是你规定好步骤模型照着走。Agent是你给它工具和目标它自己决定调哪个工具、调几次。举个例子Chain模式下你会写先检索→再总结→再翻译。Agent模式下你只告诉它帮我查一下这个问题的答案并翻译成英文它会自己判断先调检索工具再调翻译工具。这个自主性来自模型的function calling能力。模型看到你注册的工具描述后会输出一个结构化的我要调用某工具、参数是什么的指令LangChain负责执行并把结果回传给模型循环直到模型认为可以给出最终答案。5.2 定义一个工具并注册给Agentfrom langchain_core.tools import tool tool def get_word_length(word: str) - int: 返回一个单词的字符数。 return len(word) tool def search_docs(query: str) - str: 在知识库中搜索相关内容。 docs retriever.invoke(query) return \n.join(d.page_content for d in docs) tools [get_word_length, search_docs]工具的函数名、docstring、参数类型都会被自动提取成给模型看的描述。所以docstring一定要写清楚这个工具干什么、什么时候用模型靠这个判断该不该调。5.3 用create_react_agent快速搭一个Agentfrom langchain.agents import create_react_agent, AgentExecutor from langchain import hub prompt hub.pull(hwchase17/react) agent create_react_agent(llm, tools, prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue, max_iterations5) result agent_executor.invoke({input: 知识库里关于部署讲了什么顺便告诉我deployment这个词有多少个字母。})verboseTrue会打印出Agent的思考过程调试时非常有用。max_iterations是防止Agent陷入死循环的保险丝一定要设。5.4 Agent调试中最容易踩的三个坑第一个坑工具描述太模糊。如果两个工具功能相近模型会反复横跳。解决办法是把每个工具的适用边界写清楚必要时在描述里加当……时使用本工具。第二个坑没有设迭代上限。模型有时会陷入调工具→不满意→再调的循环烧钱又费时。max_iterations和max_execution_time都要设。第三个坑工具报错没处理。工具抛异常时如果不捕获整个Agent会崩。建议在工具内部try/except返回一个调用失败原因的字符串让模型知道发生了什么并决定下一步。6. 记忆与多轮对话让应用记住上下文6.1 为什么无状态调用会失忆每次chain.invoke都是独立的模型看不到上一轮说了什么。做聊天机器人必须显式地把历史消息传进去。最朴素的做法是维护一个消息列表from langchain_core.messages import HumanMessage, AIMessage history [] def chat(user_input): history.append(HumanMessage(contentuser_input)) response llm.invoke(history) history.append(AIMessage(contentresponse.content)) return response.content但这样history会无限增长迟早超出上下文窗口。6.2 几种Memory方案的取舍方案原理适用全量保留存所有消息短对话窗口记忆只留最近N轮一般聊天摘要记忆用模型压缩历史长对话向量记忆检索相关历史超长对话LangChain提供了ConversationBufferMemory、ConversationBufferWindowMemory、ConversationSummaryMemory等。不过新版本更推荐用RunnableWithMessageHistory配合自定义存储更灵活。from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import ChatMessageHistory store {} def get_session_history(session_id): if session_id not in store: store[session_id] ChatMessageHistory() return store[session_id] chain_with_history RunnableWithMessageHistory( chain, get_session_history, input_messages_keyquestion, history_messages_keyhistory ) chain_with_history.invoke( {question: 我叫小明}, config{configurable: {session_id: user1}} )session_id是多用户隔离的关键不同用户用不同id历史互不干扰。6.3 长对话的摘要压缩实践对话超过几十轮后全量塞进prompt既贵又慢。我的做法是保留最近5轮原文更早的用模型压缩成一段摘要。summary_prompt ChatPromptTemplate.from_template( 请用三句话总结以下对话的关键信息\n{history} )每次历史超过阈值就触发一次压缩把摘要作为system消息的一部分。这样既保留了长期记忆又控制了token消耗。7. 从LangChain到LangGraph什么时候该换工具7.1 LangGraph解决的是循环和状态问题LangChain的Chain是DAG有向无环图不能有环。但很多真实流程需要循环Agent思考→行动→观察→再思考这就是一个环。LangGraph把流程建模成图节点是函数边是跳转支持条件边和循环。from langgraph.graph import StateGraph, END from typing import TypedDict class State(TypedDict): messages: list def call_model(state): response llm.invoke(state[messages]) return {messages: state[messages] [response]} def should_continue(state): last state[messages][-1] return continue if last.tool_calls else end graph StateGraph(State) graph.add_node(model, call_model) graph.add_conditional_edges(model, should_continue, {continue: tools, end: END})这段代码搭了一个最简的模型→判断是否调工具→继续或结束的循环。LangGraph的核心概念就三个State共享状态、Node处理函数、Edge跳转规则。7.2 Human-in-the-loop怎么实现有些场景需要人工审核后再继续比如Agent要执行一个敏感操作。LangGraph支持在节点前后中断graph.compile(checkpointermemory, interrupt_before[tools])interrupt_before表示执行到tools节点前暂停等人工确认后再invoke(None, config)继续。这个能力在审批流、内容审核类应用里非常关键LangChain的Chain做不到。7.3 选型建议别为了用而用我的判断标准很简单流程是线性的、步骤固定 → LangChain Chain/LCEL有循环、有分支、需要人工介入、多Agent协作 → LangGraph只是简单调模型 → 直接调API别上框架见过太多项目为了用LangGraph而把简单流程复杂化最后维护成本飙升。工具是拿来解决问题的不是拿来炫技的。8. 工程化落地那些文档里不会写的经验8.1 版本锁定与依赖冲突处理LangChain生态迭代快版本不锁迟早出事。生产项目一定要用requirements.txt或poetry.lock锁死版本。我踩过的坑某次升级langchain-core后langchain-community的某个集成直接报ImportError因为内部API变了。pip freeze requirements.txt另外pydantic的v1和v2不兼容LangChain新版本都要求v2如果你的项目里还有其他依赖v1的库会冲突。解决办法是尽量统一到v2或者用虚拟环境隔离。8.2 可观测性LangSmith与日志调试LangChain应用光看print不够。LangSmith是官方的可观测平台能看到每次调用的输入输出、耗时、token消耗、完整调用链。export LANGCHAIN_TRACING_V2true export LANGCHAIN_API_KEYyour-langsmith-key开启后所有chain和agent的执行都会自动上报。排查为什么这次检索结果不对为什么Agent调了三次工具这类问题有trace会快很多。不想用云服务的话也可以用回调函数自己打日志。8.3 成本与延迟优化几个实测有效的优化点缓存相同问题重复问用set_llm_cache缓存结果省token。小模型打头阵简单分类、路由用便宜的小模型复杂生成才用大模型。流式输出用户感知延迟大幅降低虽然总耗时没变。批处理批量任务用batch而不是循环invoke并发跑快很多。精简promptsystem prompt每多100字每次调用都多花token长期下来很可观。8.4 常见报错与排查思路报错常见原因解决RateLimitError调用超频加重试退避ContextLengthExceededprompt太长截断历史或换长上下文模型OutputParserException模型没按格式输出用OutputFixingParser或降temperatureImportError包版本不匹配检查langchain-core版本ConnectionError本地模型服务没起确认Ollama在跑排查顺序建议先看是不是网络/服务问题再看prompt和输入最后看解析和版本。大部分问题出在中间那层。9. 关于LangChain过时了吗这件事网上隔一阵就有人问LangChain是不是过时了。我的看法是LangChain的抽象层确实有历史包袱早期API设计不够干净导致很多人觉得它重。但它的生态、集成数量、社区活跃度短期内没有替代品。真正需要警惕的不是用不用LangChain而是会不会被框架绑架。好的做法是核心业务逻辑尽量用原生Python写LangChain只用在编排和集成层。这样即使哪天要换框架业务代码不用动。至于LangGraph它更像是LangChain团队对Agent编排这个问题的重新回答。如果你现在开始新项目且明确要做复杂Agent直接从LangGraph入手也完全可行它的抽象比LangChain的AgentExecutor更清晰。我自己的项目里RAG部分用LangChain的Retriever和LCELAgent部分用LangGraph两者通过Runnable接口无缝衔接。这套组合目前跑得挺稳既享受了LangChain的集成生态又拿到了LangGraph的状态管理能力。最后分享一个小心得学LangChain别一上来就啃官方文档的全部内容那会让你淹没在几百个集成里。挑一条主线——Prompt、Model、Parser、Retriever、Tool、Agent——把这条线上的核心类玩熟剩下的用到再查。框架是工具能解决问题才是目的。