1. 从零起步:我为什么选择大模型应用开发这条路
2023年初,我第一次用API调用大模型完成了一个自动摘要脚本,当时的感觉就像第一次用上智能手机——知道这东西会改变很多事,但具体怎么改变、自己能从中抓住什么,完全没想清楚。两年多下来,我从一个只会写CRUD的后端开发,逐步摸到了大模型应用开发的门道,做过RAG知识库、搭过Agent工作流、踩过上下文管理的坑,也经历过“Demo很惊艳、上线就翻车”的尴尬。这篇文章不是教程,而是把我这两年多的学习路径、技术选型思路、实操中真正卡住我的地方,原原本本拆开讲一遍。
如果你也是后端或全栈出身,想切入大模型应用开发但不知道从哪下手;或者你已经会调API,但一提到Agent、RAG、LangChain就感觉知识点散落一地——那这篇内容应该能帮你省下不少瞎折腾的时间。我会围绕大模型应用开发这条主线,把Agent开发、LangChain框架、RAG知识库、上下文工程这几个核心模块串起来,讲清楚每个阶段该学什么、为什么这么学、实际做项目时会遇到什么问题。
先说一个我自己的判断:大模型应用开发不是“学一个框架”就能搞定的事。它更像是后端开发+数据工程+提示词设计+系统架构的混合体。你不需要成为算法专家,但必须理解模型的边界在哪里,否则你设计出来的系统一定会在某个环节崩掉。下面我按自己实际走过的路径,从基础能力建设到完整项目落地,逐层拆解。
2. 学习路径的整体设计与阶段拆解
2.1 为什么不能一上来就学LangChain
我见过太多人(包括我自己)一开始就扎进LangChain的文档里,结果被Chain、Agent、Tool、Memory、Retriever这些概念绕晕,写了几个Demo之后发现除了“能跑”之外什么也没学到。问题出在顺序错了。
LangChain本质上是一个编排框架,它解决的是“如何把大模型调用、工具调用、数据检索、状态管理串起来”的问题。但如果你不理解大模型本身的调用方式、不理解Token和上下文窗口的限制、不理解Embedding和向量检索的原理,那你用LangChain只是在抄代码,出了问题完全不知道怎么排查。
我的建议是分四个阶段:
- 阶段一:裸调API,理解模型的基本行为。用Python直接调大模型接口,感受Temperature、Top-P、Max Tokens这些参数对输出的影响,理解System Prompt和User Prompt的区别,搞清楚什么是流式输出、什么是Function Calling。
- 阶段二:手写一个最小RAG。不用任何框架,用Embedding模型+向量数据库+Prompt拼接,自己实现一个“基于文档问答”的流程。这一步会让你真正理解RAG的每个环节在做什么。
- 阶段三:引入LangChain/LangGraph做编排。当你手写过一遍之后,再看LangChain的抽象就会很清晰——它只是把你手写的步骤封装成了可复用的组件。
- 阶段四:做Agent和上下文工程。这是进阶阶段,涉及多轮工具调用、状态管理、上下文压缩、评估与迭代。
这个顺序的核心逻辑是:先理解原理,再使用工具;先跑通最小闭环,再追求工程化。
2.2 每个阶段的核心产出物
光说阶段太虚,我列一下我当时每个阶段要求自己必须交付的东西:
| 阶段 | 核心产出 | 验证标准 |
|---|---|---|
| 裸调API | 一个命令行问答脚本 | 能流式输出,能切换模型,能控制参数 |
| 手写RAG | 一个本地文档问答工具 | 能对PDF/Markdown切片、检索、生成回答 |
| LangChain编排 | 一个带记忆的对话机器人 | 能记住上下文,能调用外部工具 |
| Agent+上下文工程 | 一个多步骤任务Agent | 能拆解任务、调用多个工具、处理失败重试 |
每个产出物都不追求功能多,但要求自己能讲清楚每一行代码为什么这么写。这个习惯后来帮了我大忙——线上出问题时,我能快速定位是检索环节、Prompt环节还是模型本身的问题。
2.3 关于“动手学大模型”这类课程的使用方式
网上有很多公开的学习资料,比如上海交大的“动手学大模型”课程,质量确实不错。但我的经验是:课程用来建立知识框架,真正的能力来自自己动手做项目。看课的时候觉得什么都懂了,一上手就发现连环境都配不明白。所以我的做法是,每看完一个章节,立刻用自己的一台机器复现一遍,哪怕只是把示例代码改几个参数跑通,也比光看强。
3. 核心模块深度解析:RAG、Agent与上下文工程
3.1 RAG知识库:从“能问答”到“答得准”的关键细节
RAG是我做的第一个真正有实用价值的大模型应用。当时的需求很简单:公司内部有几百份技术文档,想让新同事能用一个问答界面快速找到答案。我一开始觉得这事不难——文档切片、向量化、检索、拼Prompt、调模型,五步搞定。结果第一版上线后,回答准确率大概只有60%,经常答非所问。
问题出在几个地方,我逐个拆解。
切片策略比你想的重要得多。我最初用的是固定长度切片,每500个字符切一段。结果很多段落被从中间切断,语义不完整。后来改成按标题层级切分,再配合重叠窗口(overlap),检索质量明显提升。具体做法是:先用Markdown的标题结构做一级切分,如果某个章节太长,再按段落做二级切分,每个切片保留前一段的最后50个字符作为上下文衔接。
Embedding模型的选择直接影响检索效果。我试过几种方案:OpenAI的text-embedding-ada-002效果稳定但需要网络调用;本地部署可以用BGE-M3或者M3E,中文场景下BGE-M3的表现相当不错。如果你对数据隐私有要求,本地Embedding模型是更好的选择。实测下来,BGE-M3在中文技术文档上的检索命中率比ada-002高出大约8个百分点。
向量数据库选型要看场景。我用过Milvus、PGVector和Chroma。Milvus适合大规模数据(百万级以上),性能好但运维复杂;PGVector的优势是如果你已经在用PostgreSQL,直接加个扩展就行,不用额外维护一套数据库;Chroma适合快速原型,但生产环境不太推荐。我最终选了PGVector,因为我们的业务数据本来就在PG里,省了一套运维。
检索策略上,混合检索比纯向量检索更稳。纯向量检索在语义相似度上表现好,但对精确关键词(比如错误码、函数名)的匹配能力弱。我的做法是向量检索+全文检索(BM25)各取Top-K,然后用RRF(Reciprocal Rank Fusion)做融合排序。这个改动让检索准确率又提升了大概10个百分点。
实操心得:RAG的调优不是一次性的,需要建立评估集。我当时的做法是人工标注了100个问题和对应的正确文档片段,每次调整切片策略或检索参数后,跑一遍评估集看命中率变化。没有评估集的调优就是瞎调。
3.2 Agent开发:从“单次问答”到“多步执行”的跨越
Agent是我觉得最有意思也最容易翻车的部分。简单说,Agent就是让大模型不仅能回答问题,还能决定调用什么工具、按什么顺序调用、根据结果决定下一步做什么。
我做的第一个Agent是一个“技术文档助手”,能根据用户问题自动决定是检索知识库、查询API文档还是执行代码示例。用的框架是LangGraph,因为它对状态管理和条件分支的支持比LangChain的AgentExecutor更灵活。
Agent的核心难点不在“调用工具”,而在上下文管理。每次工具调用都会产生新的上下文,如果不加控制,几轮之后上下文窗口就爆了。我的解决方案是:
- 工具返回结果做摘要压缩,只保留关键信息
- 维护一个“任务状态”对象,记录当前进展和已完成步骤
- 超过一定轮次后,强制触发上下文压缩,把历史对话总结成简短摘要
这里要提一下上下文工程这个概念。很多人把它和提示词工程混为一谈,其实不一样。提示词工程关注的是“怎么问”,上下文工程关注的是“在什么信息环境下问”。对于Agent来说,上下文工程决定了它能记住什么、忘记什么、什么时候该检索新信息。这个能力比会写几句漂亮的Prompt重要得多。
3.3 LangChain与LangGraph:什么时候用哪个
这个问题我被问过很多次。我的理解是:
- LangChain适合线性的、步骤固定的流程。比如“检索→拼接→生成”这种RAG流程,用LangChain的Chain就能很清晰地表达。
- LangGraph适合有状态、有分支、有循环的流程。比如Agent需要根据工具返回结果决定下一步走哪个分支,或者需要多轮迭代直到满足条件,这时候LangGraph的图结构更合适。
我自己的项目里,RAG部分用LangChain,Agent部分用LangGraph,两者可以共存。不需要二选一。
3.4 模型部署:本地还是云端
这取决于你的场景。如果只是学习和原型开发,直接用云端API最省事。但如果涉及敏感数据或者需要控制成本,本地部署是更好的选择。
本地部署我推荐用vLLM或者Ollama。vLLM的吞吐量更好,适合生产环境;Ollama安装简单,适合个人开发。模型选择上,7B到14B参数的模型在消费级显卡上就能跑,比如Qwen2.5-7B-Instruct在中文任务上表现不错。如果显卡显存够(24G以上),可以尝试32B级别的模型,效果会更好。
注意:本地部署模型时,一定要关注量化方式。GPTQ和AWQ是两种常见的量化方案,前者兼容性好,后者推理速度更快。我实测下来,AWQ量化后的模型在相同显存下能支持更长的上下文。
4. 实操过程:从零搭建一个Agentic RAG系统
4.1 整体架构设计
这一章我把自己做过的一个完整项目拆开讲。需求是:做一个内部技术知识库助手,能回答技术问题、能查询API文档、能执行简单的代码片段验证。
技术栈选型:
- 后端框架:FastAPI(轻量、异步支持好)
- 编排框架:LangChain + LangGraph
- 向量数据库:PGVector
- Embedding模型:BGE-M3(本地部署)
- 大模型:Qwen2.5-14B-Instruct(本地vLLM部署)
- 前端:简单的Streamlit界面
整体流程是:用户提问 → LangGraph入口节点判断意图 → 如果是知识问答走RAG分支 → 如果是API查询走工具调用分支 → 如果是代码验证走代码执行分支 → 汇总结果返回。
4.2 知识库构建的完整步骤
第一步是文档预处理。我把所有技术文档统一转成Markdown格式,然后用LangChain的MarkdownHeaderTextSplitter按标题层级切分。切分后的每个片段加上元数据(来源文件、章节标题、更新时间)。
第二步是向量化。用BGE-M3对每个片段生成向量,存入PGVector。这里有个细节:BGE-M3支持多语言,而且对长文本的处理比很多模型好,适合技术文档这种中英文混杂的场景。
第三步是建立索引。PGVector支持IVFFlat和HNSW两种索引。IVFFlat构建快但查询精度略低,HNSW查询快但构建慢。我选了HNSW,因为查询性能对用户体验影响更大。
# 向量化与存储的核心代码示意 from langchain_community.embeddings import HuggingFaceBgeEmbeddings from langchain_community.vectorstores import PGVector embedding = HuggingFaceBgeEmbeddings( model_name="BAAI/bge-m3", model_kwargs={"device": "cuda"}, encode_kwargs={"normalize_embeddings": True} ) vectorstore = PGVector.from_documents( documents=chunks, embedding=embedding, collection_name="tech_docs", connection_string="postgresql://user:pass@localhost:5432/vectordb", use_jsonb=True )4.3 Agent工作流的实现细节
LangGraph的核心是定义状态和节点。我定义了一个AgentState,包含用户问题、当前步骤、已收集的信息、工具调用历史。
节点设计上,我分了四个:
- 意图识别节点:判断用户问题属于哪一类
- RAG检索节点:从知识库检索相关文档
- 工具调用节点:执行API查询或代码验证
- 结果汇总节点:整合所有信息生成最终回答
条件边的逻辑是:如果意图识别结果是“知识问答”,走RAG分支;如果是“API查询”,走工具调用分支;如果两者都涉及,先走RAG再走工具调用。
这里有个关键设计:每个节点执行完后都会更新状态,但状态中只保留摘要信息,不保留完整的工具返回结果。这样做是为了控制上下文长度。完整的工具返回结果存在外部,状态里只存引用ID。
4.4 上下文压缩的具体实现
上下文压缩我用了两种策略:
- 滑动窗口+摘要:保留最近N轮对话的完整内容,更早的对话用大模型生成摘要
- 工具结果压缩:工具返回的长文本先用小模型做摘要,只把摘要放入上下文
实测下来,这两种策略结合使用,能把上下文长度控制在模型窗口的60%以内,同时保留关键信息。
实操心得:上下文压缩不要等到快满了才做,要在每轮对话结束后就判断是否需要压缩。我一开始是等到报错才处理,结果经常在关键时刻掉链子。
5. 常见问题与排查技巧实录
5.1 RAG检索不准的排查思路
这是最高频的问题。我的排查顺序是:
- 先看切片质量:把检索到的片段打印出来,看是否语义完整。如果片段被切断,调整切片策略。
- 再看Embedding效果:用几个已知答案的问题测试,看正确文档是否在Top-K里。如果不在,考虑换Embedding模型或加入混合检索。
- 最后看Prompt拼接:检索到了正确文档但模型没用好,说明Prompt需要调整。我通常会在Prompt里明确要求“只根据提供的文档回答,不要编造”。
5.2 Agent陷入循环怎么办
Agent有时候会反复调用同一个工具,陷入死循环。我的解决方案是:
- 设置最大迭代次数(通常5-8次)
- 在状态里记录已调用过的工具和参数,如果重复调用相同组合,强制跳出
- 在Prompt里明确告知“如果已经获取了足够信息,请直接生成回答”
5.3 本地模型部署的显存问题
14B模型用FP16加载大概需要28G显存,如果显卡不够,可以用4-bit量化,显存降到8G左右。但量化会损失一些精度,需要评估是否可接受。我的经验是,对于RAG场景,量化后的模型在生成质量上差异不大,但在复杂推理任务上会有明显下降。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方向 |
|---|---|---|
| 回答与问题无关 | 检索结果不相关 | 检查切片策略和Embedding模型 |
| 回答内容编造 | Prompt约束不够 | 加强“只根据文档回答”的指令 |
| Agent反复调用工具 | 缺少终止条件 | 设置最大迭代次数和重复检测 |
| 上下文超限 | 未做压缩 | 引入滑动窗口和摘要机制 |
| 本地模型推理慢 | 未用量化或硬件不足 | 尝试AWQ量化或换更小模型 |
6. 我踩过的坑与后来才明白的事
第一个坑是过早追求框架化。我一开始就用LangChain把所有东西包起来,结果出了问题完全不知道是框架的锅还是自己的锅。后来我把关键环节手写了一遍,再回头看LangChain的源码,才发现很多“魔法”其实很简单。
第二个坑是忽视评估。没有评估集的调优就是盲人摸象。我后来花了整整一周时间标注了200个测试用例,虽然枯燥,但之后每次改动都能快速验证效果,效率反而高了。
第三个坑是低估上下文管理的重要性。我最初觉得上下文就是“把历史对话拼进去”,后来才发现,什么时候该检索、什么时候该压缩、什么时候该丢弃,这些决策直接决定了Agent的智能程度。上下文工程不是提示词工程的子集,它是独立且更底层的能力。
如果让我重新走一遍这条路,我会把更多时间花在理解模型行为和设计评估体系上,而不是追新框架。框架会变,但对问题的理解和解决问题的能力不会变。