上一篇已经使用 MLX-LM 直接加载本地 Qwen3-14B-AWQ-4bit-MLX,完成中文生成测试,并把模型启动成只监听 127.0.0.1:18080 的本地 HTTP 服务。
直接加载模型适合验证模型文件和推理环境,但业务代码会和 MLX-LM 绑定在一起。上一篇已经完成从直接加载到 HTTP 服务的部署过程,本文从客户端角度说明怎样调用这个服务。
本文只介绍两种客户端调用方式:
- 使用 OpenAI Python SDK 调用本地模型。
- 使用 LangChain 的 ChatOpenAI 调用同一个本地模型。
两种方式都访问上一篇部署的 MLX-LM Server,不再在客户端代码中直接执行 mlx_lm.load() 和 generate()。
两种客户端与本地模型服务的关系如下:

图中的 OpenAI 兼容端点属于 MLX-LM Server,并不是另一层独立服务。两个客户端发送的 HTTP 请求结构相近,但向上提供的调用方法和返回对象不同。
1. 什么是 OpenAI 兼容接口
1.1 从直接加载变成服务调用
上一篇先验证了直接调用过程:
Python 脚本 -> MLX-LM -> 本地模型文件
这种方式下,每个脚本都要自己加载模型。如果多个程序需要调用模型,就会出现重复加载、代码绑定和资源管理困难等问题。
随后将模型启动为 MLX-LM Server,调用过程变成:
OpenAI SDK / LangChain
↓ HTTP 请求:http://127.0.0.1:18080/v1/chat/completions
MLX-LM Server(提供 OpenAI 兼容接口)
↓ 加载模型并完成推理
本地 Qwen3 模型
模型只由服务端加载,客户端只负责发送消息和接收回答。
1.2 “兼容”不等于使用 OpenAI 在线模型
OpenAI 兼容接口是指服务端提供与 OpenAI API 相近的路径、请求字段和响应结构。本地服务地址为:
POST /v1/chat/completions
请求中仍然有 model、messages、temperature、max_tokens 等字段,但真正完成推理的是本机 Qwen3,不会把问题发送到 OpenAI 在线服务。
这种接口的价值在于:只要客户端允许修改 base_url,同一套调用方式就可以连接不同的兼容服务。
2. 准备客户端项目
2.1 当前代码目录
项目位于:
/path/to/llm-learning
本阶段代码结构如下:
llm-learning/
├── .venv_langchain/
├── Qwen3-14B-AWQ-4bit-MLX/
└── langchain/
└── p03_openai_compatible_llm/
├── 01_test_openai_client.py
├── 02_test_langchain_chatopenai.py
├── README.md
└── requirements.txt
Qwen3-14B-AWQ-4bit-MLX 是上一篇创建的项目共用模型目录,不需要重新下载或复制 7.8GB 权重。作者实测时这个目录是指向已有权重目录的符号链接;首次按上一篇下载时,它就是保存模型文件的普通目录,两种情况都使用相同的描述性名称。
2.2 激活 Python 3.12 虚拟环境
进入项目并激活虚拟环境:
cd /path/to/llm-learning
source .venv_langchain/bin/activate
检查解释器:
python -V
python -c 'import sys; print(sys.executable)'
本机输出:
Python 3.12.11
/Users/bianhn/Documents/git/llm-learning/.venv_langchain/bin/python
这里必须确认解释器位于项目的 .venv_langchain 中。终端可能同时显示 Conda 的 (base) 和 venv 名称,不能只根据提示符判断环境是否正确。
2.3 确认上一篇的服务仍在运行
本文不再重复服务部署。运行客户端之前,应先按照上一篇的步骤启动 MLX-LM Server,然后在当前终端执行:
curl http://127.0.0.1:18080/v1/models | \
python -c 'import json,sys; data=json.load(sys.stdin); print({"object": data["object"], "data": data["data"]})'
服务返回:
{'object': 'list', 'data': []}
这说明客户端当前可以访问 HTTP 路由。上一篇已经通过 /v1/chat/completions 完成真实生成验证;如果这里连接失败,应先回到上一篇检查服务进程、端口和服务端日志。
3. 使用 OpenAI Python SDK 调用本地模型
3.1 安装 OpenAI 组件
在第二个终端进入相同虚拟环境:
cd /path/to/llm-learning
source .venv_langchain/bin/activate
python -m pip install 'openai==2.7.1'
检查安装结果:
python -c 'import importlib.metadata as m; print(m.version("openai"))'
确认命令能够打印版本号后继续。OpenAI SDK 会使用 httpx 发送 HTTP 请求。
3.2 编写 OpenAI SDK 代码
代码文件为 langchain/p03_openai_compatible_llm/01_test_openai_client.py:
# 这个文件只使用 OpenAI Python SDK 调用已经启动的本地 Qwen3 服务。
# 本地服务通过命令 mlx_lm.server 启动,本文件不负责启动服务。
from openai import OpenAI
# 创建 OpenAI 客户端,并把请求地址改成本地 OpenAI 兼容接口。
client = OpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed", # 本地服务不校验 key,但 SDK 要求提供这个参数。
)
# 调用本地服务的 Chat Completions 接口。
response = client.chat.completions.create(
model="Qwen3-14B-AWQ-4bit-MLX",
messages=[{"role": "user", "content": "请介绍一下李白"}],
temperature=0.8,
max_tokens=128,
presence_penalty=1.5,
# 启用思维链模式
extra_body={"chat_template_kwargs": {"entable_thinking": True}},
)
# 只打印模型返回的文本,便于初学者观察调用结果。
print(response.choices[0].message.content)
这段代码可以分成三步:
- 创建 OpenAI 客户端,并直接写出本地服务地址。
- 调用 chat.completions.create(),直接写出本次使用的模型名称。
- 从响应对象中读取模型回答。
3.3 关键参数
| 参数 | 作用 |
|---|---|
| base_url | 指向本地 OpenAI 兼容接口,而不是 OpenAI 在线地址 |
| api_key | OpenAI SDK 要求提供;当前本地服务不校验该值 |
| model | 直接指定本次部署的 Qwen3-14B-AWQ-4bit-MLX |
| messages | 按角色保存系统要求和用户问题 |
| temperature=0.8 | 减少随机性,便于重复测试 |
| max_tokens=128 | 最多生成 128 个 token |
3.4 运行代码
执行:
python langchain/p03_openai_compatible_llm/01_test_openai_client.py
本次复测关键输出:
当然可以!李白(701年-762年),字太白,号青莲居士,是唐代最著名的诗人之一,被后人尊称为“诗仙”。他与杜甫并称“李杜”,是中国文学史上最伟大的诗人之一。
### 一、生平简介
李白出生于碎叶(今中亚地区),祖籍陇西成纪(今甘肃天水)。他自幼聪慧,博览群书,尤其喜爱诗歌和剑术。他性格豪放不羁,喜欢饮酒,常与文人墨客交往,游历四方,足迹
最后一句没有生成完整,是因为示例将 max_tokens 限制为 128。这不影响接口连通性验证;需要完整回答时,可以适当增大该值。
到这里已经证明 OpenAI Python SDK 可以通过本地兼容接口调用 Qwen3,并不依赖 OpenAI 在线模型。
3.5 response 对象结构
ChatCompletion 响应:重要字段速查
| 字段 | 字段 | 含义 | 你的示例 |
|---|---|---|---|
response.choices[0].message.content |
正文 | 模型生成的回答 | 李白生平介绍… |
response.choices[0].finish_reason |
停止原因 | 为何结束生成 | length = 达到 max_tokens 被截断 |
response.model |
模型名 | 服务端实际用的模型 | Qwen3-14B-AWQ-4bit-MLX |
| 字段 | 字段 | 含义 |
|---|---|---|
response.usage.completion_tokens |
输出 token 数 | 生成了多少 token(示例:128) |
response.usage.prompt_tokens |
输入 token 数 | 提示占多少 token |
response.id |
请求 ID | 日志排查用 |
response.choices[0].message.role |
角色 | 固定为 assistant |
| 层级 | 对象 | 你关心什么 |
|---|---|---|
| 1 | ChatCompletion |
model、usage、元信息 |
| 2 | choices[0] |
finish_reason |
| 3 | message |
content(正文) |
print(response.choices[0].message.content) # 正文
print(response.choices[0].finish_reason) # length / stop
print(response.model) # Qwen3-14B-AWQ-4bit-MLX
4. 使用 LangChain 调用本地模型
4.1 安装 LangChain 组件
安装 LangChain 组件:
python -m pip install \
'langchain==1.0.3' \
'langgraph==1.0.2' \
'langchain-core==1.0.2' \
'langchain-openai==1.0.1' \
'langchain-community==0.4.1'
也可以直接安装本阶段依赖文件:
python -m pip install \
-r langchain/p03_openai_compatible_llm/requirements.txt
当前代码直接使用的是 langchain-openai 中的 ChatOpenAI。langgraph 和 langchain-community 在本篇还没有用到,先按照当前环境统一安装,后续文章再分别介绍。
4.2 编写 LangChain 代码
代码文件为 langchain/p03_openai_compatible_llm/02_test_langchain_chatopenai.py:
# 这个文件使用 LangChain 的 ChatOpenAI 调用已经启动的本地 Qwen3 服务。
# 本地服务通过命令 mlx_lm.server 启动,本文件只负责发送问题并打印回答。
from langchain_openai import ChatOpenAI
# 创建 LangChain 聊天模型,并把请求地址改成本地 OpenAI 兼容接口。
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed", # 本地服务不校验 key,但 ChatOpenAI 要求提供这个参数。
model="Qwen3-14B-AWQ-4bit-MLX",
temperature=0,
max_tokens=128,
)
messages = [
{"role": "system", "content": "你是一个中文老师"},
{"role": "user", "content": "请介绍一下李白"}]
# invoke 是 LangChain 聊天模型的统一调用方法,返回 AIMessage。
response = llm.invoke(messages)
# AIMessage.content 保存模型生成的文本。
print("这里打印结果:")
print(response.content)
虽然导入语句是:
from langchain_openai import ChatOpenAI
这仍然是在使用 LangChain。LangChain 1.x 把不同模型厂商的集成拆分成独立包,OpenAI 及其兼容接口由 langchain-openai 提供。
4.3 ChatOpenAI 做了什么
ChatOpenAI 在 OpenAI 兼容接口上增加了 LangChain 的聊天模型抽象。
本文最需要理解的是两个地方:
llm = ChatOpenAI(...)
response = llm.invoke("请介绍一下李白")
invoke() 是 LangChain 模型组件的统一调用方法。它返回的不是 OpenAI SDK 原始响应,而是 LangChain 的 AIMessage 对象,因此使用:
response.content
读取模型回答。下一篇介绍消息类型时,会继续说明 AIMessage、HumanMessage 和 SystemMessage。
4.4 运行代码
执行:
python langchain/p03_openai_compatible_llm/02_test_langchain_chatopenai.py
本次复测关键输出:
这里打印结果:
李白(701年-762年),字太白,号青莲居士,唐代著名诗人,被后人尊称为“诗仙”。他是中国文学史上最杰出的浪漫主义诗人之一,与杜甫并称“李杜”,在中国诗歌史上占有极其重要的地位。
### 一、生平简介
李白出生于碎叶(今吉尔吉斯斯坦境内),祖籍陇西成纪(今甘肃天水)。他自幼聪慧,博览群书,尤其喜爱道家思想,向往自由与自然。他一生游历四方,足迹遍布大江南北
Qwen3 推理发生在独立的 MLX-LM Server 中,LangChain 客户端只通过 HTTP 请求服务,不会在客户端重新加载这份模型权重。
5. OpenAI SDK 与 LangChain 的区别
两段代码访问的是同一个地址、同一个模型,区别主要在客户端抽象层。
| 对比项 | OpenAI Python SDK | LangChain ChatOpenAI |
|---|---|---|
| 主要定位 | 直接调用 OpenAI 或兼容接口 | 把接口包装为 LangChain 聊天模型 |
| 核心调用 | client.chat.completions.create() | llm.invoke() |
| 消息格式 | role/content 字典 | 字符串或 LangChain Message |
| 返回结果 | OpenAI SDK 响应对象 | LangChain AIMessage |
| 适合场景 | 接口连通性测试、简单聊天 | Prompt、Message、Chain、Tool、Agent 等应用开发 |
| 代码依赖 | openai | langchain-openai、langchain-core |
如果只是判断接口能不能调用,OpenAI SDK 更接近底层请求,排查问题也更直接。
如果后续准备使用 LangChain 的消息、提示词模板、结构化输出和 Agent,就使用 ChatOpenAI。它的价值不在于这一次调用少写几行代码,而在于后续组件可以使用统一模型接口继续组合。
6. 本次遇到的问题
客户端调用失败时,可以先按下面的顺序区分网络和参数问题:

