☰
LangChain速成课:用Python构建OpenAI LLM应用的四步核心
2026/10/10 13:18:51 网站建设 项目流程

跟LangChain打交道这一年多,我最大的感受是:很多人被它琳琅满目的组件和抽象概念劝退,总觉得要先读几十篇文档再动手。但实际上,用LangChain接OpenAI的LLM,写一个能跑的Python应用,比你想的简单得多,核心就三个单词——Prompt、Model、Parser,再进阶一点加个Chain。这篇文章就是我给所有想快速上手的读者准备的一份速成课,全程围绕用Python构建OpenAI LLM驱动应用的真实路径,把那些文档里不会告诉你的弯路、坑和效率技巧一并交代清楚。无论你是写过几天Python还是完全从零开始,照着敲一遍代码,大概两个小时就能拥有第一个正经的LangChain应用。

先说明一下这篇第一课适合谁:你了解Python基础语法,知道什么是API,想用大模型能力做点实际东西,但面对LangChain官方文档和琳琅满目的术语时有点懵。这一课我们从零开始,先把最重要的四个概念用代码打通,不会扯那些花哨的Agent和Memory,那些放在后续课程里慢慢展开。

1. 速成课认知:LangChain拆解LLM应用的三板斧

说起LangChain,很多人第一反应是"又一个AI框架"。但如果你直接拿它和Flask、Spring这种Web框架类比,那会走偏。LangChain更准确的定位是一套面向LLM应用开发的编排工具集——它的核心价值不是替你写模型,而是让你把"调用大模型"这件事,变成像拼积木一样可组合、可维护、可替换的过程。

1.1 从裸写OpenAI API到用LangChain的真实差异

先看一段没有LangChain时的Python代码,这是很多初学者一开始会写的:

import openai import os openai.api_key = os.getenv("OPENAI_API_KEY") def chat_with_gpt(prompt): response = openai.ChatCompletion.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一个乐于助人的助手"}, {"role": "user", "content": prompt} ], temperature=0.7 ) return response.choices[0].message.content print(chat_with_gpt("介绍一下LangChain"))

这段代码单看没什么问题,但它存在几个隐患:一是messages结构完全手工维护,prompt一变就得改调用逻辑;二是输出取choices[0].message.content,这一长串链式索引每次写都容易手滑;三是你没法很方便地在"调用模型"前后插入额外处理,比如格式化输出、多次重试、日志记录。

用LangChain重写同样的逻辑:

from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个乐于助人的助手"), ("user", "{input}") ]) chain = prompt | llm result = chain.invoke({"input": "介绍一下LangChain"}) print(result.content)

注意区别在哪里?prompt | llm这种管道符写法,是LangChain 0.1之后主推的LCEL(LangChain Expression Language)表达式语法。它把提示词模板和模型调用通过一个|符号组合成一个完整执行链。将来你想在中间加一个输出解析器,只需要在管道后面再接一段:chain = prompt | llm | parser——操作逻辑和Unix管道一模一样,极其直观。

提示:新版LangChain的ChatOpenAI从langchain_openai导入,而不是旧的langchain.llms。早期教程里那种from langchain.llms import OpenAI的写法在新版中已经不建议使用,如果你照着老教程装,大概率会遇到ImportError。

1.2 第一课只需要掌握四个核心概念

LangChain的组件非常多,什么Document Loaders、Vector Stores、Tools、Memory、Agents,听着就头大。但第一课我建议大家只抓四个概念,其他的先不管:

  • Chat Models:负责和你选择的LLM打交道。你告诉它模型名、温度、API Key,它帮你发请求、收响应。我们用的ChatOpenAI就是这类组件。
  • Prompt Templates:把提示词从硬编码字符串变成可填充的模板。用户输入的动态内容通过变量占位符注入,不再需要每次拼接字符串。
  • Output Parsers:把模型返回的文本转成你需要的结构。比如从一大段话里提取出JSON、把回答整理成列表,甚至直接映射成一个Python数据类对象。
  • Chains:把前面三个串起来的执行流程。可以是一行管道表达式,也可以是多个步骤拼接的长管道。

