基础模型调用
前一篇笔记中,我们对LangChain的适用场景、生态进行了简单介绍,并提供了一个Demo例子。通过之前例子我们可以发现,LangChain构建的应用其实都是围绕着大语言模型(LLM)的周边展开的,因此调用LLM也是LangChain中最核心的操作。这篇笔记我们将介绍LangChain中基础对话模型(ChatModel)的配置和使用方式。
LLM Provider包
LangChain其实支持了大量的Provider。在LangChain 1.x版本中,LLM Provider的相关代码都被解耦拆分到了独立的模块中。第一篇笔记中我们曾安装过langchain-openai,它底层基于OpenAI官方SDK构建,支持OpenAI的LLM API和兼容OpenAI标准的大量第三方Provider,也是日常开发中最通用的Provider包。
uv add langchain-openai
实际上,一些第三方Provider可能既有自己的专用的Provider包也可以采用OpenAI兼容方式调用,专用的Provider包可能具备更好的兼容性并提供更多参数设置和功能,但缺点是如果我们要在多个Provider之间切换,不如统一用OpenAI兼容方式简单,而后者目前被更多开发者所接受。具体如何选择,就需要我们根据实际情况考虑了。
除此之外,如果你使用Anthropic的API则需要langchain-anthropic,使用Google Generative AI的API则需要langchain-google-genai,出于商业因素考虑,Anthropic和Google显然不会提供OpenAI兼容模式。
调用LLM
LangChain框架中,各种Provider包提供的对话模型类都派生自BaseChatModel类,而BaseChatModel类又派生自LangChain的Runnable类,它可以理解为LangChain框架中各种组件的统一可调用接口,其提供的invoke()方法可用于阻塞调用,stream()和astream()可用于流式调用,pipe()则用于将多个组件组成“链”。有关Runnable类和LCEL我们将在后面章节详细介绍,这里我们先看调用对话模型的例子。
阻塞式调用
对于对话模型,最简单、最基础的阻塞式调用方式如下,下面代码会向LLM接口发起请求,然后阻塞等待直到获得完整输出。
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="qwen3:30b-a3b",
base_url="http://localhost:11434/v1/",
api_key="dummy",
temperature=1,
top_p=1,
max_tokens=16384,
timeout=120,
max_retries=6
)
resp = model.invoke("你好")
print(resp.content)
创建ChatOpenAI模型对象时,我们传入了许多参数:
model:该字段是模型ID,对于Ollama,这个名字必须和ollama list命令中列出的一致,对于其它第三方Provider,通常也会明确提供这个参数值以供我们调用。base_url:OpenAI-compatible API的端点地址,也可以理解为服务端的基础路径,如果不传这个参数,LangChain默认会将其设置为OpenAI官方的接口基础地址。api_key:OpenAI官方API需要鉴权,鉴权的API Key可以在这里设置,Ollama如果未开启鉴权则随便传一个值就行(但不能不传,否则会报错,这是OpenAI官方SDK的限制)。除了直接设置api_key参数,ChatOpenAI其实默认还会从环境变量中读取API Key,如果两个地方都未设置会报错。temperature:温度,控制模型输出的随机性。top_p:控制nucleus sampling(核采样)的参数,1表示不裁剪分布概率,如果传小于1的值表示只从累计概率前p的token中采样,输出会更保守。max_tokens:限制模型生成的最大token数。timeout:单次请求的超时时间。max_retries:请求失败时自动重试次数。
对话模型的invoke()可以直接传入一个输入字符串作为调用LLM的输入,调用后的返回结果是一个AIMessage对象,代表LLM的输出,其中通过content属性可以获得其中的文本结果。
流式调用
对话模型的stream()方法可以实现同步的流式调用。
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="qwen3:30b-a3b",
base_url="http://localhost:11434/v1/",
api_key="dummy",
temperature=1,
top_p=1,
max_tokens=16384,
timeout=120,
max_retries=6
)
for chunk in model.stream("你好"):
print(chunk.content, end="", flush=True)
不过真实开发中,如果我们开发的是一个服务端程序,同步流式调用性能不如异步方式,异步流式调用需要使用astream()方法,并结合Python的asyncio在异步上下文中编写流式输出。
async for chunk in model.astream(messages):
await websocket.send_text(chunk.content)
消息列表
我们知道LLM实际上是无状态的,它并没有什么“记忆”。在多轮对话中,LLM能够记得之前说过的话,是因为我们在应用层将对话历史全部传给了LLM。LangChain中,有SystemMessage、HumanMessage、AIMessage和ToolMessage这几种消息类型,分别对应系统提示词、人类输入、AI输出和工具调用信息。我们这里主要演示前3种消息,有关工具调用将在后面章节主要介绍。
下面例子种,演示了LLM多轮对话中的“短期记忆”,消息列表中,用户曾告诉LLM自己的名字,最后的提问则是让LLM重新输出自己的名字。
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="qwen/qwen3-vl-30b-a3b-thinking",
base_url="https://openrouter.ai/api/v1",
api_key="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",
temperature=1,
top_p=1,
max_tokens=16384,
timeout=120,
max_retries=6
)
messages = [
SystemMessage(content="You are a helpful assistant"),
HumanMessage(content="你好,我是Tom."),
AIMessage(content="你好"),
HumanMessage(content="我是谁?")
]
resp = model.invoke(messages)
print(resp.content)
模型可能会输出如下。
你是Tom,我刚刚听你说自己是Tom。有什么我可以帮你的吗?
可以看到,模型“记得”用户的名字,当然,LLM并不是真的有“记忆”,只是我们把历史消息列表发给它了。
SystemMessage、HumanMessage、AIMessage这些消息对象除了content还支持一些额外字段,不过大多和特定Provider相关,例如某些提供商可能支持查看历史消息日志,消息对象会支持ID参数,这些具体用到时查看相关文档即可。如果没用这些额外参数,除了直接创建消息对象,LangChain也支持一种简化写法,代码例子如下,LangChain框架内部做了一些处理,下面写法和之前是等价的。
messages = [
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "你好,我是Tom."},
{"role": "ai", "content": "你好"},
{"role": "user", "content": "我是谁?"}
]
LLM调用异常处理
调用LLM进行推理本身其实是一个成本极高的耗时操作,在等待输出过程中,各种异常都可能发生,例如连接超时、服务端接口暂时限流了、在提供商那里没用余额了等,而对于用户来说,等了半天最后却报错了,体验会非常糟糕。因此调用LLM时,加入健壮的Fallback和重试机制能让我们的AI应用用户体验更友好。
重试机制
以ChatOpenAI类为例,它内置了一套重试规则,它默认针对网络错误(包括限速的429、服务器错误5xx、读超时等各种情况)进行最多6次重试,重试使用指数退避策略。不同Provider提供的对话模型类可能略有区别,主要取决于底层SDK实现和LangChain包装。
model = ChatOpenAI(
model="qwen3:30b-a3b",
base_url="http://localhost:11434/v1/",
api_key="dummy",
temperature=1,
top_p=1,
max_tokens=16384,
timeout=120,
max_retries=6
)
Fallback
Fallback能让主模型超时、限流等情况时,自动切换到备用模型。下面例子代码中,我们配置了两个OpenRouter的模型,如果第一个模型调用失败,则Fallback到第二个模型。
from langchain_openai import ChatOpenAI
openrouter_main_model = ChatOpenAI(
model="qwen/qwen3-next-80b-a3b-instruct:free",
base_url="https://openrouter.ai/api/v1",
temperature=1,
top_p=1,
max_tokens=16384,
timeout=120
)
openrouter_secondary_model = ChatOpenAI(
model="qwen/qwen3-vl-30b-a3b-thinking",
base_url="https://openrouter.ai/api/v1",
temperature=1,
top_p=1,
max_tokens=16384,
timeout=120
)
model = openrouter_main_model.with_fallbacks([openrouter_secondary_model])
resp = model.invoke("你好")
print(resp.content)
基于BaseChatModel自定义模型类
目前,绝大多数本地/私有/第三方LLM推理服务提供商都提供了OpenAI兼容的API,因此一般来说都没用理由要对接自定义LLM API,最简单、最推荐的方式就是直接用ChatOpenAI并配置自定义base_url,如果你的LLM API就是如此特殊导致不兼容ChatOpenAI,那我建议你改服务端或写一个反向代理将其包装成OpenAI兼容接口,这样它可以很方便的接入几乎所有AI框架、工具中。
如果你真的确定就是要在应用层对接一个奇怪的自定义LLM API,那么方法是继承BaseChatModel实现自定义模型类。
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import AIMessage, BaseMessage
from langchain_core.outputs import ChatResult, ChatGeneration
class CustomChatModel(BaseChatModel):
def _generate(
self,
messages: list[BaseMessage],
stop: list[str] | None = None,
**kwargs
) -> ChatResult:
last_message = messages[-1].content if messages else ""
generation = ChatGeneration(
message=AIMessage(content=f"你说: {last_message},我收到啦!")
)
return ChatResult(generations=[generation])
@property
def _llm_type(self) -> str:
return "my_custom_llm"
model = CustomChatModel()
resp = model.invoke("你好")
print(resp.content)
_generate()方法是BaseChatModel必须实现的生成入口,LangChain会自动调用它处理消息。不过我们这里没用真的调用LLM推理,而是直接取出消息列表的最后一条消息回显了出来。_llm_type()是模型的唯一类型标识,它是一个用@property标注的属性方法,它也是必须存在的,用于LangChain中的序列化、调试和LangSmith遥测数据收集。
基础对话模型(ChatModel) vs 智能体(Agent)
这篇笔记我们介绍了如何配置和调用基础对话模型(ChatModel),但你会发现我们的代码和第一章中的内容完全不同,因为第一章我们创建的是智能体(Agent)。ChatModel是LangChain中对LLM对话能力的基础封装。BaseChatModel是面向对话的模型基类,它以消息列表作为输入,产生AIMessage作为输出。它的核心职责就是接受输入、返回输出,你给它一段文本或消息列表,它返回一个AI生成的回复。而智能体则不同,它是构建在ChatModel之上的,以典型的reAct模式的智能体为例,它的核心是一个循环,在智能体中,LLM充当了智能决策者,它与处理输入、与工具交互、观察工具返回结果,最终才生成有意义的输出,这是一个动态决策的过程,而不是遵循固定的执行序列。
有关智能体我们将在后续章节详细介绍。