服务端环境错用、模型生成失败和 502 的排查已经放在上一篇。本篇先确认服务正在运行,再检查客户端参数。
6.1 浏览器返回 Not Found
正确地址是:
http://127.0.0.1:18080/v1/models
如果写成 /v1/modles,服务端会返回 404。浏览器 JSON 插件可能默认折叠 data 数组,需要展开数组才能看到模型信息,也可以直接使用 curl 查看完整响应。
6.2 客户端模型名称必须与服务启动参数一致
两个客户端都直接写出模型名称:
model="Qwen3-14B-AWQ-4bit-MLX"
这个字符串与上一篇启动服务时的 --model Qwen3-14B-AWQ-4bit-MLX 完全一致,也就是项目根目录中的模型目录名称。读者不需要再跳转到函数或配置文件查询实际模型。
不能缩写成 model="Qwen3-14B",因为这个字符串不是本文模型目录的有效路径。MLX-LM Server 会尝试把它当成另一份模型加载,因而可能报模型路径或仓库不存在。
当前 MLX-LM 服务没有在 /v1/models 中列出这份本地模型。是否真正成功不能只看模型列表,仍要以聊天请求返回内容为准。
6.3 回答在最后一句被截断
OpenAI SDK 示例的回答停在“足迹”,原因是:
max_tokens=128
max_tokens 限制的是最多生成多少 token,不是字符数。连通性测试使用较小数值可以缩短等待时间;需要完整长回答时可以改为 256 或更大,同时观察内存和响应时间。
7. 小结
上一篇完成了从直接加载模型到启动 HTTP 服务的转换;这一篇则验证了两种客户端都能通过统一地址调用该服务。
OpenAI Python SDK 更适合验证兼容接口本身,LangChain ChatOpenAI 则把同一个接口包装成 LangChain 聊天模型。两者不是互相替代:ChatOpenAI 的底层仍然按照 OpenAI 兼容格式发送请求,但向上提供了 LangChain 的 invoke() 和 AIMessage。
下一篇将围绕这个 ChatOpenAI 对象介绍系统消息、用户消息、AI 消息和多轮对话,不需要在每个示例中重新加载 7.8GB 模型权重。
8. 最终验证结果
| 验证项 | 实测结果 |
|---|---|
| 本地服务地址 | http://127.0.0.1:18080/v1 |
| /v1/models | HTTP 200;返回空 data |
| OpenAI SDK | 成功返回真实 Qwen3 回答 |
| LangChain | ChatOpenAI 成功返回 AIMessage |
| LangChain 返回类型 | AIMessage,通过 response.content 读取文本 |
| 模型服务环境 | 独立运行的 MLX-LM Server |
| 最终状态 | OpenAI SDK 与 LangChain 均成功调用本地 Qwen3 |