只要把这四个概念理解透,LangChain的日常用法你已经掌握了七成。Memory(记忆)和Agents(智能体)本质上是Chain和工具的延伸,等基础牢固之后再去碰,会轻松得多。

为什么我强调"先只学四个"?因为学习框架最大的陷阱是过早接触过多抽象概念。我见过太多人被LangChain的生态地图吓退,其实你真正每天写的代码,用到的就是这几个核心类而已。

2. 动手前准备:Python环境、API Key和三个常踩的坑

这一节没有高深理论,但偏偏是很多人卡住最久的地方。我见过太多人在代码层面完全没问题,最后卡在安装依赖和认证上,所以这部分务必仔细看完。按照下面的顺序来,十分钟内就能进入"可以写代码"的状态。

2.1 环境搭建:Python版本和虚拟环境

LangChain对Python版本要求不算苛刻,Python 3.9到3.12都能跑。但我在实测中建议用3.10或3.11,因为3.12在某些依赖库(尤其是一些涉及C扩展的包)上偶尔会出兼容性问题,而3.9以下版本则太老了,逐渐被生态抛弃。

我推荐使用venv创建独立环境,否则你未来处理多个项目时,依赖版本冲突会让人奔溃:

python -m venv .venv source .venv/bin/activate # Windows下是 .venv\Scripts\activate

激活后用pip安装核心依赖,注意是下面三个:

pip install langchain langchain-openai langchain-core

大概率你还会用到python-dotenv来管理API Key,以及pydantic来做结构化输出(装LangChain时一般会带,但建议确认一下):

pip install python-dotenv pydantic

注意:langchain-openai是新版LangChain中专门负责OpenAI模型接入的扩展包。langchain这个主包本身已经不再内置任何模型厂商的实现,这是为了抽象解耦。初学时不用纠结设计哲学,只需要知道:装这两个,所有OpenAI相关的功能就齐了。

2.2 配置OpenAI API Key:别把密钥写进代码

截止到现在,OpenAI的API调用都还是用API Key认证。获取方式很简单:登录OpenAI平台,进入API Keys页面,创建一个新的密钥,复制保存。需要强调的是,这个密钥只完整显示一次,丢失就得重新生成。

有了Key之后,我强烈建议你用环境变量管理,而不是硬编码在Python文件里。在项目根目录创建一个.env文件:

OPENAI_API_KEY=sk-xxxxxx

然后在代码里只需要三行:

from dotenv import load_dotenv load_dotenv()

ChatOpenAI在初始化时会自动读取名为OPENAI_API_KEY的环境变量,不需要你手动传参。这一点很方便,也避免了Key泄露进Git仓库的风险。如果你已经不小心把Key提交到公开仓库了,别犹豫,立刻去OpenAI后台把这个Key作废,重新生成一个。

关于API付费,OpenAI对调用按token计费,不同模型价格不同。新手阶段我推荐使用gpt-4o-mini,它的价格是gpt-4o的几十分之一,但日常任务的能力已经绰绰有余,非常适合学习和跑Demo。等到逻辑验证通过,再换成更强的模型不迟。

2.3 三个新人必踩的坑:版本、超时和默认参数

环境这块,我总结了三个高频问题,提前给你打预防针:

一是安装版本冲突。如果你电脑上之前装过旧版LangChain,或者照着某篇老博客装了一堆langchain-experimental之类的包,就可能出现各种ImportError。最干净的办法是创建一个全新虚拟环境,然后重新安装。如果问题依旧,就pip install --upgrade langchain langchain-openai langchain-core把所有包升到最新。

二是请求超时。默认情况下OpenAI接口在国外,如果你的网络环境不太稳定,代码会卡在漫长的连接等待中。不用急着想什么特殊方案,合法合规的做法是配置合理超时参数:

llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, max_retries=2, # 失败重试次数 request_timeout=30 # 单次请求超时时间,单位秒 )

