恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
LangChain开发路线图:从提示词工程到智能体实战
首页
资讯中心
/
LangChain开发路线图:从提示词工程到智能体实战
LangChain开发路线图:从提示词工程到智能体实战
发布时间:2026/10/22 8:10:29
我接触LangChain不算早早期很长一段时间都停留在“写提示词”的阶段——把模型API的调用包一层能聊天、能翻译就觉得差不多了。真正让我决定认真把整条路子走一遍是第一次接到一个“让AI自动查库存并生成补货建议”的需求。那一次我才意识到提示词写得再好也解决不了模型根本不知道库存数据在哪的问题。后来我把LangChain从提示词工程一路捋到智能体自己搭过问答链、RAG流程、工具调用、带记忆的多轮对话也踩过不少版本和调试上的坑。这篇文章就是一份完整的LangChain开发路线图每一站解决什么问题、核心组件怎么用、代码骨架长什么样、我最后怎么收口。适合已经开始接触大模型、但还没有系统做过Agent类应用的开发者也适合想从零搭一个可维护AI应用的团队做路线参考。需要先说明的是LangChain版本迭代挺快具体API会有细节差异但这条路线的主线一直没变提示词工程解决单次质量链解决多步流程RAG解决数据外挂智能体解决动态决策。整条路线可以拆成六站提示词工程、LangChain基础抽象、链与记忆、RAG、工具调用、智能体。下面逐个展开。1. 为什么提示词工程不是终点先看清整段路的边界1.1 提示词工程解决的是输出质量不是系统集成从模型API的角度看能做的只是“输入一段文本返回一段文本”。提示词工程优化的是同样的模型怎么让它更稳定、更准确地输出我想要的内容。这件事性价比很高也确实考验功力但边界也非常清楚提示词不会帮你解决数据从哪来、历史放哪里、要不要调用外部接口。换句话说它优化的是“最后一跳”的质量而不是整条链路的构成。我在实际项目里见过两种典型卡点。第一种是把模型当数据库用问它内部业务数据模型答不上来就开始堆提示词堆到后面连基础的问答都开始变形。第二种是希望模型自己决定“要不要查一下实时价格”但提示词里根本无法描述清楚实时系统的各种分支。这两个问题都不是提示词工程能解的靠的是后面的RAG和智能体。所以这个路线图的第一课不是教你写更多提示词而是帮你把提示词的边界画清楚。1.2 智能体改的是“谁来决策”不是“能不能用工具”很多朋友第一次接触智能体觉得它就是“给模型接几个工具”。这个理解不准确。工具本身只是能力智能体的核心变化是决策权在普通链里调用哪些工具、按什么顺序调用是开发者在代码里写死的在智能体里模型会在每一轮先看用户请求再决定“现在该调用哪个工具、传什么参数、要不要继续还是直接结束”。举个最简单的例子用户问“帮我查一下订单状态然后催一下发货”。如果是固定链你得先写死“查订单→催发货”。可如果用户只问一句“最近订单怎么这么慢”固定链就不知道要不要触发催发货。智能体的思路是模型先判断用户意图再决定是否调用查单工具、催单工具甚至先问用户一句“您希望我先查哪一笔订单”。这种动态决策才是从“能说”变成“能做”的本质也是为什么整个路线图最终要导向智能体。1.3 六站依赖关系一览我用一张表展示路线的依赖顺序和每站的交付物站点核心问题交付物提示词工程模型输出不稳定、格式不统一可复用的稳定提示词模板LangChain基础抽象换模型、改业务时代码到处是胶水统一模型调用与消息结构链与记忆单次调用没法完成多步任务可编排的链式流程与会话状态RAG模型不知道私有数据和最新数据外部知识检索增强的问答能力工具调用模型只能输出文本无法操作真实系统标准化的外部动作接口智能体流程固定无法应对开放需求能自主决策调用链路的智能体这张表基本就是我的学习顺序。每一站都建立在前一站之上提示词工程解决单次质量LangChain抽象解决可维护性链解决多步编排RAG解决数据外挂工具调用解决动作执行智能体解决动态决策。如果跳过其中任何一站后面都会返工。比如直接上智能体却不懂链你会发现智能体的内部结构完全是个黑盒出了问题不知道在哪一层断的。2. 第一站提示词工程——把需求翻译成模型能听懂的语言2.1 别追求“聪明”追求“可预测”我在各个项目里沉淀下来的提示词结构一般是四到六段角色定位、背景信息、目标任务、处理步骤、输出格式、禁忌项。别小看这个结构它的价值在于“可复用”而不是“这一条多聪明”。比如客服助手的模板写成这样你是一位客服助手负责处理用户的订单咨询。 背景用户可能来自不同渠道问题涉及订单状态、退款进度、物流轨迹。 任务理解用户诉求判断问题类型并给出简洁友好的答复。 步骤 1. 先识别用户意图归类到[订单查询/退款/物流/其他]。 2. 如果信息不足明确告知用户需要补充什么。 3. 最后给出下一步建议。 输出格式严格按以下JSON返回 {category:..., reply:...} 禁忌不要编造订单状态不知道就明确说不知道。这类模板看起来朴素实操中非常管用。关键不是让它“更像人”而是让它“可预测”。可预测的意思是同一类输入输出结构和风格稳定后续程序才好接。如果模型今天给你一句“亲您的快递已经在路上啦”明天给你一长段小作文那下游解析和产品体验都会非常痛苦。2.2 输出格式比输出内容更容易被忽略落地提示词工程时我越来越关注一个点输出格式。因为内容写得再好如果返回的是自由文本下游程序很难稳定解析如果能让模型稳定输出JSON、固定字段整个系统的可靠性立刻上一个台阶。做法上除了在提示词里声明格式还建议给一两个例子——Few-shot。比如上面那段客服模板里补一条“输入: 我的快递怎么还没到输出: {category:物流, reply:您好请提供订单号我帮您核实。}”模型照着例子走准确率高很多。另一种更硬核的做法是写JSON Schema让模型只输出符合结构的字段。这块在LangChain里有专门的输出解析器放在下一站讲但提示词阶段就应该先把“要JSON”这个意识立起来。2.3 提示词迭代的要领与止盈点我见过很多团队在提示词上无限堆规则。这里说一个反直觉的经验规则越堆越多模型会倾向于过度理解甚至开始“编造”条款。我的做法是每轮只改一个变量用同一批测试样本对比切面稳定了再动下一个。温度可以设低一点比如0.0到0.3减少随机性让差别主要体现在提示词上而不是模型当天的心情。另一个止盈点是如果一个问题你已经花了几十条提示词不如换个更强的模型或者把它拆成两个环节。提示词工程的价值不是“用提示词解决所有问题”而是用最便宜的代价把输出质量拉高到能接下一步流程。一旦发现代价失控就该往前走——上流程、上结构、上框架。3. 第二站LangChain核心抽象——多模型下的一致体验3.1 封装的价值不在少写代码而在可替换性如果没有LangChain很多项目的典型写法是业务代码里直接调模型SDKprompt字符串散落在各处返回的文本自生自灭。听起来还能忍可一旦发生两件事就崩了——换一个模型服务商要改所有调用点多个组件需要共享同一个模型的会话上下文时代码到处是胶水。LangChain把这类问题收敛成几组抽象ChatModel负责模型调用Message负责对话消息PromptTemplate负责提示词模板OutputParser负责解析输出Runnable负责把上面这些串成可复用的流程。你写的业务代码依赖的是这套抽象而不是某个具体SDK。换底层模型时只换一个工厂函数链路本身不用动。3.2 用ChatModel和Message组织一次对话以Python为例一次最简单的对话大致长这样from langchain_core.messages import SystemMessage, HumanMessage # create_chat_model 表示接具体模型服务商的封装 # 由你自己实现或按服务商提供的SDK替换 chat_model create_chat_model( your-model-id, temperature0.2, ) messages [ SystemMessage(content你是一个数据库运维助手。), HumanMessage(content帮我解释一下连接池耗尽的原因。), ] response chat_model.invoke(messages) print(response.content)这三行里消息类型决定了模型的输入结构SystemMessage放系统设定HumanMessage放用户输入模型返回的是AIMessage。很多新手习惯把全部东西拼成一个长字符串扔进去能用但不利于系统管理。把消息结构化之后后面要加历史记录、要区分用户和助手轮次都很自然。3.3 提示模板把提示词变成可复用模板实际项目里提示词一定不会写死在代码里。用ChatPromptTemplate把上面客服案例写出来from langchain_core.prompts import ChatPromptTemplate prompt ChatPromptTemplate.from_messages([ (system, 你是一位客服助手负责处理用户的订单咨询。), (user, 用户问题{question}\n请按JSON格式返回{{category: ..., reply: ...}}), ]) pipeline { question: ... # 实际传给模板的变量 }注意两点。一是用占位符{question}把变量抽出来同样的模板可复用在多个接口二是模板本身也是一个Runnable后面可以直接拼进链路。我在项目里的习惯是模板集中放在单独的文件里不散落在业务逻辑中这样产品改一句话也不用翻代码。3.4 输出解析器从文本到结构化结果提示词让模型“尽量JSON”程序还是得自己解析字符串。LangChain提供输出解析器来收口这一层。比如用结构化解析器声明好字段解析不合法时抛错或自动修复。代码示意from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate chain prompt | chat_model | StrOutputParser() result chain.invoke({question: 我的订单怎么还没发货})这里用|把prompt、模型、解析器串成一个整体是LangChain里最有特色的设计——一切都Runnable。StrOutputParser只是取content字段后面你还会遇到能解析成结构化对象的Parser。建议把“解析器”看作数据校验层尽可能早地暴露模型输出的格式问题而不是等业务代码里到处做防御式解析。4. 第三站链与记忆——从单次对话到多步流程4.1 为什么不能一直靠“单次invoke”单次调用很清晰但真实任务很少是单次的。比如“总结这周工单并给出分类统计”需要取工单数据、逐个或批量做分类、汇总结果、生成报告。如果把这么长的逻辑写在一个prompt里上下文会越塞越长模型输出质量也会下降。正确思路是拆成多个小环节每个环节做一件事通过链串起来。LangChain里的链最推荐的写法是LCEL也就是用竖线把Runnable连起来。前面见过prompt | model | parser这就是一条最小的链。还可以插入函数、条件分支、并行分支。关键收益是每一步都是可组合的对象可以单独测试、替换、加回调。4.2 加记忆别把所有历史都塞进上下文多轮对话场景躲不开记忆。最常见的做法是把对话历史拼进messages里。但这里有个新手很容易踩的坑把所有历史无脑塞进上下文很快token就不够模型也会“忘记”更重要的系统指令。我常用的分层做法是这样。短记忆中放最近几轮原始消息直接作为输入中记忆用摘要压缩更早的历史长记忆从对话里抽取用户偏好、故障信息、关键结论存到外部存储。LangChain提供多种记忆组件不过我后来更倾向于只接“消息列表”这类基础记忆其余用业务系统自己管。原因很简单记忆策略和你的业务强相关通用组件往往不够精细。4.3 一条链的实操示例工单分类流水线给一个具体例子会更直观。假设要做工单自动分类加摘要from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser classify_prompt ChatPromptTemplate.from_messages([ (system, 你是一个工单分类器只输出工单类别。), (user, 工单内容{ticket}), ]) summarize_prompt ChatPromptTemplate.from_messages([ (system, 你是一个工单摘要器用一句话概括问题和处理建议。), (user, 工单内容{ticket}\n类别{category}), ]) def load_tickets(): # 从业务系统读取待处理工单 return [...] def process_one(ticket: str): category (classify_prompt | chat_model | StrOutputParser()).invoke( {ticket: ticket} ) summary (summarize_prompt | chat_model | StrOutputParser()).invoke( {ticket: ticket, category: category} ) return {ticket: ticket, category: category, summary: summary} results [process_one(t) for t in load_tickets()]这个例子本身不难难在要不要为每一步单独建链。我的建议是只要一个环节可能出现独立的调整需求就拆成一条子链。比如之后想给分类环节加Few-shot只需要改classify_prompt所在那一段不影响摘要环节。4.4 运行配置与可观测性链一旦长了调试就变成痛点。LangChain提供了运行时配置对象可以传回调、线程ID、用户ID等。我最常用的是回调用来打印每一步的token数、耗时和中间结果。一个朴素的观察是大部分跑偏问题不是模型不行而是某个中间环节返回了脏数据链上根本没暴露出来。所以我的建议是从搭第一条链开始就把“日志打印每一步输入输出”当成标配不要等项目大了再补。链式设计的另一个好处是每一步都可以单独invoke调试时直接把某个环节拿出来喂固定数据省掉很多猜谜时间。5. 第四站RAG——让模型学会查资料再回答5.1 RAG解决的是“模型不知道你不知道的事”任何大模型的训练数据都有截止时间也不可能包含企业内部文档。想让模型回答“上个月项目复盘结论是什么”要么把结论写进提示词要么让它去检索。RAG的思路是后者先从一个知识库里检索出相关的片段把片段拼进上下文再让模型基于这些片段作答。RAG不是LangChain发明的但LangChain把整条链路组件化了文档加载、文本切分、向量化、相似度检索、生成。优势在于每一步都可以替换比如换一个向量库、换一种切分策略链路骨架不用推倒重来。5.2 高质量检索的前提是合理切分很多人觉得RAG效果差是“模型不够聪明”实际排查下来有相当比例是文档切分和召回阶段出了问题。文本切分不是简单按字符数截断切得太短语义不完整切得太长检索噪声大还可能超出窗口。我的经验是优先按文档结构切比如Markdown标题、段落、表格没有结构时用带重叠窗口的字符切分重叠量一般取切分块大小的10%-20%。比如块大小设500字符、重叠80字符这样跨边界的上下文不至于被拦腰截断。切完后建议人工抽20条典型问题跑一遍检索看召回的内容是不是真的有助于回答这一步永远值得做。5.3 向量化与向量存储的选型思路向量化的核心是Embedding模型它把文本变成向量语义相近的文本在向量空间里距离更近。选型上小规模试点用现成的开源或云上向量化服务都可以关键是做一次领域对比把你们业务里的术语问卷、产品文档片段放进测试集算一下检索命中率。不要只看模型榜单分数。向量库的选择我给个朴素标准百万级向量以内、团队不想额外运维用轻量本地方案再往上或者需要高并发、跨地域考虑分布式向量服务。多租户隔离、单条数据更新频率也是选型关键尤其企业内部知识库经常要“删除某条过期数据”如果向量库不支持按条件删除运维会非常痛苦。5.4 一条完整RAG链的最小实现下面代码我不写具体SDK导入因为换成你手头的向量库和Embedding方案也就是两行的事关键是链路形态from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate # 示意占位 from some_embedding_sdk import create_embeddings from some_vector_store import VectorStore docs load_your_documents() # 1. 加载业务文档 splitter SemanticChunker(...) # 2. 按结构切分 chunks splitter.split_documents(docs) vector_store VectorStore(embeddingscreate_embeddings(...)) vector_store.add_documents(chunks) # 3. 写入向量库 retriever vector_store.as_retriever(search_kwargs{k: 5}) rag_prompt ChatPromptTemplate.from_messages([ (system, 只依据资料回答不要编造。资料不足就说明不知道。), (user, 资料\n{context}\n\n问题{question}), ]) def format_docs(docs): return \n\n.join(d.page_content for d in docs) rag_chain ( {context: retriever | format_docs, question: ...} | rag_prompt | chat_model | StrOutputParser() )这段代码把Retriever当Runnable用在LCEL里直接作为数据源输入。注意format_docs这一步很重要它把检索到的多条文档拼成一段干净的上下文保证模型看到的是整块资料而不是列表对象。实际项目中我会在rag_prompt里再加“引用来源编号”的要求方便后续展示溯源。6. 第五站工具与智能体——把“能说”变成“能做”6.1 工具调用让模型输出结构化调用意图如果说RAG让模型能看到之前看不到的数据工具调用就让模型能操作系统里的动作。底层机制是开发者把一批工具的描述和参数Schema告诉模型模型在生成回答时如果判断需要某个能力就输出一个结构化的调用请求框架帮你解析并执行然后把执行结果回传给模型继续生成。这个过程很像人拿到一部电话簿不看内容只看“哪个号码能解决什么问题”。模型的优势不是执行精确计算而是“决策调用哪个工具、传什么参数”执行由真实系统完成。这里有一个很关键的观念转换不要指望模型在prompt里“模拟”调用工具要让模型输出调用意图你去执行真工具再把真实结果反馈给它。6.2 用装饰器把业务函数变成工具大致写法from langchain_core.tools import tool tool def query_inventory(product_id: str) - str: 查询实时库存SKU数量。参数: product_id 商品ID stock your_stock_api(product_id) return f{product_id} 当前库存 {stock} 件 tools [query_inventory]关键不在装饰器本身而在docstring的质量。模型的调用决策主要依赖函数名和说明文字写得含糊模型会瞎猜。我把docstring当成“给模型看的接口说明”来写包含功能边界、参数格式、返回内容、什么情况下适合调用。这比注释给人类看还要重要。6.3 手写一个工具调用循环先别上高层封装有了工具之后最简单的实现是手写循环messages [SystemMessage(...), HumanMessage(...)] for step in range(MAX_STEPS): resp chat_model.invoke(messages) if resp.tool_calls: for call in resp.tool_calls: result run_tool(call) messages.append(ToolMessage(result, call_idcall.id)) else: final_answer resp.content break这个循环是智能体的心脏模型说“我要调用工具A”→你执行→把结果放回messages→模型再看。一直循环到模型不再请求工具输出最终答案。上限步数必须设通常5到10步足够防止模型陷入死循环。LangChain提供了更高层的Agent封装把循环、解析、规划都包好了但我不建议一上来就用高层封装。先把上面这个循环手写两三遍你会对“模型输出调用意图→框架执行→结果回填”有非常清晰的感受再去看高层Agent会容易得多。6.4 别把Agent复杂化它只是一条带决策的链这点多啰嗦一句。很多人一做Agent就想上复杂框架其实通用的智能体在概念上就是“循环执行工具调用的链”。你需要关心的只有三件事给模型哪些工具、工具描述是否清晰、循环终止条件。其他都是工程问题。把这三件事想清楚Agent就自然而然地出现了。7. 第六站从零搭一个可复用的智能体骨架7.1 目标场景内部支持助手我们要做一个内部支持助手既能回答知识库问题又能调用查询工具。用户问“帮我查一下XX项目的交付日期”它就检索项目知识库同时调用项目管理系统查询真实日期。这个场景覆盖了RAG和工具调用非常适合作为智能体骨架。我强调一下做这类系统不要把“会回答”当目标要有明确的评估指标正确检索覆盖率、工具调用准确率、最终回答的引用是否真实。我在实际项目中会把这三项每周拉一次指标长期迭代。指标不清的Agent项目最后都会变成“感觉还行但不知道哪里不行”。7.2 核心代码骨架下面给出一个合并RAG和工具调用的最小结构去掉业务细节但保留关键设计from langchain_core.messages import SystemMessage, HumanMessage from langchain_core.tools import tool tool def query_delivery_date(project_id: str) - str: 查询项目交付日期。参数project_id为项目编号。 return your_pms_query(project_id) tools [query_delivery_date] tool_map {t.name: t for t in tools} SYSTEM_PROMPT 你是内部支持助手。可以检索知识库资料也可以调用工具获取真实数据。 工具调用必须传合法参数。如果资料和数据冲突以工具返回的数据为准。 def retrieve_context(question: str) - str: return format_docs(retriever.invoke(question)) def agent_step(messages): resp chat_model.invoke(messages) if not resp.tool_calls: return resp.content for call in resp.tool_calls: fn tool_map[call[name]] output fn.invoke(call[args]) messages.append(ToolMessage(output, tool_call_idcall[id])) return agent_step(messages) def run_agent(question: str): context retrieve_context(question) messages [ SystemMessage(SYSTEM_PROMPT), HumanMessage(context \n问题 question), ] return agent_step(messages)这里面有两个关键设计。一是先检索把知识库片段拼进系统消息让模型在回答时拥有背景知识二是工具结果作为ToolMessage回填模型可以依据真实数据修正初始判断。把项目管理系统换成邮件、工单、日历骨架不需要大改。7.3 运行中的常见坑第一个坑是参数格式。模型可能把project_id传成“项目一”这种自然语言参数。解决办法是在docstring里明确“参数必须严格使用系统里的项目编号如PJ-2024-001不要用项目名称”必要时再加一层参数校验。第二个坑是工具返回过长占用上下文。建议工具返回前先做摘要只保留关键字段。第三个坑是Agent在循环里反复调用同一个失败工具。我的解是在ToolMessage里明确返回“该工具不可用不要重试”这比单纯设limit步数更有效因为它直接给了模型决策依据。8. 避坑清单与学习节奏我的最后建议8.1 高频踩坑清单坑现象解题思路提示词堆太满模型开始编造少加规则多加例子消息全部拼成一个字符串系统指令和用户内容混合用Message区分角色历史无脑全放上下文上下文爆炸分层记忆文档切分不看质量RAG召回内容答非所问按结构切分加重叠窗口工具描述写得像给人看模型不会调用docstring当模型说明书Agent不设步数上限死循环烧token设MAX_STEPS并限定重试动作这张表我基本是踩一遍记一遍。前三个坑集中在前期后三个坑集中在后期但它们的共性问题是一样的把问题都压给模型解决没有在工程上做分流。8.2 学习节奏建议我给还没走完这条路的朋友一个可操作的节奏。第一阶段花一周专心把提示词工程稳定下来并掌握LangChain的消息、模板、解析器。第二阶段用两周做一个垂直场景的小应用强制自己用RAG接一份真实内部文档不看教程也要能把链路搭通。第三阶段开始接工具先手写循环再尝试高层Agent封装。整体建议三到四周关键是每一步都留一个可运行的最小Demo而不是只读文档。8.3 我个人的几点体会我在实际项目里的感受是LangChain最有价值的不是那些华丽的组件而是它逼着你想清楚抽象边界。每一层做什么、输入输出长什么样全在Runnable这个统一接口里。想明白这一层即使哪天不用LangChain、自己封装一套工具编排体系核心思路也一样。最后分享一个小技巧给模型准备工具时先把业务系统的字段字典整理好做成一个“工具资料包”既能让模型调用更准确也能让新工具接入变得标准化。这个方法我们踩了几次坑才沉淀下来后来在好几个项目里都直接复用。这条路线看起来很长其实每一站都是围着同一个问题转让模型在合适的时机获取合适的信息做出合适的动作。走完一遍之后再回头看提示词工程你会发现它依然是地基但你已经知道上面能盖多高的楼了。