返回博客

用 Jev 构建 Agent Harness

Agent 循环的速度由最慢的决策决定。Jev 这个 System One 模型该放在 Harness 的哪个位置:模型路由、动作护栏与升级路径。

更新于 JevStationJevStation
用 Jev 构建 Agent Harness

Agent 就是一个循环:模型决定做什么,工具执行,再由某个环节检查结果,然后进入下一轮。这个循环的速度,取决于其中最慢的一次决策 —— 而今天,循环里几乎每一次决策都是一次完整的对话模型调用。

Tool calling 和结构化输出解决了接口问题:模型现在能发出工具请求,也能返回代码可以解析的数据。但它们没有解决成本问题 —— 一个以生成文本形式返回的决策,依然是一次逐 token 的补全。LangChain 在用 Jev 构建 Harness 那篇文章里,从框架的角度说了同一件事:只要每个决策都要再调一次模型,这个循环就快不起来,也便宜不了。

Jev 接过的正是另一半工作。它是 TypeSafe 的 System One 模型:你发送一份 state 和一组带类型的问题,拿回带概率的类型化答案 —— 没有散文,不需要解析,也没有流式输出要拼接。

这篇文章是同一件事的「落地版」:Jev 的调用该放在 Harness 的哪些位置,以及拿回那些数字之后该怎么用。

循环里藏着的那些决策

一个看起来只是「思考、行动、观察」的循环,实际由一连串小判断组成:

  • 任务完成了吗,还是需要再来一轮?
  • 这个请求够简单,可以交给便宜模型吗?
  • 这个工具调用不经过人工确认就能执行吗?
  • 这五张工单里,哪一张是真的紧急?

每一处,换成对话模型就意味着生成一段答案、再由你的代码去解读。而 Jev 可以直接回答这四个问题 —— 你付的是分类的价格,而不是生成的价格。TypeSafe 声称在分类任务上,它的推理速度最高可达同类 LLM 的 200 倍、成本低至 1/400。这是厂商数据,值得用你自己的流量去验证,但真正让这个模式成立的是数量级本身。

Jev 返回什么

System One 模型通过「面向校准决策的强化学习」(RLCD)训练,而「校准」正是关键:它给出的概率应该被当成概率使用,而不是一种语气。你发送一份 state(文本、JSON 记录或消息列表),再加上任意多个针对这份 state 的类型化问题。

{
  "model": "jev-latest",
  "state": "你好,我连接 Stripe 账户已经试了 3 天一直失败。我正在丢单,请尽快帮我处理。",
  "questions": {
    "is_urgent": {
      "type": "noul",
      "instructions": "这段话传达出紧迫性或时间敏感性"
    }
  }
}
{
  "is_urgent": { "type": "noul", "noul": 0.999 }
}

三种问题类型,基本覆盖了 Harness 需要做的判断:

  • Choice:从你定义的选项中选出一个,返回每个选项的概率以及一个置信度。
  • Score:按有序档位给 state 打分,返回按概率加权的分数、完整分布与置信度。
  • Noul:回答一个是/否问题,只返回 0 到 1 之间的一个概率。它没有置信度字段 —— 因为这个概率本身就是答案。

有两点让它可以放心地放进控制循环。第一,所有问题针对同一份 state 并行评估,所以问六个问题与问一个问题的耗时几乎一样:多出来的是输入 token,而不是等待时间。第二,一次评估就是一次往返:要么拿到完整答案,要么干净地失败,不会出现解析到一半的流。

模式一:先路由,再花钱

让循环变快最便宜的办法,是别再让简单活儿去找贵模型。模型路由本身就是一个 Jev 问题 —— 「这是什么类型的任务?」—— 在第一次补全之前问出来。

from langchain.agents import create_agent
from langchain_typesafe.experimental.middleware import (
    ModelChoice,
    ModelRouterMiddleware,
)

router = ModelRouterMiddleware(
    choices={
        "fast": ModelChoice(
            model="openai:luna",
            criteria="Direct lookups, extraction, and localized changes.",
        ),
        "powerful": ModelChoice(
            model="openai:sol",
            criteria="Architecture and high-stakes decisions.",
        ),
    },
    instructions="Choose the least costly model that can complete the task.",
)

agent = create_agent("openai:gpt-5.6-luna", middleware=[router])

路由中间件只对最新一条用户消息做一次分类,这一轮就按选中的模型跑下去。把概率分布和选择一起记录,「这次为什么这么贵」就从一次排查变成了一个查询。

模式二:在工具执行前设护栏

更难的问题是信任。一个能执行 bash 的 Agent,可能被糊弄着执行错误的命令 —— 可能来自一个没说清需求的用户,也可能来自它刚读过的文件里藏着的指令。Claude Code、Codex、Cursor 这类编程 Harness 都上线了「先判断再执行」的分类器;此前这套过滤逻辑大多锁在闭源 Harness 内部。

这道检查并不免费:它在每一次受控调用前面多加一次往返,所以那些 Harness 才会限定范围、或者抽样检查。Jev 有意思的地方在于,它把这道检查压到足够便宜,便宜到可以对每一次调用都做。