三是max_tokens默认值问题。旧版OpenAI API中,如果你不传max_tokens,模型会按上下文窗口上限输出;但某些模型和某些版本组合下,不传会采用一个偏小的默认值,导致中文内容输出到一半戛然而止。稳健的做法是显式设置:max_tokens=1024,根据自己的需要调整。

3. 第一个LangChain程序:完成一次有人情味的对话

环境准备好之后,我们立刻开始写第一个完整程序。这一节不绕弯子,从最基础的模型调用讲到提示词模板的封装,让读者能直观感受LangChain的编码节奏。

3.1 Chat Models的三种角色:System、Human、AI

调用OpenAI的模型,本质上是把一段"对话历史"发给它。这个对话由不同角色的消息组成,在LangChain里对应关系很清晰:

  • System Message(系统消息):设置模型的整体行为和人设。比如"你是一个资深的Python开发导师,回答要通俗易懂"。系统消息通常在整个对话中只出现一次,起纲领性作用。
  • Human Message(用户消息):用户当前输入的内容。每次用户说话就是一条Human消息。
  • AIMessage(AI消息):模型之前的回复。你把它放回对话历史里,模型就知道之前聊过什么,从而保持上下文连续。

在LangChain中,创建这些消息最简单的方式是用元组语法传入ChatPromptTemplate:

from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "你是一位语言简练的资深Python导师,回答问题不超过200字。"), ("user", "{input}") ])

这套写法会把("system", "……")自动识别为SystemMessage,("user", "{input}")识别为HumanMessage。{input}是占位符,后续用字典传入真实内容。

要理解为什么这么设计,可以想象你在咖啡馆跟一个记性很好的朋友聊天:你先告诉他"我叫小明,喜欢简洁的回答"(系统消息),然后开口提问(用户消息),朋友的答复(AI消息)又会被记录在案,下一轮继续聊的时候再发回去。这套消息机制就是LLM对话的基本协议,LangChain只是把它封装成了人类可读的接口而已。

3.2 Prompt Templates:把提示词从字符串变成可复用的模板

我见过不少初学者用f-string拼接提示词,一开始觉得挺好用,写多了就会发现:到处都是f"""...""",一旦提示词变长,可读性就崩了,而且你无法在多个地方复用同一个提示词结构。

LangChain的ChatPromptTemplate就是来解决这个问题的。核心特性有两个:

一是变量注入。在模板里用{变量名}作为占位符,传参时只需要一个字典。比如上面那个例子,prompt.invoke({"input": "请用List[dict]返回LangChain的核心概念列表"}),模板会自动把输入填充进去,返回一个完整的ChatPromptValue对象。

二是模板复用。同一个模板可以生成多个不同的完整提示词。你在后台定义一次,前端来什么请求都能套用。

实际开发中,我习惯把所有PromptTemplate集中放在一个prompts.py文件里,这样提示词和业务逻辑分离,修改提示词时不用翻代码,对非程序员同事也更友好。这是一个很小的工程习惯,但越到后期越能感受到它的价值。

3.3 组合模型和模板:完成第一个完整应用

现在把前面所有东西组合起来,完成一个完整的LangChain应用。这个应用做一件事:接收用户输入的"技术关键词",让模型用通俗的语言讲解,并限制在150字以内。

from dotenv import load_dotenv load_dotenv() from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # 1. 配置模型 llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.3, max_tokens=500 ) # 2. 定义提示词模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一位技术讲师,擅长用生活化类比解释复杂概念。回答不超过150字。"), ("user", "请解释一下什么是{concept}") ]) # 3. 用管道符构建执行链 chain = prompt | llm # 4. 调用 response = chain.invoke({"concept": "LangChain的PromptTemplate"}) print(response.content)

运行结果会是一段通俗的解释文字。这段代码虽然只有十几行,但它已经是一个"完整应用":有模型配置、有提示词模板、有执行链、有调用入口。

