关于 langgraph-decision-models
langgraph-decision-models 是面向 LangGraph 的 LangChain 技能,用带类型的概率分类替代生成文本后再解析决策。适合构建路由、分流、过滤、护栏和批量分类节点的开发者。
主要功能
- 01输出二元判断概率
- 02按标签选择并返回分布
- 03按评分标准计算分数
- 04单次请求处理最多 32 个问题
- 05提供 LangGraph 路由与批处理指导
适用场景
- 01按账单相关性路由工单
- 02筛选需要人工审核的条目
- 03依据语义标准过滤内容
- 04对批次中的每个项目分类
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ langchain-ai/ langchain-skills --skill langgraph-decision-models安装后在 Claude Code 中输入/langgraph-decision-models确认已加载。
附带文件references/conversion-playbook.md
详细介绍
Noul(instructions=...)— 二元问题,返回答案为“是”的概率Choice(instructions=..., criteria={...})— 选择一个标签,返回完整的概率分布及置信度Score(instructions=..., criteria=[...])— 按有序评分标准打分,返回期望值及置信度
TypeSafeClassifier 是 LangChain 的 Runnable[ClassifierRequest, ClassifierResponse],因此可像其他 runnable 一样直接用作节点。一次请求最多可包含 32 个问题,且会分别回答——由你的代码组合这些结果。
适合使用的场景: 节点只生成文本,而你只是想从中解析出一个决策,例如路由、分诊、过滤、护栏,或对批次中的每个项目进行分类。
不适合使用的场景: 节点的输出本身就是最终产物(摘要、草稿、代码),或判断需要多步推理。决策模型负责分类,不负责思考。
安装和接入
langchain-typesafe 仍处于 alpha 阶段(0.0.1a3),且 TypeSafeClassifier 标记为 @beta——请固定版本,并做好接口变动的准备。
uv add langchain-typesafe
有三种方式可连接模型。分类器会向 {base_url}/v1/systemone 发送 POST 请求,并附带 Authorization: Bearer {api_key},因此切换提供商只需修改构造参数:
1. TypeSafe directly (Jev). Reads TYPESAFE_API_KEY when api_key is omitted.
classifier = TypeSafeClassifier(model="jev-latest")
2. SemIf, hosted on the LangSmith Gateway. Note: LangSmith key, not a TypeSafe key.
classifier = TypeSafeClassifier( model="semif-qwen3.5-4b", api_key=os.environ["LANGSMITH_API_KEY"], base_url="https://gateway.smith.langchain.com", )
3. Jev through the Gateway (BYOK). The typesafe/ prefix routes to a
TYPESAFE_API_KEY stored in LangSmith workspace secrets. Without that secret
every typesafe/* id returns 424 Failed Dependency -- before the model name is
even validated, so a 424 does not confirm the id is real.
classifier = TypeSafeClassifier( model="typesafe/jev-1.13.0", api_key=os.environ["LANGSMITH_API_KEY"], base_url="https://gateway.smith.langchain.com", )
</python>
</ex-wiring>
---
## 转换模式
在**一次**请求中询问有关某个页面/项目的所有问题,将类型化响应放入状态,再用普通函数进行路由。路由器是普通的 Python 代码,无需访问网络即可测试。
<ex-classify-and-route>
<python>
```python
from typing import TypedDict
from langchain_typesafe import ClassifierResponse, Noul, Score, TypeSafeClassifier
from langgraph.graph import StateGraph, START, END
QUESTIONS = {
"relevant": Score(
instructions="How relevant is this ticket to a billing problem?",
criteria=["Unrelated.", "Possibly related.", "Directly about billing."],
),
"angry": Noul(instructions="Is the customer expressing anger?"),
}
class State(TypedDict):
text: str
answers: ClassifierResponse
route: str
classifier = TypeSafeClassifier(model="jev-latest")
def classify(state: State) -> dict:
# One request, every question. They are answered independently.
return {"answers": classifier.invoke(
{"state": state["text"], "questions": QUESTIONS}
)}
def route(state: State) -> str:
a = state["answers"]
if a.nouls["angry"].noul > 0.7:
return "escalate"
if a.scores["relevant"].score < 0.5:
return "close"
return "handle"
读取答案:response.nouls[id].noul、response.choices[id].choice、response.scores[id].score。每个视图都以你的问题 ID 为键;response.answers 则包含所有答案。
读取答案时的三个陷阱
这些问题会导致静默误路由,而不会抛出异常。
1. Score.score 是期望值,不是等级。 它是按概率对评分标准加权后得到的平均值,通常会有小数。score == 0 几乎不会成立——“不相关”的项目会得到 0.07,而不是 0。应始终与一个区间比较。
if a.scores["relevant"].score < 0.5: # correct
if a.scores["relevant"].score == 0: # WRONG -- nearly never true
2. 置信度衡量的是分布形态,而非正确性。 对于 Score,置信度表示评分标准的分布有多集中。如果某个项目恰好处于两个等级之间,即使模型对此判断十分明确,置信度也会较低。因此,一律按 confidence < X -> escalate 处理,会把模型已经判定的项目也升级处理。仅在存在歧义的中间区间内根据置信度设门槛:
if score < NOT_RELEVANT: # decisive -- trust it
return "close"
if score < RELEVANT or confidence < MIN_CONF: # ambiguous -- escalate
return "human_review"
return "handle"
3. 阈值不能在不同模型间沿用。 校准是模型的一部分。同一策略应用于同一批项目,在 Jev、SemIf 和 LLM 适配器上的路由结果会有所不同。每次更换模型时都要重新调整阈值,并固定模型 ID。
问题措辞对准确率影响最大
宽泛的问题会产生自信却错误的答案,任何阈值都无法弥补。使用 criteria 说明每种结果的含义,包括哪些内容不应计入。
在一个实测案例中,问题“这是否为与律师之间的保密通信?”将一份常规财务备忘录评为 0.798。将问题改写为明确指出实际判定标准——由律师撰写或发送给律师,并明确排除财务和会计内容——同一页面的分数便降至 0.005,而一份确实享有法律特权的页面则保持在 0.991。
Noul(
instructions=(
"Was this written by or to a lawyer, or does it convey a lawyer's legal "
"advice? Answer no for ordinary business or accounting discussion, even "
"when the subject is litigation-sensitive."
),
criteria=NoulCriteria(
true="A named attorney is author or recipient, or it relays legal advice.",
false="Business or accounting content with no attorney involved.",
),
)
在归咎于模型之前,先改写问题并重新测量。
审查现有 Agent
要找到决策模型适用的位置,请在代码库中查找以下内容——完整指南请参阅 references/conversion-playbook.md。
| 信号 | 查找内容 |
|---|---|
| 生成后解析 | LLM 调用的输出会立即经过正则表达式处理、json.loads 解析,或字符串匹配后进入某个分支 |
| 提示词分类器 | 提示词中包含“从以下选项中回复一个”“回答是或否”“按 1 到 5 评分”等内容 |
| 为降低成本而抽样 | 注释或配置表明只检查前 N 个项目,因为检查全部项目成本太高 |
| 脆弱的规则 | 用关键词列表或正则表达式代替语义判断 |
| 重复读取上下文 | 针对每个单独的问题,都将同一份文档重新发送给模型 |
最后两项最重要:低成本的语义判断改变的是你能构建什么,而不仅仅是费用。如果评估每个项目都变得经济可行,你会停止抽查什么?
预期表现
在包含 24 个项目的批次上测得;LangGraph 图和路由策略完全相同,仅替换了分类器:
| 每个项目 | tokens(6 个项目) | 备注 | |
|---|---|---|---|
| Jev 1.13.0 | 0.27s | 3,648 输入 / 318 输出 | 会报告用量 |
| SemIf 4B | 0.49s | 未报告 | 托管在 Gateway 上 |
| Claude Sonnet 5 | 2.87s | 7,930 输入 / 864 输出 | 通过结构化输出 |
不同引擎在 6 个项目中有 4 到 5 个项目的路由结果一致;不一致主要集中在确实难以判定的项目上。请将这些数据视为大致情况,而非基准测试——请在自己的工作负载上进行测量。
如果要与 LLM 基线进行比较,请使用 method="json_schema",以确保比较公平。LangChain 的 with_structured_output 默认使用 method="function_calling",这会在每个请求中注入工具架构——对于同一个单字段探测,原生 output_config.format 路径需要 228/12 个 tokens,而该方式需要 556/35 个。
使用 Send 进行批处理
分类按项目进行,且彼此独立,因此可以使用 Send 扇出,让每个项目各自进行路由。
def fan_out(state): return [Send("classify_item", {"text": t}) for t in state["items"]]
builder.add_conditional_edges(START, fan_out, ["classify_item"])
</python>
</ex-fan-out>
扇出可以隐藏延迟,因此对慢速分类器最有利:在上述运行中,Sonnet 因并发获得了 8 倍提升,Jev 仅提升 1.4 倍——但 Jev 仍然最先完成。请比较吞吐量,而非加速倍数。
---
## 相关技能
- **langgraph-fundamentals** — StateGraph、`Send`、`Command`、条件边
- **langgraph-human-in-the-loop** — 使用 `interrupt()` 处理上述升级分支
- **langchain-middleware** — 需要使用 LLM 而非分类器时使用结构化输出