Python & AI Tutorials Logo
LangChain & LangGraph

3. 构建你的第一个流式 CLI 聊天

在第 1 章中,你完成了第一次 LLM 调用,并看到一次性出现的完整响应。在第 2 章中,你学习了代理式 AI(agentic AI)的概念基础,以及 LangChain 为什么存在。现在是时候构建一些实用的东西了:一个让人感觉响应迅速且专业的流式聊天应用。

为什么流式输出很重要:当你向 LLM 提出一个复杂问题时,等待 10–30 秒才能看到完整响应会让人感觉程序“坏了”。流式输出让 token 在生成时就逐步显示出来,从而形成自然的对话节奏。本章将构建一个带流式输出的 CLI 聊天应用,包含正确的配置管理、调试能力,以及健壮的错误处理。

你将构建什么:在本章结束时,你将拥有一个可用的 chat.py 脚本,它能够:

  • 将 LLM 响应按 token 流式输出到终端
  • 从环境变量中安全加载 API Key
  • 使用合适的参数处理不同模型类型(聊天模型 vs 推理模型)
  • 提供调试工具,用于检查实际发送给 LLM 的内容
  • 优雅处理常见错误(缺少 API Key、网络故障、无效输入)

3.1) 创建工作文件夹并安装包

在编写任何代码之前,你需要一个干净的项目结构和正确的依赖。本节将为一个可维护的 Python 项目打下基础。

项目结构

为你的聊天应用创建一个新目录:

bash
mkdir langchain-chat
cd langchain-chat

Python 环境设置

创建虚拟环境以隔离依赖:

bash
# 创建虚拟环境
python -m venv venv
 
# 激活(macOS/Linux)
source venv/bin/activate
 
# 激活(Windows)
venv\Scripts\activate

