LangChain入门:从裸调大模型到构建RAG知识库问答应用

发布时间:2026/10/22 5:57:45

LangChain入门:从裸调大模型到构建RAG知识库问答应用 这套系列教程走到第九章前八章把提示词编写、模型接口调用、参数调优、输出不稳定这些基础问题都过了一遍。但真到动手做项目时你会发现那些示例代码和“能用”之间还差着一大截鉴权逻辑散落在每个函数里、提示词和业务代码缠在一起、换个模型服务商就要把所有调用点翻出来改一遍更不用说记忆和检索这种需要自己维护的东西。这一章我们聊一个专门解决这些麻烦的框架LangChain。LangChain 不直接提供大模型它把模型接入、提示词管理、输出解析、检索、记忆、工具调用这些高频能力做成统一模块再用一套表达式把它们串成“链”。你可以把它理解成后端开发里的 ORM——只靠手写 SQL 当然也能跑但项目一多、表一多你就需要一个标准化的中间层。这篇内容适合有 Python 基础、会调模型 API、但还没系统搭过完整应用的开发者读完至少能搭出一个带知识库的多轮问答应用也清楚后面该往哪儿深入。1. LangChain 在设计之初想解决的问题1.1 一次普通的模型调用能有多麻烦大部分入门教程教你的调用方式长这样import requests def call_llm(prompt, historyNone): messages history or [] messages.append({role: user, content: prompt}) payload { model: model-name, messages: messages, temperature: 0.7, } resp requests.post( https://api.example.com/v1/chat/completions, headers{Authorization: Bearer YOUR_API_KEY}, jsonpayload, timeout30, ) if resp.status_code ! 200: raise RuntimeError(fAPI error: {resp.status_code}, {resp.text}) return resp.json()[choices][0][message][content]这段代码看起来能跑但拿到工程里就是灾难。首先是鉴权逻辑散落在每个函数里项目里有十个调用点就得写十遍请求头其次是提示词和业务代码缠在一起你想调整系统提示词就得钻进函数内部去改字符串拼接再者是输出解析完全没有模型返回的是原始文本想要 JSON 结构化还得自己写正则或 json.loads最后是错误处理超时、限流、上下文超长这些异常每个都要单独写分支。做个生活化的类比这就像做菜时每次都要重新洗菜、切菜、配调料而不是先把半成品预制好。一个模型应用如果只有一两个调用点裸调倒也没什么但当你开始做知识库问答、Agent 工具调用、多轮对话记忆时重复劳动会指数级增长。LangChain 的价值就在于此把大模型应用里那些绕不开的“公共麻烦”抽成模块让开发者专注于业务逻辑本身。1.2 LLM 应用的共同结构沉下心看几个不同类型的应用——聊天机器人、文档问答、内容总结、数据分析助手——你会发现它们的骨架惊人地相似。无非是这六个环节模型接入、提示词组装、输出处理、上下文/记忆、外部数据、工具调用。裸调时每个环节都在重复造轮子LangChain 则给每个环节都安排了对应模块应用环节裸调时要重复做的事LangChain 对应模块模型接入配置地址、鉴权、超时重试Model统一模型接口提示词手拼字符串、硬编码在业务代码里Prompt Template输出处理正则解析、JSON 抽取、异常兜底Output Parser上下文/记忆手动维护消息数组、截断历史Messages / Memory外部数据自己写文本切分、向量化、检索Retriever / VectorStore工具调用自己写函数循环判断、处理结果Agent / Tool这张表基本就是 LangChain 的模块地图。你不需要一次性把所有模块都学会先抓住主线——Model、Prompt Template、Output Parser、Chain——就能拼出一个能用的应用。检索、记忆、Agent 都是在这个主干上做加法。2. 环境准备与项目搭建2.1 安装与版本选择先交代一下环境Python 建议用 3.9 及以上虚拟环境是标配别直接装到系统 Python 里。安装命令很简单pip install langchain langchain-core langchain-community pip install langchain-xxx # 替换为你实际使用的模型服务商适配包这里有个新手很容易踩的坑LangChain 的版本迭代速度很快0.1 版本和 0.3 版本的 API 差异很大。早期版本里from langchain.llms import OpenAI这种写法在后续版本中逐渐被拆分成langchain-openai、langchain-anthropic等独立适配包。不同功能也被拆到了langchain-core核心抽象、langchain-community社区集成里。我的建议是新项目直接用当前最新稳定版但一定要在 requirements 里锁定版本号。比如langchain0.3.x、langchain-core0.3.x。为什么这么强调因为 LangChain 的链式 API 在版本升级时经常调整你上个月写的链用invoke能跑升级后可能要改成ainvoke或者干脆换了包名。加上“第九章”在系列中的位置前几章的很多示例代码其实都是在固定版本下验证过的锁定版本才能保证前后一致避免本来好好的代码升级完突然报错。2.2 配置文件与模型连接大模型的 API Key 不能写死在代码里这是个基本素养。我习惯用.env文件配合环境变量方式来管理import os from dotenv import load_dotenv load_dotenv() api_key os.getenv(LLM_API_KEY)然后初始化模型from langchain_xxx import ChatXxx model ChatXxx( modelmodel-name, api_keyapi_key, temperature0.3, )注意这里我用了ChatXxx占位符实际使用时替换为对应模型服务商的适配类。不同服务商的包名、类名、模型名都不一样但核心用法几乎一致这就是 LangChain 统一接口的意义你只需要改一行初始化的代码就能在几个模型服务商之间切换业务逻辑完全不用动。两个实际情况提醒你。第一模型名必须严格按官方文档填填错往往不会在初始化时报错而是在第一次 invoke 时才给你抛异常排查起来很不直观。第二temperature这类生成参数可以直接在初始化时设置但也可以在调用时临时覆盖这个灵活性在调试不同业务场景时非常有用比如写代码的链用低温度创意写作的链用高温度。3. 核心概念逐个拆解从模型接口到链式组装3.1 统一模型接口invoke / stream / batch模型模块在 LangChain 里被抽象成ChatModel对话模型最常用的方法有三个invoke单次调用、stream流式返回、batch批量调用。不少人只用了invoke但流式输出在真实体验中非常关键。# 流式输出 for chunk in model.stream(给我讲个笑话): print(chunk.content, end, flushTrue)这行代码实现了打字机式的输出效果。如果你做过聊天应用就懂用户看到逐字输出和等三秒一次性弹出全文心理体验是天壤之别。invoke返回的是一个消息对象不是纯字符串要拿到纯文本通常是.content属性——这也是新手常见困惑点打印出来看是一大堆content...以为自己写错了。batch适合批量处理离线任务比如一次性给 20 篇文档写摘要。它的内部会自动处理并发请求但注意批量太大时容易触发服务商的限流具体并发数要根据你的实际情况调整。把这些接口理解成统一入口之后就简单了无论背后是哪个模型服务商开发体验都是一样的。3.2 提示词模板把不变量和变量分开直接拼接提示词字符串写几个 demo 没问题但项目一旦复杂就会失控。核心问题是把“不变量”和“变量”混在一起了——系统指令是不变量用户的输入、检索到的资料是变量。提示词模板就是把这层关系显式化from langchain_core.prompts import ChatPromptTemplate prompt ChatPromptTemplate.from_messages([ (system, 你是一个产品问答助手。请用简洁、专业的中文回答问题。如果资料中没有相关信息直接说不知道。), (human, 资料\n{context}\n\n问题{question}), ])ChatPromptTemplate支持三种消息角色system系统消息、human人类用户消息、ai模型助手消息。模板里的{context}、{question}是占位符调用时传入真实值即可。用了模板之后系统提示词集中管理业务代码里看到的都是清晰的变量名而不是一长串字符串拼接。这在多人协作时的优势尤其明显产品想调整提示词语气开发不用改代码逻辑只改模板文件就行。3.3 LCEL 与链用管道符描述业务流程LangChain 最有辨识度的地方是它的表达式语言简称 LCEL。它用|管道符把各个模块串起来整个业务流程一眼看穿from langchain_core.output_parsers import StrOutputParser parser StrOutputParser() chain prompt | model | parser answer chain.invoke({ question: 你好请介绍一下你自己, context: , }) print(answer)prompt | model | parser读起来就是“提示词模板传给模型模型输出传给解析器”。每个“环节”对应一个可运行对象有点像 Unix 命令行里的管道——cat file | grep keyword这种思维。StrOutputParser是输出解析器里最基础的一个作用是把模型返回的消息对象转成纯字符串。为什么强调 LCEL因为它让代码变得“可声明”。以前你要写response model(prompt)再把结果逐个传给下一步现在你只需要声明链条结构框架自己处理数据流转。调试时也可以带着中间的print去观察每一层的输入输出定位问题的效率高很多。等你熟悉了这条主线后面所有复杂的应用——检索增强、Agent 循环——都可以看成是往这条管道里插入新环节。4. 实操从零构建一个带知识库的产品问答助手4.1 需求拆解与模块设计直接上一个可以落地的项目做一个简单的产品问答助手。假设某个产品线收到大量重复咨询——发票怎么开、怎么退货、保修多久客服压力很大。我们做一个能基于已有 FAQ 文档回答问题的 Mini Demo。这类应用有个专业名字叫 RAG检索增强生成核心思路是“先检索再生成”。用户提问后先从资料库里找出相关的几个片段把这些片段作为上下文塞进提示词由模型基于资料生成回答。这样模型不需要提前记忆你的产品信息也极大降低了乱编的概率。拆解下来需要五个模块文档加载读入 FAQ 文本、文本切分把长文档切成小块、向量化把文本转成向量、检索找出最相关的块、生成模型基于上下文回答。其中向量化这一步在生产环境通常要借助专门的向量数据库但完整跑起来涉及第三方服务对入门体验不太友好。所以我下面用 LangChain 的Document 一个简化检索器代替重点先让你看懂链路怎么通。from langchain_core.documents import Document # 模拟加载 FAQ 文档 faq_text [ 发票开具付款成功后可在个人中心申请电子发票通常在24小时内发送到邮箱。, 退货政策签收后7天内支持无理由退货商品需保持完好且不影响二次销售。, 保修服务整机保修一年主要零部件保修三年非人为损坏免费维修。, ] docs [Document(page_contentt) for t in faq_text]这段代码把三句 FAQ 变成了三个Document对象。真实项目里你会用文档加载器读取几十页的 PDF、Word 或网页LangChain 提供了大量现成的加载器这一步思路是一样的。4.2 核心链路组装与运行先定义一个简化检索器功能是按关键词打分挑出最相关的资料块class SimpleRetriever: def __init__(self, docs): self.docs docs def __call__(self, question: str) - str: # 对所有资料块打分返回得分最高的前两个 scored [] keywords [发票, 退货, 保修] for doc in self.docs: score sum(1 for kw in keywords if kw in doc.page_content and kw in question) scored.append((score, doc.page_content)) scored.sort(keylambda x: x[0], reverseTrue) top scored[:2] return \n\n.join(block for _, block in top if block)这里为了演示我用简单的关键词匹配。真实项目里这一步应该是“向量相似度检索”用 embedding 模型把 query 和所有文档块转成向量然后算余弦相似度取 Top-K。用简化版是想先让你理解 Retriever 的本质——它就是一个“喂进问题返回资料块”的可调用对象至于是关键词选还是向量选只是内部实现差异。接下来组装整条链from langchain_xxx import ChatXxx from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough # 1. 初始化模型 model ChatXxx( modelmodel-name, temperature0.3, ) # 2. 提示词模板明确要求模型只依据资料回答 prompt ChatPromptTemplate.from_messages([ (system, 你是一个产品问答助手。请使用中文回答问题。只依据给定资料不要编造信息。资料不足时明确说明。), (human, 资料\n{context}\n\n问题{question}), ]) # 3. 构造检索器 retriever SimpleRetriever(docsdocs) # 4. 组装链 chain ( {context: retriever, question: RunnablePassthrough()} | prompt | model | StrOutputParser() ) # 5. 调用测试 question 我买的设备有问题想退货怎么处理 answer chain.invoke(question) print(answer)这段代码是整章的核心我拆开解释一下组装过程。{context: retriever, question: RunnablePassthrough()}是一个“字典输入加速器”它同时负责两件事把用户的问题原封不动传给question字段同时把问题交给retriever去检索资料检索结果存入context字段。这两个结果会一起流到下一步的prompt里填充模板占位符。RunnablePassthrough()可以理解成一个透明通道“什么都不做原样传递”。这种写法在 LCEL 里很常见它的作用是明确告诉链有一部分输入直接透传有一部分需要经过某个处理环节。这样的好处是各环节职责清晰以后你想换成真正的向量数据库检索器只需要替换retriever这个对象链的其他部分完全不用动。跑一下示例。问“我买的设备有问题想退货怎么处理”模型会基于检索到的退货政策资料回答而不是自己发挥。这正好体现了 RAG 的核心价值答案有依据可溯源不依赖模型的内部知识。4.3 运行效果与调优方向在实际调试时你会发现有几个参数对回答质量影响很大。第一个是temperature问答类场景建议 0.2~0.4。设太高模型容易自由发挥甚至脱离资料设太低又显得机械。第二个是检索返回的文档块数量我的示例返回了前 2 块如果资料库很大可能需要返回 5~8 块才能覆盖足够的信息面但也别太多超出上下文窗口限制不说无关内容越多模型越容易被带偏。文档切分是一个被人低估的细节。假设你有一份完整的产品说明书几万字的文档直接整个塞给模型既超上下文窗口检索效果也差。通常做法是按章节切分每块控制在 500~1000 字左右相邻块之间留一部分重叠否则中间断开的信息会丢失语义。切分太大检索定位就不精确切分太小上下文信息就不完整这个平衡需要根据你的文档类型慢慢试。还有提示词里必须明确“只依据资料回答不知道就说不知道”。不加这句模型碰到资料里没有的信息时会强行脑补编出看似合理实则错误的答案。加了这句之后回答质量会明显上一个台阶这是成本最低但是收益最高的调优措施。5. 进阶场景记忆与工具调用5.1 用显式消息历史实现多轮对话记忆上面的问答助手是无状态的每次调用都是孤立的。你问“退货政策是什么”它回答了你再追问“具体怎么申请”它并不知道你刚才问过退货。多轮对话需要把历史消息传给模型LangChain 里最推荐的做法是用MessagesPlaceholder在模板中留一个“历史消息”插槽from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import HumanMessage, AIMessage prompt_with_history ChatPromptTemplate.from_messages([ (system, 你是一个耐心的产品助手。), MessagesPlaceholder(variable_namehistory), (human, {question}), ]) chain prompt_with_history | model # 调用时把历史消息作为列表传入 answer chain.invoke({ history: [ HumanMessage(content我想退货), AIMessage(content请问您在哪个平台购买的), ], question: 我在商城买的怎么申请, })这种做法比早期版本的ConversationBufferMemory更直观。旧式 Memory 把历史状态藏在对象内部调用时还要单独维护记忆链状态一多很容易糊涂。显式把history作为输入传进去语义清晰也方便调试。你可以把历史的构建逻辑封装成一个函数比如从数据库或 Redis 里读取最近 N 轮消息拼装成列表再传给链。这里有两个容易忽略的成本问题。第一历史消息越长每次请求消耗的 token 越多每次多花的钱都是真实成本第二模型服务商的上下文窗口有上限历史太长会把窗口占满导致当前问题被截断。务实的做法是只保留最近 5~10 轮消息或者用一个摘要器把旧对话压缩成一段话的概要。这些都属于工程优化入门阶段先把显式历史消息跑通后面再逐步加策略。5.2 让模型学会调用外部工具Agent 是 LangChain 里最有想象力、也最容易让人沉迷的功能。它的思路是把若干“工具”交给模型由模型自己判断什么时候调用哪个工具而不是我们写死调用逻辑。比如给模型准备两个工具查天气、算数。用户问“帮我算一下 3 箱每箱 12 瓶一共多少瓶”模型识别出这是计算任务就会调用计算工具拿到结果再组织语言回答。这种行为循环有个专门的称呼叫 ReAct——Reasoning思考 Acting行动大致过程是模型先想“这个问题需要工具”然后调用工具拿到观测结果最后基于观测结果给出答复。LangChain 中定义一个工具非常简单from langchain_core.tools import tool tool def calculate(expression: str) - str: 计算数学表达式比如 3*12 返回 36。 # 生产环境建议用安全的表达式解析库不要直接用 eval return str(eval(expression))重点在函数的文档字符串模型就是靠这段描述来决定何时调用这个工具的所以工具说明要写清楚“是干什么的”和“输入格式是什么”。接入 Agent 之后模型会在内部反复执行“思考-调用-观察”的循环直到得出最终答案。这个机制很强大但也有明显弱点循环次数不可控延迟和费用都会增加模型选错工具的概率也不是零生产环境对 Agent 的工具调用结果必须做严格的校验和限制。我的建议是先把前面主线玩熟再来碰 Agent否则很容易陷入“功能很炫但工程上不稳定”的泥潭。6. 常见问题与排查技巧实录6.1 高频报错与处理方案我把新手阶段最常遇到的问题整理成了表格每个我都实际踩过或见过现象可能原因解决思路调用时报认证失败API Key 没配好或已失效检查环境变量是否正确加载确认 Key 有权限且未过期初始化成功但 invoke 报模型不存在模型名填写错误对照官方当前模型列表确认服务商是否支持该模型ImportError: 找不到某个模块缺少对应适配包或导包路径错误确认已安装langchain-xxx适配包检查 import 路径是否带core前缀链运行时提示版本 API 差异升级了 LangChain 导致接口变更查看 release notes按新版调整最稳妥的是回退到验证过的版本提示上下文超长文档块太多或历史消息太长减少检索返回块数量、截断历史消息、增大文本切分块大小输出不是预期结构没有用 Output Parser 或模板约束不严在提示词里给出明确的输出格式示例配合 PydanticOutputParser 做结构化输出检索结果为空或无关切分粒度不合适、检索器打分策略太粗糙调整切分块大小与重叠长度换用真正的向量检索或扩大返回数量以“上下文超长”为例详细说一下。如果你完整地把一份几万字的产品手册塞给模型服务商会返回长度超限的错误。这本质上是在提醒你你的输入已经超过模型的窗口能力。解决思路有两个方向一是减少输入二是换更大的窗口模型。减少输入最直接的手段就是控制检索块的数量和长度这也就是为什么 RAG 系统要把文档切碎而不是整篇灌进去。6.2 稳定性与成本控制经验实操中最大的感慨是Demo 能跑和系统能稳定跑是完全不同的事。几个经验分享给你。第一固定版本是底线。LangChain 更新的速度堪称疯狂0.1 到 0.3 的 API 变化已经让大量旧代码失效。团队项目里一旦升级版本往往要花一天时间全局排查性价比极低。生产项目锁版本升级必须单独评估。第二链路中任何一个环节都可能出错所以要分层排查。我会先单独测试模型直接model.invoke(你好)确认模型接入没问题再测 prompt打印填充后的消息内容确认变量都没漏最后才测整条链。像 4.2 里的链式写法在哪一层断掉的检查对应的组件即可这比对着堆栈逐行猜快得多。第三注意成本。LLM 应用的成本是隐形的线上问答助手跑一天账单数字可能超出预期。做两件事来控成本设置每个用户每小时的调用频率上限对高重复度问题加一层缓存检索结果和模型输出一样可以缓存。这一块算是从“会搭”走向“能上线”的关键门槛。第四保持一份“提示词与链路变更记录”。改 prompt 和改代码在效果上的影响是同样重要的。但日常开发中代码有版本管理prompt 往往改来改去就忘了上一版为什么改。养成把每次 prompt 调整的原因和效果写下来的习惯后期调优会省大量时间。最后一个实践心得刚开始学的时候不要把注意力全放在 Agent 上。先把模型接入、提示词模板、输出解析、链式组装这条主线玩熟再接触检索和记忆。RAG 应用已经能覆盖大量真实场景了Agent 是锦上添花而不是必需品。工具调用玩了三天之后我最大的体会就是模型选工具的能力确实比想象中聪明但产品落地时稳定性永远是排在第一位的。
延伸阅读

更多相关文章

2026/10/22 5:57:45

铁磁谐振与微机消谐装置:现场判断、选型与实战解析

半夜两点电话响起来的那次经历,我到现在还记得很清楚。某110kV变电站的10kV母线电压互感器柜发出周期性异响,后台报文一直弹"母线接地告警",值班员对照规程准备按单相接地去拉路找故障线路。我拦下来了:不是接地&#x…

2026/10/22 5:52:45

光伏短期功率预测:多云天气误差突增的根因与工程解法

简介:本资源聚焦光伏电站短期发电功率预测这一关键工程问题,面向新能源电力系统研究人员、智能算法开发者及高校相关专业师生,提供一套融合LSTM与SVR的混合建模完整实现方案。资源包共78个文件,涵盖6个Python核心脚本(…

2026/10/22 5:52:45

Qt5.14.2 MinGW静态编译版部署与避坑指南

简介:本资源是基于Qt 5.14.2源码静态编译完成的MinGW 7.3.0 32位开发套件,专为Qt桌面端C开发者设计,尤其适用于需生成无依赖可执行文件、规避动态链接库部署问题的嵌入式调试、离线分发或教学演示场景。压缩包为RAR格式,体积达865…

2026/10/22 7:12:48

ORACLE操作同义词语句

1.创建同义词 create (or replace)(public) synonym name (同义词名字) for target(目标名称); 示例:A用户data表,要给B用户使用,再赋予B用户权限后,B用户使用data表需要加前缀&…

2026/10/22 7:12:48

【iOS】工具库:Nordic DFU、CryptoKit、Kingfisher

文章目录第一部分:Kingfisher —— 图片加载神器1. 安装2. 基础用法3. 进阶技巧:占位图与过渡动画**学习重点**:理解 Kingfisher 的 Options 配置,比如 downsampling (降采样) 可以防止大图撑爆内存。第二部分:CryptoK…

2026/10/21 4:53:08

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/22 0:01:49

电商智能运营体系从0到1:架构设计、落地步骤与避坑指南

前两年我们团队接了一个电商平台的运营中台项目,目标很直白:把过去靠运营拍脑袋做的选品、定价、发券、推活动这些事,逐步用数据算法和自动化流程代替。项目名字内部就叫“电商智能运营体系”。最近有不少朋友问我当时是怎么设计的、踩过哪些…

2026/10/22 0:01:49

若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定

最近一段时间,我一直想把后台管理系统里那些机械重复的增删改查代码交给AI去写。框架用的是若依,按理说这类业务代码模式非常固定,应该是AI最擅长的事情。可真试下来却不是那么回事——你让AI生成一个用户管理模块,它给出的Contro…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