我来说下temperature=0.3这个参数。temperature控制输出的随机性:取值0到2,越低越保守、越倾向确定性输出,越高越发散和"有创意"。解释概念这种任务希望稳定准确,用0.3比较合适;如果是头脑风暴或写故事,调到0.8甚至1.0都行。新手容易犯的错是不区分任务类型统一用默认值,结果发现同一个问题每次回答都不一样,其实不是模型坏了,是你温度设太高了。

4. 让模型输出变成结构化数据:Output Parsers实战

第一个程序能跑通之后,很多人会陷入"只会聊天"的瓶颈——得到一个字符串,但不知道怎么把它变成程序里的字典、列表、类对象。这一节我们要攻克的正是这个问题,也是从"写死Demo"迈向"做正经应用"的关键一步。

4.1 为什么需要Output Parser:当一段文字需要变成一份数据

LLM的输出永远是文本,但你的业务逻辑想要的往往是结构化数据:提取一份简历里的姓名、电话和技能列表;把一条评论分类成"正面/负面";从一篇文章中抽取标题和摘要。这些场景的共同点是:结果需要进入Python程序继续处理,而一坨自然语言文本无法直接参与计算。

最笨的办法是让模型以JSON格式输出,然后你json.loads()它。听起来挺合理?但实测会踩不少坑:

  • 模型偶尔会在JSON前后加上解释性文字,导致json.loads直接报错;
  • 模型生成的JSON键名和你的类属性对不上,还得写一堆映射代码;
  • 模型生成的嵌套结构不稳定,时而有字段时而无字段,你的解析代码得写一堆if判断。

Output Parser就是来解决这些问题的。它负责约束模型的输出格式,并在代码层面把模型返回的文本自动解析成指定结构。一旦解析失败,它会自动要求模型重新生成,直到得到合法结果。

4.2 第一个简单Parser:StrOutputParser

在所有Parser中,StrOutputParser是最简单的无脑Parser。它的作用是什么都不做——把模型输出直接转成字符串。你可能会问:"模型输出本来就是字符串,为什么要转?"

这就要说回LangChain的"管道"机制了。ChatOpenAI返回的是一个AIMessage对象,里面有content属性。如果你用chain = prompt | llm,最终invoke返回的确实是一个AIMessage。但如果你希望整条链的最终输出是一个纯净的字符串,而不是一个对象,就在管道后面加一个StrOutputParser:

from langchain_core.output_parsers import StrOutputParser chain = prompt | llm | StrOutputParser() result = chain.invoke({"concept": "LangChain的PromptTemplate"}) print(result) # 直接是字符串

别看这只是一个微小的变化,它让"输出类型"和"输入类型"在整个链条中更统一,也方便你在长管道里对字符串做后续字符串处理。我建议所有简单场景的Chain,都默认加StrOutputParser,这是一个好习惯。

4.3 进阶:用PydanticOutputParser定义结构

现在进入重头戏:让模型按你定义的Pydantic模型输出。什么是Pydantic?简单说,它让你用Python类定义"数据结构长什么样",LangChain再利用这个类去硬性约束LLM的输出。

我先定义一个Pydantic类,让模型把一条技术介绍拆成三个字段:

from pydantic import BaseModel, Field class TechConceptInfo(BaseModel): name: str = Field(description="概念名称") metaphor: str = Field(description="用于解释该概念的生活化类比") key_point: str = Field(description="核心要点,一句话")

然后用PydanticOutputParser配合提示词模板一起使用:

from langchain_core.output_parsers import PydanticOutputParser parser = PydanticOutputParser(pydantic_object=TechConceptInfo) prompt = ChatPromptTemplate.from_messages([ ("system", "请根据用户的概念用提供的格式返回。 {format_instructions}"), ("user", "解释一下{concept}") ]) # 把Parser生成格式要求填充进模板 format_instructions = parser.get_format_instructions() prompt_with_format = prompt.partial(format_instructions=format_instructions) chain = prompt_with_format | llm | parser result = chain.invoke({"concept": "LangChain"}) print(result.name) print(result.metaphor) print(result.key_point)