为什么要使用虚拟环境? LangChain 有很多依赖(例如 OpenAI SDK、Pydantic、异步库)。虚拟环境可以确保:

  • 你的系统 Python 保持干净
  • 不同项目可以使用不同版本的 LangChain
  • 依赖可复现(通过 requirements.txt

激活后,你会在终端提示符中看到 (venv)

安装 LangChain

安装 LangChain 的核心包:

bash
pip install langchain-core==1.2.7 langchain-openai==1.1.7 python-dotenv

包拆解

  • langchain-core:核心抽象(消息、提示词、链、runnables)
  • langchain-openai:OpenAI 专用实现(ChatOpenAI、嵌入)
  • python-dotenv:从 .env 文件加载环境变量

版本说明:本书以 2026 年 1 月的 LangChain 1.2.x 为准。如果你在未来阅读本书,请查看 LangChain 文档 获取最新版本。

验证安装

创建一个简单测试以确认一切正常:

python
# test_install.py
try:
    from langchain_core.messages import HumanMessage
    from langchain_openai import ChatOpenAI
    print("✓ langchain-core: OK")
    print("✓ langchain-openai: OK")
    print("\n安装成功!")
except ImportError as e:
    print(f"✗ 导入失败: {e}")
    print("请确保你的虚拟环境已激活。")

运行它:

bash
python test_install.py

期望输出:

✓ langchain-core: OK
✓ langchain-openai: OK
 
安装成功!

如果你看到“安装成功!”,就可以继续了。如果你遇到导入错误,请再次检查:

  • 你的虚拟环境已激活(提示符中应有 (venv)
  • 这些包已成功安装(尝试运行 pip list

创建 requirements.txt

你刚刚用 pip install 命令安装了包。虽然这对学习来说可行,但有一种更好的方式:requirements.txt 文件。这是 Python 项目中的标准做法,原因有很多:

为什么使用 requirements.txt?

  • 可复现性:其他人(或 6 个月后的你)可以安装完全相同的包版本
  • 清晰的依赖管理:一眼就能看到项目需要哪些包
  • 团队协作:团队成员使用完全一致的版本,避免“在我机器上能跑”的问题
  • 自动化:服务器或 CI/CD 流水线可以用一行命令完成环境搭建:pip install -r requirements.txt

在项目根目录创建一个 requirements.txt 文件:

txt
# requirements.txt
langchain-core==1.2.7
langchain-openai==1.1.7
python-dotenv

注意语法

  • ==1.2.7 固定到精确版本(推荐用于可复现)
  • 不写版本号(如 python-dotenv)会安装最新的稳定版本
  • # 开头的行是注释

现在任何人都可以用一条命令安装所有依赖:

bash
pip install -r requirements.txt

这通常比逐个输入包名更高效,也更符合团队协作的常见实践。如果队友克隆了你的项目,他们只需要:

  1. 创建虚拟环境
  2. 运行 pip install -r requirements.txt

无需记住包名或版本——文件里都写好了。

你的项目结构

完成本节后,你的文件夹应如下所示:

langchain-chat/
├── venv/                 # 虚拟环境(不要提交到 git)
├── requirements.txt      # 依赖列表
└── test_install.py       # 安装验证脚本

下一步:第 3.2 节将展示如何使用 .env 文件安全加载 API Key。

3.2) 使用 .env 的环境变量

API Key(密钥)属于机密。把它们硬编码到代码里存在安全风险(尤其是当你提交到 git 时)。本节将展示标准做法:将机密写入 .env 文件,并在运行时加载到环境变量中。

为什么使用环境变量?

硬编码 API Key 的问题

python
# ❌ 千万不要这么做
llm = ChatOpenAI(api_key="sk-proj-abc123...")

如果你把这段代码提交到 GitHub,你的 API Key 就公开了。任何人都能使用它、在你的账号上产生费用,或者导致你的 API Key 被吊销。

解决方案:把机密存放在环境变量中,并在运行时加载。

创建 .env 文件

在项目根目录创建一个 .env 文件:

bash
# .env
OPENAI_API_KEY=sk-proj-your-actual-key-here

获取你的 API Key

  1. 前往 platform.openai.com/api-keys
  2. 创建一个新的 secret key
  3. 立即复制(之后无法再次查看)
  4. 将其粘贴到你的 .env 文件中,替换 sk-proj-your-actual-key-here

关键安全步骤:在做任何其他事情之前,先保护你的 API Key 不被提交到 git。

在项目根目录创建一个 .gitignore 文件,并添加以下几行:

bash
# .gitignore
venv/
__pycache__/
*.pyc
.env

.env 这一行会告诉 git 忽略你的 API Key 文件,从而防止你不小心把机密提交到版本控制中。

你现在的项目结构

langchain-chat/
├── venv/
├── .env                  # 你的 API Key(被 git 忽略)
├── .gitignore           # 包含:.env、venv/ 等
├── requirements.txt
└── test_install.py

加载环境变量

python-dotenv 包会把 .env 文件加载到 os.environ 中:

python
# chat.py
import os
from dotenv import load_dotenv
 
# 加载 .env 文件
load_dotenv()
 
# 访问环境变量
api_key = os.environ.get("OPENAI_API_KEY")
 
if not api_key:
    raise ValueError("在环境中未找到 OPENAI_API_KEY")
 
print(f"已加载 API Key:{api_key[:8]}...")  # 只显示前 8 个字符

load_dotenv() 如何工作

  1. 从你运行脚本的位置开始查找 .env 文件
  2. 读取每一行 KEY=value 格式的内容
  3. 将每个变量写入 os.environ
  4. 如果变量已被设置(例如由托管平台设置),它不会覆盖——将保留现有值

在 LangChain 中使用 API Key

LangChain 的 OpenAI 集成(ChatOpenAI 等)会自动在 os.environ 中查找 OPENAI_API_KEY

python
from langchain_openai import ChatOpenAI
 
load_dotenv()
 
# 这会自动使用 os.environ["OPENAI_API_KEY"]
llm = ChatOpenAI(model="gpt-4o-mini")

LangChain 的约定:当你创建 ChatOpenAI() 且不传入 api_key 参数时,它会自动在环境中查找 OPENAI_API_KEY。这是 LangChain 各类集成中常见的标准模式。

显式传入 API Key(用于测试或多个 API Key 场景)

python
llm = ChatOpenAI(
    model="gpt-4o-mini",
    api_key=os.environ.get("OPENAI_API_KEY")
)

当你有多个 API Key(开发 vs 生产)或希望明确指定使用哪个 API Key 时,这会很有用。

生产环境中的环境变量

在生产环境(云平台、Docker 容器)中,你不会使用 .env 文件。相反,你会通过平台设置来配置环境变量:

  • Docker:运行容器时使用 -e 参数
  • 云平台:在配置面板中设置环境变量
  • CI/CD:使用机密管理工具

重要的是:你的代码不需要改变。无论变量来自 .env 文件还是云平台,os.environ.get("OPENAI_API_KEY") 的工作方式都一样。我们将在后续章节详细介绍部署。

验证你的设置

为确认一切正常,你可以测试前面展示的环境变量加载代码。如果你的 .env 文件配置正确,os.environ.get("OPENAI_API_KEY") 将返回你的 API Key。

如果 os.environ.get("OPENAI_API_KEY") 返回 None,请确认:

  1. 你在访问环境变量之前调用了 load_dotenv()
  2. .env 存在于项目根目录
  3. .env 中正确写入了 OPENAI_API_KEY=sk-proj-...
  4. 你是在项目根目录运行脚本

下一步:第 3.3 节将实现带流式输出的实际聊天循环。

3.3) 用流式输出实现聊天循环

现在你将构建核心聊天循环。本节将介绍流式输出(streaming)——它是“卡顿的聊天机器人”和“响应迅速的聊天机器人”的关键区别。

理解流式输出

不使用流式输出(第 1 章的方法):

python
response = llm.invoke("写一篇关于 AI 的 500 字短文")
print(response.content)  # 等 20 秒,然后整篇文章一次性出现

使用流式输出

python
for chunk in llm.stream("写一篇关于 AI 的 500 字短文"):
    print(chunk.content, end="", flush=True)  # token 在生成时逐步出现

为什么流式输出很重要

  • 即时反馈:不是盯着空白屏幕等 20 秒,而是立刻就能看到文字出现
  • 更自然的对话感:就像和人聊天——回答是渐进出现的,而不是一次性给出
  • 节省时间和成本:如果 LLM 开始给出错误答案,你可以提前停止,而不是等到得到完整(但无用)的响应
  • 更好的调试:在构建应用时,你可以在问题发生时(例如格式错误)立刻发现,而不是长时间等待后才看到结果

流式输出到底是什么:流式输出是对同一段响应文本的增量传输。它不会暴露隐藏推理或模型内部过程——只是让你在 API 逐步返回部分输出时就能看到。把它想象成下载文件:你会随着分块到达看到进度,但无论一次性下载还是分块下载,文件内容都是一样的。

关于 chunk 边界的说明:chunk 不保证与单词或句子对齐。API 为了效率会以小批量发送 token,因此一个 chunk 可能是 "Hel"、"lo! How"、" can I"、" help you"、"?"。这很正常且符合预期——不要尝试从单个 chunk 中解析语义。

基本聊天循环

下面是一个最小的流式聊天循环:

python
# chat.py
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
def main():
    load_dotenv()
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    print("聊天已开始。输入 'quit' 或 'exit' 结束。\n")
    
    while True:
        user_input = input("You: ")
        
        if user_input.lower() in ["quit", "exit"]:
            print("再见!")
            break
        
        print("Assistant: ", end="", flush=True)
        
        for chunk in llm.stream([HumanMessage(content=user_input)]):
            print(chunk.content, end="", flush=True)
        
        print("\n")
 
if __name__ == "__main__":
    main()

它是如何工作的

  1. while True::用于持续对话的无限循环
  2. input("你: "):从终端获取用户输入
  3. llm.stream([HumanMessage(...)]):流式获取 LLM 响应
  4. 带特殊参数的流式输出
    • end="":每个 chunk 输出后不加换行(保持同一行输出)
    • flush=True:强制立即输出到终端,不进行缓冲

为什么是 [HumanMessage(content=user_input)]

LangChain 的聊天模型期望输入是消息列表,而不是原始字符串。每条消息都有一个角色:

  • HumanMessage:用户输入
  • AIMessage:LLM 响应
  • SystemMessage:给 LLM 的指令(第 4 章将介绍)

即使只有一条用户消息,你也要传入列表:[HumanMessage(content="Hello")]

关键限制——单轮对话:这个聊天循环刻意不保存状态。每次请求只发送当前消息,不发送之前的对话历史。这意味着:

  • LLM 不会记得你之前问过什么
  • 在你问了“法国的首都是什么?”之后,再问“它的人口是多少?”这类追问将无法工作
  • 这是 LLM 的基本特性——除非你显式提供上下文,否则它们没有记忆

该限制的示例

你: 法国的首都是什么?
助手: 巴黎。
你: 它的人口是多少?
助手: 我没有足够的上下文。你在问哪个城市?

while True 循环提供了 UX 的连续性(你可以一直聊天),但每一轮都是独立的。第 8 章将介绍:我们会通过存储并在每次请求时重新发送消息历史来实现对话记忆。

运行聊天循环

bash
python chat.py

示例交互:

聊天已开始。输入 'quit' 或 'exit' 结束。
 
You: LangChain 是什么?
Assistant: LangChain 是一个用于开发由语言模型驱动的应用的框架。它提供了提示词管理、链、代理和记忆等工具。
 
You: 给我一个简单示例
Assistant: 这里有一个基础示例:...
 
You: quit
再见!

理解流式 API

什么是 “chunk”?

每个 chunk 是一个 AIMessageChunk 对象,包含:

  • content:生成的文本 token
  • response_metadata:模型信息、token 计数等
python
for chunk in llm.stream([HumanMessage(content="Hello")]):
    print(f"Chunk: {chunk}")
    print(f"Content: {chunk.content}")
    print(f"Type: {type(chunk)}")

输出:

Chunk: content='Hello' response_metadata={'model_provider': 'openai', ...}
Content: Hello
Type: <class 'langchain_core.messages.ai.AIMessageChunk'>
 
Chunk: content='!' response_metadata={...}
Content: !
Type: <class 'langchain_core.messages.ai.AIMessageChunk'>
 
Chunk: content=' How' response_metadata={...}
Content:  How
Type: <class 'langchain_core.messages.ai.AIMessageChunk'>

累积完整响应

有时你需要完整响应(用于日志、测试或进一步处理):

python
def chat_with_accumulation():
    load_dotenv()
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    user_input = input("你: ")
    
    full_response = ""
    print("助手: ", end="", flush=True)
    
    for chunk in llm.stream([HumanMessage(content=user_input)]):
        print(chunk.content, end="", flush=True)
        full_response += chunk.content
    
    print("\n")
    
    # 现在你已经拥有完整响应
    print(f"[DEBUG] 完整响应长度: {len(full_response)} 字符")
    return full_response

当你需要做到以下事情时,这种模式很常见:

  • 将对话保存到数据库
  • 将响应解析为结构化数据
  • 计算 token 使用量或成本
LLMChatLoopUserLLMChatLoopUserloop[Multiple chunks]"What is LangChain?"stream([HumanMessage(...)])chunk: "Lang"print("Lang")chunk: "Chain "print("Chain ")chunk: "is a"print("is a")Stream complete"\n" (new line)Next input...

完成本节后的项目结构

langchain-chat/
├── venv/
├── .env
├── .gitignore
├── requirements.txt
├── test_install.py
└── chat.py              # 流式聊天循环(新增!)

下一步:第 3.4 节将展示如何通过智能参数配置来处理不同模型类型。

3.4) 智能配置:处理推理模型与聊天模型的参数差异

