1. 从“会用”到“会造”:大模型应用开发的学习路径拆解
我真正开始系统学习大模型应用开发,是在把聊天窗口里那些“哇,好神奇”的新鲜感消耗完之后。那时候我发现一个很尴尬的事实:我能跟大模型聊得火热,却没法把它变成一个能解决具体问题的产品。问它问题它答得头头是道,但让它去查我本地的文档、调用我的接口、记住我上一轮说过的话,它就立刻露馅。这个落差,就是我从“大模型使用者”转向“大模型应用开发者”的起点。
如果你也处在这个阶段——能调用API,但不知道怎么把大模型串进一个完整的业务流里;听说过LangChain、LangGraph、RAG这些词,但不知道它们各自解决什么问题、什么时候该用哪个;想做一个属于自己的知识库问答或者Agent项目,却卡在环境配置和框架选型上——那这篇内容就是写给你的。我会把我自己踩过的路、绕过的弯、以及最后沉淀下来的学习顺序和实操方法,完整地摊开来讲。核心关键词就几个:大模型、Agent、LangChain、LangGraph、RAG,但我会把它们放在真实的开发场景里讲,而不是干巴巴地念概念。
先说一个我自己的判断:大模型应用开发这件事,门槛不在“模型”本身,而在“应用”这两个字。模型是别人训练好的,你改不了底层参数(除非做微调,那是另一条路),你能控制的是怎么给它喂上下文、怎么让它调用工具、怎么管理它的记忆、怎么把它的输出变成结构化数据。这四件事,分别对应了RAG、Agent、LangGraph和输出解析。把这四块吃透,你就能从“调API的人”变成“做应用的人”。
我给自己定的学习路线是这样的:先用LangChain把最基础的链式调用跑通,理解Prompt、Model、OutputParser这三件套;然后立刻上手RAG,因为这是最容易看到效果、也最贴近实际需求的方向;接着学Agent,让模型学会自己决定调用什么工具;最后用LangGraph把前面这些零散的能力编排成一个有状态、可循环、能处理复杂分支的完整工作流。这个顺序不是拍脑袋定的,而是按照“依赖关系”来的——没有RAG和Agent的基础,直接上LangGraph会非常痛苦,因为你不知道每个节点里该放什么。
提示:不要一上来就追求“全栈大模型工程师”的标签。先把一条链路跑通,哪怕只是“读取本地PDF→切分→向量化→检索→生成回答”这么简单的RAG,跑通一遍胜过看十篇综述。
2. 核心概念拆解:LangChain、LangGraph、RAG、Agent到底怎么分工
2.1 LangChain:不是框架,是“胶水层”
很多人对LangChain的第一印象是“太重了”“抽象太多”。我一开始也这么觉得,尤其是看官方文档的时候,各种Chain、Runnable、Tool、Memory的概念扑面而来,很容易劝退。但后来我想明白了一件事:LangChain的本质不是让你写更少的代码,而是给你一套统一的接口,让你能把不同厂商的模型、不同的向量数据库、不同的工具用同一种方式串起来。
举个例子,你今天用OpenAI的模型,明天想换成Ollama本地部署的模型,如果没有LangChain,你得把代码里所有调用模型的地方都改一遍。但用了LangChain之后,你只需要换一个ChatOpenAI为ChatOllama,其他代码基本不动。这就是“胶水层”的价值——它不生产能力,它只是能力的搬运工。
我学LangChain的方法是“用多少学多少”,而不是从头到尾读文档。具体来说,我先跑通一个最简单的链:
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser prompt = ChatPromptTemplate.from_template("用一句话解释{concept}") model = ChatOpenAI(model="gpt-4o-mini") parser = StrOutputParser() chain = prompt | model | parser print(chain.invoke({"concept": "RAG"}))这段代码虽然简单,但它包含了LangChain最核心的表达式语言(LCEL)思想:用管道符|把各个组件串起来。你理解了这一行,就理解了LangChain一半的设计哲学。剩下的Memory、Tool、Retriever,都是在这个基础上往外扩。
2.2 RAG:给大模型装上“外挂知识库”
RAG是我认为最值得优先投入时间的方向,因为它解决的是大模型最致命的两个问题:知识过时和幻觉。你问大模型一个关于你公司内部文档的问题,它要么不知道,要么一本正经地胡说八道。RAG的思路很直接:既然模型不知道,那我就把相关资料找出来,塞进Prompt里让它基于资料回答。
RAG的完整链路包括:文档加载→文本切分→向量化→存储→检索→重排→生成。每一步都有坑。比如文本切分,你切得太碎,语义不完整;切得太大,检索精度下降。我自己的经验是,中文文档用RecursiveCharacterTextSplitter,chunk_size设在500到800之间,chunk_overlap设在50到100之间,这个范围在大多数场景下比较稳。
向量化模型的选择也很关键。如果你做的是中文RAG,不要直接用OpenAI的text-embedding-ada-002,它在中文上的表现只能说凑合。可以考虑BGE系列或者M3E,这些在中文语义相似度任务上表现更好。如果你要本地部署,Ollama里可以拉nomic-embed-text或者bge-m3,配合Chroma或者FAISS做本地知识库,整套流程可以完全离线跑。
注意:RAG不是“向量检索”的同义词。完整的RAG还包括检索后的重排(Rerank)和生成时的Prompt约束。只做向量检索不做重排,召回的相关文档可能排在后面,导致模型看不到关键信息。
2.3 Agent:让模型自己决定“下一步做什么”
Agent和普通链式调用的区别,我用一个类比来解释:普通链就像流水线,每一步都是你提前定好的,第一步做完做第二步,第二步做完做第三步。Agent则像一个有自主权的员工,你给它一个目标,它自己决定先做什么、再做什么、要不要调用工具、调用哪个工具。
LangChain里的Agent核心是“工具调用”机制。你定义几个工具(比如搜索、计算器、查数据库),然后让模型根据用户的问题自己选择调用哪个。这个过程是循环的:模型思考→选择工具→执行工具→观察结果→继续思考,直到它认为可以给出最终答案。
我踩过的一个坑是:一开始给Agent定义了太多工具,结果模型经常选错。后来我学乖了,工具数量控制在5个以内,每个工具的description写得非常明确,说明“什么时候该用这个工具”,而不是只写“这个工具是干什么的”。比如不要写“查询天气”,而要写“当用户询问某个城市的实时天气、温度、降水概率时使用此工具”。
2.4 LangGraph:把Agent的“思考过程”画成图
LangGraph是我认为最被低估的一个工具。很多人觉得LangChain已经够用了,为什么还要学LangGraph?我的回答是:当你需要循环、分支、状态共享、人工介入的时候,LangChain的链式结构就不够用了。
LangGraph把工作流建模成一张图,节点是函数,边是流转条件。你可以定义条件边,让流程根据当前状态决定下一步走哪个节点。这听起来很抽象,但实际用起来非常自然。比如做一个客服Agent,用户问题先分类,如果是退款就走退款流程,如果是咨询就走咨询流程,如果分类置信度低就转人工。这种逻辑用LangGraph表达非常清晰。
LangGraph和LangChain的关系,我的理解是:LangChain提供了组件(模型、工具、检索器),LangGraph提供了编排这些组件的方式。你可以把LangGraph看作LangChain的上层建筑,它不替代LangChain,而是让LangChain的组件在更复杂的场景下协同工作。
| 维度 | LangChain | LangGraph |
|---|---|---|
| 核心抽象 | Chain / Runnable | StateGraph / Node / Edge |
| 流程控制 | 线性为主,支持简单分支 | 支持循环、条件分支、并行 |
| 状态管理 | 通过Memory传递 | 显式定义State,全局共享 |
| 适用场景 | 简单链式调用、RAG | 复杂Agent、多轮决策、人工介入 |
| 学习曲线 | 较平缓 | 需要理解图论基础概念 |
3. 实操环境搭建:从零把开发环境跑起来
3.1 Python环境与依赖管理
我强烈建议用Conda来管理大模型应用开发的Python环境,不要用系统自带的Python。原因很简单:大模型相关的库更新极快,依赖冲突是家常便饭。Conda可以给每个项目建独立环境,互不干扰。
conda create -n llm-dev python=3.11 conda activate llm-dev pip install langchain langchain-openai langchain-community pip install chromadb faiss-cpu pip install langgraph pip install streamlitPython版本选3.11而不是3.12,是因为有些向量数据库的底层库对3.12的支持还不完善。这个坑我踩过,装FAISS的时候编译报错,换回3.11就顺利了。
3.2 模型接入:云端API与本地部署的取舍
学习阶段我建议先用云端API,因为便宜、稳定、不用折腾硬件。OpenAI的gpt-4o-mini性价比很高,适合练手。但如果你对数据隐私有要求,或者想完全离线运行,那就需要本地部署。
本地部署大模型,目前最省心的方案是Ollama。它把模型下载、量化、推理服务都封装好了,你只需要一行命令:
ollama pull qwen2.5:7b ollama pull nomic-embed-text然后LangChain里这样接:
from langchain_ollama import ChatOllama, OllamaEmbeddings model = ChatOllama(model="qwen2.5:7b", temperature=0) embeddings = OllamaEmbeddings(model="nomic-embed-text")选哪个本地模型?我的经验是:7B参数级别的模型,Qwen2.5在中文任务上表现很稳,适合做RAG的生成模型。嵌入模型用nomic-embed-text或者bge-m3,后者对中文更友好。如果你的显卡显存只有8GB,7B模型用4-bit量化可以跑,但速度会慢一些。显存12GB以上可以考虑14B模型,效果明显更好。
提示:本地部署的模型在指令遵循能力上通常不如云端大模型。做Agent的时候,如果发现模型不按格式输出工具调用,先换云端模型验证逻辑,再回来调本地模型的Prompt。
3.3 向量数据库选型:Chroma vs FAISS
学习阶段我推荐Chroma,因为它自带持久化、API简单、和LangChain集成度高。FAISS性能更好,但它是纯索引库,不负责文档存储,你需要自己管理文档和元数据。
from langchain_chroma import Chroma vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db" ) retriever = vectorstore.as_retriever(search_kwargs={"k": 4})k=4是我常用的检索数量。太小可能漏掉关键信息,太大则噪音多、消耗Token。你可以根据文档的粒度调整,如果chunk比较小,可以设到6到8。
4. RAG实战:从文档到可问答的知识库
4.1 文档加载与切分策略
RAG的第一步是把你的资料变成模型能消化的文本块。LangChain提供了大量DocumentLoader,PDF用PyPDFLoader,Word用Docx2txtLoader,网页用WebBaseLoader。但我要提醒一句:PDF解析是RAG里最脏最累的活。扫描版PDF需要OCR,表格多的PDF解析出来格式全乱,这些都会直接影响后续检索质量。
切分策略上,我试过固定长度切分、按段落切分、按语义切分。最后发现最稳的还是RecursiveCharacterTextSplitter,它按照分隔符优先级递归切分,尽量保持语义完整。
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=600, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_documents(docs)注意separators里我把中文标点加进去了,因为默认的分隔符只考虑英文标点,对中文文档不友好。这个细节很多教程不会讲,但实际做中文RAG的时候非常关键。
4.2 检索优化:混合检索与重排
单纯的向量检索有个问题:它对关键词匹配不敏感。比如用户问“RX6750GRE能跑大模型吗”,向量检索可能返回一堆关于“显卡跑大模型”的通用文档,但漏掉具体提到这个型号的文档。解决办法是混合检索:向量检索加BM25关键词检索,两路召回后合并去重。
LangChain里可以用EnsembleRetriever来实现:
from langchain.retrievers import EnsembleRetriever, BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 4 vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) ensemble = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.4, 0.6] )权重分配上,我一般让向量检索占6成,BM25占4成。如果你的文档里专有名词多,可以调高BM25的权重。
检索之后如果还想再提精度,可以加一个重排模型。重排模型(Reranker)会对召回的文档做精细打分,把最相关的排到最前面。常用的有bge-reranker系列,可以用FlagEmbedding或者sentence-transformers加载。这一步会增加延迟,但对精度提升明显,尤其是召回文档多的时候。
4.3 Prompt模板与幻觉抑制
RAG的生成环节,Prompt写得好不好直接决定回答质量。我的模板是这样的:
RAG_PROMPT = """你是一个严谨的问答助手。请仅根据以下参考资料回答用户问题。 如果参考资料中没有相关信息,请直接说“根据现有资料无法回答”,不要编造。 参考资料: {context} 用户问题:{question} 回答要求: 1. 只使用参考资料中的信息 2. 如果资料中有矛盾,指出矛盾 3. 回答要简洁,不要重复问题 """这个模板里最关键的是“如果资料中没有相关信息,请直接说无法回答”。不加这句话,模型会倾向于用它的预训练知识来补全,导致幻觉。加了之后,虽然有时候会显得“笨”,但可靠性大幅提升。
5. Agent与LangGraph:从单步调用到复杂工作流
5.1 用LangChain构建第一个Agent
Agent的核心是工具调用。我先定义一个简单的工具集:
from langchain_core.tools import tool @tool def search_knowledge_base(query: str) -> str: """当用户询问产品文档、技术手册相关问题时使用此工具。输入应该是完整的问题描述。""" docs = retriever.invoke(query) return "\n\n".join([d.page_content for d in docs]) @tool def calculate(expression: str) -> str: """当用户需要进行数学计算时使用此工具。输入应该是合法的Python数学表达式。""" return str(eval(expression))然后创建Agent:
from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个乐于助人的助手,可以调用工具来回答问题。"), ("human", "{input}"), ("placeholder", "{agent_scratchpad}") ]) agent = create_tool_calling_agent(model, [search_knowledge_base, calculate], prompt) executor = AgentExecutor(agent=agent, tools=[search_knowledge_base, calculate], verbose=True)verbose=True在学习阶段非常有用,它会把Agent的思考过程和工具调用完整打印出来,你能清楚看到模型为什么选这个工具、传了什么参数。
5.2 LangGraph入门:把流程画成图
LangGraph的核心概念是StateGraph。你先定义状态的结构,然后添加节点和边。
from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated from langgraph.graph.message import add_messages class State(TypedDict): messages: Annotated[list, add_messages] route: str def classify(state: State): last_msg = state["messages"][-1].content if "退款" in last_msg: return {"route": "refund"} return {"route": "consult"} def handle_refund(state: State): return {"messages": [("ai", "正在为您处理退款...")]} def handle_consult(state: State): return {"messages": [("ai", "请描述您要咨询的问题...")]} graph = StateGraph(State) graph.add_node("classify", classify) graph.add_node("refund", handle_refund) graph.add_node("consult", handle_consult) graph.set_entry_point("classify") graph.add_conditional_edges("classify", lambda s: s["route"], { "refund": "refund", "consult": "consult" }) graph.add_edge("refund", END) graph.add_edge("consult", END) app = graph.compile()这段代码定义了一个最简单的路由图:先分类,再根据分类结果走不同分支。实际项目中,你可以在每个节点里放更复杂的逻辑,比如调用RAG、调用外部API、甚至嵌套另一个Agent。
5.3 LangGraph的循环与人工介入
LangGraph真正强大的地方是支持循环。比如一个“反思-改进”的写作Agent:生成初稿→自我评审→如果评审不通过就修改→再评审,直到通过或者达到最大轮数。
def generate(state): return {"draft": model.invoke(f"写一段关于{state['topic']}的介绍")} def review(state): result = model.invoke(f"评审以下文字,只回答'通过'或'不通过':{state['draft']}") return {"review_result": result.content} def should_continue(state): if state["review_result"] == "通过" or state.get("round", 0) >= 3: return "end" return "revise" graph.add_node("generate", generate) graph.add_node("review", review) graph.add_node("revise", lambda s: {"draft": model.invoke(f"改进:{s['draft']}")}) graph.add_edge("generate", "review") graph.add_conditional_edges("review", should_continue, {"end": END, "revise": "revise"}) graph.add_edge("revise", "review")这种循环结构用LangChain的链是表达不出来的,但用LangGraph就非常自然。人工介入也是类似,你可以在某个节点前加一个interrupt,让流程暂停等待人工确认。
6. 常见问题与排查技巧实录
6.1 环境与依赖类问题
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 安装FAISS报编译错误 | Python版本过高或缺少C++编译环境 | 换Python 3.11,或安装faiss-cpu预编译包 |
| Ollama连接超时 | 服务未启动或端口被占用 | 运行ollama serve,检查11434端口 |
| LangChain导入报错 | 版本不兼容 | 固定版本,如langchain==0.3.0 |
| 向量检索返回空 | 嵌入模型与文档语言不匹配 | 换用中文嵌入模型如bge-m3 |
6.2 模型输出类问题
Agent不调用工具,而是直接回答,通常是因为工具的description不够明确,或者模型的指令遵循能力不足。解决办法:把工具描述写得更具体,明确“什么时候用”;在system prompt里强调“你必须使用工具来获取信息”;换用工具调用能力更强的模型。
RAG回答“根据现有资料无法回答”,但资料里明明有答案,大概率是检索没召回相关文档。排查步骤:先打印检索到的文档内容,确认是否包含答案;如果没包含,调整chunk_size和检索数量;如果包含了但模型没看到,检查Prompt模板里context是否正确填充。
6.3 性能与成本类问题
本地部署模型推理慢,优先检查是否用了GPU。Ollama默认会用GPU,但如果显存不够会回退到CPU。可以用ollama ps查看模型加载情况。另外,量化等级也影响速度,Q4比Q8快很多,但精度会降。
云端API成本高,学习阶段用gpt-4o-mini或者国内的通义千问、智谱GLM,价格都很低。做Agent的时候,循环调用会消耗大量Token,建议设置最大迭代次数,避免无限循环。
提示:LangGraph的
recursion_limit默认是25,如果流程复杂容易触发。可以在app.invoke(input, config={"recursion_limit": 50})里调高,但不要设太大,否则出问题的时候很难排查。
7. 学习路线复盘:我踩过的坑和给你的建议
回头看我的学习过程,最大的弯路是“贪多”。一开始同时看LangChain、LlamaIndex、AutoGen、CrewAI,每个都浅尝辄止,结果哪个都没学透。后来我强迫自己只用一个框架——LangChain加LangGraph——把RAG和Agent两条线做深,反而进步最快。
第二个坑是“只看不练”。大模型应用开发是实践性极强的领域,你看十篇RAG教程,不如自己拿一份PDF跑一遍完整流程。跑的过程中会遇到各种教程里没写的问题:PDF解析乱码、中文切分不对、检索召回不准、Prompt不生效。这些问题才是真正让你成长的东西。
第三个坑是“忽视评估”。我一开始做完RAG就觉得万事大吉,直到用户问了一个稍微偏一点的问题,回答完全不对。后来我建了一个简单的测试集,把常见问题和预期答案列出来,每次调整参数后跑一遍,看准确率变化。这个习惯让我少走了很多弯路。
如果你现在刚开始,我的建议是:第一周只做一件事,用LangChain加Chroma做一个本地PDF问答,跑通就行,不求完美。第二周加BM25做混合检索,感受一下召回率的变化。第三周学Agent,让模型学会调用你的知识库工具。第四周学LangGraph,把前面的东西串成一个有状态的工作流。一个月下来,你对大模型应用开发的理解会超过绝大多数只看不练的人。
最后分享一个我常用的调试技巧:在LangGraph的每个节点里加日志,把输入状态和输出状态都打印出来。这样当流程走错分支的时候,你能立刻定位是哪个节点的判断出了问题。这个习惯在复杂工作流里能救命。