整个过程LangChain做了什么?它会把TechConceptInfo类的字段定义翻译成一段很详细的JSON格式说明,塞进format_instructions,引导模型输出符合这个结构的JSON。parser收到输出后,先尝试json.loads,再用Pydantic做类型校验,最终返回一个TechConceptInfo实例。

你可能注意到了prompt.partial(format_instructions=format_instructions)这个写法。它的作用是在模板渲染之前预先填充一个固定变量,这样每次调用时只需要传concept一个变量。这个技巧在实际项目里非常好用,因为格式说明通常是固定的,没必要每次都重复传。

4.4 更省事的时代方案:with_structured_output

PydanticOutputParser是经典方案,但如果你用的是新版langchain-openai,有一个更简洁的推荐方案——model.with_structured_output()。它不需要像上面那样手动把格式说明塞进提示词,而是直接把Pydantic类传给模型绑定方法:

from pydantic import BaseModel, Field from langchain_openai import ChatOpenAI class TechConceptInfo(BaseModel): name: str = Field(description="概念名称") metaphor: str = Field(description="生活化类比") key_point: str = Field(description="核心要点") model = ChatOpenAI(model="gpt-4o-mini", temperature=0.2) structured_model = model.with_structured_output(TechConceptInfo) resp = structured_model.invoke("请用结构化方式解释LangChain") print(resp.name) print(resp.metaphor) print(resp.key_point)

这段代码可能是我日常用得最多的写法。with_structured_output基于模型的函数调用能力(Function Calling),你不需要在提示词里写format_instructions,模型自己就知道要返回什么结构。代码少了三板斧,出错概率也大幅下降。

提示:with_structured_output背后依赖OpenAI的Function Calling能力,所以要求模型支持这个功能。gpt-4o-mini和gpt-4o都支持,实测非常稳定。如果未来你想切换到其他厂商模型,要先确认它也支持Function Calling,否则还是老老实实用PydanticOutputParser。

在实战中,我建议优先用with_structured_output,它简洁且稳健。但PydanticOutputParser依然是很好的理解底层机制的学习工具,两者都值得掌握。

5. 三行代码把任务拆成流水线:LCEL语法与Chain串联

把模型调用、提示词模板、解析器组合起来之后,你就会开始遇到新的需求:"能不能让模型先做A任务,再做B任务,最后用C格式输出?"这一节就是教你如何用LangChain的表达式语法,把多个任务串成流水线,并让它们高效执行。

5.1 理解LCEL管道符:为什么它不是魔法

你前面已经反复看到|这个管道符,现在来说透它。prompt | llm | parser,这行代码的语义是:先把变量传入prompt生成消息,再把消息交给llm调用模型,最后把模型输出交给parser解析。每一步的输出,就是下一步的输入。

为什么选择管道符?因为LLM应用的开发本质就是数据流的加工过程:原材料(用户输入)经过多道工序(模板/模型/解析),最终产出成品(结构化对象)。Unix设计哲学里的管道,用在这里几乎天衣无缝。你不需要去定义中间变量、不需要写一堆嵌套函数调用,所有流程一目了然。

用普通Python写同样的逻辑,大概是这样的:

messages = prompt.format(concept="LangChain") ai_message = llm.invoke(messages) final_result = parser.invoke(ai_message)

这种写法也不算差,但问题在于:一旦你需要在这三步中间加错误处理、加缓存、加异步,代码会变得冗长。而LCEL语法由于每一步都是标准接口,框架层面就能提供统一的invoke、batch、stream、ainvoke等通用方法,这才是它真正的价值所在。

5.2 一次有代表性的流水线示例:总结、翻译、提取关键词

现在我们来串一条更长的链条,演示三个模型调用如何协作。假设任务是这样的:用户输入一篇英文技术文章的摘要,系统需要先总结成一段中文,再从总结中提取关键词,最后把关键词和总结拼接成JSON格式。