OpenAI 提供两类模型,它们的能力和控制机制不同:

聊天模型(gpt-4o、gpt-4o-mini):

  • 快速、对话感强
  • 支持 temperature 来控制随机性与创造性
  • 适合通用任务、创意写作、日常编码

推理模型(o1、o3、GPT-5):

  • 更慢,但逻辑性与一致性更强
  • 不支持 temperature(改用内部推理)
  • 适合复杂数学、多步规划、正式分析

关键差异:聊天模型使用概率采样(你来控制随机性),而推理模型使用确定性的内部逻辑(模型控制自身的推理过程)。

理解 Temperature(仅聊天模型)

什么是 temperature?

Temperature 是一个介于 0.0 到 2.0 的数字,用于控制模型回答的创造性。值越低(接近 0),回答越一致、可预测;值越高(接近 2.0),回答越富有创意、变化更大。你可以把它理解为一个“创造力旋钮”。

它如何工作:在生成每个词时,模型会看到多个可能的下一个词,并带有不同概率。Temperature 会影响模型如何选择:

  • 低 temperature(0.0):几乎总是选择概率最高的词 → 回答一致、聚焦
  • 高 temperature(2.0):更可能选择低概率词 → 回答多样、富有创意

重要:Temperature 只适用于聊天模型(gpt-4o、gpt-4o-mini)。 它不适用于推理模型(GPT-5、o1、o3),后者使用内部逻辑而非概率采样。

