不给你代聊:用 Jev 式类型安全决策构建聊天“AI 副驾”

2026-09-22 20 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

TypeSafe 开源的 Jev 被称为一种“System One Model”:它不把生成一段自然语言当作主要目标,而是接收非结构化状态,直接返回类型安全的概率化决策。放进聊天软件里,这种能力更适合做一个克制的副驾——判断对方是在催进度、约时间还是表达不满,然后提示用户采取什么动作,但不冒充用户组织措辞,更不自动点击发送。

这种设计值得关注,不是因为它能把聊天变得更“像 AI”,而是因为它试图把模型能力压缩成一次可测试、可组合的函数调用。

从“帮我回复”改成“帮我判断”

主流生成式方案通常会把一段聊天记录交给大模型,再要求它生成回复。产品很快就会遇到几个问题:

  • 输出是开放文本,难以穷举和验证;
  • 模型可能补充聊天记录中不存在的事实;
  • 自动生成的语气未必符合用户身份;
  • 如果产品进一步自动发送,误判会直接变成对外行为;
  • 下游代码仍要从自然语言中猜测模型究竟想表达什么。

Jev 所代表的思路则更接近一个概率分类器。应用预先声明有限的决策空间,例如:

REQUEST_STATUS   对方在询问进度
SCHEDULE_MEETING 对方希望安排会议
NEEDS_CLARIFY    信息不足,需要追问
NEGATIVE_SIGNAL  对方表达不满或风险
NO_ACTION        暂时不需要动作

模型不必生成“好的,我们下周二下午三点开会吧”,而是返回类似这样的结果:

{
  "intent": "SCHEDULE_MEETING",
  "probabilities": {
    "SCHEDULE_MEETING": 0.81,
    "NEEDS_CLARIFY": 0.12,
    "NO_ACTION": 0.07
  }
}

应用拿到的是一个有边界的决策,而不是一段还要再次解析的文字。日历入口、待办按钮、风险提醒都可以根据枚举值触发;真正要发给对方的内容仍由用户决定。

为什么概率比一个标签更有用

如果接口只返回 SCHEDULE_MEETING,应用很容易把模型判断当成确定事实。概率分布则让产品能够显式处理不确定性。

例如可以制定三档策略:

  • 最高概率低于 0.55:不展示建议,避免用低质量提示打扰用户;
  • 概率位于 0.55~0.80:显示弱提示,例如“这可能是一个会议请求”;
  • 概率高于 0.80:展示明确操作按钮,但仍不自动执行外部动作。

这里的阈值不能拍脑袋决定。团队应该使用真实且经过脱敏、授权的样本做离线评估,分别观察误报率、漏报率和概率校准情况。对于“普通提醒”,漏掉一次可能只是体验问题;对于“对方明显不满”这种高风险判断,误报和漏报的成本完全不同,阈值也不应共用。

类型安全同样重要。假设模型被允许输出任意字符串,那么 schedule_meetingmeeting_requestbook_calendar 都可能指向同一含义,下游代码却要维护大量兼容分支。将输出约束为枚举或联合类型后,新增意图必须经过代码审查、测试和产品设计,而不是由模型临场创造。

一个可运行的聊天副驾骨架

下面的 Python 示例演示的是这种集成模式,而不是 Jev 官方 SDK。由于摘要没有给出具体调用接口,示例用一个可替换的本地打分器模拟模型边界;接入 Jev 时,只需要替换 predict(),并把实际响应转换成同一个 Decision 类型。

将以下内容保存为 chat_copilot.py,然后运行 python chat_copilot.py

from dataclasses import dataclass
from enum import Enum
from typing import Mapping, Optional


class Intent(str, Enum):
    REQUEST_STATUS = "REQUEST_STATUS"
    SCHEDULE_MEETING = "SCHEDULE_MEETING"
    NEEDS_CLARIFY = "NEEDS_CLARIFY"
    NEGATIVE_SIGNAL = "NEGATIVE_SIGNAL"
    NO_ACTION = "NO_ACTION"


@dataclass(frozen=True)
class Decision:
    probabilities: Mapping[Intent, float]
    selected: Optional[Intent]

    @property
    def confidence(self) -> float:
        if self.selected is None:
            return max(self.probabilities.values(), default=0.0)
        return self.probabilities[self.selected]


