
简介面向希望快速上手 LangChain 的 Python 开发者这份入门实践代码包以模块化方式演示了 LLM 接口、提示词模板、链式操作与记忆功能等核心组件的协作方法并涵盖 OpenAI、Hugging Face 等主流模型接入方式可用于智能问答、对话机器人等场景的初始搭建。包体仅 3 个文件、大小 5KB体积轻量内含 HTML 说明文档、InsCode 环境配置及 Git 忽略规则文件便于在云端直接查看与复用。目前已有 103 人学习适合刚接触大模型应用开发的初学者。通过阅读说明与示例代码能够快速理解 LangChain 的基础用法并以此为起点拓展自己的项目同时资源也间接展示了文档加载、文本处理与向量存储等扩展功能的应用思路。虽然包体不大但清楚展示了从环境准备到功能调用的关键路径对建立整体认知和后续深入学习有实际参考价值。1. 项目整体思路与方案选型1.1 为什么用 LangChain而不是自己写胶水代码先交代一下背景。这个项目最初的诉求其实很简单我要做一个能连大模型、能读本地文档、能记住上下文、还能自己调用外部工具的问答系统。当时摆在面前的有三条路直接用 OpenAISDK / vLLM 的 HTTP 接口裸写所有逻辑自己拼。用 LangChain 这类编排框架。后期再考虑 LangGraph 这种图编排框架做复杂状态流。我最终选了 2但不是无脑选。当时核心判断是这个项目至少有 60% 的工作量是把模型调用、提示词拼接、文档检索、工具调用串联起来这部分如果自己手写每一层都得对接不同 SDK、处理不同 API 的差异维护成本极高。LangChain 恰好把这几件事抽象成了标准组件而且它的生态足够大社区踩过的坑也多搜一个问题基本都有答案。但这里要泼一盆冷水LangChain 不是银弹。如果你只是单次调用模型不涉及多步推理、不涉及记忆、不涉及外部工具那纯手写反而更轻。LangChain 的价值在于多组件编排的复杂度而不是单次调用的性能。项目里一旦出现三个以上的环节需要串起来用 LangChain 至少能省一半的样板代码。1.2 LangChain 和 LangGraph到底先学哪个这是我在项目开始前最纠结的问题也是搜索热词里出现频率最高的对比题。直白点说LangChain 是基础库LangGraph 是它的上层状态机框架。LangChain 擅长的是线性链Prompt 进结果出最多加个 RAG 检索或工具调用流程是固定的。而 LangGraph 的定位是复杂工作流你有分支、循环、条件跳转、人工审批节点、多智能体协同。用我自己的理解打个比方LangChain 是做菜时的菜谱告诉你先放油再放菜LangGraph 是中央厨房管理系统能调度多个厨师同时处理不同订单还能根据顾客反馈中途换菜。我在这个项目里最终是先用 LangChain 把核心链路跑通再用 LangGraph 去重构了其中一个多轮 Agent 场景。实际感受是如果一上来就直接学 LangGraph会被 state、node、edge、conditional routing 这些概念砸晕但如果你已经有 LangChain 的基础再去看 LangGraph 反而会觉得顺理成章因为它本质上是把 LCEL 链拆成图节点。我的建议入门阶段 80% 的精力放在 LangChain 的常规用法上LCEL、RAG、Agent等确实遇到流程会分叉的需求再引入 LangGraph。不要为了追新而一开始就上重框架。2. 环境搭建与模型接入2.1 依赖安装与版本锁定的教训项目一开始我直接pip install langchain装最新版结果第二天就踩坑了。LangChain 的版本迭代非常激进0.1 和 0.2 的 API 差异巨大甚至 0.2 和 0.3 之间也有破坏性变更。更头疼的是langchain-core、langchain-community、langchain-openai这些子包的版本需要互相兼容单独升一个包经常导致莫名其妙的报错。我的最终锁定方案是langchain0.2.14 langchain-core0.2.35 langchain-openai0.1.23 langchain-community0.2.12 langchain-text-splitters0.2.4 langgraph0.2.21这里有个原则项目里一定要锁定主版本。如果你的代码是基于 0.2 写的升到 0.3 之后RunnableSequence.invoke()的返回类型变了create_sql_agent的 import 路径变了之前能跑的代码可能全部报错。而且 LangChain 官方文档默认展示的永远是最新版 API你在网上搜到的旧教程很可能就是 0.1 的写法照着敲完才发现 import 都过不去。2.2 对接 OpenAI、vLLM、Ollama 的参数差异项目里我同时接了三个模型后端OpenAI 官方 API、vLLM 部署的开源模型、本地 Ollama 跑的量化模型。LangChain 在这里最大的好处是统一了调用层。对 OpenAIfrom langchain_openai import ChatOpenAI llm ChatOpenAI( modelgpt-4o-mini, temperature0.2, api_keyos.getenv(OPENAI_API_KEY), )对 vLLM本地部署的 Qwen2.5-7B-Instructllm ChatOpenAI( modelQwen2.5-7B-Instruct, api_keyEMPTY, # vLLM 服务端不校验 key base_urlhttp://localhost:8000/v1, # vLLM 兼容 OpenAI 协议 )对 Ollamafrom langchain_ollama import ChatOllama llm ChatOllama( modelqwen2.5:7b, temperature0.2, base_urlhttp://localhost:11434, )实际上三种后端都走了 OpenAI 兼容协议切换时只需要改model和base_url业务代码一行不用动。这也是 LangChain 架构设计里最值得肯定的部分它把模型提供商抽象成统一的BaseChatModel接口你说的话术、你写的 Prompt、你定义的工具列表全部可以跨后端复用。但这里有个细节要注意vLLM 和 Ollama 对temperature、top_p这些采样参数的支持不完全一致。我之前用 vLLM 部署模型时直接把 OpenAI 的presence_penalty0.6传过去结果服务端直接报 400。后来查文档发现 vLLM 对采样参数的校验比 OpenAI 严格不支持的参数会直接拒绝请求。建议在低成本方案里只传temperature其他采样参数都别带。3. 核心模块拆解与实操代码3.1 Prompt 模板与 LCEL 表达式链这个项目的第一个核心模块是多策略问答。我不希望每次调用时都手拼 Prompt而是把提问策略抽象成不同的模板然后用 LangChain 的 LCEL 把它们串成一条链。一个实际在用的例子from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser prompt ChatPromptTemplate.from_messages([ (system, 你是一名资深的{domain}专家回答需要结合上下文严禁编造信息。), (human, 问题{question}\n参考材料{context}\n如果材料中没有答案请明确说明。), ]) chain prompt | llm | StrOutputParser() result chain.invoke({ domain: Python, question: 生成器函数的 yield 和 return 有什么区别, context: ... })那个|管道符一开始看着挺玄乎其实原理很简单它把前一个组件的输出作为后一个组件的输入。prompt | llm | StrOutputParser()就是用RunnableSequence把三样东西按顺序串起来。invoke()传的 dict 会被自动映射到ChatPromptTemplate的变量里最后从StrOutputParser出来的就是纯字符串。在代码里用这种链式写法的好处是每一环都可以独立替换。比如后期你想把StrOutputParser换成 JSON 解析器只改一行想在中间插一个查数据库的步骤在链里加一个RunnableLambda就完事。这种可插拔的维护体验是手写代码很难给的。3.2 RAG从文档加载到召回全流程项目里最硬核的部分是 RAG检索增强生成。从数据准备到问答我拆成了四步文档加载TextLoader/PyPDFLoader/UnstructuredWordDocumentLoader。文本分块RecursiveCharacterTextSplitter。向量化存储OpenAIEmbeddingsChroma。检索问答VectorStoreRetriever merge context LLM。实际代码from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma from langchain_core.runnables import RunnableParallel, RunnablePassthrough loader PyPDFLoader(docs/langchain-whitepaper.pdf) documents loader.load() text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , , ., , ], ) split_docs text_splitter.split_documents(documents) embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore Chroma.from_documents( documentssplit_docs, embeddingembeddings, persist_directory./chroma_db, ) retriever vectorstore.as_retriever(search_kwargs{k: 4}) def format_docs(docs): return \n\n.join(f【片段{i1}】\n{doc.page_content} for i, doc in enumerate(docs)) rag_chain ( RunnableParallel( contextretriever | format_docs, questionRunnablePassthrough() ) | prompt | llm | StrOutputParser() )分块这块我踩了一堆坑后面专门写一节。这里先讲一个关键点分块器里的separators一定要考虑中文。默认的 separators 是按照英文习惯排的中文按标点断句的效果会很差。我从实际观测的参数来看chunk_size500、chunk_overlap80这个组合在中文技术文档上的表现比默认参数好很多——召回的相关性更高生成时上下文也不至于把无关内容带进来。3.3 记忆Memory会话上下文怎么存项目最初用 LangChain 的ConversationBufferMemory后来发现两个问题第一它会在每次请求时把完整历史消息重放给模型对话一长Token 消耗飞速上涨第二它不能按对话轮次裁剪问个 50 轮之后上下文早就把模型的限制塞满了。第二种方案尝试了ConversationSummaryMemory让模型自己总结历史。效果比全量重放好一些但总结本身也有 Token 开销和一次延迟。这个机制在关键信息密度高、用户问题环环相扣的场景下容易丢失细节。最终项目用的是自己封装的一段式记忆class ConversationMemory: def __init__(self, max_rounds: int 6): self.max_rounds max_rounds self.history [] def add(self, human: str, assistant: str): self.history.append((user, human)) self.history.append((assistant, assistant)) if len(self.history) self.max_rounds * 2: self.history self.history[-self.max_rounds * 2:] def to_messages(self): from langchain_core.messages import HumanMessage, AIMessage messages [] for role, content in self.history: if role user: messages.append(HumanMessage(contentcontent)) else: messages.append(AIMessage(contentcontent)) return messages这样每次只保留最近max_rounds轮对话系统提示词里告诉模型只参考最近对话不要假设更早内容存在。实际体验下来这种截断式记忆在通用问答中完全够用而且实现简单、可控性强。只有在需要长期记忆任务比如用户长期偏好、跨会话状态时才值得上真正的记忆数据库或 LangGraph 的持久化 checkpoint。3.4 Agent 与 MCP 工具调用实战Agent智能体是这个项目里最让我头疼也最兴奋的部分。LangChain 的 Agent 核心思路是模型不是直接回答问题而是观察、思考、决定调用什么工具、分析工具结果、再决定下一步。一个最简的 ReAct Agent 代码from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_community.tools import DuckDuckGoSearchRun, Tool def multiply(a: int, b: int) - int: return a * b tools [ DuckDuckGoSearchRun(), Tool( namemultiply, funcmultiply, description计算两个整数的乘积输入为JSON数组如[3,5] ) ] agent create_tool_calling_agent(llm, tools, prompt) agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, return_intermediate_stepsTrue, )create_tool_calling_agent要求模型是支持原生函数调用tool calling的模型——GPT-4o 系列、Claude 3.5、Qwen2.5 系列都支持。它的内部逻辑是模型被要求如果需要外部信息就输出一个工具调用的指令LangChain 拿到指令后执行对应工具再把结果以 message 形式喂回模型模型再看够不够、不够再调。这里要说一个新东西MCPModel Context Protocol。搜索热词里也有它这说明关注度在上升。MCP 的官方目标是统一模型接入外部工具和数据源的协议而不是每个模型开发者都自己发明一套工具调用格式。LangChain 官方社区也对 MCP 有专门的集成包提供MCPAdaptor之类的工具类。在实际项目中我用 MCP 连接了一个内部的数据查询服务通过load_mcp_tools加载工具后再传给AgentExecutor使用。这样做最大的价值在于工具和 Agent 解耦。你换一个新的 Agent 框架只要它支持 MCP已实现的工具定义和调用逻辑可以直接搬走不需要重写。MCP 目前还在协议铺设期但值得在新项目里提前拥抱。4. 踩坑记录与问题排查4.1 回调地狱LCEL 的中间结果怎么拿用 LCEL 写链时遇到一个最典型的场景链跑完你想看看中间那步检索出来的文档到底是什么或者模型生成的原始 Token 到底是多少。如果直接chain.invoke()这些中间结果全被吞掉了。排查思路是用RunnableConfig传回调from langchain_core.callbacks import FileCallbackHandler with open(trace.log, w) as f: handler FileCallbackHandler(f) config {callbacks: [handler]} result rag_chain.invoke({question: ...}, configconfig)另外用chain.get_graph().print_ascii()可以看 LCEL 链路内部的图结构。这个方法能帮你定位每一步的真实执行对象比看着报错瞎猜要高效得多。4.2 版本升级带来的 API 破坏前面提过版本锁定的问题。实际项目里我遇到过一个印象深刻的坑项目早期用的是from langchain.document_loaders import PyPDFLoader跑得好好的后来升级 langchain-community 后这个 import 路径被移除了报错是ModuleNotFoundError。排查思路是看报错信息里的 import 位置以及官方 migration 文档。这里有两条经验任何时候不要从顶层langchain包 import 具体组件。应该从langchain_core、langchain_community、langchain_openai这些子包里面 import语法上来讲也没有耦合。固定版本、固定 requirements.txt / pyproject.toml。在项目入口或环境变量里写清楚langchain、langchain-core、langchain-community的版本并建议用pip freeze把整个环境锁定一遍。4.3 中文分块与 Embedding 的坑这个坑几乎每个做中文 RAG 的人都会踩。默认的RecursiveCharacterTextSplitter的separators是按英文语法来设定的比如[\n\n, \n, , ]。对于中文长段落它经常在句子中途随意切开导致一个 chunk 里出现半个句子检索召回时信息被截断模型的回答质量明显下降。我的解决办法是自定义分隔符text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[ \n\n, \n, 。, , , , \n, . , , , ], )另一个坑是 Embedding 模型的选择。OpenAI 的text-embedding-3-small对中文效果尚可但如果是纯中文文档我更建议用text-embedding-3-large或BAAI/bge-large-zh-v1.5这类中文优化模型。项目里实测过同样 100 份中文文档换成 bge 系列之后召回结果的Reciprocal Rank平均提升了两三成。如果不想额外付费在本地用text2vec-large-chinese或Qwen3-Embedding也能撑住基本场景。4.4 常见问题速查表问题典型报错解决思路import 路径报错ModuleNotFoundError: No module named langchain.document_loaders子包导入或降级 langchain 版本vLLM 报采样参数错误ValueError: ... is not supported只传 temperature去掉其他采样参数中文分块把句子切碎了检索结果质量差、上下文混乱自定义 separators加入中文标点Agent 不调用工具模型一直输出普通文本检查模型是否支持 tool calling检查工具 description 是否写清楚Chroma 持久化后检索不到向量库文件损坏删除persist_directory重新入库LCEL 链不执行中间步骤invoke()只返回最终结果传 callbacks 或用 get_graph() 查看内部结构对话太长导致 Token 爆掉maximum context length exceeded用截断式记忆限制历史轮次LangGraph 状态更新失败TypeError: unsupported operation on state检查 state schema 的 reducer 是否正确5. 面试视角与项目扩展思路5.1 LangChain 面试中真正会问的问题写完这个项目之后我拿它当素材过了一遍面试题整理出几个高频考点LangChain 和 LangGraph 的区别LangChain 是线性编排LangGraph 是图状态机前者适合确定性流程后者适合有分支、循环、人工干预的流程。回答时最好带一个自己的项目实例说明。LCEL 和 Runnable 的关系LCEL 是声明式链式语法Runnable 是其底层接口prompt | llm实际是RunnableSequence可以嵌套。RAG 的完整链路和优化方向加载、分块、向量化、检索、增强生成优化方向包括分块策略、Embedding 模型、重排序、混合检索、HyDE 查询改写等。Agent 的工作流程观察 → 思考 → 工具调用 → 结果反馈 → 决定结束或继续核心在模型判断力与工具设计。记忆机制的选型截断、摘要、向量检索记忆各自的 Token 成本和信息保留能力。面试时最忌讳的是只会背概念。最好能把某个细节说到我在项目里遇到这个坑当时是这样解决的的程度。我上述任何一个踩坑经验拿出来讲都比干背 LangChain 文档要有说服力。如果你正在准备 LangChain 相关的工作建议在简历上写一两个真实的项目案例注明自己用的是 LCEL 还是 AgentExecutor有没有用 LangGraph 做过状态流转。把每个环节的设计动机说清楚在面试里是非常加分的。5.2 这个项目后续还能怎么扩展现在的项目已经能跑通多模型切换 RAG 检索 记忆 简单 Agent这一条完整链路。如果再去扩展我心里的优先级是重排序Rerank召回 Top-20 后用 bge-reranker 做二次排序能明显提升最终生成质量这是老牌基线方法效果稳定。用 LangGraph 改造 Agent 流程加用户确认失败重试最大步数限制等节点让流程可控可观测。接入 MCP 生态把现有工具包逐步迁移到 MCP 标准未来替换框架时不心疼。性能测试与乐观锁并发量上来之后LLM 调用、向量库检索的响应时间分布要测一测考虑缓存、异步、流式输出。多 Agent 协作比如检索 Agent 总结 Agent 审核 Agent互相调用最终结果交给主 Agent 统一输出。这个方向最能体现 LangGraph 的价值。如果做开源框架或者学习入门我认为按LCEL → RAG → Memory → Agent → LangGraph这条路线推进是合理的。它本身就是一个不断向深度演进的路线。6. 项目实操的一些个人体会最后聊点代码之外的东西。我实际使用 LangChain 的第 1 周最大的感受是框架太重、抽象太多动不动就要查文档、查源码。但用满一个月、把核心链路都跑通之后我开始觉得这些抽象是值得的它把一个多模型、多工具、多数据源的项目从几坨互相滴血的脚本变成了可以独立替换每一层螺丝的机器。我的个人建议是不要一上来就背整个框架的 API而是从一个最小的场景出发——比如让模型根据本地文件回答问题——然后用 LCEL 把它串起来。在这个过程中你会自然接触 Prompt 模板、RAG、Retriever然后才会遇到 Agent 和记忆。等这些环节都亲自动手调过再回头理解 LangChain 的设计逻辑会顺很多。另外一个很重要的习惯不要盲目追新版本。LangChain 迭代速度太快今天写的新 API 明天可能就变。我的做法是每次升级前先读官方changelog确认我用到的那几个 API 有没有破坏性变更再决定要不要升级。项目稳定运行比用什么新功能重要得多。最后送你一个实用小技巧如果你在踩坑时怎么都搜不到答案直接去翻 LangChain 源码重点看langchain_core里各种runnable基类的实现。很多报错在源码注释里写得很清楚比官方文档准确得多。毕竟哪怕 AI 界的技术变化再快底层那套输入 → 处理 → 输出的思维框架本质上还是没变过。本文还有配套的精品资源点击获取