Temperature 取值指南

  • 0.0:高度确定、聚焦且一致

    • 适用:事实问答、常规代码生成、结构化输出
    • 同样输入 → 每次输出几乎一致
    • 示例:“2+2 等于多少?” → 总是 “4”
  • 0.7–1.0:标准采样行为(默认值为 1.0

    • 适用:一般对话、解释说明、平衡型回答
    • 措辞与示例有适度变化
    • 示例:“解释光合作用” → 每次措辞不同,但核心信息一致
  • 1.2–2.0:更有创意、更发散、可预测性更低

    • 适用:创意写作、头脑风暴、构思
    • 语气、结构、用词差异更大
    • 示例:“写一首关于月亮的诗” → 每次风格都很不同

注意:超过 1.0 会提升创造性,但可能降低事实准确性与连贯性。最大值为 2.0。

示例:Temperature 的影响(仅聊天模型)

python
# Temperature 0.0 - 确定性强,每次答案都一样
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
response = llm.invoke([HumanMessage(content="2+2 等于多少?")])
print(response.content)  # 输出: 4
 
# Temperature 1.0 - 默认行为,可能有轻微措辞变化
llm = ChatOpenAI(model="gpt-4o-mini", temperature=1.0)
response = llm.invoke([HumanMessage(content="2+2 等于多少?")])
print(response.content)  # 输出: 4(可能会带简短解释)

对于封闭的事实问题,temperature 对正确性影响不大。

对于开放式或创意任务,temperature 会显著影响多样性、语气与风格。

如果在推理模型上使用聊天模型参数会怎样?

取决于模型——有的会拒绝,有的会悄悄忽略:

python
# ❌ 这会在 o3 模型上失败
llm = ChatOpenAI(model="o3-mini", temperature=0.7)

错误:

BadRequestError: Temperature is not supported with this model

不同模型,不同策略

  • o1 / o3 模型:会显式拒绝不支持的参数。如果包含 temperature,API 会立即返回 400 BadRequest 错误。
  • GPT-5 模型:更宽松——参数会被接受但会被静默忽略。请求会成功,但 temperature 没有效果。

为什么这很重要:始终确认你正在使用哪种模型,并据此配置参数。错误的参数要么直接导致报错,要么静默失效,浪费你的调试时间。

如何控制推理模型行为

现在你知道聊天模型使用 temperature,而推理模型不使用。那么你如何控制推理模型?

推理模型通过提示词设计来调优,而不是参数:

  • 推理模型暴露 temperature 或类似控制项
  • 你需要通过提示词写法来引导行为:
    • 明确指令:“逐步思考”“展示你的过程”
    • 将约束写成规则:“你不能假设……”“务必验证……”
    • 结构化要求:“以 JSON 格式输出”“在答案前包含推理”
    • 决策逻辑:“如果条件 A,则做 X,否则做 Y”

示例:聊天参数 vs 推理提示词

python
# ❌ 聊天方式 - 对推理模型不适用
llm = ChatOpenAI(model="o3-mini", temperature=0.5)
# 错误: BadRequestError: Temperature is not supported
 
# ✅ 推理方式 - 通过提示词结构引导
prompt = """
逐步解决这个问题:
1. 说明你已知的信息
2. 展示你的计算过程
3. 验证你的答案
 
问题:如果 x + 5 = 12,x 等于多少?
"""
llm = ChatOpenAI(model="o3-mini")
response = llm.invoke([HumanMessage(content=prompt)])
print(response.content)

输出:

1. 我已知的信息:x + 5 = 12
2. 计算过程:x = 12 - 5 = 7
3. 验证:7 + 5 = 12 ✓
 
答案:x = 7

关键洞察:聊天模型通过参数控制,推理模型通过提示词控制。

模型选择决策表

既然你已经理解了如何控制两类模型,这里给出何时使用各类模型:

任务类型推荐模型原因
通用对话gpt-4o-mini快速、低成本、对话感强
简单问答gpt-4o-mini足以用于事实性查询
创意写作gpt-4o-mini(temp 0.8–1.0)Temperature 带来创造性
代码生成GPT-5更好的逻辑规划
复杂推理GPT-5针对多步逻辑优化
数学问题o3 / o1专用推理模型
多步规划GPT-5擅长长时程规划
正式分析(法律/政策)o3严格确定性

成本与延迟的权衡

理解实际权衡有助于你为用例选择合适模型:

模型类型速度(典型延迟)成本(相对)最适合
gpt-4o-mini非常快(<2s)非常低通用对话、简单任务
gpt-4o快(1–4s)中等更高质量聊天、多模态任务
GPT-5中等(3–8s)复杂推理、规划
o1 / o3最慢(5–15s+)最高确定性推理、形式逻辑

说明

  • 速度反映典型响应延迟(会随提示词长度与复杂度变化)
  • 成本是相对比较——请以 OpenAI 官网最新定价为准
  • 推理模型用速度与成本换取一致性与正确性
  • 聊天模型优先保证响应速度与效率

何时使用推理模型(GPT-5、o1、o3):

  • 需要正确中间步骤的多步数学与 STEM(Science, Technology, Engineering, Mathematics) 问题
  • 带有依赖关系与约束条件的复杂逻辑分析
  • 多原因交互导致的代码调试
  • 规则多、边界情况多、权衡多的规划任务
  • 需要一致性与长时程思考的代理工作流

何时使用聊天模型(gpt-4o、gpt-4o-mini):

  • 通用对话与交互式聊天
  • 推理深度有限的简单问答
  • 内容生成(博客、摘要、创意写作)
  • 日常代码生成与样板任务
  • 速度与成本比深度推理更重要的应用

下一步:第 3.5 节将展示调试技巧,用于检查实际发送给 LLM 的内容。

3.5) 调试:检查响应与 Token 用量

