4. 快速上手-编码
1. 调用大模型的基本流程
抛开链式结构不谈,调用大语言模型的基本逻辑分为三步:
- 定义大模型——选择并配置要使用的大模型(如 GPT-4o-mini)
- 构造消息——组合系统提示和用户输入
- 调用模型——将消息传递给模型,获取响应
下面围绕这个流程逐步展开代码编写。
2. 申请 API Key 并配置环境变量
2.1 申请 API Key
以 OpenAI 为例,前往 platform.openai.com(需科学上网)注册或登录账号。
进入 Settings → API keys 页面:
点击 “Create new secret key” 按钮创建新密钥:
将生成的 API Key 保存在本地,后续接入时需要用到。
2.2 配置环境变量
将 API Key 配置到系统环境变量中,避免在代码中直接暴露密钥。
环境变量名称:OPENAI_API_KEY
# Windows 系统setOPENAI_API_KEY=your-api-key-here# Linux/macOS 系统exportOPENAI_API_KEY=your-api-key-hereTip:重启编译器
如果在编译器(如 VS Code、PyCharm)打开后才配置环境变量,需要重启编译器才能读取到最新的环境变量。
LangChain 的ChatOpenAI默认从系统环境变量OPENAI_API_KEY读取密钥,无需在代码中显式传入。
3. 定义大模型
3.1 安装依赖包
pipinstall-Ulangchain-openai
-U表示升级到最新版本,首次安装可不带此选项。
3.2 定义模型
fromlangchain_openaiimportChatOpenAI model=ChatOpenAI(model="gpt-4o-mini")ChatOpenAI会从环境变量OPENAI_API_KEY自动读取密钥。如果环境变量未配置,也可显式传入:
model=ChatOpenAI(model="gpt-4o-mini",api_key="your-api-key-here"# 不推荐硬编码)Note:远程调用
通过 API Key 调用 OpenAI 模型属于远程调用,模型在 OpenAI 服务器上运行,非本地部署。
3.3 ChatOpenAI 常用参数
ChatOpenAI提供了丰富的初始化参数来控制模型行为:
| 参数 | 说明 |
|---|---|
model | 模型名称(如gpt-4o-mini、gpt-5) |
temperature | 采样温度。值越高回答越天马行空,越低越保守 |
max_tokens | 最大输出令牌数,None表示由模型自动决定 |
timeout | 请求超时时间(秒) |
max_retries | 请求失败时的最大重试次数 |
api_key | API 密钥,默认从环境变量OPENAI_API_KEY读取 |
base_url | API 请求的基础 URL,用于兼容第三方服务商 |
organization | OpenAI 组织 ID,默认从环境变量OPENAI_ORG_ID读取 |
temperature(采样温度)
temperature 是控制模型输出随机性的核心参数:
# 保守模式:适合代码生成model=ChatOpenAI(model="gpt-4o-mini",temperature=0)# 创意模式:适合故事创作model=ChatOpenAI(model="gpt-4o-mini",temperature=1.5)max_tokens(最大输出令牌数)与 Token 概念
max_tokens控制模型单次回复的最大长度。理解它需要先了解Token的概念。
Token 是自然语言处理中文本的基本计量单位,并非简单对应一个词或一个字:
- 英文:1 Token ≈ 4 个英文字符 ≈ 0.75 个单词
- 中文:1 个汉字 ≈ 1.5 ~ 2 个 Token
这意味着中文调用大模型消耗的 Token 更多,成本也更高。这也是"用英文调用大模型更省钱"说法的底层原因。
# 限制输出为约 10 个 Token(约 5~6 个汉字)model=ChatOpenAI(model="gpt-4o-mini",max_tokens=10)超时与重试
model=ChatOpenAI(model="gpt-4o-mini",timeout=30,# 30 秒超时max_retries=3,# 失败重试 3 次)3.4 通过 base_url 接入兼容模型(如 DeepSeek)
许多第三方模型兼容 OpenAI 的 API 格式,可通过base_url和api_key直接使用ChatOpenAI接入:
model=ChatOpenAI(base_url="https://api.deepseek.com/v1",api_key="your-deepseek-api-key",model="deepseek-chat",)Note:base_url 中的 v1
此处v1是 OpenAI 兼容 API 的前缀,与模型版本无关。DeepSeek 原生 API 不带此路径,加v1是为了兼容 OpenAI 的调用格式。
4. 定义消息
LangChain 使用标准化的消息类型来区分不同角色,常见的消息类型有以下几种:
| 消息类型 | 角色 | 说明 |
|---|---|---|
SystemMessage | 系统提示 | 通常作为输入消息序列的第一条,用于设定 AI 的行为和任务 |
HumanMessage | 用户消息 | 来自用户的输入,传递给模型进行处理 |
AIMessage | AI 回复 | 模型返回的响应消息 |
4.1 定义消息列表
fromlangchain_core.messagesimportHumanMessage,SystemMessage messages=[SystemMessage(content="Translate the following from English into Chinese"),HumanMessage(content="hi!"),]SystemMessage:定义 AI 的角色和行为,如"将英文翻译为中文"HumanMessage:用户的实际输入内容,如"hi!"
之后只需构造新的HumanMessage(如 “my name is xiaoming”),系统提示词无需重复编写,模型会自动结合系统消息进行处理。
5. 调用大模型
在上面的代码中,model是 LangChainRunnable(可运行接口)的实例,提供了标准化的交互方式。对于什么是 Runnable 我们在下一小节中学习
5.1 使用 .invoke() 调用
result=model.invoke(messages)print(result)输出结果:
content='你好!' additional_kwargs={'refusal': None} ...5.2 输出说明
result的类型为AIMessage,包含以下关键字段:
| 字段 | 说明 | 用途场景 |
|---|---|---|
content | 消息的内容——模型返回的文本结果 | 提取最终答案 |
additional_kwargs | 额外有效负载(如工具调用编码) | 工具调用场景 |
response_metadata | 响应元数据:请求 ID、模型版本、服务商原始返回数据 | 调试、日志、获取请求上下文 |
usage_metadata | 资源消耗:本次调用消耗的 Token 数量 | 成本计算、监控、预算控制 |
Tip:response_metadata vs usage_metadata
response_metadata侧重于响应本身的信息,如请求 ID、模型版本usage_metadata侧重于资源消耗的量化信息,如input_tokens、output_tokens、total_tokens
输出示例(格式化后):
# response_metadata{'model_name':'gpt-4o-mini-2024-07-18','system_fingerprint':'fp_...','finish_reason':'stop','logprobs':None}# usage_metadata{'input_tokens':20,'output_tokens':2,'total_tokens':22}6. 输出解析
有时我们只关心模型返回的文本内容,而非完整的AIMessage对象。LangChain 提供了输出解析器(Output Parser)来提取和格式化结果。
6.1 使用 StrOutputParser
fromlangchain_core.output_parsersimportStrOutputParser parser=StrOutputParser()result_str=parser.invoke(result)print(result_str)# 输出: 你好!StrOutputParser将AIMessage解析为最可能的字符串输出,只保留content部分。
后续还会学习其他类型的输出解析器,用于输出 JSON、结构化数据等格式。
7. 链式执行
以上每一步都需要手动调用.invoke():先调模型,取结果,再调解析器。LangChain 提供了链式执行能力,将多个组件串联,一次调用即可得到最终结果。
7.1 定义链
chain=model|parser|(管道操作符)是 LangChain Expression Language (LCEL) 的核心语法,model | parser表示将model的输出作为parser的输入。
7.2 执行链
result=chain.invoke(messages)print(result)# 输出: 你好!链的执行流程:
Tip:链的顺序
链按照从前到后的顺序执行:先执行第一个组件(model),再将结果传递给下一个组件(parser)。顺序不能颠倒。
7.3 链式 vs 非链式对比
| 对比项 | 传统方式 | 链式方式 |
|---|---|---|
| 调用方式 | 每个组件手动.invoke() | 定义链后一次.invoke() |
| 代码量 | 较多,需手动传递中间结果 | 简洁,组件通过 ` |
7.4 Runnable 接口:一切组件的基础
为什么model、parser、chain都可以调用.invoke()?因为它们都实现了Runnable 接口。
理解 Runnable,关键在于理解为什么需要它。下面从"面临的问题→解决方案→设计理念"这条线展开。
7.4.1 为什么需要 Runnable?——旧 Chain 时代的痛点
在 LangChain 早期版本(0.0.x)中,核心抽象是各自独立的 Chain 类(如LLMChain、SequentialChain、RetrievalQA),存在几个突出问题:
| 痛点 | 具体表现 |
|---|---|
| 接口不统一 | LLMChain用.run(),Retriever用.get_relevant_docs(),OutputParser用.parse()——每个组件有自己的一套调用方式 |
| 组合困难 | 链与链之间靠input_variables/output_variables字段名匹配,改一个字段名牵一发动全身 |
| 流式/批量/异步支持零散 | 每个组件各自实现,不原生,开发者需手动封装 |
| 扩展性差 | 想加一个"重试机制"或"日志中间件"需要继承重写整个 Chain 类 |
| 可观测性缺失 | 无法统一注入回调、追踪、标签,调试困难 |
这些问题的根源在于:组件之间缺乏统一的交互协议。就像 U 盘、键盘、显示器各自用不同的接口——每次连接都在找不同的口、找不同的转接头。Runnable 就是 LangChain 的"USB-C 标准"。
7.4.2 Runnable 是什么?——统一执行协议
Runnable 是LangChain 所有可执行组件的统一抽象接口,它不仅仅是一个基类,更是一套协议(Protocol)。任何实现了这个协议的对象,都承诺提供以下五组核心方法:
| 能力 | 方法 | 说明 | 典型场景 |
|---|---|---|---|
| 同步调用 | invoke | 单个输入 → 单个输出 | 实时交互、测试 |
| 异步调用 | ainvoke | 异步单次调用 | FastAPI 等高并发服务 |
| 批处理 | batch | 批量输入 → 批量输出(自动线程池并发) | 批量文档处理 |
| 流式 | stream | 输出在生成时逐块返回 | 聊天界面"打字机"效果 |
| 组合 | pipe/ ` | ` | 通过 LCEL 串联多个 Runnable |
关键认识:LangChain 中几乎所有核心组件本质上都是 Runnable 实例:
# 以下都是 Runnable——调用方式完全相同prompt=ChatPromptTemplate.from_template("讲个关于{topic}的笑话")model=ChatOpenAI(model="gpt-4o-mini")parser=StrOutputParser()chain=prompt|model|parser# 链本身也是 Runnableprompt.invoke({"topic":"程序员"})# → PromptValuemodel.invoke(messages)# → AIMessageparser.invoke(ai_message)# → strchain.invoke({"topic":"程序员"})# → str(最终结果)7.4.3 为什么这样设计
1. 统一接口
Prompt、Model、Parser、Retriever 无论内部多复杂,对外一律只认.invoke()。昨天用 GPT,今天换 DeepSeek,明天换本地模型——调用代码零改动。这就是 7.4.2 中"一个 .invoke() 走天下"的底层逻辑。
2.一次定义,四种执行模式
chain=prompt|model|parser# 只定义一次chain.invoke(x)# 单次执行chain.batch([x1,x2])# 批量执行(自动线程池并发)chain.stream(x)# 逐 token 流式awaitchain.ainvoke(x)# 异步如果组件没实现流式,框架自动把invoke结果包成迭代器 yield 出来。组件可以按需升级,上层代码零感知。
例如,一个 LLM 可能只实现了.invoke()(一次性返回完整结果),但调用方使用.stream()时,LangChain 会自动将:
# 底层实现(伪代码)defstream(self,input):result=self.invoke(input)# 调用同步方法yieldresult# 包装成单步迭代器返回当该 LLM 后来原生实现了流式接口后,只需把真实实现挂到_stream方法上,调用方代码完全不用改——这就是"零感知"的含义:接口统一,上下游解耦。
理解 Runnable 后,下面两件事会变得很自然:
- 学一个新组件时:不需要重新学它的调用方式——只要是 Runnable,
invoke / batch / stream就是肌肉记忆 - 调试时:可以专注在"组合逻辑对不对"而非"这个组件的调用语法是什么"
这些设计思想在下一节(7.5 多种定义链的方式)中马上就会用到。
7.5 多种定义链的方式
除了|管道操作符,LangChain 还提供了另外两种定义链的方式:
fromlangchain_core.runnablesimportRunnableSequence# 方式一:管道操作符(最推荐,最直观)chain=model|parser# 方式二:RunnableSequencechain=RunnableSequence(first=model,last=parser)# 方式三:.pipe() 方法(参考 Linux 管道设计)chain=model.pipe(parser)三种方式效果完全相同。方式一最简洁直观,推荐日常使用。.pipe()方法的设计参考了 Linux 系统的管道(pipe)机制,LCEL 借此实现了优雅的组件编排。
8. 完整代码示例
fromlangchain_core.messagesimportHumanMessage,SystemMessagefromlangchain_core.output_parsersimportStrOutputParserfromlangchain_openaiimportChatOpenAI# 1. 定义大模型(默认从环境变量读取 OPENAI_API_KEY)model=ChatOpenAI(model="gpt-4o-mini")# 2. 定义消息列表messages=[SystemMessage(content="请帮我进行翻译,由英文翻译成中文!"),HumanMessage(content="my name is xiaoming")]# 3. 定义输出解析器parser=StrOutputParser()# 4. 定义链并执行chain=model|parser result=chain.invoke(messages)print(result)# 输出: 我叫小明总结
通过本节的快速上手编码,我们完成了:
- 接入大模型——使用
ChatOpenAI定义 GPT 模型,通过 API Key 远程调用 - 构造消息——使用
SystemMessage和HumanMessage构造符合 LangChain 规范的消息列表 - 调用模型——使用
.invoke()方法获取模型响应,理解AIMessage的结构 - 输出解析——使用
StrOutputParser提取纯文本结果 - 链式执行——使用 LCEL 的
|操作符将组件串联,一次调用即可得到最终结果 - Runnable 接口——理解 LangChain 所有组件的标准化接口,掌握
invoke/batch/stream等核心方法 - ChatOpenAI 参数调优——掌握 temperature、max_tokens、Token 概念、超时重试及第三方兼容接入
接下来将深入学习聊天模型的高级定义方式(
init_chat_model工厂函数、本地部署)以及模型工具调用能力。