Skip to content

Model I/O - 模型调用

LangChain V1.1.0


参考资料

1、Model I/O 介绍

1.1 什么是 Model I/O

上一节课我们认识了 LangChain 的整体架构(三层:基础层 → 能力层 → 应用层)。从本节开始,我们正式进入代码实战,第一站就是 Model I/O——LangChain 中与大语言模型交互的核心流程。

Model I/O 回答的是一个最基本的问题:"怎么把问题喂给模型,并拿到有用的结果?"

1.2 Model I/O 的三个环节

环节做什么
Prompts(提示词模板)把用户输入和系统指令格式化成模型能理解的消息
Models(模型调用)统一接口调用不同平台的模型(OpenAI、DeepSeek、本地模型等)
Output Parsers(输出解析)把模型返回的文本转换为 JSON、Pydantic 对象等结构化数据

本节聚焦中间的 Models 环节——怎么连接模型、怎么调用、怎么接入不同平台。Prompts 和 Output Parsers 将在下一节课件中展开。

1.3 LangChain 中的三类模型

LangChain 中有三类"模型",但它们的用途完全不同,不要混淆:

                    LangChain 模型类型

          ┌───────────────┼───────────────┐
          ▼               ▼               ▼
    Chat Models         LLMs         Embeddings
     "对话型"          "补全型"         "向量型"
                                    
  输入:消息列表      输入:字符串     输入:文本
  输出:AI消息        输出:字符串     输出:数字向量
                                    
  ★ 主流,本课重点    ⚠ 已过时       ★ RAG课件再讲
类型输入 → 输出代表类说明
Chat Models消息列表 → AI消息ChatOpenAIChatAnthropicChatOllama当前主流,所有现代模型(GPT-4o、Claude、DeepSeek 等)都是对话模型。本课程全程使用此类型
LLMs字符串 → 字符串OpenAI(旧版)已基本淘汰。早期的文本补全模型(如 GPT-3 text-davinci),不支持消息格式。LangChain v1.x 仍保留接口但不推荐使用
Embeddings文本 → 浮点数向量OpenAIEmbeddingsHuggingFaceEmbeddings用途不同。不生成文本,而是将文本转换为数字向量,用于语义搜索和 RAG。将在 RAG 课件中详细讲解

结论:本课程中提到的"模型调用",除非特别说明,都是指 Chat Models

1.4 统一接口的价值

不管你用的是 OpenAI、DeepSeek、Claude 还是本地的 Ollama,LangChain 都提供了完全一致的调用方式:

python
# 不同平台,同一套代码
llm = ChatOpenAI(model="gpt-4o-mini")          # OpenAI
llm = ChatOpenAI(model="deepseek-chat", ...)    # DeepSeek
llm = ChatAnthropic(model="claude-sonnet-4-20250514")  # Anthropic
llm = ChatOllama(model="qwen2.5:7b")           # 本地模型

# 调用方式完全一致
response = llm.invoke("你好")          # 同步调用
response = await llm.ainvoke("你好")   # 异步调用
for chunk in llm.stream("你好"):       # 流式输出
    print(chunk.content, end="")
responses = llm.batch(["问题1", "问题2"])  # 批量调用

这就是 Model I/O 中 Models 层的核心价值:一次学会,到处能用。


2、调用在线模型

前置约定:本章所有代码示例均假设你已在项目根目录创建了 .env 文件,配置好 OPENAI_API_KEYOPENAI_BASE_URL(参见课件01 第4.8节)。

2.1 为什么不直接用原生 SDK?

刚接触 LangChain 的同学可能会问:"我直接用 OpenAI 的 SDK 不就行了,为什么要多学一层框架?"

在回答这个问题之前,先了解一下"原生 SDK"是什么、行业现状如何。

2.1.1 OpenAI SDK:行业事实标准

OpenAI 的 GPT 系列模型不仅推动了大模型技术的发展,还定义了整个行业的开发范式和接口标准。目前大部分模型(Qwen、ChatGLM、DeepSeek 等)的 API 都遵循 OpenAI 定义的规范,可以直接使用 OpenAI SDK 来调用。

OpenAI 的 API 经历了两代演进:

API发布时间说明
Chat Completions API2023年经典 API,行业标准,几乎所有模型都兼容这套格式
Responses API2025年中新一代 API,支持服务端内置工具调用、服务端维护对话状态(短期记忆)等

官方文档Chat Completions API | Responses API

Chat Completions API 调用示例(经典,也是 LangChain 底层使用的格式):

python
# uv add openai
from openai import OpenAI
import os
from dotenv import load_dotenv

load_dotenv()

client = OpenAI(
    base_url=os.getenv("OPENAI_BASE_URL"),
    api_key=os.getenv("OPENAI_API_KEY"),
)

completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "将'你好'翻译成意大利语"}],
)
print(completion.choices[0].message.content)

Responses API 调用示例(新一代,支持内置工具):

python
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-4o-mini",
    input="中国国内今天发生了哪些大事儿?",
    tools=[{"type": "web_search"}]   # 服务端内置工具,无需自己实现
)
print(response.output_text)