当你的 LLM 行为不符合预期时,你需要看到到底发送了什么、接收了什么。本节将展示如何检查 LLM 调用并调试问题。

为什么调试很重要

常见调试场景

  • “为什么 LLM 给了这个答案?” → 检查精确提示词
  • “这次请求花了多少钱?” → 检查 token 用量
  • “为什么这么慢?” → 测量延迟
  • “我的消息格式正确吗?” → 检查消息结构

挑战:当你调用 llm.invoke() 时,你会拿到一个响应对象。但里面到底有什么?可用于调试的信息有哪些?

理解响应对象

在调试之前,你需要理解 llm.invoke() 返回的是什么。

基本结构

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hello")])
 
# 响应里有什么?
print(type(response))  # AIMessage
print(response.content)  # 实际文本
print(response.response_metadata)  # token 用量、模型信息等

输出:

<class 'langchain_core.messages.ai.AIMessage'>
Hello! How can I assist you today?
{
  'token_usage': {
    'completion_tokens': 9,
    'prompt_tokens': 8,
    'total_tokens': 17
  },
  'model_name': 'gpt-4o-mini-2024-07-18',
  'finish_reason': 'stop',
  ...
}

响应的关键部分

  • response.content:LLM 生成的文本
  • response.response_metadata:一个字典,包含:
    • token_usage:使用了多少 token(用于成本计算)
    • model_name:实际响应的精确模型版本
    • finish_reason:生成为什么停止(详见“调试模式”小节)

