上一篇已经创建天气 Agent,并确认 graph 的类型是 CompiledStateGraph。本篇启动两个本地服务:Qwen3 负责模型推理,Agent Server 负责运行 Graph、执行工具并提供 HTTP 接口。
最终需要验证的不只是“服务端口能访问”,还要确认模型真的生成了工具调用,工具结果也确实返回模型。
1. 两个服务分别做什么
本例包含两个独立服务:
| 服务 | 端口 | 作用 |
|---|---|---|
| Qwen3 MLX Server | 18080 | 提供 OpenAI 兼容模型接口 |
| LangGraph Agent Server | 2024 | 加载 Graph、管理运行并执行工具 |
Agent Server 不包含 Qwen3 权重。它运行到模型节点时,会通过 ChatOpenAI 请求 127.0.0.1:18080/v1。
下图把服务边界和 Graph 内部循环放在一起:Studio 或 API 客户端只请求 2024 端口;模型节点请求 18080 端口;工具节点在 Agent Server 进程中执行;开发状态写入 .langgraph_api。

2. 启动本地 Qwen3
前面的普通聊天接口可以使用主 .venv,但 Agent 工具调用还要求推理服务返回完整的 Tool Call ID。主环境中的旧版 MLX-LM 会返回 id=None,ToolNode 无法据此创建匹配的 ToolMessage。
因此从本篇开始为模型服务创建独立的 .venv_tool_server。直接依赖固定为:
mlx-lm==0.31.1
mlx==0.31.0
transformers==5.3.0
这些依赖已经写入 p03_local_agent_server/requirements-server.in,完整安装结果保存在同目录的 requirements-server-lock.txt。
打开第一个终端,进入 llm_learning 根目录并创建环境:
cd source/_posts/llm_learning
python3.12 -m venv \
--prompt llm_learning_tool_server \
.venv_tool_server
source .venv_tool_server/bin/activate
python -m pip install \
-r langgraph/p03_local_agent_server/requirements-server-lock.txt
python -m pip check
实际检查结果:
No broken requirements found.
保持这个环境处于激活状态,启动模型服务:
"$VIRTUAL_ENV/bin/python" -m mlx_lm server \
--model model \
--host 127.0.0.1 \
--port 18080 \
--chat-template-args '{"enable_thinking": false}'
服务启动后不要关闭终端。另开一个终端检查模型接口:
curl --noproxy '*' \
http://127.0.0.1:18080/v1/models
只要返回 object: list,并且 data 中存在模型,说明 Qwen3 服务已经就绪。
如果这一步失败,不要继续排查 LangGraph。Agent Server 可以启动,但运行到模型节点时一定会报连接错误。
3. 启动 Agent Server
打开第二个终端:
cd source/_posts/llm_learning
source .venv_langgraph/bin/activate
cd langgraph/p02_local_agent_project
确认已经准备 .env:
cp .env.example .env
启动本地服务:
langgraph dev \
--host 127.0.0.1 \
--port 2024 \
--no-browser
–no-browser 表示启动后不自动打开 Studio,方便先观察终端日志。启动成功时会给出三个地址:
API: http://127.0.0.1:2024
Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
API Docs: http://127.0.0.1:2024/docs
This in-memory server is designed for development and testing.
启动日志还会出现:
Importing graph with id weather_agent
Using auth of type=noop
Starting In-Memory runtime
这些稳定内容分别说明:
- langgraph.json 中的 weather_agent 已被加载。
- 本地开发服务没有配置鉴权。
- 当前使用内存运行时,不是生产部署。
文章不保留服务启动时间、动态请求 ID 等每次运行都会变化的字段。
4. 编写健康检查脚本
langgraph/p03_local_agent_server/01_check_server.py 的完整代码如下:
"""检查本地 LangGraph Agent Server 的健康接口和 OpenAPI 文档。"""
import httpx
# trust_env=False 可以避免 VPN 或系统代理转发本机请求。
with httpx.Client(base_url="http://127.0.0.1:2024", trust_env=False) as client:
health_response = client.get("/ok")
health_response.raise_for_status()
openapi_response = client.get("/openapi.json")
openapi_response.raise_for_status()
openapi = openapi_response.json()
print(f"健康检查状态码:{health_response.status_code}")
print(f"OpenAPI 状态码:{openapi_response.status_code}")
print(f"API 标题:{openapi['info']['title']}")
print(f"接口数量:{len(openapi['paths'])}")
运行:
cd source/_posts/llm_learning
source .venv_langgraph/bin/activate
python langgraph/p03_local_agent_server/01_check_server.py
真实结果:
健康检查状态码:200
OpenAPI 状态码:200
API 标题:LangSmith Deployment
接口数量:47
这里的 LangSmith Deployment 是这一版 Agent Server OpenAPI 文档中的标题,不表示代码已经部署到 LangSmith 云端。服务仍然监听本机 127.0.0.1:2024。
5. 查看本地 API 文档
浏览器打开:
http://127.0.0.1:2024/docs
页面中可以看到 Assistants、Threads 和 Runs 等接口:
- Assistant:已经配置好的 Graph 实例。
- Thread:一次可持续保存状态的会话。
- Run:在某个 Thread 中执行一次 Assistant。
本篇只认识这些对象,不展开 SDK 和 REST API 的完整调用方式。
6. 使用 Studio 调试 Graph
终端会给出 Studio URL:
https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
Studio 是托管的调试页面,baseUrl 指向本机 Agent Server。页面负责发送请求和展示过程,Agent 和工具仍然在本机执行。
这种连接方式与官方 Studio 本地开发说明一致。LangSmith Studio 本地开发
打开页面后选择 weather_agent,输入:
北京天气怎么样?
正常情况下可以看到四条关键消息:
HumanMessage
→ AIMessage,包含 get_weather 工具调用
→ ToolMessage,包含天气工具返回值
→ AIMessage,生成最终回答
本次真实运行结果如下。截图已经展开模型节点和工具节点,可以直接看到 get_weather 的城市参数、工具返回值和最终回答。顶部动态 Thread ID、相对时间和页面提示没有保留,避免这些运行时信息干扰正文。
如果 Studio 页面因为网络、浏览器本地网络策略或代理设置无法连接,先使用 /docs 和健康检查确认本地服务。Studio 只是调试界面,不能代替本地 API 是否正常的判断。
7. 验证 Agent 是否真的调用工具
本次通过 Agent Server 发起运行后,得到的稳定结果如下。动态消息 ID 已省略:
1. human
content=北京天气怎么样?
2. ai
tool=get_weather args={'city': '北京'}
3. tool
content=北京今天的天气晴朗,气温是 28 摄氏度。
4. ai
content=北京今天的天气晴朗,气温是 28 摄氏度。
这段结果能证明完整链路已经运行:
- 用户问题进入 Agent。
- Qwen3 没有直接编造答案,而是生成 get_weather 工具调用。
- LangGraph 执行工具并创建 ToolMessage。
- 工具结果再次交给模型,模型生成最终回答。
天气内容来自模拟函数,不是实时天气接口。这个案例验证的是 Agent 执行过程,不是天气数据准确性。
8. 为什么能够看到一张 Graph
上一篇只写了 create_agent(),没有手动创建节点和边,但它会在内部生成一张编译图。
这张图至少包含以下逻辑:
开始
→ 模型节点
→ 判断 AIMessage 是否包含 tool_calls
→ 有工具调用:进入工具节点,再返回模型节点
→ 没有工具调用:结束
因此天气案例虽然代码很少,仍然具备 LangGraph 的状态和循环结构。后续学习 StateGraph 时,会把这些节点、状态和条件边显式写出来。
9. 本地服务的数据会保存吗
langgraph dev 使用面向开发和测试的单进程内存运行时,但“内存运行时”不等于“退出进程后所有本地状态都会消失”。这一版开发服务器会把 checkpoint 和 store 数据写入项目目录下的 .langgraph_api/:
p02_local_agent_project/
.langgraph_api/
.langgraph_checkpoint.*.pckl
.langgraph_store.*.pckl
因此,只要没有删除 .langgraph_api/,重新启动同一个项目后,开发环境中的 Thread 状态可以继续读取。这个目录是本地运行数据,已经被 .gitignore 排除,不应提交到代码仓库。
LangGraph CLI 也将 langgraph dev 定义为会把状态保存到本地目录的轻量开发服务器。LangGraph CLI
这与前面 SQLite 聊天历史、Chroma 知识库不同:
- SQLite 和 Chroma 是应用主动选择的数据存储。
- .langgraph_api/ 是 langgraph dev 为本地开发保存的 checkpoint 和 store 文件。
- 本地 pickle 文件可以支持开发过程中的重启恢复,但不是生产级数据库,也不适合多实例并发、备份恢复和长期运维。
所以,langgraph dev 适合本机调试,不能因为能够恢复本地状态就直接作为生产部署方案。后面学习 Checkpointer 和正式部署时,还需要使用适合生产环境的持久化后端。
本次使用一个测试 Thread 写入四条消息,正常停止并重新启动 Agent Server,再读取同一个 Thread。动态 Thread ID 已省略,稳定结果如下:
重启前消息数量:4
重启后 Thread 状态:idle
重启后消息数量:4
最后一条消息类型:ai
最后一条消息内容:北京今天的天气晴朗,气温是 28 摄氏度。
测试完成后已经删除这个测试 Thread。重启前后消息数量和最后一条消息保持一致,说明本地开发状态确实从 .langgraph_api/ 恢复。
10. 常见问题
10.1 Agent Server 启动成功,但运行时报 502
先检查 Qwen3:
curl --noproxy '*' \
http://127.0.0.1:18080/v1/models
Agent Server 自己正常不代表模型服务正常。
10.2 端口已经被占用
检查端口:
lsof -nP -iTCP:18080 -sTCP:LISTEN
lsof -nP -iTCP:2024 -sTCP:LISTEN
结束旧进程或换一个端口。修改模型端口时,也要同步修改 graph.py 中的 base_url。
10.3 graph 导入失败
检查 langgraph.json:
"weather_agent": "./src/weather_agent/graph.py:graph"
文件路径和冒号后的变量名都必须存在。
10.4 模型没有调用工具
依次检查:
- 工具是否具有清晰的函数名。
- 参数是否有类型标注。
- 是否编写了工具文档字符串。
- 系统提示词是否明确要求天气问题调用工具。
- 模型服务是否支持 OpenAI 工具调用格式。
10.5 工具节点提示 unexpected type: str
如果服务日志出现:
Tool get_weather returned unexpected type: <class 'str'>
先检查模型返回的 AIMessage.tool_calls。如果工具调用的 id 是 None,工具执行结果就无法与这次调用配对。不要在工具函数里手工伪造固定 ID,应使用本篇锁定的 .venv_tool_server 启动 Qwen3,再确认每次工具调用都获得独立 ID。
10.6 Studio 无法连接本地服务
先排除本地服务问题:
curl --noproxy '*' http://127.0.0.1:2024/ok
如果返回 200,再检查 VPN、系统代理、浏览器对外部 HTTPS 页面访问本地 HTTP 地址的限制。即使 Studio 暂时不可用,也可以通过本地 API 文档和后续 SDK 完成测试。
10.7 是否需要 LangSmith API Key
本篇使用本地 in-memory 模式,并设置:
LANGSMITH_TRACING=false
本次服务启动日志明确显示没有设置控制平面或 License Key,并跳过了元数据循环。本地健康检查和 Agent 运行仍然成功。
11. 停止服务
分别回到两个终端按 Control + C:
- 先停止 Agent Server。
- 再停止 Qwen3 MLX Server。
停止后可以检查端口是否释放:
lsof -nP -iTCP:2024 -sTCP:LISTEN
lsof -nP -iTCP:18080 -sTCP:LISTEN
12. 小结
本篇完成了本地 Qwen3、LangGraph Agent Server、API 文档和天气工具调用的完整验证。到这里,Graph 已经不只是 Python 进程中的对象,而是可以由本地 HTTP 服务加载和运行的 Agent。
| 检查项 | 实际结果 |
|---|---|
| Qwen3 /v1/models | 返回 200 |
| Agent Server /ok | 返回 200 |
| /openapi.json | 返回 200 |
| OpenAPI 接口数量 | 47 |
| Graph ID | weather_agent |
| 模型服务环境 | .venv_tool_server |
| MLX-LM | 0.31.1 |
| 用户问题 | 北京天气怎么样? |
| 工具调用 | get_weather(city=”北京”) |
| 工具消息 | 北京今天的天气晴朗,气温是 28 摄氏度。 |
| 最终回答 | 北京今天的天气晴朗,气温是 28 摄氏度。 |
| 运行模式 | 本地 in-memory 开发服务 |