AutoModeMiddleware 就是把这个模式开放给所有 Agent:用 Jev 检查工具调用中的高风险决策,并在工具执行之前将其拦下。

from langchain.agents import create_agent
from langchain_typesafe.experimental.middleware import AutoModeMiddleware

guardrail = AutoModeMiddleware(tools=["bash"])

agent = create_agent("openai:gpt-5.6-luna", middleware=[guardrail])

位置就是全部意义所在。工具执行之后才跑的护栏只是一行日志;执行之前跑的护栏才是控制。因为 Jev 只需要一次很快的往返,你可以对每一次调用都做检查,而不是抽样检查。

动手之前有两点需要知道:这个中间件对高风险调用是直接拒绝,而不是发起审批 —— 如果你希望由人来拍板,就再配一个 human-in-the-loop 中间件。另外,它只对你点名列出的工具做分类,所以护栏的覆盖面完全等于那份清单。

模式三:置信度低的时候怎么办

概率只有在能改变行为时才有价值。一个可用的默认做法是分三段,边界由「判错时的代价」决定:

  • 高置信度 —— 自动执行。分布集中,不需要人工介入就可以继续。
  • 中置信度 —— 带条件推进:请用户确认、再补一条上下文,或者把动作放进待复核队列。
  • 低置信度 —— 不要执行。转交人工、追问澄清,或退回到一条确定性的兜底路径。
const CONFIDENCE = { auto: 0.9, review: 0.6 };

function gate(answer: { choice: string; confidence: number }) {
  if (answer.confidence >= CONFIDENCE.auto) return { action: answer.choice };
  if (answer.confidence >= CONFIDENCE.review) {
    return { action: 'review', proposed: answer.choice };
  }
  return { action: 'escalate' };
}

破坏性工具的阈值就该高于只读工具。这些数字是策略,不是物理定律 —— 它们应该是整个系统里最容易改的东西。

还有一个容易被忽略的运维细节:jev-latest 这类别名会随着新版本发布而移动,也就是说,你据以调阈值的那套答案可能在你自己毫无改动的情况下发生变化。响应里会报告实际作答的版本号(目前是 jev-1.13.0),把它记进日志;等分段阈值调稳之后,就固定写这个版本号而不是别名 —— 让模型升级变成一个主动决定,而不是一次意外。

不依赖框架也能跑

上面这些都不需要 LangChain。只要 Harness 会发 HTTP 请求,就可以直接调用 Jev —— JevStation 试验台做的就是这件事:每次评估就是一次 System One 调用,state 是到目前为止的整段对话,答案以概率分布而不是句子的形式呈现。

流程和你要自己搭的那套完全一致:

  1. 放入 state —— 一张客服工单、一条评价、一段需求说明,或一条 JSON 记录。
  2. 编写问题集 —— 在同一次调用里混用 Choice、Score 与 Noul;id 由你命名,答案会以同样的键返回。
  3. 读取数字 —— 每个答案都带自己的概率分布,Choice 与 Score 还会给出置信度。
  4. 把调用搬进代码 —— 用你自己的 API Key 把同样的 state 与 questions POST 到 TypeSafe API,或者继续在托管试验台里打磨问题集;请求结构见 Jev 文档。

在 JevStation 里,每次评估消耗 1 个额度:调用模型前先校验余额,运行失败或被取消则自动退回,所以跑飞的循环不会悄悄掏空账户。

Jev 不是什么

Jev 不能取代那个负责写最终答案的模型。它写不了邮件、解释不了 bug、也总结不了文档 —— 它根本不生成文本。真正有用的分工是:LLM 负责开放式的推理与生成,Jev 负责中间那些「结果是分支而不是段落」的决策。

两个必须说清的注意点。第一,问题集就是质量上限 —— 一个含糊的问题只会得到一条平坦的分布,那其实是模型在告诉你:它分不出来。第二,置信度不等于正确率:一个自信但分类错误的判断依然是错的。所以阈值不妨一开始设得比你以为需要的更保守,等有了日志再收紧。

校准描述的是「一组答案」的整体性质,而不是对某一条答案的承诺;已经出现的独立测试也发现,它在不同数据集上的校准程度并不一致。把置信度当成一个很强的先验,用你自己的流量去验证它,并且在「判错代价很高」的场景里始终保留人工路径。至于问题本身怎么写,见《设计 Jev 能回答的问题》。

要点回顾

  • 把循环里的每个分支都当成一次决策,而不是一次生成:路由、护栏、分诊、完成度检查,都是分类问题。
  • 把便宜的分类器放在它所保护的那件昂贵事情之前 —— 在模型调用之前,在工具执行之前。
  • 用显式的分段让置信度驱动行为,并把阈值留在配置里,而不是代码里。

如果你想在接进任何系统之前先看看答案长什么样,最快的办法是把一张真实的工单、一行日志或一段需求说明贴进试验台,对它问两三个问题。想从框架视角看同一件事,LangChain 的原文值得对照阅读。