访问 token 用量

python
token_usage = response.response_metadata['token_usage']
print(f"Prompt tokens: {token_usage['prompt_tokens']}")
print(f"Response tokens: {token_usage['completion_tokens']}")
print(f"Total: {token_usage['total_tokens']}")

输出:

Prompt tokens: 8
Response tokens: 9
Total: 17

为什么这很重要:你需要这些值来调试、追踪成本并优化提示词。

根据 Token 用量计算成本

token 用量决定成本。不同模型有不同定价:

GPT-4o-mini(截至 2026 年 1 月):

  • 输入:每 100 万 token $0.15
  • 输出:每 100 万 token $0.60

GPT-4o

  • 输入:每 100 万 token $2.50
  • 输出:每 100 万 token $10.00

成本计算函数

python
def calculate_cost(token_usage, model_name):
    """根据 token 用量计算成本。"""
    prompt_tokens = token_usage.get('prompt_tokens', 0)
    completion_tokens = token_usage.get('completion_tokens', 0)
    
    # 每 100 万 token 的定价(截至 2026 年 1 月)
    pricing = {
        'gpt-4o-mini': {'input': 0.15, 'output': 0.60},
        'gpt-4o': {'input': 2.50, 'output': 10.00},
        'gpt-5': {'input': 1.25, 'output': 10.00},
    }
    
    if model_name not in pricing:
        return None
    
    input_cost = (prompt_tokens / 1_000_000) * pricing[model_name]['input']
    output_cost = (completion_tokens / 1_000_000) * pricing[model_name]['output']
    
    return input_cost + output_cost
 
