Skip to main content

01 · 第一次对话:为什么所有大模型都长一个样

开头的现象

小林第一次跑通大模型对话,是在一个周日下午。他按照 DeepSeek 的文档,用 Python 的 requests 库手写了三十多行代码:构造 headers、拼 JSON、发 POST 请求、解析返回……跑通的那一刻,控制台打出了”你好,我是 DeepSeek!“——他高兴了不到三秒钟,老板的微信就来了:“小林,咱们准备换用智谱的模型,你评估一下。” 小林盯着屏幕上那三十行代码,心里咯噔一下:换模型?那这三十行是不是全得重写?

第一幕:小林的手写 API 调用,和他的”换模型恐惧症”

小林的第一个版本长这样——他当时觉得写得很漂亮:
他换到智谱,去查智谱的文档。结果发现:
  • 地址变成了 https://open.bigmodel.cn/api/anthropic/v1/messages(连协议都换了)
  • 请求头从 Authorization 变成了 x-api-key
  • messages 里的写法变了,max_tokens 居然变成必填
  • 取回复的方式变成了 data["content"][0]["text"]——不是 choices[0].message.content 了!
小林把脸埋进手里,心里默念:“我只是想换个供应商,怎么像是换了一门语言?” 他当时的默认假设是:“API 就是长这样的,模型供应商不同,我就得学不同的 API。” 这个假设让他想到一个恐怖的前景——公司每换一次模型,他就要重写一遍调用代码。他在脑海里已经看到了自己未来十年在 Ctrl+C / Ctrl+V 里度过的样子。
想亲眼看这个”换供应商 = 换 API”的恐怖吗?跑一遍上面那三十行 DeepSeek 代码,然后把 base_url 换成智谱的——你会收到一个四百多行的报错,小林当时盯着它看了十分钟。

第二幕:小林发现了一个”统一插口”

小林在翻智谱文档的时候,看到了一行字:“我们提供 Anthropic 兼容接口”。他一开始没当回事,直到他注意到——智谱、Claude 官方、还有一堆别的平台,居然都认同一个接口协议。他脑子里闪过一个念头:那有没有可能,大家其实在说同一门语言,只是我没找到那个”翻译官”? 他在网上搜”LangChain”,看到它的自我介绍只有一句话:
LangChain 为所有大模型提供统一的调用接口——你写一次代码,换模型只改一个参数。
小林的耳朵竖起来了。他装了 langchainlangchain-anthropic 两个包,然后写了他人生中第一段 LangChain 代码——只有几行:
他盯着这段代码看了半天,心里涌起一股不真实感:三十行手写代码,变成六行了? 他决定验证一件事——把 ChatAnthropic 换成 ChatDeepSeek,把 glm-4.7 换成 deepseek-v4-flash,把 base_url 换成 DeepSeek 的。他战战兢兢地按了运行,结果……跑通了。一模一样地跑通了。 他这才明白:LangChain 干的事,是把”每个供应商的方言”翻译成”统一的普通话”。 他之前以为 API 就该各长各样,真相是——那些供应商早就用着同一套接口协议(OpenAI 兼容 / Anthropic 兼容),LangChain 只是把这套协议包装成了一个统一的 invoke()
想验证”换模型只改三行”吗?在 learn-langchain 环境跑 01_first_chat.py,然后把 ChatAnthropic 换成 ChatDeepSeek、把 glm-4.7 换成 deepseek-v4-flash——同样的 invoke(),同样的 .content

第三幕:这个”统一”是怎么实现的——三层结构

小林没有满足于”能用”。他拆开了 LangChain 的包装,发现里面是三层:
  • 顶层(你的代码):只写 invoke("你好"),不关心底层是谁。
  • 中间层(协议适配)ChatAnthropic 内部知道怎么把 invoke 翻译成智谱要的 HTTP 请求,ChatDeepSeek 知道怎么翻译成 DeepSeek 要的。
  • 底层(真实 API):各平台原样收请求、原样返回。
