LangChain 入门:为什么我们需要给 LLM 套上一层壳?
LangChain 入门(一):为什么我们需要给 LLM 套上一层壳?
1. 提出问题:直接调用 API 有什么不好吗?
在开始学习 LangChain 之前,我们需要先问自己一个问题:如果我可以直接使用 openai 的官方 Python 库来调用 GPT 模型,为什么我还需要学习一个复杂的框架?
假设我们要写一个简单的程序:让 AI 给我们讲一个关于特定主题的冷笑话。
如果不使用 LangChain,我们通常会这样写代码:
1 | import os |
这段代码看起来没什么问题,对吧?但是,当你试图构建一个真正的应用程序时,你会遇到以下痛点:
-
模型切换困难:如果你老板明天说,“GPT-3.5 太贵了,换成 Claude 3”或者“换成本地部署的 Llama 3”,你必须重写所有调用代码,因为不同公司的 API 格式完全不同。
-
提示词管理混乱:上面的代码中,提示词是硬编码在函数里的 (
f"请给我讲一个...")。如果提示词很长,或者需要版本管理,混在 Python 代码里会非常难维护。 -
输出处理繁琐:LLM 返回的永远是一个复杂的 JSON 对象。你需要每次都写
response.choices[0].message.content这种长代码来提取真正的文本。 -
缺乏流程编排:如果你想把“讲笑话”的结果,传给下一个步骤(比如“把这个笑话翻译成英文”),你需要手动写很多胶水代码来传递变量。
LangChain 的出现,就是为了解决这些“脏活累活”。 它提供了一套标准化的接口,把模型调用、提示词管理、结果解析等步骤封装成了积木,让你专注于业务逻辑。
2. 环境准备
在开始写代码之前,我们需要安装必要的库。
注意:LangChain 最近进行了架构拆分,为了保证代码的现代性和兼容性,我们不再只安装 langchain 一个大包,而是按需安装。
打开你的终端(Terminal)或者命令行,执行以下命令:
1 | # 安装 LangChain 核心组件 |
此外,你需要拥有一个 OpenAI 的 API Key。在代码运行前,请确保在环境变量中设置了它,或者在代码中显式传递(不推荐显式传递,为了安全)。
3. LangChain 的核心概念:LCEL (LangChain 表达式语言)
LangChain 最重要的概念就是 Chain(链)。你可以把它想象成工厂的流水线。
一个最基础的流水线通常包含三个工位:
-
Prompt Template(提示词模版):负责把原材料(用户输入的主题)包装成半成品(完整的提示词)。
-
Model(模型):负责加工(进行思考和生成)。
-
Output Parser(输出解析器):负责包装(把模型吐出的复杂对象,清洗成我们不仅能看懂,还能直接用的字符串)。
LangChain 使用一种非常有表现力的语法来定义这个流水线,叫做 LCEL。它的核心符号是 |(管道符)。
它的逻辑是:输入变量 | 提示词模版 | 模型 | 输出解析器。
4. 实战代码:重构“冷笑话”生成器
下面我们用 LangChain 重写上面的逻辑。请仔细阅读每一行代码和对应的注释。
1 | import os |
5. 代码深度解析(小白必读)
如果你是第一次看到这段代码,可能会有几个疑问,我们逐一拆解:
为什么要是 ChatOpenAI 而不是 OpenAI?
LangChain 中的 ChatOpenAI 是一个类(Class)。当你运行 llm = ChatOpenAI(...) 时,你并没有真的去调用 API,你只是在配置一个“工人”。你告诉这个工人:“等会儿干活的时候,你要用 gpt-3.5-turbo 这个工具”。这样做的好处是,如果你想换成 Google 的 Gemini 模型,你只需要把这行代码改成 from langchain_google_genai import ChatGoogleGenerativeAI,后续的代码几乎不用动。
prompt_template 到底做了什么?
在 .invoke({"topic": "程序员"}) 发生的那一瞬间,prompt_template 接收到了字典。它内部会执行类似于字符串替换的操作,把 {topic} 替换成 "程序员"。
更重要的是,它把这串文本格式化成了 OpenAI API 所需要的特殊 JSON 格式(包含 role: system, role: user 等结构)。
| 管道符是如何工作的?
在 Python 中,| 通常是“或”运算。但是 LangChain 重写了这个符号的定义(这叫运算符重载)。
在 LangChain 里,A | B 的意思是:运行 A,拿到 A 的输出结果,把它直接作为 B 的输入参数,然后运行 B。
如果没有这个符号,我们的代码会变成极其丑陋的嵌套调用:
parser.invoke(llm.invoke(prompt_template.invoke({"topic": "程序员"})))
你可以看到,嵌套写法非常反人类,阅读顺序是从里到外的。而管道写法是从左到右的,符合人类直觉。
6. 进阶一点点:如果我有两个变量怎么办?
现实生活中,我们的提示词往往不止一个变量。比如,我们不仅要指定主题,还要指定笑话的风格。
修改起来非常简单,只需要改动模版和调用处:
1 | # 修改模版,增加 {style} 占位符 |
7. 本篇总结
在这一篇,我们并没有通过构建什么惊天动地的大项目来吓唬你。我们只做了一件事:标准化。
-
标准化模型:用
ChatOpenAI统一了接口。 -
标准化输入:用
ChatPromptTemplate管理了提示词。 -
标准化输出:用
StrOutputParser清洗了数据。 -
标准化流程:用
|把它们串联起来。
这就是 LangChain 的基石。在下一篇文章中,我们将面对一个 LLM 最大的缺陷:它不知道它训练之后发生的事情,也不知道你公司的私有数据。我们将介绍 RAG(检索增强生成),让 AI 学会“翻书考试”。