# 示例
response = llm.invoke([HumanMessage(content="Explain quantum computing")])
token_usage = response.response_metadata['token_usage']
cost = calculate_cost(token_usage, "gpt-4o-mini")
print(f"Cost: ${cost:.6f}")

输出:

Cost: $0.000123

为什么这很重要:生产应用每天可以处理 50,000+ 次请求。按每次请求 $0.002 计算,就是 $3,000/月。用错模型或提示词冗长,成本会飙升到 $30,000/月。一个重试循环的 bug 可能一夜烧掉数千美元。从第一天起就追踪 token 用量。

启用调试模式(当你需要原始 API 细节时)

响应对象与自定义封装可以处理大多数调试需求。但有时你需要看到LangChain 发送给 OpenAI 的确切内容——原始 JSON 请求与响应。

你可能需要它的场景

  • 调试 LangChain 的消息格式化
  • 验证 API 参数是否正确设置
  • 调查意外的 API 错误
  • 理解确切的 API payload

LangChain 通过 langchain_core.globals 提供内置调试日志:

python
from langchain_core.globals import set_debug
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
set_debug(True)
 
# 现在所有 LLM 调用都会打印调试信息
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hello")])

输出:

[llm/start] [llm:ChatOpenAI] Entering LLM run with input:
{
  "prompts": [
    "Human: Hello"
  ]
}
[llm/end] [llm:ChatOpenAI] [1.45s] Exiting LLM run with output:
{
  "generations": [
    [
      {
        "text": "Hello! How can I assist you today?",
        "generation_info": {
          "finish_reason": "stop",
          "logprobs": null
        },
        "type": "ChatGeneration",
        ...
      }
    ]
  ],
  "llm_output": {
    "token_usage": {
      "completion_tokens": 9,
      "prompt_tokens": 8,
      "total_tokens": 17,
      ...
    },
    "model_provider": "openai",
    "model_name": "gpt-4o-mini-2024-07-18",
    ...
  },
}

注意:输出格式会因 LLM provider 而异。此示例展示了 OpenAI 的结构。

调试输出揭示了什么

调试模式展示了完整的 LangChain → OpenAI 通信流程:

1. 消息格式转换

python
# 你的代码
[HumanMessage(content="Hello")]
 
# 你在调试输出中看到的内容
{
  "prompts": ["Human: Hello"]
}

调试模式展示了 LangChain 在发送到 LLM 之前如何在内部表示你的消息。

2. 生成完成状态

python
"finish_reason": "stop"

生成为什么结束:

  • "stop":模型自然完成响应
  • "length":响应因达到 max_tokens 限制而被截断
  • "tool_calls":模型通过生成工具调用指令来结束生成,而不是给出最终文本响应(第 12 章)
  • "content_filter":响应因安全或内容审核规则而被屏蔽或抑制

如果你看到 "length",请提高 max_tokens 以获取完整响应。

3. Token 用量明细

python
"token_usage": {
  "completion_tokens": 9,
  "prompt_tokens": 8,
  "total_tokens": 17,
  "completion_tokens_details": {
    "reasoning_tokens": 0  # 推理模型专用(o1/o3 等)
  },
  "prompt_tokens_details": {
    "cached_tokens": 0  # 提示词缓存(节省成本)
  }
}

除了基础计数之外,你还可以看到:

  • reasoning_tokens:内部推理步骤(仅推理模型)
  • cached_tokens:有多少提示词 token 命中缓存(降低成本)