def predict(message: str, threshold: float = 0.55) -> Decision:
    """本地演示打分器;生产环境中可替换为 Jev 适配器。"""
    scores = {intent: 0.02 for intent in Intent}
    text = message.lower()

    if any(word in text for word in ("进度", "状态", "完成了吗", "status")):
        scores[Intent.REQUEST_STATUS] += 0.78
    if any(word in text for word in ("开会", "会议", "几点有空", "meeting")):
        scores[Intent.SCHEDULE_MEETING] += 0.78
    if any(word in text for word in ("不满意", "失望", "投诉", "unhappy")):
        scores[Intent.NEGATIVE_SIGNAL] += 0.82
    if len(message.strip()) < 4 or "什么意思" in text:
        scores[Intent.NEEDS_CLARIFY] += 0.60

    if max(scores.values()) <= 0.02:
        scores[Intent.NO_ACTION] += 0.70

    total = sum(scores.values())
    probabilities = {key: value / total for key, value in scores.items()}
    best = max(probabilities, key=probabilities.get)
    selected = best if probabilities[best] >= threshold else None
    return Decision(probabilities=probabilities, selected=selected)


def ui_action(decision: Decision) -> str:
    """只返回界面建议,不生成消息,也不执行外部动作。"""
    if decision.selected is None:
        return "不展示建议:模型置信度不足"

    actions = {
        Intent.REQUEST_STATUS: "展示:打开项目状态面板",
        Intent.SCHEDULE_MEETING: "展示:查看日历空闲时间",
        Intent.NEEDS_CLARIFY: "展示:提醒用户补充上下文",
        Intent.NEGATIVE_SIGNAL: "展示:标记为需人工关注",
        Intent.NO_ACTION: "不展示建议",
    }
    return actions[decision.selected]


if __name__ == "__main__":
    message = "这个项目现在做到哪一步了?今天能完成吗?"
    decision = predict(message)

    print("selected:", decision.selected)
    print("confidence:", round(decision.confidence, 3))
    print("probabilities:")
    for intent, probability in sorted(
        decision.probabilities.items(), key=lambda item: item[1], reverse=True
    ):
        print(f"  {intent.value:18} {probability:.3f}")
    print("action:", ui_action(decision))

这个骨架刻意把系统拆成了三层:

  1. predict() 只负责把非结构化消息映射为概率分布;
  2. Decision 固定模型与业务代码之间的类型契约;
  3. ui_action() 决定产品如何消费判断结果。

接入真实模型时,不要让模型响应直接穿透到界面层。可以增加一个适配器,在边界处完成枚举校验、概率归一化、超时处理和版本记录。如果模型返回未知标签,安全做法是拒绝该结果,而不是偷偷映射到最接近的业务动作。

“不替你发”是一条系统边界

聊天副驾最敏感的不是分类准确率,而是分类结果最终能获得多大权限。一个稳妥的权限梯度可以这样设计:

  1. 只读判断:识别意图并记录本地指标;
  2. 界面提示:显示日历、任务或风险入口;
  3. 预填操作:预填会议时间或待办标题,等待用户确认;
  4. 执行外部动作:创建日程、修改工单或发送消息。

每上升一级,错误成本都会显著增加。尤其是发送消息,它不仅改变系统状态,还代表用户进行社交表达。因此,即使分类结果置信度很高,也不意味着模型有权自动回复。

隐私边界也不能交给模型自己处理。聊天记录可能包含客户名称、合同金额、健康信息或内部事故。进入推理服务之前,应明确数据保留策略,尽量裁剪无关上下文,对敏感字段做脱敏,并为组织管理员提供关闭或审计能力。

适合落地的检查清单

Jev 式模型适合那些输出空间能够提前定义、决策可以被软件消费的场景,例如消息路由、工单分类、提醒优先级和风险打分。若任务的核心是创作、解释或长文本总结,生成式模型仍然更自然。

准备上线聊天副驾时,可以逐项确认:

  • 意图集合是否有限、互斥且有清晰定义;
  • 是否保存完整概率分布,而不只保存最高标签;
  • 低置信度结果是否可以安全地“不做任何事”;
  • 未知标签、超时和模型不可用时是否默认失败关闭;
  • 阈值是否根据真实数据和错误成本进行校准;
  • 模型版本、输入裁剪规则和最终动作是否可审计;
  • 所有对外发送和高影响操作是否仍需用户确认。

这类模型最有价值的地方,不是让软件表现得像一个会说话的人,而是让不确定的机器判断以明确类型、概率和权限边界进入普通程序。对聊天产品而言,真正可靠的“副驾”应当帮助用户看清局面,而不是抢过方向盘。


相关推荐