LangGraph 入门第十课:调用真实模型,用 Pydantic 校验后再路由
配套代码:精简版
structured_output_demo.py;工程化完整版本见structured_output_full.py。精简版会发起真实模型请求,不是纯离线示例。
前面的课程用规则或本地函数决定流程。本课把模型接入一个简单的分类工作流:用户提出学习问题,模型返回 JSON,程序校验数据,再由 LangGraph 根据校验后的结果选择分支。
先说清这份示例的组成:当前精简版使用 httpx 直接请求兼容 Chat Completions 的接口,LangGraph 负责组织节点和路由;它没有使用 LangChain 的 ChatModel 集成,也没有调用 .with_structured_output()。因此,接口端的 JSON 模式与本地 Pydantic 校验是两层不同的保障。
精简版的 code 和 explain 节点只写入分支提示;clarify 输出澄清问题后结束,并不会暂停等待用户回复;invalid 处理的是模型正文未通过内容校验的情况。
先定义程序期望的模型输出
本例希望模型返回主题、输出形式、是否需要澄清和澄清问题:
python
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
class LearningRequest(BaseModel):
model_config = ConfigDict(strict=True, extra="forbid")
topic: str = Field(min_length=1, max_length=80)
output_format: Literal["explanation", "code"]
needs_clarification: bool
clarifying_question: str = Field(max_length=160)
这里有几种不同的约束:
strict=True减少把错误类型悄悄转换成正确类型的情况,例如不把字符串"false"当作布尔值False。extra="forbid"拒绝 schema 中没有定义的额外字段。Literal[...]限定output_format的允许值。Field对字符串长度设定范围。
Pydantic 严格模式主要解决值的类型和结构是否符合规则;它无法保证模型对用户意图的判断一定正确。Pydantic 严格模式说明
JSON 模式不是完整的 Schema 校验
请求体中会使用:
python
"response_format": {"type": "json_object"}
这要求兼容接口按 JSON 对象形式返回正文。但 JSON 对象本身不一定符合 LearningRequest 的字段、类型和业务约束,所以程序仍会在本地调用 Pydantic 校验。LangChain 的结构化输出接口还支持不同策略,例如 provider JSON Schema、tool calling 或 JSON mode,具体能力要以模型集成为准;本课精简代码走的是直接 HTTP 请求加本地解析这一条路径。LangChain 结构化输出说明
请求模型,然后解析和校验
ask_model 把系统提示和用户问题分开提交,再读取响应正文:
python
response = httpx.post(
base_url.rstrip("/") + "/chat/completions",
headers={"Authorization": "Bearer " + api_key},
json={
"model": model_name,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": question},
],
"response_format": {"type": "json_object"},
"max_tokens": 1024,
},
timeout=60,
)
收到 HTTP 成功响应后,代码检查 finish_reason,再取出模型正文:
python
choice = response.json()["choices"][0]
if choice["finish_reason"] != "stop":
raise ValueError("模型输出未正常结束")
content = choice["message"]["content"]
if not isinstance(content, str):
raise ValueError("模型正文必须是字符串")
本例对任何非 stop 的结束原因都停止处理。不要把所有非 stop 情况都简单等同于"输出被截断";它表示本程序只接受预期的正常结束标记。
Pydantic 检查数据,Python 处理字段之间的约束
在 classify 节点里,JSON 文本先由 Pydantic 解析:
python
decision = LearningRequest.model_validate_json(raw)
当前精简版用普通 Python 判断两个字段之间的业务规则:需要澄清时必须有问题;不需要澄清时,问题字段必须为空。校验通过后,将模型对象序列化成字典写入图 State:
python
return {
"decision": decision.model_dump(),
"status": "classified",
}
这里的 RequestState 是 TypedDict,主要描述图状态的结构;外部模型返回数据的运行时校验由 LearningRequest 负责。若使用 Pydantic @model_validator,也可以把跨字段规则放进模型定义;当前精简版选择在 classify 中显式写 if 判断。
把校验结果交给图来路由
分类节点把通过验证的结果放进 State 后,条件路由函数只根据状态选择下一节点:
python
def route(state):
if state["status"] == "invalid_output":
return "invalid"
if state["decision"]["needs_clarification"]:
return "clarify"
if state["decision"]["output_format"] == "code":
return "code"
return "explain"
把模型负责的工作限定为提出一个候选分类,再由程序校验字段并执行明确的路由规则,流程就容易检查和调试。但模型仍可能给出结构正确、分类错误的答案:比如用户要代码,模型返回 output_format="explanation"。这类语义错误不会自动被 Pydantic 发现,需要评估样本或额外业务规则处理。
错误分层:不要把所有失败都送到 invalid
classify 中对模型正文的 Pydantic ValidationError 和业务 ValueError 进行捕获,并把 status 设为 invalid_output,让条件边进入 invalid。
但 ask_model 的网络请求发生在这段内容校验 try 之前。因此 HTTP 状态错误、网络连接失败、配置文件读取问题等会按各自的异常处理路径传到程序入口;它们不会因为有 invalid 节点就自动被处理。invalid 只兜住本例明确捕获的输出内容错误,不会修复模型答案,也不会自动重试请求。
可以把故障路径简化成:
| 失败类型 | 本例处理方式 |
|---|---|
| JSON 或字段校验失败 | classify 写入 invalid_output,路由到 invalid |
| HTTP 错误 | 由入口捕获 HTTPStatusError 并提示状态码 |
| 网络请求错误 | 由入口捕获 RequestError |
| 配置或响应结构异常 | 由入口按预期异常类型给出提示 |
| 模型语义判断错误 | 结构校验无法发现,需要评估或业务校验 |
配置文件与密钥
精简版从脚本旁的 key.env 读取 model、api-key 和 base_url 三项配置,不读取系统环境变量。可以在本机创建配置文件,填入自己所用服务提供的模型名、密钥和兼容接口地址:
text
model: <模型名称>
api-key: <仅在本机填写的密钥>
base_url: <兼容接口地址>
不要把真实密钥放进文章、截图或公开仓库。将用户问题发送到外部模型服务前,也应确认内容适合提交给该服务。
怎样运行?
精简版直接运行会发起真实请求:
powershell
python -B -X utf8 structured_output_demo.py
修改文件顶部的 QUESTION 可以更换问题。运行前需要正确配置本机 key.env,并安装 httpx、pydantic、typing_extensions 和 langgraph 等依赖。若只想先做离线检查,可参考完整版本的:
powershell
python -B -X utf8 structured_output_full.py --check
完整版和精简版的配置处理细节不同,阅读时要按对应脚本的说明执行。精简版没有 --check 参数,也没有等待模型回复的中断点。
本课小结
- 模型给出候选结构化数据,Pydantic 在本地校验字段、类型和约束。
- JSON 模式不等于完整 Schema 校验;本例仍以 Pydantic 模型作为本地数据契约。
TypedDict描述图 State 结构,Pydantic 模型负责外部模型输出的运行时校验。- 条件边依据已经校验的 State 决定后续节点;结构合法不代表语义判断正确。
- 输出校验失败、HTTP 错误、网络错误和配置错误属于不同失败路径,应分别处理。
- 本例通过
httpx直连兼容接口;LangGraph 负责编排,不代表这里使用了 LangChain 模型集成。
至此,课程已经从 State、路由、持久化和中断,逐步覆盖到并发、子图、失败恢复、动态分发、Command,以及真实模型输出的程序化校验。