本课程说明:LangChain 目前底层使用的是 Chat Completions API 格式。Responses API 较新,LangChain 的支持还在演进中。了解两者的区别即可,后续代码统一基于 Chat Completions API。

看起来原生 SDK 已经很好用了?那为什么还需要 LangChain?

2.1.2 原生 SDK 的痛点:切换模型

用一个真实场景来感受——"同一个任务,先用 GPT-4o-mini 跑,再换成 DeepSeek 跑,再换成 Claude 跑,对比结果"

用原生 SDK:每换一个平台,改一堆代码

python
from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()

# ---- 调用 OpenAI ----
client_openai = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL"),
)
resp1 = client_openai.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "用一句话解释量子计算"}],
)
print(resp1.choices[0].message.content)

# ---- 换成 DeepSeek ----
# 要重新创建 client、改 api_key、改 base_url、改 model……
client_deepseek = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com/v1",
)
resp2 = client_deepseek.chat.completions.create(
    model="deepseek-chat",
    messages=[{"role": "user", "content": "用一句话解释量子计算"}],
)
print(resp2.choices[0].message.content)

# ---- 换成 Anthropic?不兼容 OpenAI 格式,整套代码重写…… ----
import anthropic
client_claude = anthropic.Anthropic(
    api_key=os.getenv("ANTHROPIC_API_KEY"),
    base_url=os.getenv("ANTHROPIC_BASE_URL"),
)
message = client_claude.messages.create(
    model="claude-sonnet-4-20250514",
    messages=[{"role": "user", "content": "用一句话解释量子计算"}],
)
print(message.content[0].text)   # 注意:取值方式都不一样!

三个平台,三套 client,三种取值方式。如果还要加流式输出、错误重试、对话记忆……代码量会爆炸式增长。

2.1.3 用 LangChain:只改一行初始化,其余代码不动

python
# uv add langchain-openai langchain-anthropic
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from dotenv import load_dotenv
import os

load_dotenv()

# ---- 调用 OpenAI ----
llm = ChatOpenAI(model="gpt-4o-mini")
print(llm.invoke("用一句话解释量子计算").content)

# ---- 换成 DeepSeek?改一行 ----
llm = ChatOpenAI(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com/v1",
)
print(llm.invoke("用一句话解释量子计算").content)  # 调用代码完全一样

# ---- 换成 Anthropic?也是改一行初始化 ----
llm = ChatAnthropic(model="claude-sonnet-4-20250514")
print(llm.invoke("用一句话解释量子计算").content)  # 调用代码还是一样

差距在哪?

