LangChain快速入门:从零构建LLM应用的核心组件与实战
1. 项目概述:为什么你需要关注LangChain?
如果你最近在捣鼓大语言模型(LLM),比如想做个智能客服、文档问答机器人,或者想把AI能力集成到自己的应用里,那你大概率已经听过“LangChain”这个名字了。它现在几乎是LLM应用开发领域的事实标准框架,热度居高不下。但你可能也听过一些抱怨,比如“学习曲线陡峭”、“概念太多”、“文档看着头大”。这恰恰说明了它的强大和复杂——它试图解决的,正是如何将强大的LLM与你的数据、工具和业务流程可靠地连接起来这个核心难题。
简单来说,LangChain不是一个模型,而是一个开发框架。你可以把它想象成乐高积木的底板和一套标准连接件。LLM(如GPT-4、Claude、本地部署的模型)是功能各异的积木块,你的数据、API、数据库是另一堆形状不一的积木。LangChain提供了一套标准化的“接口”和“组装逻辑”,让你能把这些原本不兼容的部件,快速、稳定地拼装成一个能跑起来的完整应用。它帮你处理了对话记忆管理、工具调用编排、复杂任务分解、文档检索与加工(RAG)等一系列繁琐但关键的工程问题。
所以,这篇快速入门的目标很明确:帮你绕过初期最让人困惑的概念丛林,直接上手搭建出几个能工作的原型,在实操中理解LangChain的核心思想。无论你是想验证一个AI点子,还是为现有产品添加智能特性,掌握LangChain都能让你事半功倍。我们不会面面俱到,而是聚焦于最常用、最能体现其价值的几个组件,通过代码示例带你快速跑起来。
2. 核心概念与架构拆解:理解LangChain的“乐高哲学”
在动手写代码前,花几分钟理解LangChain的几个核心抽象至关重要。这能帮你从“跟着代码敲”升级到“知道为什么这么写”。
2.1 核心六大组件
LangChain的架构围绕几个核心组件构建,它们像乐高积木一样可以灵活组合:
模型 I/O (Model I/O):这是与LLM交互的入口。它主要包含:
- 提示词模板 (Prompt Templates):将用户输入、上下文变量等动态填充到预设的提示词中,避免硬编码。比如,一个客服模板可能是:“请基于以下上下文回答问题:{context}\n问题:{question}\n回答:”。
- 语言模型 (LLMs/Chat Models):LangChain封装了调用各类模型(OpenAI, Anthropic, 本地模型等)的统一接口。
ChatModels专为多轮对话设计(接收消息列表),而LLMs接收简单字符串。 - 输出解析器 (Output Parsers):将模型返回的非结构化文本(如一段JSON字符串)解析成你程序里好用的结构(如Python字典、Pydantic对象)。这是保证程序稳定性的关键。
检索 (Retrieval):这是实现RAG(检索增强生成)的核心。它负责从你的知识库(文档、数据库)中,找到与用户问题最相关的片段,作为上下文喂给模型。核心步骤包括:文档加载 -> 文本分割 -> 向量化存储 -> 相似度检索。
记忆 (Memory):让对话或交互拥有“记忆”。最简单的形式是保存聊天历史。LangChain提供了多种记忆后端,从简单的对话缓冲区到基于向量的长期记忆。
链 (Chains):这是LangChain的“胶水”。一个链将多个组件(或多个其他链)按预定顺序组合起来,形成一个完整的处理流程。例如,一个典型的问答链可能是:
检索文档 -> 构建提示词 -> 调用模型 -> 解析输出。代理 (Agents):这是LangChain最强大的概念之一。代理=大模型+工具+决策逻辑。模型扮演“大脑”,根据用户目标,自主决定调用哪个工具(如搜索API、计算器、数据库查询),并理解工具返回的结果,直到完成任务。它让应用从“按固定流程执行”升级为“自主规划与执行”。
回调 (Callbacks):用于在链或代理执行的各个阶段插入日志记录、流式输出、监控等逻辑,便于调试和观察内部状态。
2.2 LangChain vs. LangGraph:流程编排的两种范式
这是最近的热门话题。简单理解:
- LangChain (Chains/Agents):更像是声明式的编排。你定义好组件和它们之间的连接关系(一个接一个的链,或代理的决策循环),执行时按这个预定结构走。适合逻辑相对固定、可预测的任务流。
- LangGraph:更像是图编程或状态机。你将应用逻辑定义为一个有向图,节点是处理函数或工具,边是条件跳转逻辑。模型或逻辑可以决定下一步走到哪个节点,从而实现更复杂、带循环、分支条件多的动态工作流。它是对LangChain能力的补充和增强,特别适合需要多轮规划、回溯、复杂协作(如CrewAI中的多智能体)的场景。
对于快速入门,我们首先掌握好LangChain的基础链和代理,这已经能解决80%的需求。LangGraph可以在你遇到更复杂的工作流时再深入学习。
3. 环境准备与第一个“Hello World”
理论说再多不如跑一行代码。我们从最简单的开始:让LangChain调用一个模型。
3.1 安装与基础配置
首先,确保你安装了Python(建议3.8+)。然后通过pip安装LangChain。为了调用模型,我们通常还需要对应模型的SDK。这里以OpenAI的模型为例,因为它最通用。
# 安装LangChain核心包 pip install langchain langchain-core # 安装OpenAI的SDK(如果你打算使用OpenAI的模型) pip install openai # 安装一些常用的社区工具包,如用于网页检索 pip install langchain-community接下来,你需要一个OpenAI的API密钥。如果你没有,可以去OpenAI官网注册获取。切记不要将密钥直接硬编码在代码中,更不要上传到GitHub等公开平台。
安全的方式是设置为环境变量:
# 在终端中设置(临时) export OPENAI_API_KEY='your-api-key-here' # 或者在代码中通过python-dotenv等库从.env文件加载3.2 调用模型与提示词模板
现在,我们来写第一个脚本:向模型问好。
# 导入必要的模块 from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # 1. 初始化模型 # 默认使用 gpt-3.5-turbo,你可以通过 model_name 参数指定其他模型,如 gpt-4 llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) # temperature 控制创造性,0.0最确定,1.0最随机。对于事实性任务,建议调低(如0.1-0.3)。 # 2. 创建一个简单的提示词模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个乐于助人的助手。"), ("user", "{input}") ]) # 这里定义了两个角色消息:system(设定助手行为)和 user(用户输入)。 # {input} 是一个占位符,将在运行时被替换。 # 3. 将提示词模板和模型组合成一个链 chain = prompt | llm # 这是LangChain v0.1+ 推荐的管道操作符语法,非常直观:prompt -> llm。 # 4. 调用链并传入输入 response = chain.invoke({"input": "LangChain是什么?"}) # 5. 打印结果 print(response.content)运行这段代码,你应该能得到一个关于LangChain的解释。response是一个AIMessage对象,其content属性包含了模型的文本回复。
实操心得一:模型初始化参数
temperature:这是最重要的参数之一。对于需要稳定、事实性输出的场景(如摘要、数据提取),设为较低值(0.1-0.3)。对于创意写作、头脑风暴,可以调高(0.7-0.9)。max_tokens:限制模型生成的最大长度,防止意外产生过长的回复消耗大量token。streaming:如果设为True,则可以实现流式输出,适合需要实时显示生成结果的Web应用。
4. 构建一个简单的问答链:引入检索与输出解析
单纯调用模型回答通用问题意义不大。接下来,我们构建一个更实用的链:基于自定义文档进行问答。这需要用到检索和输出解析。
假设我们想创建一个关于“公司内部政策”的问答机器人。我们有一份简单的政策文档。
4.1 准备文档与向量数据库
首先,我们需要将文档处理成向量并存储起来,以便快速检索。
from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma # 1. 加载文档(这里我们直接用一个字符串模拟,实际可以从文件、网页、PDF加载) policy_text = """ 公司休假政策: 1. 所有全职员工每年享有15天带薪年假。 2. 年假需提前至少一周向直属主管申请。 3. 病假需在当天上午10点前通知主管。 4. 公司提供5天带薪病假。 5. 法定节假日按照国家规定执行。 """ # 实际项目中,使用 TextLoader("policy.txt") 或 DirectoryLoader 加载文件。 # 2. 文本分割 # 文档可能很长,需要分割成小块,以便检索最相关的一段。 text_splitter = RecursiveCharacterTextSplitter( chunk_size=200, # 每个块的最大字符数 chunk_overlap=50, # 块之间的重叠字符,避免上下文断裂 separators=["\n\n", "\n", "。", ",", " ", ""] # 分割符优先级 ) documents = text_splitter.create_documents([policy_text]) print(f"将文档分割成了 {len(documents)} 个块") # 3. 创建向量存储 # 使用OpenAI的嵌入模型将文本转换为向量,并用Chroma(轻量级向量数据库)存储。 embeddings = OpenAIEmbeddings() vectorstore = Chroma.from_documents(documents=documents, embedding=embeddings, persist_directory="./policy_db") # persist_directory 指定持久化目录,下次可以直接加载,无需重新生成向量。注意事项:文本分割的艺术
chunk_size是关键。太小会丢失上下文,太大会引入无关噪声。对于事实性问答,200-500字符是不错的起点。对于需要长上下文推理的任务,可以适当增大。chunk_overlap能有效防止一个完整的句子或概念被割裂在两个块中。- 不同的文档类型(代码、Markdown、论文)可能需要定制的分割器。LangChain提供了多种
TextSplitter。
4.2 构建检索问答链
现在,我们将检索器、提示词模板和模型组装成一个完整的问答链。
from langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain from langchain_core.prompts import ChatPromptTemplate # 1. 从向量库创建检索器 retriever = vectorstore.as_retriever(search_kwargs={"k": 2}) # search_kwargs 中的 `k` 表示检索返回的最相关文档块数量。不是越多越好,一般2-4个足够。 # 2. 定义系统提示词模板 system_prompt = """ 请仅根据以下提供的上下文信息来回答问题。如果上下文信息中没有明确答案,请直接说“根据现有政策,我无法回答这个问题。”,不要编造信息。 上下文: {context} """ prompt = ChatPromptTemplate.from_messages([ ("system", system_prompt), ("user", "{input}") ]) # 3. 初始化模型 llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.1) # 低temperature保证答案忠实于上下文 # 4. 创建“组合文档”链:负责将检索到的多个文档块合并到提示词中 combine_docs_chain = create_stuff_documents_chain(llm, prompt) # 5. 创建最终的检索链:将检索器和组合文档链连接起来 qa_chain = create_retrieval_chain(retriever, combine_docs_chain) # 6. 提问! question = "我每年有多少天带薪年假?" result = qa_chain.invoke({"input": question}) print(f"问题:{question}") print(f"答案:{result['answer']}") print("\n检索到的上下文:") for i, doc in enumerate(result["context"]): print(f"[{i}] {doc.page_content[:100]}...")运行后,模型会从我们提供的政策文本中检索出相关片段(关于年假天数的句子),并基于此生成答案。你会看到答案明确指向“15天”,并且模型不会胡编乱造。
实操心得二:提示词工程在RAG中,系统提示词至关重要。上面的例子中,我们明确指令模型“仅根据上下文回答”,并提供了拒绝回答的模板。这大大减少了模型“幻觉”(即编造信息)的概率。在实际应用中,你可能需要反复调整提示词,以获得更精准、更符合业务口吻的回答。
4.3 使用输出解析器结构化结果
有时我们希望模型的输出不是一段自由文本,而是结构化的数据,比如JSON,方便后续程序处理。这时就需要输出解析器。
假设我们想让模型从一段员工反馈中提取结构化信息。
from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import PydanticOutputParser from pydantic import BaseModel, Field from typing import List # 1. 使用Pydantic定义我们希望输出的数据结构 class FeedbackInfo(BaseModel): employee_name: str = Field(description="员工姓名") department: str = Field(description="所属部门") rating: int = Field(description="评分,1-5分") key_points: List[str] = Field(description="反馈中的关键点列表") sentiment: str = Field(description="情感倾向,positive/negative/neutral") # 2. 基于Pydantic模型创建输出解析器 parser = PydanticOutputParser(pydantic_object=FeedbackInfo) # 3. 在提示词中告诉模型需要输出的格式 # 获取解析器提供的格式指令 format_instructions = parser.get_format_instructions() prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个信息提取助手。请从用户的输入中提取信息。\n{format_instructions}"), ("user", "{input}") ]) # 4. 构建链 chain = prompt | llm | parser # 5. 输入一段文本 feedback_text = "我是技术部的张三。这次项目支持我觉得可以打4分。团队协作很好,但交付时间有点紧张。总体是积极的体验。" result = chain.invoke({ "input": feedback_text, "format_instructions": format_instructions }) print("提取的结构化信息:") print(f"姓名:{result.employee_name}") print(f"部门:{result.department}") print(f"评分:{result.rating}") print(f"关键点:{result.key_points}") print(f"情感:{result.sentiment}") # 输出将是一个FeedbackInfo对象,可以直接通过属性访问。输出解析器会在模型输出不符合预期格式时抛出异常,这保证了下游代码的稳定性。PydanticOutputParser是功能最强大的一种,你还可以使用CommaSeparatedListOutputParser(输出列表)或StructuredOutputParser等。
5. 探索智能体:让模型学会使用工具
链是预定义的流程,而代理则赋予了模型“自主权”。我们给模型一些工具(函数),它可以根据用户的问题,自己决定是否使用、使用哪个、以及如何使用这些工具。
5.1 创建一个简单的数学计算代理
我们先定义一个简单的计算器工具,然后看看模型如何调用它。
from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.tools import tool from langchain_openai import ChatOpenAI # 1. 使用 @tool 装饰器定义一个工具 # 工具的描述非常重要!模型完全依赖描述来决定是否以及如何调用它。 @tool def calculate(expression: str) -> str: """计算一个数学表达式的值。支持加减乘除(+-*/)和括号。""" # 警告:这里使用eval仅作演示,在实际生产环境中极其危险,必须替换为安全的计算库(如ast.literal_eval限制操作,或使用math等)。 try: # 简单演示,实际请勿直接eval不可信输入 result = eval(expression) return str(result) except Exception as e: return f"计算错误:{e}" # 2. 准备工具列表和模型 tools = [calculate] llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) # 为代理创建专用的提示词(LangChain提供了内置模板) from langchain import hub prompt = hub.pull("hwchase17/openai-tools-agent") # 3. 创建代理 agent = create_tool_calling_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # verbose=True 会打印思考过程 # 4. 运行代理 result = agent_executor.invoke({ "input": "请计算一下 (12 + 34) * 2 等于多少?" }) print(f"\n最终答案:{result['output']}")运行代码时,你会看到类似以下的verbose输出,这揭示了代理的思考过程:
> Entering new AgentExecutor chain... 我应该使用计算器工具来计算这个数学表达式。 调用:calculate,参数:{"expression": "(12 + 34) * 2"} 观察:92 (12 + 34) * 2 等于 92。 > Finished chain. 最终答案:(12 + 34) * 2 等于 92。模型识别出这是一个数学问题,决定调用calculate工具,并正确传入了表达式(12 + 34) * 2,最后将工具返回的结果整合成了自然语言回复。
5.2 使用预构建工具与搜索引擎
LangChain社区提供了大量预构建的工具,比如联网搜索。我们结合搜索工具和计算器,创建一个更强大的代理。
from langchain_community.tools import TavilySearchResults from langchain.agents import AgentExecutor, create_tool_calling_agent # 1. 初始化搜索工具(需要TAVILY_API_KEY,可在 https://app.tavily.com 免费获取) import os os.environ["TAVILY_API_KEY"] = "your_tavily_api_key" search_tool = TavilySearchResults(max_results=2) # 限制返回2条结果 # 2. 工具列表 tools = [calculate, search_tool] # 3. 创建并运行代理 agent = create_tool_calling_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True) # handle_parsing_errors 能更好地处理模型输出格式错误的情况。 question = "昨天旧金山的天气怎么样?另外,如果气温是华氏70度,那是多少摄氏度?" result = agent_executor.invoke({"input": question}) print(f"\n最终答案:{result['output']}")这个代理会先调用搜索工具查询旧金山昨天的天气,然后可能再调用计算器将华氏度转换为摄氏度(如果搜索结果没有直接提供的话)。你会在verbose日志中看到它依次调用不同工具的过程。
实操心得三:代理的调试与优化
- 工具描述:务必清晰、准确。模型对工具功能的理解完全基于描述。
verbose=True:开发阶段务必开启,这是理解代理“思考”过程、排查问题的最重要手段。- 错误处理:代理可能陷入循环、调用错误参数或无法解析输出。设置
max_iterations(最大迭代次数)和handle_parsing_errors=True是基本的防护措施。 - ReAct模式:这是代理的经典范式(Reason + Act)。你会在日志中看到模型“思考”(Reason)下一步该做什么,然后“行动”(Act)调用工具。如果代理表现不佳,可以尝试使用更强大的模型(如GPT-4),或优化提示词。
6. 常见问题与排查技巧实录
在实际使用LangChain时,你肯定会遇到各种问题。这里记录了一些典型场景和解决思路。
6.1 连接与API错误
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
AuthenticationError/RateLimitError | API密钥错误、过期或额度不足。 | 1. 检查环境变量OPENAI_API_KEY是否正确设置且生效。2. 登录OpenAI控制台,检查密钥状态、用量和余额。 3. 对于速率限制,考虑增加请求间隔或升级套餐。 |
| 连接超时 | 网络问题或代理配置。 | 1. 检查本地网络。 2. 如果你在特殊网络环境,某些SDK可能需要配置 http_client或api_base。OpenAI SDK可通过openai.base_url设置。 |
ModuleNotFoundError | 缺少依赖包。 | 1. LangChain采用模块化设计。langchain是核心,但很多功能在子包中。2. 例如,使用 Chroma需要langchain-chroma,使用Tavily需要langchain-community。仔细阅读错误信息,安装对应的包。 |
6.2 检索效果不佳
| 问题现象 | 可能原因 | 优化方向 |
|---|---|---|
| 答案不准确,未引用文档内容。 | 1. 检索到的文档块不相关。 2. 提示词未强制模型使用上下文。 | 1.优化检索:调整chunk_size和chunk_overlap;尝试不同的TextSplitter(如按标记分割TokenTextSplitter);优化嵌入模型;调整检索的相似度阈值或k值。2.强化提示词:在系统提示词中明确强调“仅使用以下上下文”,并设计当上下文不相关时的拒绝回答话术。 |
| 答案包含正确信息但冗余或格式差。 | 模型未能很好总结或格式化。 | 在提示词中增加指令,如“请用简洁明了的语言总结答案”、“请以要点列表的形式回答”。 |
| 对于复杂问题,单个文档块信息不足。 | 检索的上下文碎片化。 | 1. 增加k值,返回更多文档块。2. 使用 ContextualCompressionRetriever等高级检索器,在检索后对文档块进行压缩或重新排序,聚焦最相关信息。 |
6.3 代理行为异常
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 代理陷入循环,不停调用同一个工具。 | 1. 工具未能返回有效结果,导致模型重复尝试。 2. 任务描述模糊,模型无法确定完成状态。 | 1. 检查工具函数是否稳定,确保返回清晰的结果(包括错误信息)。 2. 在提示词中明确任务完成的标志。 3. 设置 max_iterations(如10)强制停止。 |
| 模型不调用工具,直接生成答案。 | 1. 工具描述不够清晰,模型不理解其用途。 2. 问题太简单,模型觉得可以自己回答。 3. 使用的模型(如 gpt-3.5-turbo)工具调用能力较弱。 | 1. 细化工具描述,说明适用场景、输入输出格式。 2. 在用户问题中明确要求“请使用XX工具”。 3. 升级到工具调用能力更强的模型,如 gpt-4-turbo。 |
| 代理调用工具时参数格式错误。 | 模型对工具输入参数的理解有偏差。 | 1. 使用@tool装饰器时,确保函数参数有明确的类型注解和文档字符串。2. 可以使用 StructuredTool.from_function更精细地定义参数模式(schema)。 |
6.4 性能与成本优化
- 缓存:对于重复的查询,使用缓存可以大幅减少API调用和延迟。LangChain内置了
InMemoryCache、SQLiteCache,也可以集成Redis。from langchain.globals import set_llm_cache from langchain.cache import InMemoryCache set_llm_cache(InMemoryCache()) - 流式输出:对于需要长时间生成的文本,使用流式输出可以提升用户体验。在初始化
ChatOpenAI时设置streaming=True,并使用相应的回调处理器。 - 本地模型与嵌入:如果数据敏感或希望控制成本,可以考虑使用本地模型(通过
Ollama,vLLM,LocalAI等集成)和本地嵌入模型(如BAAI/bge-small-zh-v1.5)。这需要更多的本地资源,但提供了完全的隐私和可控性。 - 异步调用:如果你的应用是异步框架(如FastAPI),使用LangChain的异步接口(
ainvoke,astream)可以更好地处理并发请求。
7. 从原型到生产:下一步的方向
通过上面的步骤,你应该已经能用LangChain搭建起一个具备基本问答和工具调用能力的AI应用原型了。但这距离一个健壮的生产系统还有距离。接下来,你可以从以下几个方向深入:
- 更复杂的链与工作流:学习使用
LangGraph来编排带有循环、分支和状态的工作流,处理多步骤、需要回溯的复杂任务。 - 评估与监控:如何衡量你的RAG系统或代理的好坏?需要建立评估体系,包括答案相关性、事实准确性、延迟等指标。可以使用
RAGAS、TruLens等评估框架。 - 生产部署:考虑将你的链或代理封装成API(使用
FastAPI),加入身份验证、速率限制、持久化存储(向量数据库如Pinecone,Weaviate,Qdrant)和监控告警。 - 高级检索技巧:探索混合搜索(结合关键词和向量)、重排序(
Cohere Rerank)、多索引检索等,进一步提升检索精度。 - 智能体优化:为代理提供更多、更强大的工具(数据库查询、代码执行、内部API),并研究更高效的规划与推理策略。
我个人在从入门到实战的过程中,最大的体会是:不要试图一次性理解LangChain的所有概念。最好的学习路径是“以战促学”——先定一个明确的小目标(比如“做一个能查询我个人笔记的机器人”),然后围绕这个目标去学习必要的组件(文档加载、向量化、检索链),遇到问题再查阅文档或社区。当你成功实现第一个小应用后,那些抽象的概念自然会变得具体而清晰。LangChain的生态在不断进化,保持动手实践,才是跟上节奏的最好方式。
