Jev是来自 TypeSafe 的决策模型。它接收非结构化输入,并返回带概率的类型化判断,然后由你的代码决定下一步做什么。本指南展示如何在你自己的问题上使用 Jev,并通过一个示例端到端地完整演示:用 TypeScript 审核市场上的商品列表。
Jev 的工作原理
好了,现在你明白了 Jev 的用途,也觉得自己走在正确的轨道上。那么问题是:Jev 请求在底层看起来是什么样的?
让我们从头开始。每个 Jev 请求都由一个state和一组questions组成。状态是 Jev 应该评估的输入。它可以是一个字符串、JSON 对象或字符串数组。如果你试图评估一系列条目,例如对话中的消息,那么数组就是合适的选择。问题是 Jev 应该对状态执行的一组判断。一个问题由两个元素组成,即instructions和criteria。指令描述应该执行的判断。标准描述可能的答案。在评估状态时,Jev 会确定每个标准与状态的契合程度,并将其作为结果返回。当状态是 JSON 对象时,指令可以通过将字段名包裹在反引号中来引用状态中的具名字段。我们将其称为字段路径。例如,一个有效的字段路径可以是listing.description。当提供此指令时,Jev 会读取具名字段中的值并对其执行判断。
问题类型有choice、noul和score。选择题从你定义的一组选项中选择一个选项。noul 问题判断某个陈述是否为真。评分问题将对象放在你定义的有序等级量表上。
Jev 会针对同一个状态分别并行评估你的每个问题。每个问题只会给你一个答案。每个答案都有一个type,它要么是一个choice、一个noul,要么是一个score。一个choice答案由以下部分组成:1. 获胜选项,2. 每个选项的概率,3. 一个介于 0 和 1 之间的置信度,由 TypeSafe 根据概率分布的形状计算得出。如果概率高度集中在获胜选项上,置信度就会很高。如果概率分散,置信度就会较低,即使获胜选项单独来看具有最高概率。置信度不是获胜选项的概率。它是整个分布的摘要。Anoul答案由以下部分组成:1. 一个表示该陈述为真的单一概率,2. 没有置信度,因为该noul概率已经表达了模型的置信程度。Ascore答案由以下部分组成:1. 等级数字的概率加权平均值,2. 每个等级数字的概率,3. 一个置信度,以及 4. 一个legend将等级数字映射到相应描述的映射。
每个答案都是一个选项或一个数字,你的代码可以对其进行比较、设定阈值和组合。不会生成需要你解析的文本。这就是为什么优先选择 Jev 而不是让聊天模型回答 YES 或 NO 的原因。你会得到在你定义的答案上的一个分布,因此不确定性是一个你可以据此进行路由的数字。
Jev 运行在 OpenRouter Decisions API 上,使用typesafe/jev-1.13模型 ID。任何对该 API 的使用都会计入你的 OpenRouter 账户。对 Jev 的一次请求仅由文本组成,token 预算会被状态和所提问题共同消耗。Jev 模型页面目前显示了预算和定价。新用户可以按照Jev 教程在几分钟内获得他们的第一个答案。
以市场作为贯穿示例
假设你正在构建一个市场,用户可以在其中列出二手物品出售。卖家撰写标题和描述,选择类别,注明物品状况,并上传照片。在商品上架发布之前,你想确保没有人列出违禁物品或要求平台外付款。你还想捕捉类别错误的物品,以及与标题或所声明状况相矛盾的描述。
其中一些检查只是普通代码,但另一些需要真正阅读房源信息并解读其含义。这正是 Jev 的用途。下面每一节先总体描述使用 Jev 的一个环节,然后说明它在市场中如何运作。同样的模式也适用于工单路由、智能体工具门控,或你正在构建的任何东西。
Jev 如何融入一个程序
Jev 是一个System One模型。这个术语是 TypeSafe 对一种模型的称呼,这种模型产生带类型的决策和经过校准的概率。TypeSafe 的构建指南描述了如何用这样的模型进行构建。在一条原本普通的软件工作流链条中,把 Jev 插入需要判断的地方。代码负责控制流、确定性规则和副作用。Jev 针对由代码准备的一小组问题,提供带类型的、基于状态的答案。代码将这些答案转化为行动。Jev 不会自行决定它的下一步行动。
在市场中,这一原则在卖家点击发布与房源上线之间建立了一条流水线。它接收一个房源,并返回 publish、hold for a human 或 reject with reasons。
- 硬性规则先在代码中运行,例如照片数量、价格范围和最小文本长度。未通过它们的房源会在任何内容被发送给 Jev 之前就被拒绝。
- 状态构建将卖家的文本与代码计算出的事实结合起来,并决定 Jev 看到什么。
- 判断是一次请求,向 Jev 询问关于该状态的五个带类型的问题。Jev 返回概率,而绝不返回行动。
- 验证确保响应符合策略所期望的结构,如果不符合则停止处理。
- 策略将这些概率与基于审核员已经裁定过的房源所校准的阈值进行比较。
第 3 阶段是 Jev,其余四个阶段由你负责。所有 Jev 集成都遵循这一点。Jev 是一个从状态到证据的函数,其他一切都是普通软件。
决定哪些由 Jev 决定,哪些由你的代码决定
在集成 Jev 时的第一个设计问题是,应该把什么发送给它来判断。测试标准是:代码能否在不读取任何文本的情况下算出答案。如果可以(计数、日期比较、查找),就把它留在代码里,因为结果精确且零成本。Jev 用于需要解读自然语言的判断。
TypeSafe 的Jev 1.13 的 jaggedness 页面将算术、精确计数和日期比较列为应留在代码中、而非放入问题的项目。
应用到市场上,这个测试把各项检查分类如下。
| 检查 | 所在位置 | 原因 |
|---|---|---|
| 价格在范围内、至少一张照片、最小长度 | 代码 | 确定性的。模型只可能比if更不可靠。 |
| 价格远低于该类别通常的售价 | 代码 | 对你自己的销售数据做算术。把结果作为事实传给 Jev。 |
| 物品属于禁止类型(武器、假货、召回的儿童座椅) | Jev | 卖家很少使用违禁词。“同厂同品质”从不说假货。 |
| 商品信息要求买家在平台外付款或聊天 | Jev | 电话号码很容易用正则匹配。“我也可以电话成交”则不然。 |
| 物品符合卖家选择的类别 | Jev | 针对定义的语义判断。 |
| 描述与标题或声明的成色相矛盾 | Jev | 需要比较含义。 |
这些检查还定义了每个阶段共享的商品信息类型。每个字段都是三者之一:硬规则输入、为 Jev 计算的事实,或 Jev 读取的文本。成色按从最差到最好排序,因此声明的成色的索引与成色的级别相对应score稍后。
// listing.ts
exportconstCATEGORIES={
electronics:'手机、笔记本电脑、相机、音频设备、游戏主机及其配件。',
furniture:'桌子、椅子、沙发、床、置物架及其他家用家具。',
clothing:'服装、鞋子、包袋及时尚配饰。',
sporting_goods:'自行车、健身器材、露营装备及运动器材。',
toys_and_baby:'玩具、游戏、婴儿车、汽车座椅、婴儿床及其他儿童用品。',
}asconst;
exporttypeCategory=keyoftypeofCATEGORIES;
exportconstCONDITIONS=['for_parts','fair','good','like_new','new']asconst;
exporttypeCondition=(typeofCONDITIONS)[number];
exporttypeListing={
id:string;
title:string;
description:string;
category:Category;
condition:Condition;
priceUsd:number;
photoCount:number;
};
类别定义以散文形式书写,因为 Jev 在判断某个商品是否属于该类别时会从 state 中读取它们。这是一条通用原则。如果某个判断依赖于你的某条规则,请用散文形式将该规则表达在 state 中,并在你的问题中引用它,这样模型就会使用你的定义,而不是它自己的。
硬性规则是标准代码,会在任何对 Jev 的调用之前运行。
// rules.ts
importtype{ Category, Listing }from'./listing';
// 每个类别的典型售价,以美元计。在生产环境中,这来自你自己的销售数据。
constTYPICAL_PRICE_USD:Record<Category,number>={
electronics:180,
furniture:120,
clothing:35,
sporting_goods:90,
toys_and_baby:40,
};
exporttypePriceSignal='far_below_typical'|'typical'|'far_above_typical';
exportfunctionpriceSignal(listing:Listing):PriceSignal{
constratio=listing.priceUsd/TYPICAL_PRICE_USD[listing.category];
if(ratio<0.2)return'far_below_typical';
if(ratio>10)return'far_above_typical';
return'typical';
}
// 硬性规则。这里的任何内容都是你的代码已经知道的事实,因此不涉及任何模型。
exportfunctionhardRuleFailures(listing:Listing):string[] {
constfailures:string[]=[];
if(listing.photoCount<1) failures.push('at least one photo is required');
if(listing.priceUsd<0.01||listing.priceUsd>50_000) failures.push('price must be between $0.01 and $50,000');
if(listing.title.trim().length<8) failures.push('title must be at least 8 characters');
if(listing.description.trim().length<30) failures.push('description must be at least 30 characters');
returnfailures;
}
价格边界展示了单个检查内部的分工。边界由代码实现,但一个在通常售价为 $1,800 的类别中仅标价 $120 的手袋,会指向假货方面的担忧,而解读这一信号则是一种判断。代码完成比较,并将最终标签发送给 Jev。
组织 Jev 所看到的 state
State 是你的代码与 Jev 之间接口的输入部分。TypeSafe 的state 文档把它描述成专家小组在被要求做出判断之前会收到的内容。Jev 除了通过状态之外对该主题一无所知,而请求中的每个问题看到的都是同一个状态。
构建状态的三条规则适用于任何领域。为字段使用描述性名称,因为问题通过路径引用字段,而名称会向模型传达含义。只包含问题所需的信息,因为无关的上下文会分散模型的注意力。
将计算得出的事实作为成品标签传入。Jev 可以使用price_signal: 'far_below_typical'直接使用,而原始值price_usd: 120和typical_price: 1800则给它抛出一道除法题。
下面是一个市场商品列表的状态。
{
"listing": {
"title":"Louis Vuitton Neverfull MM tote",
"description":"Mirror quality 1:1, same factory as the boutique version. Nobody can tell the difference. Text me on WhatsApp for more photos and a better price.",
"category":"clothing",
"declared_condition":"new",
"price_usd":120
},
"category_definition":"Apparel, shoes, bags, and fashion accessories.",
"price_signal":"typical"
}
卖家 ID、时间戳和照片 URL 被省略,因为五个问题都没有用到它们。category_definition被包含进来,是因为品类匹配问题会将商品列表与它进行比较,而price_signal则是因为假冒判定标准会读取它。
为每个判断选择一个问题类型
每个 Jev 问题都有一个类型,而该类型应当由答案的含义决定。它固定了答案的形状,而形状决定了你之后能用它做什么。
使用choice当你定义的选项集合中应有一个胜出,且你的代码需要知道是哪一个时。你会得到胜出者以及每个选项的概率,因此你能看到有多少概率落在了次优选项上。使用noul当答案是一个非真即假的命题时,你会得到一个可用于设定阈值的单一概率。
你可以使用score来表示程度。它沿着一个有序量表。结果是一个数字,你可以对它做减法、与其他数字比较,并且它能给出它们之间差异的度量。
标准格式因问题类型而异。choice问题会收到一个对象,将每个选项名称映射到其描述,最多 255 个选项。noul问题会收到一个可选对象,带有true和false描述。理想的问题简洁直接,所以很多问题甚至不需要 description 对象。score问题会接收一个有序的等级描述数组(包含 2 到 10 个等级),答案以该数组的从零开始的索引来衡量。
这个市场三者都用。Prohibited item 是一个choice覆盖五个命名类别,以及一个none选项,因为策略必须知道违反了哪条规则(武器不同于假货,它们触发的消息和对应的审核员也不同)。Category fit、title and description contradiction 和 off-platform contact 是noul问题,因为每个都是是/否命题,策略唯一需要的数字就是概率。Described condition 是一个score覆盖五个等级的,因为策略要问的是声明状况与描述状况相差多远,而距离只能在有序尺度上衡量。
独立的问题应放在同一个请求里。Jev 会针对同一状态并行评估它们,所以五个问题只需一次往返,而一个触发其中两个问题的商品(本文后面提到的那个 tote 就是)会返回两个不同的原因。宽泛的问题会把多个判断藏在一个答案里。把宽泛的问题拆成狭窄、原子化的问题,会让它们更容易检查、调整和在代码中组合。
编写能经受字面解读的 Jev 标准
指令说明要决定什么,标准定义答案。Jev 会字面地应用两者。这种字面性正是 Jev 可预测的部分原因,也意味着标准承担了大部分工作。
为任何领域编写标准时,有几条规则成立。用输入文本中出现的词汇来描述选项,因为一条点名了具体情境的标准(“一个共享账号,登录信息在付款后发送”)能匹配到从未使用你策略中对应词汇的文本。对于每个noul,描述两侧,让接近边界的情况落在正确的一侧。给每个choice一个明确的none,用于什么都不符合的情况,这样模型就不会被迫随便选一个。
对于score,把每个等级描述成一个具体情境,因为等级构成了这个尺度。而像这样的问题 IDprohibited是你的代码用来读回答案的键,所以不要指望模型会把它当作指令来解释。
客户、条件量表和违禁物品标准如下。noul标准就在请求本身中。请在下一节中进一步查找。
// judge.ts
import{ OpenRouter }from'@openrouter/sdk';
import{ CATEGORIES, CONDITIONS,typeListing }from'./listing';
import{ priceSignal }from'./rules';
constopenrouter=newOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,// 仅限服务端
});
exportconstPROHIBITED={
none:'普通二手或全新物品,一般市场允许销售。',
weapon_or_weapon_part:
'枪支、枪支零件或配件、弹药、电击枪,或为格斗或自卫营销的刀具或工具。',
medication_or_medical_claim:
'处方药、受控物质,或任何以治疗、治愈或预防医疗状况为宣称销售的产品。',
counterfeit_or_replica:
'带有品牌名称、标志或标志性设计但并非该品牌制造的物品,包括被称为 replica、AAA、1:1、mirror、inspired by 或 same factory 的物品。品牌物品的 `price_signal` 为 far_below_typical 且未给出低价理由的也属于此类。',
recalled_or_unsafe_child_item:
'发生过碰撞、缺少零件或绑带、被描述为召回,或没有可见生产日期或标签的汽车座椅、婴儿床、摇篮或儿童头盔。',
account_or_digital_access:
'登录凭证、订阅或流媒体账户、礼品卡代码、许可证密钥或游戏内货币。',
}asconst;
exporttypeProhibitedKind=keyoftypeofPROHIBITED;
// 评分等级从最差到最好排序,每个等级描述一种具体状况。
exportconstCONDITION_LEVELS=[
'无法工作或缺少零件。作为零件或维修品出售。',
'能工作,但有明显磨损、损坏、污渍或缺少配件,买家会立即注意到。',
'完全能工作。正常使用造成的轻微磨损。没有损坏或缺失。',
'完全能工作,看起来未使用或几乎未使用。可能包含原包装或配件。',
'全新。密封、带标签或从未使用。']
asconst;从 TypeScript 调用 Jev API
Call the Jev API from TypeScript
你通过 OpenRouter Decisions API 访问 Jev。请求包含三个组成部分,model,state,以及questions。响应包含answers以你的问题 ID 为键,每个都标记有其type,外加usage以及成本。
你需要特殊的 API key 吗?它需要关联到 TypeSafe 账户吗?不需要!任何有效的 OpenRouter API key 都可以。使用 Decisions API 无需 TypeSafe 账户。只有两个设置步骤:用bun add --exact @openrouter/sdk@1.3.17安装 SDK,然后将OPENROUTER_API_KEY环境变量设置为你的 key。Decisions API 的完整 schema 在参考页面上。
这个函数发送市场状态和全部五个问题,然后将响应解析为一个普通的Judgment供策略读取。
// judge.ts,续
exporttypeJudgment={
prohibited:{kind:ProhibitedKind;confidence:number;probabilities:Record<string,number> };
matchesCategory:number;
descriptionContradictsTitle:number;
offsiteTransaction:number;
describedCondition:{score:number;confidence:number};
costUsd:number;
};
functionisProhibitedKind(value:string):valueisProhibitedKind{
returnObject.hasOwn(PROHIBITED, value);
}
exportasyncfunctionjudgeListing(listing:Listing):Promise<Judgment> {
constresult=awaitopenrouter.alpha.decisions.create({
decisionsRequest: {
model:'typesafe/jev-1.13',
state: {
listing: {
title: listing.title,
description: listing.description,
category: listing.category,
declared_condition: listing.condition,
price_usd: listing.priceUsd,
},
category_definition:CATEGORIES[listing.category],
price_signal:priceSignal(listing),
},
questions: {
prohibited: {
type:'choice',
instructions:'`listing.title` 和 `listing.description` 中的物品属于哪个违禁类别(如果有的话)?`price_signal` 将 `listing.price_usd` 与其类别的典型价格进行比较。',
criteria:PROHIBITED},
matches_category: {
type:
'noul',instructions:
'`listing.title` 和 `listing.description` 中描述的物品是否属于 `category_definition` 所定义的类别?',criteria: {
true:
'该物品正是类别定义所描述的那类东西。',false:
'该物品属于其他类别,或者该 listing 根本没有描述任何实体物品。',},
},
description_contradicts_title: {
type:
'noul',instructions:
instructions:`listing.description` 是否在品牌、型号、尺寸、数量或商品是否可用方面与 `listing.title` 矛盾?,
criteria: {
true:两者在上述至少一项事实上不一致,例如标题写的是一个品牌,描述里写的却是另一个。,
false:描述补充了细节或重复了标题,但没有与之矛盾。,
},
},
offsite_transaction: {
type:'noul',
instructions:该商品信息是否要求买家联系卖家、付款或在交易平台之外完成交易?,
criteria: {
true:文本中提供了电话号码、电子邮件、即时通讯应用账号、外部链接,或要求通过电汇、Cash App、加密货币或礼品卡付款。,
false:该商品信息保持在交易平台内,包括通过交易平台安排的本地自提。,
},
},
described_condition: {
type:'score',
instructions:仅根据 `listing.description`,哪个等级最能描述该商品的物理状况?,
criteria: [...CONDITION_LEVELS],
},
},
},
});
const{prohibited,matches_category,description_contradicts_title,offsite_transaction,described_condition}=
result.answers;
if(
prohibited?.type!=='choice'||
matches_category?.type!=='noul'||
description_contradicts_title?.type!=='noul'||
offsite_transaction?.type!=='noul'||
described_condition?.type!=='score'
) {
thrownewError('Decisions 响应中的答案类型不符合预期');
}
if(!isProhibitedKind(prohibited.choice)) {
thrownewError(`未知的 prohibited 类型 ${prohibited.choice}`);
}
if(prohibited.confidence===undefined||prohibited.probabilities===undefined) {
thrownewError('Choice 答案未包含 confidence 和 probabilities');
}
if(described_condition.confidence===undefined) {
thrownewError('Score 答案未包含 confidence');
}
if(result.usage.cost===undefined) {
thrownewError('响应未包含 usage.cost');
}
return{
prohibited: {
kind: prohibited.choice,
confidence: prohibited.confidence,
probabilities: prohibited.probabilities,
},
matchesCategory: matches_category.noul,
descriptionContradictsTitle: description_contradicts_title.noul,
offsiteTransaction: offsite_transaction.noul,
describedCondition: {
score: described_condition.score,
confidence: described_condition.confidence,
},
costUsd: result.usage.cost,
};
}
exportfunctiondeclaredConditionIndex(listing:Listing):number{
returnCONDITIONS.indexOf(listing.condition);
}
In theoffsite_transactioncriteria, the local-pickup line makes use of the both-sides rule. Question: what would happen if we didn’t use the both-sides rule here? That might allow someone who reads literally to interpret “pickup arranged through the marketplace” as an off-platform contact.
TheJudgmentis the output part of the interface. It includes evidence and nothing else, meaning the winner and its distribution, three probabilities, a score with its confidence, and the cost.
The block ofthrowstatements is the validation stage. The SDK’s type definitions leaveconfidenceandprobabilitiesoptional, which means a response could arrive without them. Providing a default would mask a broken response behind a plausible decision. So the function refuses, and the orchestrator turns the refusal into a hold, the same place a network failure or an API error lands.
Same request over plain HTTP
In this example, we’ll see an SDK call (which in essence is a wrappedPOST https://openrouter.ai/api/alpha/decisions). The example also includes two condensed questions and their output. The first question, thenoul, is without its criteria, and that is the point, to demonstrate that they are optional. The responseidwill not match yours.
curlhttps://openrouter.ai/api/alpha/decisions\
-H"Authorization: Bearer$OPENROUTER_API_KEY"\
-H"Content-Type: application/json"\
-d'{
"model": "typesafe/jev-1.13",
"state": {
"listing": {
"title": "Peloton Bike, original model",
"description": "Works great, we just never use it. Screen has a dead pixel line down the left side and the right pedal squeaks. Selling as is, no shoes or mat.",
"declared_condition": "like_new"
}
},
"questions": {
"offsite_transaction": {
"type": "noul",
"instructions": "Does the listing ask the buyer to contact the seller, pay, or complete the sale outside the marketplace?"
},
"described_condition": {
"type": "score",
"instructions": "Based only on `listing.description`, which level best describes the physical condition of the item?",
"criteria": [
"Does not work or is missing parts. Sold for parts or repair.",
"Works, but has clear wear, damage, stains, or missing accessories that the buyer would notice immediately.",
完全可用。正常使用造成的轻微磨损。没有损坏或缺失。
完全可用,看起来未使用或几乎未使用。可能包含原包装或配件。
全新。密封、带标签或从未使用。
]
}
}
}'
{
"model":"typesafe/jev-1.13-20260917",
"answers": {
"offsite_transaction": {"type":"noul","noul":0.05},
"described_condition": {
"type":"score",
"score":1,
"legend": {
"0":"无法使用或缺少部件。按部件或维修出售。",
"1":"可用,但有明显的磨损、损坏、污渍或缺失配件,买家会立刻注意到。",
"2":"完全可用。正常使用造成的轻微磨损。没有损坏或缺失。",
"3":"完全可用,看起来未使用或几乎未使用。可能包含原包装或配件。",
"4":"全新。密封、带标签或从未使用。"
},
"probabilities": {"0":0,"1":1,"2":0,"3":0,"4":0},
"confidence":1
}
},
"usage": {"input_tokens":492,"output_tokens":38,"cost":0.000020664},
"id":"gen-dec-1790099229-ahAseXX5gNJzoLiCZICn",
"provider":"TypeSafe"
}
在此响应中,卖家已声明了一件物品like_new,其 Jev 分数为 1,评分范围从 0 到 4。答案以问题 ID 为键并按类型标记,同时附带其关联的probabilities和confidence。该响应的model指明了生成它的typesafe/jev-1.13的带日期构建版本。
将概率转化为发布、搁置或拒绝
Jev 返回证据,策略将其转化为行动。TypeSafe 的置信度文档给出了大致框架。对高置信度采取行动,将中等置信度交给人工处理,并拒绝对低置信度采取行动。这些界限的位置取决于错误行动的代价,因此同一系统中的不同行动会有不同的阈值。由于各问题相互独立,策略也可以将它们组合起来。每个未通过的检查都会增加一个搁置理由,而单独就足够强的检查则会拒绝。
市场有三种行动。自动拒绝仅保留给证据足够强、以至于错误拒绝卖家比错误发布违规更罕见的情况。任何看起来有问题但强度不足的情况都会交给人工处理。发布则是没有任何触发时剩下的结果。
// moderate.ts
import{ declaredConditionIndex, judgeListing,typeJudgment }from'./judge';
importtype{ Listing }from'./listing';
import{ hardRuleFailures }from./rules;
exporttypeAction='publish'|'hold'|'reject';
exporttypeDecision={
action:Action;
reasons:string[]
;judgment?:Judgment
};
exportconstTHRESHOLDS={
rejectProhibitedConfidence:0.8,
holdNoneConfidence:0.5,
holdConditionConfidence:0.3,
rejectOffsite:0.8,
holdOffsite:0.4,
holdContradiction:0.6,
holdCategoryMismatch:0.3,
holdConditionGap:1.5,
};
// 纯策略:将已保存的答案转换为一个动作,因此无需新的推理即可重放阈值。
exportfunctiondecide(listing:Listing,judgment:Judgment,thresholds=THRESHOLDS):Decision{
constreasons:string[]=[];
const{prohibited,offsiteTransaction,descriptionContradictsTitle,matchesCategory,describedCondition}=judgment;
if(prohibited.kind!=='none'&&prohibited.confidence>=thresholds.rejectProhibitedConfidence) {
reasons.push(`prohibited: ${prohibited.kind} (confidence ${prohibited.confidence.toFixed(2)})`);
}
if(offsiteTransaction>=thresholds.rejectOffsite) {
reasons.push(`asks to transact off platform (p=${offsiteTransaction.toFixed(2)})`);
}
if(reasons.length>0) {
return{ action:'reject', reasons, judgment };
}
if(prohibited.kind!=='none') {
reasons.push(`possible prohibited item: ${prohibited.kind} (confidence ${prohibited.confidence.toFixed(2)})`);
}
if(prohibited.kind==='none'&&prohibited.confidence<thresholds.holdNoneConfidence) {
reasons.push(`unsure the item is allowed (none at confidence ${prohibited.confidence.toFixed(2)})`);
}
if(offsiteTransaction>=thresholds.holdOffsite) {
reasons.push(`may ask to transact off platform (p=${offsiteTransaction.toFixed(2)})`);
}
if(descriptionContradictsTitle>=thresholds.holdContradiction) {
reasons.push(`description contradicts title (p=${descriptionContradictsTitle.toFixed(2)})`);
}
if(matchesCategory<thresholds.holdCategoryMismatch) {
reasons.push(`probably miscategorized (p=${matchesCategory.toFixed(2)} that it fits ${listing.category})`);
}
if(describedCondition.confidence<thresholds.holdConditionConfidence) {
reasons.push(`unsure about the described condition (confidence ${describedCondition.confidence.toFixed(2)})`);
}
constconditionGap=declaredConditionIndex(listing)-describedCondition.score;
if(conditionGap>=thresholds.holdConditionGap) {
reasons.push(`declared ${listing.condition} 但描述写着 ${describedCondition.score.toFixed(1)},采用 0 到 4 的评分标准`);
}
return{ action: reasons.length>0?'hold':'publish', reasons, judgment };
}
exportasyncfunctionmoderate(listing:Listing):Promise<Decision> {
constfailures=hardRuleFailures(listing);
if(failures.length>0) {
return{ action:'reject', reasons: failures };
}
try{
constjudgment=awaitjudgeListing(listing);
returndecide(listing, judgment);
}catch(error) {
constmessage=errorinstanceofError?error.message:String(error);
return{ action:'hold', reasons: [`judgment unavailable: ${message}`] };
}
}
decide()是纯函数。它接收一个 listing 和一个已保存的 judgment,返回一个带 reasons 的 action,并且不发起任何网络调用。这种纯粹性正是让后续调优变得廉价的原因,因为你可以把它在你已经付过费的答案上重放。每个 reason 都会标明触发它的问题和数字,这样审核者就能看到系统所看到的内容。
reject 检查先运行并提前返回。hold 检查是累积的,因此一个模糊的 listing 会带着所有适用的 reason 进入队列。
Prohibited 检查完全基于confidence而不是去看获胜答案的概率。这是因为获胜的答案不一定等同于有把握的答案。具体的检查是:如果none获胜,而剩余概率分布在若干 prohibited 选项之间,那么这就是对 'fine' 答案的相对多数。这不应导致发布。condition 下限低于 prohibited 下限的原因是,一个score落在两个等级之间时,有可能把概率分散到相邻等级,即使描述是清晰的。condition gap 的计算方式是用声明等级的索引减去 Jev 的分数。这就是为什么这个机制用代码实现,以及为什么它只在一个方向上触发。例如,如果卖家声明like_new但描述的是fair,就会记录一个 hold。然而,如果卖家把一个好物品低估了,则不会记录 hold,因为受保护的是买家。
moderate()掌控整个流程,先运行硬规则,然后是 judgment,最后是 policy。没有照片的 listing 永远不会到达 Jev,而catch会把任何失败的 judgment 转成带错误作为 reason 的 hold。
这个形状有两个特性。第一,模型路径上的任何失败都足以让该商品进入保留队列,阻止其继续发布。第二,策略是已保存答案的纯函数,因此更改阈值只是重放你已经付过费的判断,无需再调用 Jev。
中的每个数字THRESHOLDS都只是一个起点。如果保留队列里堆满了无害的商品,就调高把它们放进去的那个阈值。如果有违规商品漏过,就调低它。
从标注样本中设定置信度阈值
某个choice或score上的置信度衡量的是概率分布的峰值程度。TypeSafe 的置信度文档直言不讳地指出,它并不能说明在你的领域上的准确率。因此阈值是基于你的数据和成本做出的判断。拒绝一个无辜的卖家,你就失去一个卖家。发布一个假货,买家就会损失金钱,而且你可能还会接到律师的电话。
调优过程对每个 Jev 集成都是一样的。取人类已经做出决定的样本,对每个样本判断一次,保存答案,然后在它们之上重放候选阈值。对于这个市场,我把 24 个商品标注为publish,hold,或reject,混合了干净商品、明显违规商品,以及真实队列中会出现的边界案例(一把归在家具类目下的厨师刀、褪黑素软糖、一把气枪玩具)。
// labeled.ts
importtype{ Listing }from'./listing';
exporttypeLabeled={listing:Listing;expected:'publish'|'hold'|'reject'};
// 为本文手动标注。在生产环境中,请拉取你的审核员已经做出决定的商品。
exportconstLABELED:Labeled[]=[
{
expected:'publish',
listing: {
id:'T-01',category:'furniture',condition:'good',priceUsd:35,photoCount:3,
title:'IKEA KALLAX shelf, 4x2, white',
description:'已组装,顶面有几处划痕。结实。仅限自提,二楼有电梯。',
},
},
{
expected:'publish',
listing: {
id:'T-02',category:'electronics',condition:'like_new',priceUsd:240,photoCount:6,
title:'Nintendo Switch OLED with two games',
description:'2024 年购买,运行完美。包含底座、两个 Joy-Con、Zelda TOTK 和 Mario Kart 8。屏幕无划痕。',
},
},
{
expected:'publish',
listing: {
id:'T-03',category:'clothing',condition:'fair',priceUsd:60,photoCount:4,
title:'Patagonia Nano Puff 夹克,女款 M',
description:'穿了两个季节,左袖上有个小勾丝,我用 tenacious tape 补上了。拉链正常。无烟家庭。',
},
},
{
expected:'publish',
listing: {
id:'T-04',category:'sporting_goods',condition:'good',priceUsd:380,photoCount:5,
title:'Trek Marlin 5 山地自行车,L 码',
description:'骑了大约 300 英里。上个月换了新刹车片。下管车漆有些磕碰。所有档位变速顺畅。',
},
},
{
expected:'publish',
listing: {
id:'T-05',category:'toys_and_baby',condition:'new',priceUsd:420,photoCount:3,
title:'LEGO Technic Bugatti Chiron 42083,未拆封',
description:'原盒未拆封,从未打开。盒子一角因存放有个小凹痕。已停产的套装。',
},
},
{
expected:'hold',
listing: {
id:'T-06',category:'furniture',condition:'good',priceUsd:25,photoCount:2,
title:'厨房主厨刀,8 英寸,Victorinox Fibrox',
description:'在我家厨房用了一年,磨过两次。刀刃无缺口。刀柄状态很好。',
},
},
{
expected:'publish',
listing: {
id:'T-07',category:'electronics',condition:'good',priceUsd:1150,photoCount:7,
title:'Canon EOS R6 单机身',
description:'快门数约 18k。功能完美。附带电池、充电器和背带。不含镜头。出售是因为我换到了 Sony。',
},
},
{
expected:'hold',
listing: {
id:'T-08',category:'toys_and_baby',condition:'new',priceUsd:12,photoCount:1,
title:'褪黑素软糖,2 瓶未拆封',
description:'促销时买多了。两瓶未开封,每瓶 60 粒,保质期到 2027 年。标准非处方助眠补充剂。',
},
},
{
expected:'reject',
listing: {
id:'T-09',category:'sporting_goods',condition:'new',priceUsd:75,photoCount:2,
title:'Glock 19 弹匣,3 个装',
description:'三个原厂 15 发弹匣,从未装弹。仅限本地见面交易。',
},
},
{
expected:'reject',
listing: {
id:'T-10',category:'toys_and_baby',condition:'new',priceUsd:300,photoCount:1,
title:'Ozempic 注射笔,还剩 2 支',
description:'医生开给我的,但我停用了。2 支未开封的 1mg 注射笔,一直冷藏保存。详情请私信。',
},
},
{
expected:'reject',
listing: {
id:'T-11',category:'electronics', condition:'new', priceUsd:20, photoCount:1,
title:'Netflix 高级账户,1 年',
description:'共享高级账户上的私人 4K 配置文件。付款后发送登录信息。保修 12 个月。',
},
},
{
expected:'reject',
listing: {
id:'T-12', category:'clothing', condition:'new', priceUsd:95, photoCount:4,
title:'Nike Air Jordan 1 Chicago,10 码',
description:'顶级批次,UA 品质,附带鞋盒和吊牌。不是零售正品,但你分辨不出来。可全球发货。',
},
},
{
expected:'reject',
listing: {
id:'T-13', category:'toys_and_baby', condition:'good', priceUsd:40, photoCount:2,
title:'Chicco KeyFit 30 婴儿汽车座椅',
description:'包含底座。不知道生产日期,标签掉了。带子在卡扣附近有些磨损,但仍能扣上。',
},
},
{
expected:'reject',
listing: {
id:'T-14', category:'electronics', condition:'like_new', priceUsd:600, photoCount:1,
title:'MacBook Pro 14 M3,几乎未使用',
description:'卖得很快,仅接受 Zelle 或 Venmo 付款,款项到账后发货。请发邮件至 quicksale.mbp@example.com。',
},
},
{
expected:'reject',
listing: {
id:'T-15', category:'sporting_goods', condition:'new', priceUsd:30, photoCount:3,
title:'战术推匕首,靴刀',
description:'用于自卫的紧凑型直刀,可放入靴子或腰带。含 Kydex 刀鞘。开箱即锋利如剃刀。',
},
},
{
expected:'reject',
listing: {
id:'T-16', category:'toys_and_baby', condition:'new', priceUsd:45, photoCount:1,
title:'姜黄提取物胶囊',
description:'30 天内治愈关节炎症并逆转早期关节炎。医生不想让你知道这个。3 瓶。',
},
},
{
expected:'hold',
listing: {
id:'T-17', category:'electronics', condition:'good', priceUsd:350, photoCount:2,
title:'Samsung 55 英寸 4K 电视',
description:'它实际上是 LG C1 48 英寸 OLED,我复用了旧的商品标题。运行良好,含遥控器。',
},
},
{
expected:'hold',
listing: {
id:'T-18', category:'electronics', condition:'like_new', priceUsd:150, photoCount:3,
title:'Dyson V11 无线吸尘器',
description:'电池只能维持约 5 分钟电量,电机发出摩擦声。按零件出售,或适合想自己修的人。',
},
},
{
expected:'hold',
listing: {
id:'T-19',category:'clothing',condition:'good',priceUsd:400,photoCount:4,
title:'West Elm 中世纪现代餐桌',
description:'实木,可坐六人。一条桌腿有点晃,桌面上有一个水渍圈。可走货运或本地自提。',
},
},
{
expected:'hold',
listing: {
id:'T-20',category:'sporting_goods',condition:'good',priceUsd:90,photoCount:2,
title:'Wilson Pro Staff 网球拍',
description:'很棒的球拍,上个月刚重新穿线。握把是新的。拍框没有裂纹。如果更方便的话,我也可以电话成交,怎么都行。',
},
},
{
expected:'publish',
listing: {
id:'T-21',category:'clothing',condition:'like_new',priceUsd:40,photoCount:3,
title:'Coach 皮革斜挎包',
description:'一位经常旅行的朋友送的礼物,不确定她是在哪里买的。皮革手感有点硬。附防尘袋。',
},
},
{
expected:'publish',
listing: {
id:'T-22',category:'toys_and_baby',condition:'good',priceUsd:45,photoCount:3,
title:'Airsoft M4 仿真枪,弹簧动力',
description:'玩具气枪步枪,橙色枪口完好,发射塑料 BB 弹。附 2 个弹匣和护目镜。适合 16 岁及以上。',
},
},
{
expected:'publish',
listing: {
id:'T-23',category:'toys_and_baby',condition:'like_new',priceUsd:50,photoCount:3,
title:'Graco Pack n Play 游戏床',
description:'在奶奶家用过几次。所有部件和床垫都齐全,2024 年的生产标签还在。',
},
},
{
expected:'publish',
listing: {
id:'T-24',category:'clothing',condition:'good',priceUsd:30,photoCount:2,
title:'处方眼镜镜框,Warby Parker',
description:'仅镜框,镜片已取下。镜框上没有划痕。附镜盒。可拿到任何眼镜店配新镜片。',
},
},
];
调优脚本会扫描最高风险的阈值,即违禁物品的自动拒绝置信度。对于每个候选值,它会打印哪些会被自动拒绝、其中有多少与标签匹配,以及有多少会进入人工审核。它还会打印被标记为publish的房源中置信度最低的答案,因为那是仅凭置信度判定为 hold 的房源必须保持在其之下的下限。对于数千条房源,借用分类手册中的有界并发.
// tune.ts
import{ judgeListing }from'./judge';
import{ LABELED }from'./labeled';
import{ decide }from'./moderate';
// 对每个已标注的房源判定一次,然后在保存的答案上重放候选阈值。
constjudged=awaitPromise.all(
LABELED.map(async({listing,expected})=>({ listing, expected, judgment:awaitjudgeListing(listing) })),
);
typeJudged=(typeofjudged)[number];
functionleastConfident(rows:Judged[],confidenceOf:(row:Judged)=>number):Judged|undefined{
returnrows.reduce<Judged|undefined>((min,row)=>(min===undefined||confidenceOf(row)<confidenceOf(min)?row:min),undefined);
}
consttotalCost=judged.reduce((sum,row)=>sum+row.judgment.costUsd,0);
console.log(`judged ${judged.length} listings for $${totalCost.toFixed(6)}\n`);
console.log('threshold auto_rejected correct wrong sent_to_review');
for(constthresholdof[0.5,0.6,0.7,0.8,0.9,0.95]) {
constflagged=judged.filter((row)=>row.judgment.prohibited.kind!=='none');
constautoRejected=flagged.filter((row)=>row.judgment.prohibited.confidence>=threshold);
constcorrect=autoRejected.filter((row)=>row.expected==='reject').length;
constwrong=autoRejected.length-correct;
constsentToReview=flagged.length-autoRejected.length;
console.log(
`${threshold.toFixed(2).padEnd(10)} ${String(autoRejected.length).padEnd(14)} ${String(correct).padEnd(8)} ${String(wrong).padEnd(6)
} ${
sentToReview
}`,);}console.log(
'\nlistings Jev flagged as prohibited:');for(const{listing,expected,
judgment}ofjudged) {if(judgment.prohibited.kind===
'none')continue;console.log(` ${listing.id} expected=${expected.padEnd(7)} ${judgment.prohibited.kind}@${judgment.prohibited.confidence.toFixed(
2
)}`);}console.log(
'\nlistings labeled reject that Jev did not flag as prohibited:');for(const{listing,expected,
judgment}ofjudged) {if(expected!=='reject'||judgment.prohibited.kind!==
'none')continue;console.log(` ${listing.id} offsite=${judgment.offsiteTransaction.toFixed(
2
)}`);}console.log(
'\nleast confident answers among listings labeled publish:');constpublishable=judged.filter((row)=>
row.expected==='publish');const
shakiestNone=leastConfident(publishable.filter((row)=>
row.judgment.prohibited.kind==='none'),(
row
)=>row.judgment.prohibited.confidence,?` none pick: ${shakiestNone.listing.id} @${shakiestNone.judgment.prohibited.confidence.toFixed(2)}`:' none pick: no publish-labeled listing came back none');
constshakiestCondition=leastConfident(publishable, (row)=>row.judgment.describedCondition.confidence);
console.log(shakiestCondition?` condition score: ${shakiestCondition.listing.id} @${shakiestCondition.judgment.describedCondition.confidence.toFixed(2)}`:' condition score: no publish-labeled listings');
console.log('\nfull policy with THRESHOLDS vs labels:');
letagreed=0;
for(const{listing,expected,judgment}ofjudged) {
constdecision=decide(listing, judgment);
if(decision.action===expected) {
agreed+= 1;continue
;}
console.
log(` ${listing.id} expected=${expected} got=${decision.action} ${decision.reasons.join('; ')||'(no reasons)'}`);}
console.
log(` ${agreed} of ${judged.length} match the label`);以下是 Jev 1.13 在 2026 年 9 月 22 日的运行结果。
judged 24 listings for $0.001131
threshold auto_rejected correct wrong sent_to_review
0.50 9 7 2 0
0.60 8 7 1 1
0.70 8 7 1 1
0.80 7 7 0 2
0.90 7 7 0 2
0.95 7 7 0 2
listings Jev flagged as prohibited:
T-08 expected=hold medication_or_medical_claim@0.77
T-09 expected=reject weapon_or_weapon_part@1.00
T-10 expected=reject medication_or_medical_claim@1.00
T-11 expected=reject account_or_digital_access@1.00
T-12 expected=reject counterfeit_or_replica@0.99
T-13 expected=reject recalled_or_unsafe_child_item@1.00
T-15 expected=reject weapon_or_weapon_part@1.00
T-16 expected=reject medication_or_medical_claim@1.00
T-22 expected=publish weapon_or_weapon_part@0.52
listings labeled reject that Jev did not flag as prohibited:
T-14 offsite=0.97
least confident answers among listings labeled publish:
none pick: T-21 @0.70
condition score: T-24 @0.56
full policy with THRESHOLDS vs labels:
T-22 expected=publish got=hold possible prohibited item: weapon_or_weapon_part (confidence 0.52)
23 of 24 match the label
Three things to read off a run like this, in any domain.
Three things to read off a run like this, in any domain.
首先,明确违规的案例相对于边缘案例落在哪里。在这个样本中,每个真正的违规得分都接近 1.0,而两个边缘条目(褪黑素软糖和一把气枪玩具)则远低于此,因此它们之间间隙中的任何阈值在这个样本上都适用。选一个两侧都有余量的阈值,因为单个概率会在不同运行之间波动,而恰好压在边缘案例上的阈值会翻转。在这个样本上,0.8 是零错误拒绝的最低阈值,这就是最终上线的值。
其次,置信度下限是否能通过你的干净示例。最后一个代码块打印出置信度最低的none以及被标记为以下类别的条目中置信度最低的状况评分publish。将每个下限设在这些值之下,并预期状况下限是两者中较低的那个。
第三,这些分歧对问题本身说明了什么。Jev 没有标记为违禁的那一个拒绝案例,是一个要求使用 Zelle 的 MacBook 条目,而单独的offsite_transaction问题抓住了它,这正是原子化问题原则的回报。标签与之不一致的那一个保留项是气枪玩具,而我会保留这个保留项。一支仿真步枪在上线前经过人工过目,是值得付出的成本。
整个运行,24 个条目、每个五个问题,花费约十分之一美分。当前定价在Jev 模型页面。
把所有部分接起来
入口点很短。对于每个条目,它调用moderate()并打印动作、原因和原始数字。在生产环境中保留这些日志记录,否则审核者只会看到一个光秃秃的HOLD,完全不知道是哪个问题触发的。
// run.ts
importtype{ Listing }from'./listing';
import{ moderate }from'./moderate';
constSAMPLES:Listing[]=[
{
id:'L-101',
title:'Sony WH-1000XM5 headphones, black',
description:
'Bought in 2025, used daily for commuting. Light scuff on the right earcup, everything works, battery still lasts a full week. Comes with the case and cable.',
category:'electronics',
condition:'good',
priceUsd:190,
photoCount:4,
},
{
id:'L-102',
title:'Louis Vuitton Neverfull MM tote',
description:
'Mirror quality 1:1, same factory as the boutique version. Nobody can tell the difference. Text me on WhatsApp for more photos and a better price.',
category:'clothing',
condition:'new',
priceUsd:120,
photoCount:3,
},
{
id:'L-103',
title:'Graco 4Ever DLX 汽车座椅',
description:
'我们的孩子已经用不下了。去年发生过一次轻微的追尾事故,但看起来完全没问题。胸扣带少了一根,但你可以在网上订购。',
category:'toys_and_baby',
condition:'like_new',
priceUsd:60,
photoCount:2,
},
{
id:'L-104',
title:'Peloton Bike,原版型号',
description:
'运行良好,我们只是从来不用它。屏幕左侧有一条坏点线,右侧踏板会吱吱响。按现状出售,不含鞋子和垫子。',
category:'sporting_goods',
condition:'like_new',
priceUsd:450,
photoCount:5,
},
];
lettotal=0;
for(constlistingofSAMPLES) {
constdecision=awaitmoderate(listing);
total+=decision.judgment?.costUsd??0;
console.log(`${listing.id} ${decision.action.toUpperCase()}`);
for(constreasonofdecision.reasons) console.log(` - ${reason}`);
if(decision.judgment) {
constj=decision.judgment;
console.log(
` prohibited=${j.prohibited.kind}@${j.prohibited.confidence.toFixed(2)} category=${j.matchesCategory.toFixed(2)} contradiction=${j.descriptionContradictsTitle.toFixed(2)} offsite=${j.offsiteTransaction.toFixed(2)} condition=${j.describedCondition.score.toFixed(2)} cost=$${j.costUsd.toFixed(6)}`,
);
}
}
console.log(`total Jev cost for ${SAMPLES.length} listings: $${total.toFixed(6)}`);
L-101 PUBLISH
prohibited=none@1.00 category=0.99 contradiction=0.04 offsite=0.03 condition=1.99 cost=$0.000048
L-102 REJECT
- prohibited: counterfeit_or_replica (confidence 1.00)
- asks to transact off platform (p=0.96)
prohibited=counterfeit_or_replica@1.00 category=0.97 contradiction=0.06 offsite=0.96 condition=3.57 cost=$0.000047
L-103 REJECT
- prohibited: recalled_or_unsafe_child_item (confidence 1.00)
prohibited=recalled_or_unsafe_child_item@1.00 category=0.98 contradiction=0.08 offsite=0.04 condition=0.90 cost=$0.000048
L-104 HOLD
- declared like_new but the description reads 1.0 on the 0 to 4 scale
prohibited=none@0.99 category=0.98 contradiction=0.07 offsite=0.04 condition=1.00 cost=$0.000047
total Jev cost for 4 listings: $0.000190
每个结果都是 Jev 原则在实际运行中的体现。耳机的发布状态中,所描述的成色正好落在卖家的good,这就是score类型处理策略,一个可以比较的数字。这个手提袋因来自两个不同问题的两个独立原因而被拒绝。
儿童安全座椅从未说“被召回”或“不安全”,却仍被拒绝,因为标准指明了实际情形:一次碰撞和缺失的束带。Peloton 被保留,是因为卖家声明了like_new在描述一个吱吱作响的踏板和坏点的同时,而声明的事实与 Jev 判断之间的这个差距,正是代码所揭示的。
再次运行,概率会变动百分之一二,而动作保持不变。这正是你想要的形态:在证据噪声下保持稳定的策略。
后续步骤
要将其扩展到市场之外,请保持这个形态。代码做确定性的工作。状态保持最小化,只保留问题所需的内容,将计算得出的事实作为标签传递。问题是狭窄且有类型的,在一次请求中一起提出,每个类型由答案的含义、字面标准以及一个明确的none来选择。答案是证据。验证它们的形态,用一个纯策略将其映射到动作,该策略的阈值来自带标签的示例,并将不确定性和失败路由给人工。
这种方法适用于工单路由、控制智能体工具以及内容分类。Jev 不生成文本,所以像“重写这个商品标题”这样的任务会交给生成式模型。如何将 Jev 与 LLM 配对,详见Jev vs LLM 文章。
要构建这个,首先创建一个 OpenRouter API key。然后将代码添加到你的项目中(每个代码块的第一行标明文件名),并用bun run run.ts在你自己的队列中的商品列表上测试它。
- TheJev 文档中心包含访问方式、定价、SDK 和演示。
- TheDecisions API 参考包含请求和响应模式。
- The工具调用门控 cookbook将相同的批准、阻止和审查形态应用于智能体动作。
- The分类 cookbook在速率限制内将 Jev 应用于大量积压任务。
- 什么是 Jev?解释了该模型、三种问题类型,以及如何解读概率。
- Jev 在分类上是否和前沿模型一样准确?在一个 77 意图的基准测试上,将 Jev 与 Claude Opus 5 进行对比测量,包括一个置信度级联。
常见问题
如何将 Jev 与 OpenRouter 一起使用?
创建一个 OpenRouter API key,安装@openrouter/sdk,并调用openrouter.alpha.decisions.create()with modeltypesafe/jev-1.13,一个state对象,以及一个或多个带类型的问题(choice,noul,或score)。Jev 返回一个带类型的答案,并为每个问题给出概率,你的代码再把这些数字转化为动作。同样的请求也可以通过普通 HTTP 在POST https://openrouter.ai/api/alpha/decisions上使用。
Jev 的 API 端点和模型 ID 是什么?
Jev 通过 OpenRouter 的 Decisions API 在POST https://openrouter.ai/api/alpha/decisions上提供服务。modelID 是typesafe/jev-1.13,别名~typesafe/jev-latest会跟踪最新版本。请求体携带模型、state,以及questions。
我需要 TypeSafe 账户才能访问 Jev 吗?
不需要。任何拥有OpenRouter API key的人都可以使用 Jev,费用计入你的 OpenRouter 账户。没有等待名单,你也不需要单独注册 TypeSafe。
我应该为 Jev 使用什么置信度阈值?
根据你自己的标注数据和错误动作的代价来选择,因为 Jev 的置信度衡量的是概率分布的集中程度,并不说明它在你的领域上的准确率。对一份标注样本判断一次,保存答案,然后在它们之上回放候选阈值。在我们的 24 条房源样本中,每一个真实违规项的得分都在 0.99 或更高,而 0.8 把两个边界案例转给了审核人员。
我应该用 Jev 做什么,什么应该留在代码里?
Jev 擅长在有限集合上做语义判断:某个东西属于哪个类别、某个命题是否为真,以及它在你定义的量表上处于什么位置。把算术、计数、日期比较、查找和执行留在你的代码里,并把结果作为状态喂给 Jev。Jev 不能生成文本,所以涉及写作的任务仍然需要生成模型。