from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.output_parsers import JsonOutputParser llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.2) # 第一步:总结 summarize_prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个专业的英文技术编辑,擅长提炼核心内容。"), ("user", "请将以下英文文本概括成一段150字以内的中文总结:\n{text}") ]) # 第二步:从总结中提取关键词 keywords_prompt = ChatPromptTemplate.from_messages([ ("system", "你是信息抽取专家。只输出关键词列表,不要其他内容。"), ("user", "从这段中文总结提取3-5个关键词:\n{summary}") ]) # 第三步:把关键词和总结打包成JSON json_prompt = ChatPromptTemplate.from_messages([ ("system", "你是数据整理助手,严格按JSON输出。"), ("user", "根据总结和关键词生成JSON,包含summary和keywords两个字段。\n总结:{final_summary}\n关键词:{keywords}") ]) json_parser = JsonOutputParser() chain = ( summarize_prompt | llm | StrOutputParser() | (lambda summary: {"summary": summary}) | keywords_prompt | llm | StrOutputParser() | (lambda keywords: {"final_summary": ..., "keywords": keywords}) | json_prompt | llm | json_parser )

上面代码中我用了两处lambda来调整数据形状,确保每一步传给下一步的变量模板能对齐。这是LangChain用管道串联多步骤时最核心的技巧——中间数据的形状管理。初学者容易在这里卡住:上一步输出一个字符串,下一步的模板需要的是一个字典,对不上就报错。一个朴素的解决办法就是在该处加一段lambda把数据包装成字典再往下传。

不过上面这个chain有一个效率问题:第二步翻译和第三步提取其实依赖第一步的结果,它们之间是严格串行的。如果某些分支彼此独立,可以用RunnableParallel并行执行以节省时间。

5.3 并行执行:RunnableParallel的使用场景

考虑另一个常见场景:用户给了一段产品描述,你想同时得到"产品简介"和"宣传标语"两段文本。这两个任务完全独立,没有依赖关系,串行跑两次模型调用纯属浪费时间。

from langchain_core.runnables import RunnableParallel llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7) intro_prompt = ChatPromptTemplate.from_template( "为{product}写一段30字以内的产品简介。" ) slogan_prompt = ChatPromptTemplate.from_template( "为{product}写一句有冲击力的宣传标语。" ) chain = RunnableParallel( introduction=intro_prompt | llm | StrOutputParser(), slogan=slogan_prompt | llm | StrOutputParser() ) result = chain.invoke({"product": "一款智能保温杯"}) print(result["introduction"]) print(result["slogan"])

RunnableParallel接受一个字典,字典的每个value都是一条子链。在invoke时,框架会同时发起两个模型请求(底层是并发调用),耗时取两者最大值,而不是简单相加。

这样做的一个很实用的场景是:如果你需要多个助手角色同时处理同一份输入,比如一个写正文、一个写标题、一个写摘要,并行之后整个流程的响应时间会大幅缩短。

5.4 第一课的边界:什么时候不要硬用Chain

学到这里,你可能会产生一种冲动:把一切都做成LCEL链。我得提醒一句,链越长越难调试。如果一条链里出现三层以上模型调用,任何一个中间节点的输出稍微不符合预期,排查起来都会很费劲。而且每一步模型调用都在消耗token,链过长既慢又贵。

我的经验法则是:两步以内的组合,随意用管道;两步以上,考虑拆成多个独立链,在业务代码里用普通Python串起来。LangChain的价值在于让每一步可组合、可替换,而不是让你把整个应用写成一坨几千行的超级链。这个边界感值得在早期就建立起来。

6. 速成课避坑实录:四个我花时间最多的"疑难杂症"

速成课最值钱的部分往往不在"怎么跑通",而在"跑不通时怎么办"。这一节把我自己踩过的、以及身边同事反复踩的坑集中列出来,每一节都是一个真实的排查过程,不是简单的答案罗列。

6.1 版本错乱引发的一切ImportError

现象:from langchain_openai import ChatOpenAI报错ModuleNotFoundError: No module named 'langchain_openai',或者from langchain.llms import OpenAI报类似错误。