维度原生 SDKLangChain
切换模型改 client、改参数、可能改整套代码只改一行初始化
调用方式每个平台不一样(OpenAI 用 client.chat.completions.create(),Anthropic 用 client.messages.create()统一 llm.invoke()
取值方式OpenAI 用 .choices[0].message.content,Anthropic 用 .content[0].text统一 .content
流式输出每个平台的 stream 写法不同统一 llm.stream()
批量调用自己写循环或并发内置 llm.batch()
加功能(记忆、工具、RAG)全部从零写框架内置,直接组合

模型只有一两个时感觉差不多,但当你要对比 3-5 个模型、加上流式输出、再接上 RAG 管道时,原生 SDK 的代码量会呈指数增长,而 LangChain 始终保持简洁。

2.2 基础使用:ChatOpenAI

ChatOpenAI 是你在 LangChain 中最常用的类。

python
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv

load_dotenv()

# 最简写法:环境变量里已配好 OPENAI_API_KEY 和 OPENAI_BASE_URL
llm = ChatOpenAI(model="gpt-4o-mini")

response = llm.invoke("你好,介绍一下LangChain")

# response 是一个 AIMessage 对象,不是普通字符串
print(type(response))            # <class 'langchain_core.messages.ai.AIMessage'>
print(response.content)          # 模型返回的文本内容
print(response.response_metadata)  # 模型名称、token消耗等元信息

注意llm.invoke() 返回的是 AIMessage 对象,不是字符串。要拿文本内容需要 .content。这个设计是为了保留消息的元信息(角色、token 用量等),在构建对话链时会用到。

2.3 核心参数详解

初始化 ChatOpenAI 时可以传入多个参数来控制模型行为:

python
llm = ChatOpenAI(
    model="gpt-4o-mini",       # 模型名称(必填)
    temperature=0.7,            # 随机性,0=确定性,1=有创意(默认因模型而异)
    max_tokens=1000,            # 最大输出长度
    timeout=60,                 # 超时时间(秒)
    max_retries=2,              # 失败重试次数
)

2.3.1 temperature 到底怎么选?

temperature 是最常调的参数,它控制模型输出的"创造力":

python
from langchain_openai import ChatOpenAI

question = "给我的咖啡店起个名字"

# temperature=0 —— 确定性输出,每次结果几乎一样
llm_precise = ChatOpenAI(model="gpt-4o-mini", temperature=0)
print(llm_precise.invoke(question).content)
# → "醇香时光咖啡馆"(每次运行结果固定)

# temperature=1 —— 有创意,每次结果不同
llm_creative = ChatOpenAI(model="gpt-4o-mini", temperature=1)
print(llm_creative.invoke(question).content)
# → "晨雾与豆语"(下次运行可能是另一个名字)

选择建议

场景推荐 temperature原因
代码生成、数据提取、翻译0 ~ 0.3需要准确、稳定的输出
问答、摘要、分析0.3 ~ 0.7兼顾准确性和流畅性
创意写作、头脑风暴、起名0.7 ~ 1.0需要多样性和创造力

2.3.2 Token 是什么?

讲 token,最容易误解的一点就是:它不是"字数",也不是"单词数"

大模型真正处理的最小单位,是 token。你可以把它理解成:模型内部用来读写文本的"最小片段"。这个片段有时候是一个字,有时候是半个词,有时候是一个完整单词,甚至可能只是一个标点。

所以,同样一句话,人眼看起来长度差不多,token 数却可能差很多

语言1 个 Token ≈示例
中文1 ~ 1.8 个汉字"你好世界" 可能被切成 2 ~ 4 个 token
英文3 ~ 4 个字母"Hello World" 通常是 2 ~ 3 个 token

这里一定要注意:同一段文本,在不同模型、不同分词器下,token 数并不一定相同。

为什么?因为每家模型厂商背后的分词器(tokenizer)不一样。

分词器本质上就是:把一段文本切成 token 的规则和词表。不同分词器的词表不同、切分策略不同,所以最后统计出来的 token 数也会不同。

比如下面三种情况就很常见:

  • 同样是 Hello world,有的分词器可能切成 2 个 token,有的会更多
  • 同样是 LangChain 很好用,中英混合文本常常比纯英文更容易出现 token 差异
  • 同样一段 Python 代码,空格、换行、括号、变量名都会参与切分,所以代码的 token 数往往比肉眼估算更高

2.3.3 常见分词器例子

  1. OpenAI 的 cl100k_base
    GPT-4、GPT-4o 这一代模型常见的分词器,很多英文文本和代码场景都会用它来计数。

  2. OpenAI 的 o200k_base
    新一代的分词器,词表更大,在部分多语言文本里会比 cl100k_base 更省 token。

  3. Anthropic / Claude 自家的分词器
    Claude 使用自己的 token 切分规则,所以同一句中文、同一段提示词,放到 Claude 里统计,结果通常不会和 OpenAI 完全一样。

一句话记忆:token 不是固定按"几个字"来算,而是按"当前模型使用的分词器怎么切"来算。

2.3.4 可以直接在线测试的工具

模型提供商通常按 token 数量计费,max_tokens 参数限制的是输出的最大 token 数。如果你发现模型的回答被截断了,通常是 max_tokens 设太小了。

补充理解:

  • 输入 token:你发给模型的提示词、上下文、历史对话
  • 输出 token:模型生成的回答
  • 总消耗 token = 输入 token + 输出 token

所以,一次调用是否贵,不只取决于回答长不长,也取决于你喂给模型的上下文有多长。

max_tokens只是限制token输出数量,不是控制模型在max_tokens范围内作答。max_tokens=100,是截取模型输出结果的前100个token,不是限制模型在100个token内回答问题。

2.4 进阶:init_chat_model(动态切换模型)

当你需要在运行时动态切换不同提供商的模型时,init_chat_modelChatOpenAI 更方便——不需要 import 不同的类:

python
# uv add langchain-openai
# uv add langchain-anthropic
# uv add google-genai

from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
load_dotenv()

# 一个函数搞定所有提供商,通过 model_provider 参数区分
llm_openai = init_chat_model("gpt-5.4-nano-2026-03-17", model_provider="openai",api_key=os.getenv("OPENAI_API_KEY"),base_url=os.getenv("OPENAI_BASE_URL")) 
llm_claude = init_chat_model("claude-opus-4-7", model_provider="anthropic",api_key=os.getenv("ANTHROPIC_API_KEY"),base_url=os.getenv("ANTHROPIC_BASE_URL"))
llm_gemini = init_chat_model("gemini-3.1-flash-lite-preview", model_provider="google_genai",api_key=os.getenv("GEMINI_API_KEY"),base_url=os.getenv("GEMINI_BASE_URL"))

# 调用方式完全一致
for name, llm in [("OpenAI", llm_openai), ("Claude", llm_claude), ("Gemini", llm_gemini)]:
    response = llm.invoke("用一句话介绍你自己")
    print(f"{name}: {response.content}")

什么时候用哪个?

场景用什么原因
日常开发,模型固定ChatOpenAI / ChatAnthropic 等具体类代码提示好,参数明确
需要动态切换模型(A/B测试、用户选择模型)init_chat_model一个函数搞定所有提供商
不兼容 OpenAI 格式的平台(Anthropic、Google)init_chat_model 或对应的专用类ChatOpenAI 只能调 OpenAI 兼容的接口