戰(zhàn):從Prompt到Agent編排的完整指南)
有一次同事拿著一份 LangChain 教程從環(huán)境配置敲到模型初始化屏幕上的 agent 也真的回答了問(wèn)題。結(jié)果一接到真實(shí)項(xiàng)目就卡住加日志報(bào)錯(cuò)換一個(gè)模型服務(wù)商連密鑰都識(shí)別不了更別提讓 Agent 正確調(diào)用工具。后來(lái)排查半天問(wèn)題不在代碼而在版本和抽象之間的錯(cuò)配。這個(gè)經(jīng)歷很典型。學(xué)習(xí) LangChain 最容易被低估的一點(diǎn)是它不是一個(gè)“安裝后就能一直用”的普通庫(kù)而是一套仍在快速演進(jìn)的應(yīng)用編排體系。從最早期的 Chain到現(xiàn)在的 LCEL、LangGraph、MCP 接入概念層一直在變化。如果只是跟著某篇教程敲代碼很容易在換場(chǎng)景后失去方向。LangChain 真正要解決的核心問(wèn)題不是“怎么調(diào)大模型 API”而是“怎么把大模型應(yīng)用做成一條可維護(hù)、可復(fù)用、可觀測(cè)、可接入外部工具的流水線”。順著這條主線環(huán)境配置、模型初始化、Middleware 中間件、ReAct、AI Agent、MCP、Skills、Prompt 這些看起來(lái)分散的概念其實(shí)都屬于同一張地圖上的不同層次。1. 先別急著寫代碼LangChain 解決的是 LLM 應(yīng)用里的“編排問(wèn)題”很多人第一次接觸 LangChain會(huì)以為它只是把prompt和model拼在一起的封裝庫(kù)。這不算錯(cuò)但只理解到這個(gè)程度遠(yuǎn)遠(yuǎn)不夠。如果只是“輸入一段文本返回一段文本”原生 SDK 甚至 HTTP 請(qǐng)求就夠了根本不需要 LangChain 這種抽象層。真正需要 LangChain 的場(chǎng)景是當(dāng)你的業(yè)務(wù)不止一步調(diào)用時(shí)。比如先判斷用戶意圖再?zèng)Q定是否需要查數(shù)據(jù)庫(kù)查完數(shù)據(jù)庫(kù)后再根據(jù)結(jié)果調(diào)用某個(gè)外部工具工具返回后還要把這些信息整理成一段結(jié)構(gòu)化報(bào)告。如果每一步都用原生 SDK 手寫第一次能跑通第二次會(huì)開(kāi)始復(fù)制粘貼第三次邏輯分支一多代碼就變得不可維護(hù)。LangChain 的價(jià)值在于它把大模型應(yīng)用拆解成可以組合的零件模型封裝、提示模板、輸出解析、記憶存儲(chǔ)、工具注冊(cè)、鏈路編排、狀態(tài)流轉(zhuǎn)。你不需要自己為每個(gè)模型服務(wù)商都寫一套適配層也不需要為了“先檢索再總結(jié)”這種流程去手工拼字符串。但這里有一個(gè)反直覺(jué)的點(diǎn)LangChain 并沒(méi)有替你解決“架構(gòu)決策”。它只是提供了更多選擇。1.1 一個(gè) LLM 應(yīng)用要控制的要素遠(yuǎn)不止“提問(wèn)”實(shí)際落地一個(gè) LLM 應(yīng)用時(shí)需要控制的變量至少有這些模型調(diào)用層不同供應(yīng)商、不同模型版本、超時(shí)時(shí)間、溫度參數(shù)、密鑰管理提示詞層系統(tǒng)提示、用戶輸入模板、few-shot 示例、工具使用說(shuō)明輸入輸出層把用戶文本轉(zhuǎn)成結(jié)構(gòu)化請(qǐng)求把模型返回轉(zhuǎn)成可解析結(jié)果外部工具層數(shù)據(jù)庫(kù)查詢、文件讀取、API 調(diào)用、MCP Server 接入狀態(tài)與記憶層多輪對(duì)話、Agent 內(nèi)部狀態(tài)、任務(wù)歷史可觀測(cè)層耗時(shí)、Token 消耗、失敗原因、調(diào)用鏈路這些要素之間的關(guān)系是“分層”的。模型是底座Prompt 是輸入規(guī)范鏈和 Agent 是流程組織方式工具是外部能力MCP 是工具接入?yún)f(xié)議中間件負(fù)責(zé)橫切邏輯。很多教程只講其中某一層導(dǎo)致讀者以為 LangChain 就是“Prompt 拼接工具”。實(shí)際上LangChain 的核心抽象思路是凡是能進(jìn)入一條“處理流水線”的零件都可以被統(tǒng)一成 Runnable。在較新的寫法里一個(gè) Prompt 模板、一個(gè)模型對(duì)象、一個(gè)輸出解析器甚至一個(gè)外部工具都可以用管道符串聯(lián)起來(lái)。# 常見(jiàn)寫法把 prompt、model、parser 串成一條鏈 from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI model ChatOpenAI(modelgpt-4o-mini, temperature0.2) prompt ChatPromptTemplate.from_messages([ (system, 你是一個(gè)嚴(yán)謹(jǐn)?shù)闹形募夹g(shù)助手。), (human, {input}), ]) chain prompt | model | StrOutputParser() result chain.invoke({input: 用一句話解釋 LangChain 是什么}) print(result)這段代碼不是要你背 API而是理解其中的管道思想。prompt負(fù)責(zé)把輸入整理成消息結(jié)構(gòu)model拿到消息后調(diào)用模型StrOutputParser負(fù)責(zé)把模型返回的內(nèi)容轉(zhuǎn)換成文本。每一步都是獨(dú)立零件可以替換也可以插樁。1.2 版本變化不是噪音而是框架演進(jìn)的信號(hào)LangChain 的演進(jìn)路徑會(huì)讓人困惑早期有Chain后來(lái)主推LCEL再往后 Agent 編排越來(lái)越多地出現(xiàn)在 LangGraph 或 Agent 相關(guān)模塊里。加上langchain-openai這類按服務(wù)商拆分的包舊教程里的from langchain.llms import OpenAI可能已經(jīng)跑不通。這其實(shí)不是框架故意折騰人而是它在從“簡(jiǎn)單鏈?zhǔn)椒庋b”走向“可控的圖狀態(tài)編排”。你越早理解這個(gè)方向就越不容易被版本變化打亂節(jié)奏。我的建議是不要把 LangChain 當(dāng)成一套固定 API 去背而是把它當(dāng)成一組能表達(dá) LLM 應(yīng)用結(jié)構(gòu)的“語(yǔ)言”。你首先要知道自己需要的是線性鏈、條件分支、循環(huán)還是復(fù)雜狀態(tài)流然后才去查對(duì)應(yīng)模塊應(yīng)該怎么用。2. 環(huán)境配置與模型初始化先跑通最小鏈路再談中間件和 Agent環(huán)境配置是 LangChain 學(xué)習(xí)中最沒(méi)技術(shù)含量、卻最容易卡住人的環(huán)節(jié)。問(wèn)題往往不在 Python 本身而在依賴組合和版本匹配。常見(jiàn)的坑是這樣的某個(gè)包要求langchain-core的版本范圍是 A另一個(gè)包卻要求 B或者網(wǎng)上教程安裝的是舊版組合你照著裝的時(shí)候已經(jīng)是最新版本接口自然對(duì)不上。所以環(huán)境配置的第一步不是“裝最新”而是“固定一套自己能復(fù)現(xiàn)的依賴組合”。2.1 環(huán)境準(zhǔn)備虛擬環(huán)境 依賴鎖定先建一個(gè)干凈的虛擬環(huán)境避免和本機(jī)其他 Python 項(xiàng)目互相污染。python -m venv .venv source .venv/bin/activate # Windows 下執(zhí)行 .venv\Scripts\activate python -m pip install --upgrade pip安裝時(shí)不要一次性把所有包盲裝。LangChain 生態(tài)里核心包、具體服務(wù)商包、工具組件包是分開(kāi)的。你需要明確自己要接哪個(gè)模型服務(wù)商。舉個(gè)例子如果接 OpenAI 風(fēng)格的接口通常需要langchain、langchain-core、langchain-openai這類相關(guān)包如果要接其他服務(wù)商則要裝對(duì)應(yīng)的langchain-xxx。不同供應(yīng)商和不同服務(wù)商的依賴關(guān)系不一樣這就意味著與其直接抄一條安裝命令不如在項(xiàng)目里用requirements.txt或pyproject.toml把版本鎖定。等能正常跑通后再用鎖文件把整套依賴固定下來(lái)。2.2 模型初始化與密鑰管理模型初始化看起來(lái)簡(jiǎn)單但有一個(gè)非常值得注意的習(xí)慣不要把 API Key 硬編碼在代碼里。常見(jiàn)的做法是放在.env文件里程序啟動(dòng)時(shí)加載環(huán)境變量。# .env 示例 LLM_API_KEYyour_api_key_here LLM_BASE_URLhttps://api.example.com/v1 LLM_MODEL_NAMEgpt-4o-mini然后在代碼中從環(huán)境變量讀取配置。import os from langchain_openai import ChatOpenAI model ChatOpenAI( modelos.getenv(LLM_MODEL_NAME, gpt-4o-mini), temperature0.2, api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL, None), )這里需要區(qū)分幾個(gè)參數(shù)的含義model決定模型的能力量級(jí)。不同模型在邏輯推理、指令遵循、成本、延遲上差異很大。temperature控制隨機(jī)性。Agent 場(chǎng)景通常不建議太高否則工具選擇會(huì)不穩(wěn)定。base_url有些服務(wù)商兼容 OpenAI 協(xié)議可以通過(guò)這個(gè)參數(shù)指向自己的服務(wù)地址。如果你的模型來(lái)自不同服務(wù)商就需要選擇對(duì)應(yīng)的 LangChain 包。密鑰管理看起來(lái)是很小的習(xí)慣但實(shí)際影響很大。一旦把 Key 寫死在代碼里項(xiàng)目很可能因?yàn)橐淮未a同步就把密鑰泄露出去。更合理的是讓密鑰與代碼分離同時(shí)為不同環(huán)境準(zhǔn)備不同配置。2.3 最小鏈路驗(yàn)證先證明“輸入到輸出”沒(méi)有斷很多項(xiàng)目失敗不是因?yàn)楹笃?Agent 設(shè)計(jì)復(fù)雜而是從一開(kāi)始就沒(méi)有把最小鏈路驗(yàn)證清楚。所謂最小鏈路就是構(gòu)造一個(gè)最簡(jiǎn)單的問(wèn)題。調(diào)用一次模型。拿到文本結(jié)果。確認(rèn)整個(gè)流程沒(méi)有報(bào)錯(cuò)。順便記錄一次 Token 消耗。不要在一開(kāi)始就引入向量庫(kù)、Agent、MCP 這些組件。每多引入一個(gè)組件就多一個(gè)排查變量。先用單次調(diào)用確認(rèn)密鑰有效、模型名稱正確、網(wǎng)絡(luò)能通、Prompt 和輸出解析器能正常工作。只有最基礎(chǔ)的鏈條穩(wěn)定后面的中間件和 Agent 才有意義。3. 新版 Middleware 中間件把橫切邏輯從業(yè)務(wù)鏈里抽出來(lái)項(xiàng)目標(biāo)題里頻繁出現(xiàn)“新版 Middleware 中間件應(yīng)用”這其實(shí)是 LangChain 系列應(yīng)用從“能跑”走向“能維護(hù)”的分水嶺。中間件這個(gè)概念本身不神秘。它是在請(qǐng)求進(jìn)入和結(jié)果返回的路徑上插入一段與具體業(yè)務(wù)無(wú)關(guān)的公共邏輯。你不需要改動(dòng)主鏈路的業(yè)務(wù)代碼就能完成日志、限流、緩存、重試、輸入校驗(yàn)等操作。LangChain 里的管道式寫法天然適合這種橫切邏輯。你可以把一次invoke()想象成這樣一條流水線用戶請(qǐng)求 - 中間件前的檢查限流 / 入?yún)⑷罩?/ 身份注入 - Prompt 模板 - LLM 模型調(diào)用 - 輸出解析 - 中間件后的處理耗時(shí)日志 / Token 統(tǒng)計(jì) / 結(jié)果緩存 - 返回給業(yè)務(wù)代碼中間件不修改“這道菜怎么做”而是在菜從廚房端出來(lái)的前后負(fù)責(zé)記錄、檢查、過(guò)濾和補(bǔ)償。3.1 為什么中間件在 AI 應(yīng)用里尤其重要傳統(tǒng)服務(wù)端有攔截器、過(guò)濾器、中間件體系是因?yàn)檎?qǐng)求會(huì)經(jīng)過(guò)網(wǎng)關(guān)節(jié)。但 LangChain 應(yīng)用的調(diào)用鏈不像 Web 請(qǐng)求那樣天然分層。每個(gè)業(yè)務(wù)鏈內(nèi)部都可能多次調(diào)用模型如果日志、重試、限流邏輯全部散落在代碼里排查起來(lái)會(huì)非常痛苦。舉一個(gè)典型例子你有一個(gè)任務(wù)需要先總結(jié)用戶輸入再調(diào)用工具查詢訂單最后生成回復(fù)。如果這三步都要記錄耗時(shí)和 Token沒(méi)有中間件時(shí)你就得在每一步前后手動(dòng)加日志。一旦鏈路變長(zhǎng)日志就會(huì)混進(jìn)業(yè)務(wù)代碼別人讀代碼時(shí)根本分不清哪些是業(yè)務(wù)邏輯哪些是公共邏輯。中間件解決的是讓“系統(tǒng)級(jí)關(guān)注點(diǎn)”和“業(yè)務(wù)級(jí)關(guān)注點(diǎn)”分離。業(yè)務(wù)代碼只關(guān)心“怎么完成任務(wù)”日志、限流、重試這類邏輯放到公共層統(tǒng)一處理。3.2 哪些邏輯適合放中間件根據(jù)實(shí)踐有四類橫切邏輯比較適合放進(jìn)中間件層日志與可觀測(cè)記錄輸入摘要、輸出摘要、耗時(shí)、Token 消耗。特別要注意脫敏不要把用戶的隱私字段、API Key、完整上下文都打出來(lái)。限流與配額在進(jìn)入模型調(diào)用之前做檢查和等待避免單個(gè)任務(wù)把請(qǐng)求額度打滿。緩存對(duì)完全可復(fù)用、且和用戶上下文無(wú)關(guān)的結(jié)果做緩存。緩存命中時(shí)可以直接跳過(guò)模型調(diào)用能節(jié)省大量成本。重試與超時(shí)控制模型偶發(fā)超時(shí)或返回格式異常時(shí)進(jìn)行有限次重試。這里特別提醒一句重試要小心。如果一條鏈只做“文本生成”重試通常安全。但如果 Agent 已經(jīng)調(diào)用了真實(shí)業(yè)務(wù)工具比如創(chuàng)建訂單、發(fā)消息、扣減庫(kù)存等盲目重試可能導(dǎo)致重復(fù)執(zhí)行。所以重試邏輯必須配合冪等設(shè)計(jì)或者在調(diào)用非冪等工具前增加人工確認(rèn)。3.3 中間件不是“背 API”而是先理解前置與后置關(guān)于新版中間件的具體類名和包結(jié)構(gòu)不同版本差別比較大。我不建議讀者直接去記憶某個(gè)固定實(shí)現(xiàn)而是先理解它的結(jié)構(gòu)在模型調(diào)用前要做哪些事在模型返回后要做哪些事在異常發(fā)生時(shí)要不要補(bǔ)償或重試。用一個(gè)通用偽代碼來(lái)表達(dá)這種結(jié)構(gòu)# 通用中間件偽代碼僅用于理解結(jié)構(gòu) class LoggingMiddleware: def wrap(self, runnable): def handler(inputs): print(before:, truncate(inputs)) result runnable.invoke(inputs) print(after:, truncate(result)) return result return handler實(shí)際項(xiàng)目里的實(shí)現(xiàn)可能復(fù)雜很多但原理是一致的。先把這種“前置邏輯 / 主調(diào)用 / 后置邏輯”的思維模型建立起來(lái)再去看具體版本對(duì)應(yīng)什么語(yǔ)法就不會(huì)被接口變化繞暈。中間件的邊界同樣要明確。不要把強(qiáng)業(yè)務(wù)分支判斷塞進(jìn)中間件也不要在中間件里塞太多統(tǒng)計(jì)邏輯。否則你會(huì)在排查問(wèn)題時(shí)遇到“流程總在中間件中斷但業(yè)務(wù)代碼里沒(méi)有任何線索”的窘境。4. ReAct 與 AI Agent不是多調(diào)一次模型而是進(jìn)入循環(huán)聊到 AI Agent很多資料都會(huì)提 ReAct。這個(gè)詞有一個(gè)很容易踩的混淆點(diǎn)它和前端框架 React 沒(méi)有任何關(guān)系。雖然拼寫接近但 ReAct 在智能體語(yǔ)境里代表的是“Reasoning Acting”也就是“推理與行動(dòng)交替進(jìn)行”。你會(huì)在搜索框里看到 React 18 的更新批處理機(jī)制、React 面試題、React Router 等內(nèi)容那些屬于前端領(lǐng)域。LangChain 里的 ReAct討論的是模型如何在一輪又一輪的推理和行動(dòng)中完成復(fù)雜任務(wù)。4.1 為什么普通 Chain 不夠用一條普通 Chain本質(zhì)上是“輸入 - Prompt - 模型 - 解析 - 輸出”。它適合任務(wù)路徑完全確定的場(chǎng)景。但現(xiàn)實(shí)里很多任務(wù)并不能一次性決定完整的執(zhí)行路徑。比如用戶問(wèn)幫我查一下訂單 A7 的狀態(tài)如果失敗了就觸發(fā)一次補(bǔ)償重試。如果用普通 Chain你會(huì)先讓模型回答一個(gè) JSON里面包含是否查詢、查詢參數(shù)、后續(xù)動(dòng)作。但這沒(méi)有真正解決“動(dòng)態(tài)決策”的問(wèn)題因?yàn)槟悴荒芴崆爸烙唵螤顟B(tài)是什么也就不能把分支預(yù)先寫在 Prompt 里。ReAct 的思路是把決策和執(zhí)行拆成循環(huán)Thought思考現(xiàn)在需要判斷“訂單 A7 是什么狀態(tài)”。Action行動(dòng)調(diào)用訂單查詢工具傳入訂單號(hào)。Observation觀察工具返回“訂單失敗原因是庫(kù)存不足”。Thought再思考庫(kù)存不足并不適合直接重試應(yīng)該返回用戶并提示補(bǔ)充庫(kù)存狀態(tài)。Final Answer最終回答向用戶說(shuō)明失敗原因。在這個(gè)過(guò)程中模型不是一次性“背出”答案而是根據(jù)工具返回結(jié)果動(dòng)態(tài)決定下一步。這才是 Agent 與 Chain 的本質(zhì)差別。4.2 一個(gè)最小 ReAct 流程的直觀理解下面這段不是某個(gè)具體框架的代碼而是幫助理解循環(huán)結(jié)構(gòu)的偽示意Task: 查詢訂單 A7 狀態(tài)并決定是否重試 Thought: 我需要先查詢訂單狀態(tài)。 Action: query_order(order_idA7) Observation: statusfailed, reasoninventory_not_enough Thought: 庫(kù)存不足時(shí)不適合直接重試應(yīng)該結(jié)束。 Final Answer: 訂單 A7 失敗原因是庫(kù)存不足建議補(bǔ)貨后再處理。關(guān)鍵點(diǎn)在于Action 的參數(shù)和下一輪 Thought 都依賴 Observation 的結(jié)果。所以 Agent 必須有“狀態(tài)”概念它要記住目前已經(jīng)觀察到什么、已經(jīng)調(diào)用過(guò)什么工具、下一步還剩哪些選擇。4.3 LangGraph 和 LangChain 的分工很多搜索詞里都會(huì)出現(xiàn)“LangChain 和 LangGraph 的區(qū)別”。簡(jiǎn)單說(shuō)LangChain 提供的是各種組件和抽象LangGraph 提供的則是能承載循環(huán)和狀態(tài)流轉(zhuǎn)的“運(yùn)行框架”。如果任務(wù)是“查一個(gè)值然后回復(fù)”普通的 Chain 就夠了。如果任務(wù)是“模型動(dòng)態(tài)決定要不要多次調(diào)用工具并根據(jù)中間結(jié)果調(diào)整策略”就進(jìn)入 Agent 場(chǎng)景。Agent 場(chǎng)景通常需要這么幾個(gè)能力維護(hù)多輪思考與觀察的狀態(tài)。支持條件分支和循環(huán)。能在超時(shí)或達(dá)到最大輪數(shù)時(shí)停止。能插入人工審核節(jié)點(diǎn)。這些能力如果全部自己實(shí)現(xiàn)會(huì)很繁瑣。LangGraph 這類方案的價(jià)值不是讓代碼更酷而是讓你把 Agent 決策過(guò)程變成可觀測(cè)、可恢復(fù)、可中止的狀態(tài)流。給一個(gè)實(shí)際建議不要為了“用 Agent 而用 Agent”。如果一個(gè)任務(wù)用鏈加條件判斷就能解決不要引入循環(huán)和狀態(tài)如果一個(gè)任務(wù)確實(shí)依賴動(dòng)態(tài)工具調(diào)用再考慮Agent。Agent 越多不可控邊界越大。4.4 讓 Agent 更容易成功控制工具范圍模型不是工具越多就越強(qiáng)。相反當(dāng)上下文里塞滿十幾個(gè)工具描述時(shí)模型做出錯(cuò)誤選擇或漏