1. 从“调API”到“造系统”:大模型应用开发到底在学什么
很多人第一次接触大模型应用开发,都是从一行openai.ChatCompletion.create()开始的。调通那一刻确实兴奋,感觉自己摸到了新时代的门槛。但很快就会发现,能跑通一个对话demo和能交付一个真正可用的应用之间,隔着一整条技术栈的鸿沟。我最初也是这样,以为学会调API就等于会做大模型开发,结果第一个真实需求就把我打回了原形——用户要的是一个能读懂公司内部文档、能记住上下文、能调用外部工具、还能在回答不确定时主动追问的助手,而不是一个只会闲聊的玩具。
这就是我决定系统学习大模型应用开发的起点。大模型应用开发,说白了就是把大模型当作一个能力内核,在它外面搭建一套完整的工程系统,让它可以稳定、可控、可扩展地解决具体业务问题。它涉及的核心技术点包括Agent(智能体)、LangChain、LangGraph、RAG(检索增强生成)这几大块。Agent负责让模型自主决策和调用工具,LangChain提供了串联各种组件的基础抽象,LangGraph解决了复杂流程的编排问题,RAG则让模型能够基于外部知识库作答。这几样东西不是孤立的,它们在实际项目里往往是交织在一起使用的。
这篇文章适合谁看?如果你已经会写Python、调过大模型API,但面对“做一个企业知识库问答”“搭一个能自动处理工单的智能体”这类需求时不知道从何下手,那这篇内容就是写给你的。我不会只讲概念,而是把我自己踩过的坑、选型时的纠结、调试时的思路都摊开来讲。你会看到我是怎么从零开始,一步步把LangChain、LangGraph、RAG这些技术点串成一条完整的学习路线的。
2. 学习路线整体设计与技术选型思路
2.1 为什么我没有一上来就啃LangGraph
刚开始学习的时候,最容易犯的错误就是贪多。网上搜“大模型学习路线”,出来的结果动辄几十个技术栈,从Transformer原理到RLHF微调,从向量数据库到Agent框架,看得人头皮发麻。我一开始也试图按那个路线走,结果学了两个月还在调环境,连一个完整项目都没跑起来。后来我调整了策略:以项目驱动学习,用到什么学什么,先跑通再深挖。
具体来说,我把学习分成了三个阶段。第一阶段是打地基,核心是理解大模型API的调用方式、Prompt Engineering的基本技巧、以及Token和上下文窗口的概念。这个阶段不需要任何框架,纯手写Python调用就行。第二阶段是学LangChain和RAG,因为这是最容易看到成果的部分——搭一个本地知识库问答系统,能立刻感受到“大模型+外部知识”的威力。第三阶段才是Agent和LangGraph,因为这两个涉及更复杂的编排逻辑,需要前两个阶段的基础打牢了才好理解。
提示:不要被“学习路线”绑架。每个人的基础不同,如果你已经有后端开发经验,完全可以跳过一些基础环节,直接从LangChain入手。关键是保持“能跑起来”的正反馈。
2.2 LangChain和LangGraph到底什么关系
这是我在学习过程中被问得最多的问题,也是我自己困惑了很久的地方。简单来说,LangChain是一套工具集和抽象层,LangGraph是一个流程编排引擎。LangChain提供了Chain、Tool、Memory、Retriever这些组件,让你可以快速拼装出一个LLM应用。但当你需要实现循环、条件分支、多Agent协作这类复杂逻辑时,LangChain的Chain抽象就不够用了——它是线性的,很难表达“如果A则走B,否则回到C”这种流程。
LangGraph就是为了解决这个问题而生的。它把整个应用建模成一个状态图(State Graph),每个节点是一个处理步骤,边定义了步骤之间的流转条件。你可以把它理解成给大模型应用画了一张流程图,模型在图的节点之间流转,状态在流转过程中不断更新。我自己的体会是:简单的线性流程用LangChain就够了,一旦涉及循环、分支、多轮工具调用,就应该上LangGraph。两者不是替代关系,而是互补关系,实际项目里经常是LangChain的组件嵌在LangGraph的节点里用。
2.3 RAG不是万能药,但没有RAG万万不能
RAG(Retrieval-Augmented Generation)是我认为最值得优先掌握的技术点。原因很简单:大模型有两个硬伤,一是知识有截止日期,二是不知道你私有的数据。RAG通过“先检索、再生成”的方式,让模型在回答时参考外部知识库,既解决了时效性问题,又避免了微调的高成本。
但RAG也不是银弹。我见过太多人以为搭个向量数据库、把文档塞进去、检索出来拼到Prompt里就完事了,结果实际效果一塌糊涂。问题出在检索质量上——如果检索出来的内容不相关,模型再强也生成不出正确答案。所以RAG的核心难点不在“生成”,而在“检索”。这就涉及到文档切分策略、Embedding模型选择、向量数据库选型、检索结果重排序等一系列工程细节。后面我会专门用一章来讲这些。
3. 核心细节解析与实操要点
3.1 环境搭建:Conda还是Venv,这是个问题
大模型应用开发的环境管理比普通Python项目要复杂,因为涉及到PyTorch、CUDA、各种向量数据库客户端等重型依赖。我一开始用Venv,后来换成了Conda,原因很简单:Conda在处理二进制依赖和CUDA版本冲突方面要省心得多。特别是如果你打算在本地跑Embedding模型或者做微调,Conda几乎是必选项。
我的环境配置是这样的:Python 3.10(不要用3.12,很多库还没适配),Conda创建独立环境,PyTorch根据显卡选择对应CUDA版本。如果你用的是AMD显卡比如RX 6750 GRE,那就要走ROCm路线,配置会麻烦一些,但社区里有不少教程可以参考。对于纯API调用为主的开发,其实不需要本地GPU,直接用云端API就行,环境配置会简单很多。
conda create -n llm-dev python=3.10 conda activate llm-dev pip install langchain langchain-community langchain-openai pip install langgraph pip install chromadb sentence-transformers注意:LangChain的包拆分很细,
langchain是核心抽象,langchain-community是社区集成,langchain-openai是OpenAI的官方集成。不要只装一个langchain就以为万事大吉了。
3.2 Prompt Engineering:别小看这门手艺
很多人觉得Prompt Engineering就是“会说话”,没什么技术含量。但我在实际项目里发现,Prompt的质量直接决定了应用的上限。同样的模型,同样的知识库,Prompt写得好和写得差,效果差距可能是天壤之别。
我总结了几条实用的Prompt原则。第一,角色设定要具体。不要写“你是一个助手”,而要写“你是一个专门处理售后工单的客服专家,熟悉退换货政策,回答时先确认订单号”。第二,输出格式要约束。如果你需要模型返回JSON,就在Prompt里明确给出JSON schema,并加上“只返回JSON,不要有其他内容”。第三,Few-shot示例比长篇指令更有效。给两三个输入输出的例子,比写五百字的规则说明管用得多。
还有一个容易被忽略的点:Prompt的版本管理。当你的应用有几十个Prompt模板时,散落在代码里会非常难维护。我后来把所有Prompt抽到一个单独的YAML文件里,用的时候加载进来,改起来方便,也方便做A/B测试。
3.3 文档切分:RAG效果的第一道分水岭
RAG的第一步是把文档切分成小块(Chunk),然后对每个块做Embedding。切分策略直接决定了检索质量。我试过固定长度切分、按段落切分、按语义切分三种方式,效果差异非常明显。
固定长度切分最简单,比如每500个字符切一块,重叠50个字符。但问题是它会把一个完整的句子或段落拦腰截断,导致检索出来的内容语义不完整。按段落切分效果好一些,但遇到长段落还是会出问题。我最后采用的是递归字符切分,LangChain里的RecursiveCharacterTextSplitter就是干这个的。它的逻辑是:先按双换行切,如果块还是太大,再按单换行切,再不行按句号切,层层递进,尽量保持语义完整性。
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=800, chunk_overlap=100, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_text(document)chunk_size设多少合适?我的经验是500到1000之间,具体取决于你的文档类型和Embedding模型的最大输入长度。chunk_overlap设成chunk_size的10%到20%,目的是让相邻块之间有上下文衔接,避免检索时丢失跨块的信息。
3.4 Embedding模型选择:不是越贵越好
Embedding模型负责把文本转成向量,它的质量直接影响检索的准确率。市面上的选择很多,从OpenAI的text-embedding-3到开源的BGE、M3E、GTE,各有优劣。我的建议是:如果预算允许且数据不出境,用OpenAI的embedding模型最省心;如果要求本地部署,BGE-large-zh-v1.5是目前中文场景下性价比很高的选择。
选Embedding模型时要注意两个参数:向量维度和最大输入长度。维度越高,表达能力越强,但存储和检索成本也越高。最大输入长度决定了你的chunk_size上限,如果模型只支持512个token,你切1000个字符的块就会被截断。我一般会先用小规模数据测试几个模型,用检索命中率来选,而不是盲目追求排行榜上的第一名。
4. 实操过程与核心环节实现
4.1 从零搭一个本地知识库问答系统
这是我学习的第一个完整项目,也是我认为最适合入门的RAG实战。目标很简单:把一批PDF文档灌进去,然后能通过自然语言提问,系统检索相关段落并生成回答。整个流程分为四步:文档加载、切分、向量化存储、检索生成。
文档加载用LangChain的PyPDFLoader或者UnstructuredFileLoader,前者适合纯文本PDF,后者适合有表格和复杂排版的文档。加载后得到的是Document对象列表,每个对象包含page_content和metadata。metadata很重要,后面检索时可以带上来源信息,方便溯源。
from langchain_community.document_loaders import PyPDFLoader from langchain_community.vectorstores import Chroma from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.chains import RetrievalQA # 加载文档 loader = PyPDFLoader("knowledge.pdf") docs = loader.load() # 切分 from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100) chunks = splitter.split_documents(docs) # 向量化存储 embeddings = OpenAIEmbeddings() vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory="./db") # 检索生成 llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) qa_chain = RetrievalQA.from_chain_type( llm=llm, retriever=vectorstore.as_retriever(search_kwargs={"k": 4}), return_source_documents=True ) result = qa_chain.invoke({"query": "公司的报销流程是什么?"}) print(result["result"])这段代码看起来简单,但里面有几个关键参数值得展开说。search_kwargs={"k": 4}表示检索最相关的4个块,k值太小可能漏掉关键信息,太大则会引入噪声。我一般会设3到5之间,然后根据实际效果调整。temperature=0是为了让回答更稳定,减少随机性,这在知识库问答场景下很重要。
4.2 检索质量优化:从“能用”到“好用”
上面那个基础版本跑通后,你会发现效果时好时坏。问题往往出在检索环节。我做了三件事来提升检索质量,效果立竿见影。
第一件事是加入重排序(Rerank)。向量检索是基于语义相似度的,但相似度高不代表相关性强。重排序模型会对初步检索出的结果做一次精细打分,把真正相关的排到前面。我用的比较多的是BGE-reranker,把它接在向量检索之后,检索准确率能提升不少。
第二件事是混合检索。纯向量检索对关键词不敏感,比如用户搜“RX6750GRE”,向量模型可能把它和“显卡”混在一起。加入BM25关键词检索,把向量检索和关键词检索的结果融合,能兼顾语义和精确匹配。LangChain里的EnsembleRetriever就是干这个的。
第三件事是查询改写。用户的提问往往很口语化,直接拿去检索效果不好。我加了一个步骤:先用LLM把用户问题改写成更适合检索的形式,再去查向量库。比如用户问“那个报销的事怎么弄”,改写成“公司报销流程和所需材料”,检索命中率会高很多。
4.3 用LangGraph搭一个多步骤Agent
当你的应用需要多步推理和工具调用时,LangGraph就派上用场了。我拿一个实际场景举例:一个能自动处理用户咨询的Agent,它需要先判断问题类型,然后决定是查知识库、查订单系统、还是转人工。
用LangGraph的思路是这样的:定义一个状态(State),包含用户输入、对话历史、当前步骤、工具调用结果等字段。然后定义节点(Node),每个节点是一个处理函数,比如“分类问题”“检索知识库”“查询订单”“生成回答”。最后定义边(Edge),决定节点之间的流转逻辑。
from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated class AgentState(TypedDict): user_input: str category: str context: str answer: str def classify(state: AgentState): # 用LLM判断问题类型 category = llm.invoke(f"判断以下问题的类型(知识库/订单/其他):{state['user_input']}") return {"category": category.content} def retrieve(state: AgentState): # 检索知识库 docs = retriever.invoke(state["user_input"]) return {"context": "\n".join([d.page_content for d in docs])} def generate(state: AgentState): # 生成回答 answer = llm.invoke(f"根据以下上下文回答问题:\n{state['context']}\n\n问题:{state['user_input']}") return {"answer": answer.content} def route(state: AgentState): if "知识库" in state["category"]: return "retrieve" return "generate" graph = StateGraph(AgentState) graph.add_node("classify", classify) graph.add_node("retrieve", retrieve) graph.add_node("generate", generate) graph.set_entry_point("classify") graph.add_conditional_edges("classify", route, {"retrieve": "retrieve", "generate": "generate"}) graph.add_edge("retrieve", "generate") graph.add_edge("generate", END) app = graph.compile() result = app.invoke({"user_input": "我的订单什么时候到?"})这个例子里,classify节点负责判断问题类型,route函数根据分类结果决定走哪条路。如果是知识库问题,先走retrieve再走generate;如果是其他问题,直接走generate。这就是LangGraph的核心价值——用图的方式表达复杂的控制流,比用if-else嵌套清晰得多,也更容易扩展。
4.4 Agent工具调用的实战细节
Agent的核心能力是调用工具。LangChain里定义工具很简单,用@tool装饰器就行。但实际用起来有几个坑要注意。
第一个坑是工具描述要写清楚。LLM是根据工具的名称和描述来决定调不调、怎么调的。如果你只写“查询订单”,模型可能不知道需要传什么参数。要写成“根据订单号查询订单状态,输入参数为订单号字符串”。
第二个坑是工具返回结果要控制长度。如果工具返回一大段JSON,会占用大量Token,还可能干扰模型判断。我一般会在工具函数里做一层处理,只返回关键字段。
第三个坑是错误处理。工具调用可能失败,比如网络超时、参数格式错误。如果不处理,整个Agent就崩了。我的做法是在工具函数里用try-except包裹,出错时返回一个友好的错误信息,让模型知道发生了什么,而不是直接抛异常。
5. 常见问题与排查技巧实录
5.1 检索不到相关内容怎么办
这是RAG项目里最常见的问题。排查思路我一般按这个顺序走:先看文档有没有成功加载,打印一下chunks的数量和内容;再看Embedding有没有正常生成,检查向量维度对不对;然后手动拿一个问题去检索,看返回的块相不相关;最后检查chunk_size是不是太大或太小。
如果文档加载没问题但检索效果差,大概率是切分策略或Embedding模型的问题。中文场景下,我强烈建议用专门针对中文训练的Embedding模型,用英文模型效果会打折扣。另外,如果文档里有大量专业术语,可以考虑在切分前先做一次术语标准化,把同义词统一。
5.2 LangChain版本升级导致代码跑不通
LangChain的迭代速度非常快,经常一个小版本升级就有API变动。我被这个问题坑过好几次,后来养成了两个习惯:一是锁定版本号,在requirements.txt里写死版本;二是关注官方迁移指南,升级前先看有哪些breaking changes。
如果你遇到ImportError或者AttributeError,先去查LangChain的官方文档看对应版本的API。很多以前在langchain包里的东西现在移到了langchain-community或者独立包里。比如OpenAI类现在要从langchain_openai导入,而不是langchain.llms。
5.3 Agent陷入死循环怎么破
Agent调用工具时,有时候会反复调用同一个工具,陷入死循环。这通常是因为工具返回的结果没有让模型满意,模型就不断重试。解决办法有两个:一是设置最大迭代次数,LangChain的AgentExecutor有max_iterations参数,设成5到10之间;二是在Prompt里明确告诉模型“如果工具返回结果不理想,不要重试,直接告知用户”。
还有一个更隐蔽的原因:工具描述有歧义,模型不确定该调哪个工具,就来回试。这时候要回去检查工具的名称和描述,确保每个工具的职责清晰、不重叠。
5.4 本地部署大模型选哪个
如果你打算本地跑大模型,Ollama是目前最省心的方案。它把模型下载、量化、推理服务都封装好了,一条命令就能跑起来。模型选择上,中文场景我推荐Qwen2.5系列,7B的版本在消费级显卡上就能跑,效果也不错。如果你的显卡显存有限,可以选量化版本,比如Q4_K_M,牺牲一点精度换更低的显存占用。
但说实话,本地部署的模型在复杂推理和工具调用能力上,和云端旗舰模型还是有差距。我的建议是:开发调试阶段用本地模型省钱,生产环境用云端API保效果。两者可以共存,通过配置切换。
| 问题类型 | 排查方向 | 常用解决手段 |
|---|---|---|
| 检索不到内容 | 文档加载、切分、Embedding | 调整chunk_size、换Embedding模型、加Rerank |
| 代码跑不通 | 版本兼容性 | 锁定版本、查迁移指南、检查导入路径 |
| Agent死循环 | 工具描述、迭代限制 | 设max_iterations、优化工具描述 |
| 回答质量差 | Prompt、检索质量 | 优化Prompt、加Few-shot、提升检索精度 |
| 响应速度慢 | 模型选择、并发 | 换小模型、加缓存、异步调用 |
5.5 关于微调,什么时候该做什么时候不该做
微调是很多人容易冲动去做的事。我的经验是:先试Prompt Engineering,再试RAG,最后才考虑微调。微调的成本很高,需要准备高质量的训练数据,需要GPU资源,而且一旦业务变化,微调过的模型可能又要重新训。大部分场景下,好的Prompt加上RAG就能解决问题。只有当你要改变模型的输出风格、或者让模型学会一种全新的任务格式时,微调才值得考虑。
6. 学习资源与进阶方向
6.1 我实际用过的学习材料
官方文档永远是最好的起点。LangChain和LangGraph的官方文档写得相当详细,而且有大量示例代码。我建议先把官方文档里的Quickstart跑一遍,然后挑几个和你需求接近的示例深入研究。除了官方文档,GitHub上的开源项目也是很好的学习材料,看看别人是怎么组织代码、怎么处理边界情况的。
动手学大模型这类课程适合补理论基础,但不要陷进去。我的原则是:理论学到够用就行,重点是把东西跑起来。遇到不懂的概念,先记下来,等实际用到的时候再回头深挖,这样学习效率最高。
6.2 从单Agent到多Agent协作
当你掌握了单Agent的开发后,下一步自然是多Agent协作。LangGraph支持多个Agent在同一个图里协作,每个Agent有自己的职责和工具集。比如一个客服系统可以有“分类Agent”“知识库Agent”“订单Agent”“质检Agent”,它们通过共享状态来协同工作。
多Agent的难点在于通信和协调。我的经验是:每个Agent的职责要单一,Agent之间的接口要清晰。不要让一个Agent既查知识库又查订单,这样它的Prompt会非常复杂,效果反而不好。拆成多个专职Agent,每个只做一件事,整体系统的可维护性和可扩展性都会好很多。
6.3 关于Agentic RAG和Ontology RAG
这两个是RAG的进阶方向。Agentic RAG的核心思想是让Agent自主决定什么时候检索、检索什么、检索几次,而不是固定的一次检索。Ontology RAG则是引入知识图谱,用实体和关系来增强检索的准确性。这两个方向目前还在快速发展中,实际落地案例不算多,但值得关注。我的建议是先把基础RAG做扎实,再考虑这些进阶方案。
7. 一些踩坑之后的真心话
学大模型应用开发这段时间,我最大的体会是:不要追求把每个技术点都学透了再动手,而是在动手的过程中逐步深入。我见过太多人卡在“Transformer原理还没搞懂”这一步,结果半年过去了连一个RAG都没搭起来。实际上,你不需要完全理解注意力机制的数学推导,也能搭出一个好用的知识库问答系统。先跑通,再优化,再深挖原理,这个顺序对工程导向的学习来说更高效。
另一个体会是:大模型应用开发的核心竞争力不在模型本身,而在工程能力。模型是别人的,API是公开的,但怎么切文档、怎么设计Prompt、怎么编排流程、怎么处理异常,这些才是拉开差距的地方。我花在调试检索质量上的时间,远比花在调API上的时间多。所以如果你问我最该投入精力学什么,我的答案是:RAG的检索优化和Agent的流程编排,这两块学好了,大部分应用场景都能覆盖。
最后分享一个我常用的调试技巧:把每一步的中间结果都打印出来。检索到了哪些块、Prompt长什么样、模型返回了什么、工具调用参数是什么,全部打日志。大模型应用是个黑盒,你不把中间过程暴露出来,根本不知道问题出在哪。我一开始嫌麻烦不打日志,结果一个简单的检索问题查了一整天,后来加上日志,十分钟就定位到了。这个习惯看起来笨,但真的省时间。