4. 模型版本与指纹

python
"model_name": "gpt-4o-mini-2024-07-18",
"system_fingerprint": "fp_8bbc38b4db"
  • model_name:精确的快照版本(解释了为什么响应会随时间变化)
  • system_fingerprint:OpenAI 后端配置 ID(当系统更新时会变化)

5. 请求耗时

python
[llm/end] [llm:ChatOpenAI] [1.56s]

[1.45s] 展示了总请求耗时——用于识别慢查询很有用。

下一步:第 3.6 节将展示如何优雅处理常见错误。

3.6) 处理故障(模拟并修复常见错误)

生产级 LLM 应用会遇到可预测的故障模式:缺少凭证、网络超时、速率限制与无效输入。本节将展示如何优雅地处理这些错误,并从第一天起构建健壮的应用。

六种常见错误

1. 缺少 API Key

发生场景:你尝试创建 ChatOpenAI 实例,但环境中未设置 OPENAI_API_KEY

示例

python
# .env 文件不存在,或 OPENAI_API_KEY 未定义
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hello")])

你会看到的错误

OpenAIError: The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variable

如何修复

  1. 检查项目根目录中是否存在 .env 文件
  2. 验证 API Key 名称是否严格为 OPENAI_API_KEY(常见拼写错误:OPENAPI_KEY
  3. 确保在创建 LLM 之前调用了 load_dotenv()

2. 错误的 API Key

发生场景:你的 .env 文件包含无效、过期或复制错误的 API Key。

示例

python
# .env 中是: OPENAI_API_KEY=sk-invalid-key-12345
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Hello")])

你会看到的错误

AuthenticationError: Incorrect API key provided

如何修复

  1. 前往 https://platform.openai.com/api-keys
  2. 验证你的 API Key 仍处于激活状态(未被吊销或过期)
  3. 如有需要,生成一个新 API Key
  4. 仔细复制完整 API Key(常见错误:缺少首/尾字符)
  5. 粘贴到 .env,不要带多余空格:
bash
OPENAI_API_KEY=sk-proj-exactkeyhere

3. 网络故障

发生场景:你的网络连接中断,或 OpenAI 服务器在请求期间暂时不可达。

示例

python
# 请求过程中 WiFi 断开,或 OpenAI API 宕机
response = llm.invoke([HumanMessage(content="Hello")])

你会看到的错误

APIConnectionError: Connection error

如何修复

  1. 检查你的网络连接
  2. https://status.openai.com 查看 OpenAI 状态

4. 速率限制

发生场景:你在短时间内发送过多请求,超过 API 配额。

示例

python
# 立即发送 1000 个请求
for i in range(1000):
    llm.invoke([HumanMessage(content=f"Request {i}")])

你会看到的错误

RateLimitError: Rate limit reached for requests

如何修复

  1. https://platform.openai.com/account/limits 查看你的速率限制
  2. 如果需要更高限制,升级你的套餐
  3. 对大规模工作负载使用批处理(第 6 章将介绍)

5. 无效的模型名称

发生场景:你指定了一个不存在的模型名,或你的套餐无法使用该模型。

示例

python
llm = ChatOpenAI(model="gpt-99-ultra")  # 不存在
response = llm.invoke([HumanMessage(content="Hello")])

你会看到的错误

NotFoundError: The model `gpt-99-ultra` does not exist or you do not have access to it

如何修复

  1. https://platform.openai.com/docs/models 查看你套餐中可用的模型

6. 超出 Token 限制

发生场景:你的提示词太长,超过了模型的最大上下文窗口。

示例

python
# 创建一个 100 万字符的提示词
huge_prompt = "x" * 1_000_000
response = llm.invoke([HumanMessage(content=huge_prompt)])

你会看到的错误

BadRequestError: This model's maximum context length is 128000 tokens. However, your messages resulted in 250000 tokens.

如何修复

  1. 在发送前检查输入长度
  2. 了解你的模型限制:
    • gpt-4o-mini: 128K tokens
    • gpt-4o: 128K tokens
    • gpt-5: 400K tokens
  3. 对长文档使用分块或摘要(第 9 章将介绍)

下一步:第 4 章将展示如何设计可复用的提示词模板,把提示词工程与应用代码解耦。