小林把这比作 USB 插口:“你买任何鼠标、键盘、U 盘,插上去都能用——因为 USB 是统一标准。LangChain 就是 AI 界的 USB 标准,invoke() 就是那个插口。” 他还注意到一个细节:为什么 base_url 一个要带 /v1 一个不要带?因为 ChatAnthropic 会自动拼 /v1/messages,所以 base_url 给了它就不要带 /v1;而 ChatOpenAI 不会自动拼,所以你得自己带 /v1同一个插口,两个牌子的充电器接法还不一样——这是小林踩的第一个坑,后面还有一堆。

🔧 技术细节:这一章涉及的关键类和签名

小林把这一章用到的”家伙”整理成了一张签名卡(实测签名): ChatAnthropic——智谱/Claude 的聊天模型类(langchain_anthropic 包)
关键方法(继承自 langchain_core 的 BaseChatModel):
BaseChatModel——所有聊天模型的地基(langchain_core.language_models) ChatAnthropicChatDeepSeekChatOpenAI 全部继承它。你调的 invoke()/stream() 接口都是它定义的——这就是”统一插口”的物理位置
③ 三种消息类——invoke 的输入/输出(langchain_core.messages)
④ AIMessage 的完整字段(model_dump() 后)——你 show_full() 打印的东西:
想验证签名吗?在 Python 里跑 help(ChatAnthropic)inspect.signature(ChatAnthropic),能看到完整参数列表。from langchain_core.language_models import BaseChatModel; isinstance(model, BaseChatModel) 返回 True,证明”统一插口”真实存在。

第四幕:边界——“统一”不统一什么

小林用了一周,慢慢摸清了这套”统一接口”的边界。invoke() 是统一的,但模型能力不是统一的 反例来了:小林想让两个模型都”打开思考模式”,结果 DeepSeek 那边怎么都读不到思考文本——不是他代码写错了,是OpenAI 兼容协议本身就不标准地返回思考内容。他想看思考过程,只有智谱这种 Anthropic 兼容协议行。统一接口统一不了”供应商根本没给你的能力”。
想验证这个边界吗?在 06_thinking.py 里用智谱跑,能看到思考块;换成 ChatDeepSeek 跑同样的问题——思考内容消失了,只剩 usage 里的 reasoning_tokens 计数。

结论(小林用一整周换来的)

LangChain 把”跟不同大模型说话”统一成了一个插口:model.invoke() 进,resp.content 出。 换模型只改三行(包名、模型名、base_url),因为所有主流平台底层就两套协议(OpenAI 兼容 / Anthropic 兼容),LangChain 的协议适配层把它们都翻译成了同一个 BaseChatModel 接口。但”统一接口”只统一了”怎么调”,统一不了”模型有什么能力”——供应商没给的能力(比如某些协议看不到思考内容),LangChain 也变不出来。

复现信号:什么时候你会想起这一章

以后你写任何调大模型的代码,看到下面任何一个信号,就该想起”统一插口”:
  1. 你准备手写 requests 调大模型 API——停一下。先问自己:这个项目以后会不会换模型供应商?会,就用 LangChain。
  2. 报错里出现 /v1base_url——想起小林的第一个坑:ChatAnthropic 自动拼 /v1(base_url 别带),ChatOpenAI 不拼(base_url 要带)。
  3. 你想给模型加”能力”——想清楚这是协议支持的(流式/工具调用)还是供应商独家的(思考内容)。统一接口帮不了你变出没有的能力。
想继续看小林的故事吗?他学会了”统一插口”之后,第一个困惑是:“我用 show_full() 打印模型返回的东西,里面那堆看不懂的字段——response_metadata、usage_metadata、tool_calls——到底都是干嘛的?” 他决定把模型返回的”信封”拆开看看——翻到第 2 篇:AIMessage 与三协议