前面的章节中,为了理清图拓扑、状态流动和分支机制,我们多次用简单的字符串拼接或占位函数模拟了 LLM 的返回结果。在真实的 Agent 系统中,节点的核心工作通常就是与模型交互。
在 LangGraph 中接入模型并不神秘:节点本身就是一个普通的 Python 函数,任何可以通过 Python SDK 或 HTTP 调用的模型,都能放在节点里执行 。但要把商业 API(如 OpenAI)和本地/开源模型(如 Mistral、Qwen、Llama)稳定可靠地接进图结构,仍有一些不可忽视的工程细节:连接池生命周期管理、结构化输出解析、不同开源模型的 Prompt 模板对齐,以及网络抖动时的节点级容错。
环境准备与依赖安装 主流模型接入推荐安装对应的 LangChain 官方生态扩展包:
1 2 3 4 5 6 7 8 pip install langchain-openai pip install langchain-huggingface huggingface_hub pip install langgraph langchain-core python-dotenv
敏感凭证推荐配置在项目根目录的 .env 文件中,通过 python-dotenv 统一加载:
1 2 3 4 5 6 7 8 import osfrom dotenv import load_dotenvload_dotenv() openai_api_key = os.getenv("OPENAI_API_KEY" ) hf_token = os.getenv("HUGGINGFACEHUB_API_TOKEN" )
核心设计法则:LLM 实例必须在节点外部初始化 在编写节点函数时,常见的新手误区是在节点函数内部初始化客户端:
1 2 3 4 5 def bad_summary_node (state: dict ) -> dict : llm = ChatOpenAI(model="gpt-4o-mini" ) res = llm.invoke(...) return {"summary" : res.content}
这种写法会导致每次节点被调度时都重新建立底层 HTTP 客户端,无法复用 HTTP 连接池(Connection Pool)和 Keep-Alive,同时还会增加内存抖动与鉴权开销。
标准模式 :在模块全局或图的组装工厂函数外部完成模型实例初始化,节点函数直接通过闭包引用该实例:
1 2 3 4 5 6 7 8 9 10 11 llm = ChatOpenAI( model="gpt-4o-mini" , temperature=0.2 , timeout=30.0 , max_retries=2 , ) def good_summary_node (state: dict ) -> dict : res = llm.invoke(...) return {"summary" : res.content}
商业 API 方案:接入 OpenAI 1. 基础对话与 Prompt 模板管理 使用 ChatPromptTemplate 组织结构化的 System/Human 消息,保持业务逻辑与提示词解耦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from langchain_openai import ChatOpenAIfrom langchain_core.prompts import ChatPromptTemplatellm = ChatOpenAI(model="gpt-4o-mini" , temperature=0.7 ) prompt_template = ChatPromptTemplate.from_messages([ ("system" , "你是一名资深的技术架构师,擅长用简练的语言做技术提炼。" ), ("human" , "请根据以下背景信息提炼 3 个关键设计考量:\n\n{context}" ), ]) summary_chain = prompt_template | llm def analyze_architecture_node (state: dict ) -> dict : context = state.get("raw_text" , "" ) response = summary_chain.invoke({"context" : context}) return {"key_considerations" : response.content}
2. 结构化输出(Structured Output)驱动状态更新 如果节点需要返回结构化字段(如评分、状态码、布尔决策),直接要求模型返回 JSON 并用正则解析很容易遇到格式错误。推荐使用 LangChain 的 with_structured_output 绑定 Pydantic 模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from typing import List from pydantic import BaseModel, Fieldclass CodeReviewResult (BaseModel ): score: int = Field(description="代码质量评分,范围 1 到 10" ) passed: bool = Field(description="是否达到合并标准" ) identified_issues: List [str ] = Field(description="发现的潜在缺陷或规范问题列表" ) optimization_suggestions: List [str ] = Field(description="重构优化建议" ) structured_llm = llm.with_structured_output(CodeReviewResult) def code_review_node (state: dict ) -> dict : code_snippet = state.get("source_code" , "" ) prompt = f"请严格审查以下 Python 代码并输出审查结果:\n\n```python\n{code_snippet} \n```" review: CodeReviewResult = structured_llm.invoke(prompt) return { "review_score" : review.score, "is_review_passed" : review.passed, "review_issues" : review.identified_issues, "review_suggestions" : review.optimization_suggestions, }
返回的字典与 StateGraph 的 TypedDict 完美契合,省去了编写防御性解析代码的成本。
开源与本地方案:接入 HuggingFace / 本地推理引擎 对于需要私有化部署、离线运行或降低成本的场景,开源模型是重要选择。
1. 使用 HuggingFace Serverless Endpoint 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from langchain_huggingface import HuggingFaceEndpointhf_llm = HuggingFaceEndpoint( repo_id="mistralai/Mistral-7B-Instruct-v0.2" , task="text-generation" , max_new_tokens=512 , temperature=0.1 , do_sample=False , ) def hf_generate_node (state: dict ) -> dict : query = state["query" ] prompt = f"[INST] 请根据用户问题给出技术解答:{query} [/INST]" response = hf_llm.invoke(prompt) return {"answer" : response}
2. 多模型 Prompt 格式的适配考量 不同开源模型在微调时使用了不同的特殊标记(Special Tokens)。如果手动拼接字符串,极容易出现格式错位:
Mistral / Mixtral :[INST] 用户指令 [/INST]
Llama 3 :<|begin_of_text|><|start_header_id|>user<|end_header_id|>\n指令<|eot_id|><|start_header_id|>assistant<|end_header_id|>
ChatML 规范(Qwen、Yi 等) :<|im_start|>user\n指令<|im_end|>\n<|im_start|>assistant\n
为了不把各模型的底层标记硬编码进节点,建议使用 ChatHuggingFace。它会自动读取模型的分词器(Tokenizer)配置并统一消息协议:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpointfrom langchain_core.messages import HumanMessage, SystemMessageendpoint = HuggingFaceEndpoint( repo_id="mistralai/Mistral-7B-Instruct-v0.2" , task="text-generation" , max_new_tokens=512 , ) chat_hf_model = ChatHuggingFace(llm=endpoint) def uniform_chat_node (state: dict ) -> dict : messages = [ SystemMessage(content="你是一个专业的运维排错专家。" ), HumanMessage(content=state["error_log" ]), ] response = chat_hf_model.invoke(messages) return {"diagnostic_report" : response.content}
如果是本地利用 vLLM 或 Ollama 搭建的推理服务,它们均兼容 OpenAI API 规范,只需在 ChatOpenAI 中指定 base_url 即可:
1 2 3 4 5 6 7 local_llm = ChatOpenAI( base_url="http://localhost:11434/v1" , api_key="ollama" , model="qwen2.5:7b" , temperature=0.1 , )
完整实战:基于真实 LLM 的多步内容生成图 我们将关键词提取、大纲生成和文章写作三个步骤,串联成一个具备完整调用的 StateGraph。
flowchart TD
Start(["输入主题 (topic)"]) --> KWNode["1. 提取核心关键词 (keywords)"]
KWNode --> OutlineNode["2. 生成结构化大纲 (outline)"]
OutlineNode --> WriteNode["3. 撰写正文 (write)"]
WriteNode --> Finish(["输出结果"])
完整运行代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 import osfrom typing import TypedDictfrom langchain_openai import ChatOpenAIfrom langchain_core.messages import HumanMessage, SystemMessagefrom langgraph.graph import StateGraph, ENDclass ContentPipeline (TypedDict ): topic: str keywords: str outline: str article: str model = ChatOpenAI( model="gpt-4o-mini" , temperature=0.3 , api_key=os.getenv("OPENAI_API_KEY" ), ) def extract_keywords_node (state: ContentPipeline ) -> dict : topic = state["topic" ] resp = model.invoke([ SystemMessage(content="你是一个 SEO 与关键词提取助手。请仅输出关键词,用逗号分隔。" ), HumanMessage(content=f"提取主题【{topic} 】的 5 个技术核心关键词。" ), ]) return {"keywords" : resp.content.strip()} def create_outline_node (state: ContentPipeline ) -> dict : topic = state["topic" ] keywords = state["keywords" ] resp = model.invoke([ SystemMessage(content="你是一名资深技术主编,擅长设计逻辑递进的技术文章大纲。" ), HumanMessage( content=f"基于主题【{topic} 】和关键词【{keywords} 】,规划 3 个核心章节大纲,包含二级小节标题。" ), ]) return {"outline" : resp.content.strip()} def write_article_node (state: ContentPipeline ) -> dict : topic = state["topic" ] outline = state["outline" ] resp = model.invoke([ SystemMessage(content="你是一名工程博主,行文偏重实际应用和工程落地,语言精炼。" ), HumanMessage( content=f"主题:{topic} \n\n大纲如下:\n{outline} \n\n请根据大纲撰写一篇 800 字左右的技术短文。" ), ]) return {"article" : resp.content.strip()} workflow = StateGraph(ContentPipeline) workflow.add_node("extract_keywords" , extract_keywords_node) workflow.add_node("create_outline" , create_outline_node) workflow.add_node("write_article" , write_article_node) workflow.set_entry_point("extract_keywords" ) workflow.add_edge("extract_keywords" , "create_outline" ) workflow.add_edge("create_outline" , "write_article" ) workflow.add_edge("write_article" , END) app = workflow.compile () if __name__ == "__main__" : inputs = { "topic" : "LangGraph 状态机的错误恢复机制" , "keywords" : "" , "outline" : "" , "article" : "" , } final_output = app.invoke(inputs) print ("========== 提取的关键词 ==========" ) print (final_output["keywords" ]) print ("\n========== 生成的结构大纲 ==========" ) print (final_output["outline" ]) print ("\n========== 正文产出 ==========" ) print (final_output["article" ])
生产级防线:节点级重试与错误熔断 大模型在实际生产中可能遭遇多种偶发错误:网络超时、API 限流(HTTP 429 Rate Limit)或偶尔的输出解析失败。如果在节点内部没有捕获,整个图的执行过程就会中断崩溃。
虽然 LangGraph 提供了 Checkpoint 机制支持断点恢复,但在单节点内部增加指数退避重试(Exponential Backoff)与 故障降级标记 ,是保证系统健壮性的第一道防线:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import timefrom typing import Optional def resilient_llm_call_node (state: dict ) -> dict : """具备重试容错与优雅降级的节点""" prompt = state.get("query_prompt" , "" ) max_retries = 3 base_delay = 1.0 last_error: Optional [Exception] = None for attempt in range (max_retries): try : response = model.invoke([HumanMessage(content=prompt)]) return { "generated_result" : response.content, "is_error" : False , "error_message" : "" , } except Exception as e: last_error = e wait_time = base_delay * (2 ** attempt) print (f"[警告] 模型调用异常 (尝试 {attempt + 1 } /{max_retries} ): {e} ,{wait_time} 秒后重试..." ) time.sleep(wait_time) return { "generated_result" : "生成失败:服务暂不可用" , "is_error" : True , "error_message" : str (last_error), }
这样下游的条件边路由器就可以检查 is_error 字段,决定是将任务切入备用兜底节点(Fallback Node),还是向管理端发出报警。
方案选型与演进路线 在工程选型中,可以根据业务阶段选择合适的模型调用方案:
评估维度
商业 API(OpenAI / Claude / DeepSeek)
开源托管 API(HF Endpoint / 硅基流动等)
本地私有化部署(vLLM / Ollama)
推理能力与遵循度
极高,复杂逻辑与结构化输出极其稳定
取决于底层开源模型规模
视显存算力而定,7B/14B 适合垂类聚焦任务
部署与运维成本
零基础设施负担,按 Token 计量
零运维,按需开销或调用计费
需维护 GPU 服务器与推理加速引擎
数据安全性与合规
数据需通过公网出境/经过服务商
数据经过托管平台
数据 100% 留存内网,满足严苛保密要求
首字时延与并发吞吐
受云端配额与公网延迟影响
免费层较慢,付费实例较快
局域网低延迟,吞吐完全由硬件决定
适用开发阶段
快速 PoC 验证、生产高要求业务
快速验证开源模型效果、中等成本场景
本地调试开发、内网安全敏感型企业落地
LangGraph 基础系列总结 通过这 7 篇的系统拆解,我们完成了对 LangGraph 基础核心模式的全面覆盖:
架构范式转变 :跳出线性链式的僵硬结构,拥抱有向图与状态机范式(01 为什么不用链式调用 )。
三件套基础 :深入 TypedDict 共享状态、纯函数节点以及 Reducer 合并机制(02 State-Node-Graph三件套 )。
顺序管线设计 :防御性校验、状态更新契约与 stream 流式调试(03 顺序图与第一个Workflow )。
条件分支与循环 :基于 Pydantic 结构化决策的动态路由与自旋重试拓扑(04 条件分支与动态路由 )。
并发执行模式 :Fan-out 分发、并发安全与 Fan-in 聚合规约(05 并行执行Fan-out与Fan-in )。
分步质检流水线 :Prompt Chaining 模式、阶段性质量门禁与增量反馈(06 Prompt Chaining分步生成 )。
真实模型集成 :连接池管理、结构化数据绑定与节点容错兜底(本篇)。
掌握了这套状态图的编排思维,后续无论是在多 Agent 协同体系中划分决策边界(如我们在后续案例中深入讨论的“收紧 LLM 的决策权”),还是实现生产级持久化断点恢复,都会拥有清晰的架构控制力。
系列导航与参考