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”的恐怖吗?跑一遍上面那三十行 DeepSeek 代码,然后把 base_url 换成智谱的——你会收到一个四百多行的报错,小林当时盯着它看了十分钟。
第二幕:小林发现了一个”统一插口”
小林在翻智谱文档的时候,看到了一行字:“我们提供 Anthropic 兼容接口”。他一开始没当回事,直到他注意到——智谱、Claude 官方、还有一堆别的平台,居然都认同一个接口协议。他脑子里闪过一个念头:那有没有可能,大家其实在说同一门语言,只是我没找到那个”翻译官”? 他在网上搜”LangChain”,看到它的自我介绍只有一句话:LangChain 为所有大模型提供统一的调用接口——你写一次代码,换模型只改一个参数。小林的耳朵竖起来了。他装了
langchain 和 langchain-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):各平台原样收请求、原样返回。
invoke() 就是那个插口。”
他还注意到一个细节:为什么 base_url 一个要带 /v1 一个不要带?因为 ChatAnthropic 会自动拼 /v1/messages,所以 base_url 给了它就不要带 /v1;而 ChatOpenAI 不会自动拼,所以你得自己带 /v1。同一个插口,两个牌子的充电器接法还不一样——这是小林踩的第一个坑,后面还有一堆。
🔧 技术细节:这一章涉及的关键类和签名
小林把这一章用到的”家伙”整理成了一张签名卡(实测签名): ①ChatAnthropic——智谱/Claude 的聊天模型类(langchain_anthropic 包)
BaseChatModel):
BaseChatModel——所有聊天模型的地基(langchain_core.language_models)
ChatAnthropic、ChatDeepSeek、ChatOpenAI 全部继承它。你调的 invoke()/stream() 接口都是它定义的——这就是”统一插口”的物理位置。
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 也变不出来。
复现信号:什么时候你会想起这一章
以后你写任何调大模型的代码,看到下面任何一个信号,就该想起”统一插口”:- 你准备手写 requests 调大模型 API——停一下。先问自己:这个项目以后会不会换模型供应商?会,就用 LangChain。
- 报错里出现
/v1或base_url——想起小林的第一个坑:ChatAnthropic 自动拼/v1(base_url 别带),ChatOpenAI 不拼(base_url 要带)。 - 你想给模型加”能力”——想清楚这是协议支持的(流式/工具调用)还是供应商独家的(思考内容)。统一接口帮不了你变出没有的能力。
show_full() 打印模型返回的东西,里面那堆看不懂的字段——response_metadata、usage_metadata、tool_calls——到底都是干嘛的?” 他决定把模型返回的”信封”拆开看看——翻到第 2 篇:AIMessage 与三协议。