排查链:先pip show langchain-openai看看装了没有;再pip list看一堆包的版本是否混乱;再回忆是不是看了2023年的老教程装的旧包。

我当时的解决办法很简单,从零开始重建环境:

pip uninstall langchain langchain-openai langchain-core -y pip install langchain langchain-openai langchain-core --upgrade

如果你在Windows上遇到某些包编译报错,优先升级pip:python -m pip install --upgrade pip。另外注意,langchain-community这个包里装了一堆第三方集成,如果你不需要,尽量别装,它往往是版本冲突的源头之一。

6.2 模型把JSON输出到最后截断了

现象:使用with_structured_output或者JsonOutputParser时,偶发解析失败,或者直接把输出截断在一半。

排查过程:第一次遇到时我先怀疑是Parser问题,后来仔细看返回原始文本,发现后半段竟然没输出完。我立刻意识到是max_tokens设得太小,模型在生成JSON的过程中触发了长度上限,导致JSON不完整,Parser当然解析不了。

解决:把max_tokens从128改成512,问题消失。经验是:涉及JSON生成时,max_tokens至少要给你预期JSON长度的两倍以上,因为模型还要生成键名、花括号、逗号等额外字符。更稳妥的办法是用max_tokens=1024起步,不够再调。

6.3 温度参数与"胡说八道"的边界

现象:模型在解释概念时偶发出现事实性错误,甚至一本正经地给出不存在的API用法。

原因分析:temperature太高导致稳定性下降是原因之一,但更重要的是——LLM本身就有幻觉倾向,哪怕温度设为0也不是100%靠谱。

我的建议:对事实准确性要求高的任务(比如提取、分类、结构化),温度设为0或0.1优先;创意生成任务可以放宽到0.7~0.9。另外一定要在System Prompt里加一句"如果不确定,请明确回答不知道",比任何参数调优都实用。不要指望模型永远不会犯错,你要做的是设计一个尽量不让错误发生的流程,以及让错误显性化的验证逻辑。

6.4 重试策略与配额限制:便宜模型也怕限流

现象:请求偶发返回429(Rate Limit)或5xx错误。

排查过程:一开始我以为是网络问题,但连续的请求日志显示,报错多发生在同一时刻发起多个并发请求之后,而单次请求正常。结合OpenAI的配额机制,基本确认是被限流了。

解决方案有两层。第一层是代码层面的重试机制,ChatOpenAI自带max_retries参数:

llm = ChatOpenAI( model="gpt-4o-mini", max_retries=3, request_timeout=30 )

第二层是业务层面的并发控制:如果你在RunnableParallel里同时发5个请求,要注意你的账户并发限制,不要把RunnableParallel用到极致。我后来写了个简单的信号量限制并发数,实测稳定很多。

这里多说一句,OpenAI按token计费,同样的代码如果写得太啰嗦,成本会明显上升。新手阶段最容易忽略的是Prompt长度——一段2000字的System Prompt每次调用都收费,虽然单价低,但积少成多。如果整个应用承载量一大,这会变成一笔真金白银的成本。优化思路是:把提示词写精练,常用知识从外部检索后按需注入,而不是一股脑塞进System Prompt。


这一课到这里应该够你亲手跑起来一个基于LangChain和OpenAI LLM的Python应用了。我强烈建议你现在就打开编辑器,把第三、四、五节的代码按顺序敲一遍,跑通一个完整版本,再试着改改参数、换换Prompt、加加Parser。遇到问题回头对照第六节的排查思路,大多数坑都能就地解决。

我个人在实际操作中的体会是:LangChain这种框架,除非你真的把代码跑起来,否则看再多文档都只是"好像懂了"。第一次用prompt | llm | parser跑出结构化输出的那一刻,你才会真正理解为什么这个管道符号是一个如此漂亮的设计。下一课我会接着讲Memory和Agent——让应用能够记住多轮对话、具备自主调用工具的能力,你可以把目前搭建的Chain当作跳板,继续往这个方向延伸。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询