如何使用 Jev:在 TypeScript 中透過 Jev API 進行內容審核
How to Use Jev: Moderation with the Jev API in TypeScript
本文介紹如何使用 TypeSafe 開發的決策模型 Jev,透過 TypeScript 與 OpenRouter Decisions API 建立完整的商品審核流程。
文章透過市集商品審核的完整端到端範例,展示如何使用 Jev 決策模型與 TypeScript 搭配 OpenRouter API 進行結構化判斷。
譯文尚未完整,完整內容請切換至原文。
Jev 是來自 TypeSafe 的決策模型。它接受非結構化輸入,並以帶機率的型別判斷回傳,接著由你的程式決定下一步。此指南示範如何在你自己的問題上使用 Jev,完整範例為在 TypeScript 中審核市場平臺的商品列表。
Jev 的工作原理
好的,您已瞭解 Jev 的用途並且覺得自己正走在正確的路上。現在,問題是:Jev 的請求在底層是什麼樣子?
先從頭開始。每一個 Jev 請求由一個 state 與一組 questions 組成。state 是 Jev 應評估的輸入。它可以是字串、JSON 物件或字串陣列。若您想評估一連串專案,例如對話中的訊息,陣列才是正確做法。questions 是 Jev 應對 state 執行的判斷清單。每一個問題由兩個元素構成,即 instructions 與 criteria。instructions 描述應執行的判斷。criteria 描述可能的答案。評估 state 時,Jev 會判斷每個 criteria 與 state 的契合度並回傳結果。若 state 為 JSON 物件,instructions 可以透過將欄位名包在反引號中來引用 state 中的命名欄位。我們稱之為欄位路徑。例如,一個有效的欄位路徑可能是 listing.description。提供此 instruction 時,Jev 會讀取該命名欄位的值並對其執行判斷。
問題型別有 choice、noul 與 score。選擇題從您定義的集合中挑選一個選項。真偽題決定一個陳述是否為真。評分題則將主題放在您定義的有序等級尺度上。
Jev 會對您每一個問題單獨且平行地評估相同的 state,並為每個問題僅回傳一個答案。每個答案包含一個 type,其值為 choice、noul 或 score。choice 型別答案由以下專案組成:1. 勝出選項;2. 每個選項的機率;3. 介於 0 與 1 的信心度,TypeSafe 以機率分佈的形狀計算。若機率高度集中於勝出選項,信心度會較高;若分散則較低,即使勝出選項個別機率最高,信心度仍會較低。信心度不是勝出選項的機率,而是整體分佈的摘要。noul 型別答案由以下專案組成:1. 一個表示陳述為真的單一機率;2. 無信心度,因為 noul 機率已表達模型的自信程度。score 型別答案由以下專案組成: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 本身不會自行決定下一步動作。
在市場中,這個原則建立了從賣家點選發布到商品上架的流程管道。它接收一筆上架資料並回傳「發布」、『待人工審核』或「拒絕」及其理由。
- 硬規則首先在程式碼中執行,例如照片數量、價格範圍與最短文字長度。未通過這些規則的上架將在送交 Jev 前即被拒絕…
- 狀態建構將賣家的文字與程式碼計算出的事實結合,並決定 Jev 看到的內容。
- 判斷是一個請求,詢問 Jev 針對該狀態的五個型別化問題。Jev 只回傳機率,永不回傳動作。
- 驗證確保回應符合政策所期望的結構,若不符合則停止處理。
- 政策將這些機率與已由審核員決定的門檻進行比較。
第 3 階段是 Jev,其他四個階段由您負責。這對所有 Jev 整合皆相同。Jev 是一個由狀態轉為證據的函式,其餘皆為普通軟體。
決定 Jev 決定的內容與您程式碼決定的內容
整合 Jev 的首要設計問題是應該將什麼送給它進行判斷。測試的標準是程式碼是否能在不閱讀任何文字的情況下計算答案。若能(例如計數、日期比較、查詢),就留在程式碼中,因為結果精確且免費。Jev 用於需要解讀自然語言的判斷。
TypeSafe 的 Jaggedness 頁面(Jev 1.13)列舉算術、精確計數與日期比較為應保留在程式碼中的專案,而非作為問題。
將此測試應用於市場,檢查專案可依下列方式分類。
| 檢查專案 | 所在位置 | 原因 |
|---|---|---|
| 價格在範圍內、至少一張照片、最小長度 | 程式碼 | 確定性。模型只能比if更不可靠。 |
| 價格遠低於此類別通常的售價 | 程式碼 | 對你自己的銷售資料做算術運算。將結果作為事實傳給 Jev。 |
| 商品屬於禁售型別(武器、偽造品、召回兒童座椅) | Jev | 賣家很少使用停用字。『鏡面品質,同一工廠』絕不會說偽造。 |
| 商品列表要求買家付款或離平臺聊天 | Jev | 電話號碼很容易用正規表示式抓取。『我也可以電話成交』則不行。 |
| 商品符合賣家選擇的類別 | Jev | 針對定義的語義判斷。 |
| 描述與標題或聲稱的狀態相衝突 | Jev | 需要比較意義。 |
檢查也定義了每個階段共用的商品型別。每個欄位都是三種之一:硬規則輸入、供 Jev 計算的事實,或 Jev 讀取的文字。條件依從最差到最佳排序,因此已宣告條件的索引與稍後的條件score等級對齊。
// listing.ts
export const CATEGORIES = {
electronics: 'Phones, laptops, cameras, audio gear, game consoles, and their accessories.',
furniture: 'Tables, chairs, sofas, beds, shelving, and other household furniture.',
clothing: 'Apparel, shoes, bags, and fashion accessories.',
sporting_goods: 'Bikes, fitness equipment, camping gear, and equipment for playing sports.',
toys_and_baby: 'Toys, games, strollers, car seats, cribs, and other children’s items.',
} as const;
export type Category = keyof typeof CATEGORIES;
export const CONDITIONS = ['for_parts', 'fair', 'good', 'like_new', 'new'] as const;
export type Condition = (typeof CONDITIONS)[number];
export type Listing = {
id: string;
title: string;
description: string;
category: Category;
condition: Condition;
priceUsd: number;
photoCount: number;
};類別定義以散文形式寫成,因為 Jev 在判斷商品是否屬於該類別時會從狀態讀取它們。這是一個一般原則。若判斷依賴你的規則,請將規則以散文寫入狀態並在問題中引用,讓模型使用你的定義而非自身。
硬規則是標準程式碼,並在任何呼叫 Jev 之前執行。
// rules.ts
import type { Category, Listing } from './listing';
// Typical sale price per category, in USD. In production this comes from your own sales data.
const TYPICAL_PRICE_USD: Record<Category, number> = {
electronics: 180,
furniture: 120,
clothing: 35,
sporting_goods: 90,
toys_and_baby: 40,
};
export type PriceSignal = 'far_below_typical' | 'typical' | 'far_above_typical';
export function priceSignal(listing: Listing): PriceSignal {
const ratio = listing.priceUsd / TYPICAL_PRICE_USD[listing.category];
if (ratio < 0.2) return 'far_below_typical';
if (ratio > 10) return 'far_above_typical';
return 'typical';
}
// Hard rules. Anything here is a fact your code already knows, so no model is involved.
export function hardRuleFailures(listing: Listing): string[] {
const failures: 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');
return failures;
}價格範圍顯示在單一檢查內的分割。範圍由程式碼實作,但若手提包在通常售價為 1,800 美元的類別中以 120 美元上市,則顯示偽造疑慮,解讀該訊號即為判斷。程式碼執行比較並將完成的標籤傳送給 Jev。
組織 Jev 所見的狀態
狀態是你程式與 Jev 之間介面的輸入端。TypeSafe 的 state docs 將其描述為專家小組在被要求判斷前會收到的資訊。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 來表示一個程度。它位於一個有序的尺度上。結果是一個數值,你可以進行減法、與其他數值比較,並以此衡量它們之間的差異。
The criteria format varies by question type. choice questions receive an object that maps each option name to its description, with a maximum of 255 options. noul questions receive an optional object with true and false descriptions. An ideal question is concise and direct, so many questions don’t even need a description object. score questions receive an ordered array of level descriptions (with between 2 and 10 levels), and the answer is measured on the zero-based index of that array.
市場使用所有三種。禁售專案是 choice 超過五個指定類別加上一個 none 選項,因為政策必須知道違規的是哪一條規則(武器與偽造品不同,觸發的訊息與審核者也不同)。類別符合、標題與說明矛盾,以及離平臺外聯絡是 noul 題目,因為每個都是是/否命題,政策所需的唯一數字是機率。描述條件是 score 超過五個等級,因為政策詢問宣稱與描述條件之間的距離,而距離只能在有序尺度上衡量。
獨立題目應放在同一個請求中。Jev 會對相同狀態並行評估它們,因此五題只需一次往返,若一個刊登觸發其中兩題(本文後面提到的 tote 就是這種情況),回覆會包含兩個不同的原因。寬泛題目會在一個答案中隱藏多個判斷。將寬泛題目拆解成狹窄、原子題目,可使其更易於檢查、調整與程式碼結合。
撰寫能在逐字閱讀下仍通過的 Jev 標準
指示說明要決定什麼,標準定義答案。Jev 會逐字套用兩者。這種逐字主義是 Jev 可預測性的來源之一,也意味著標準負擔了大部分工作。
在為任何領域撰寫標準時,以下幾條規則適用。使用輸入文字中出現的詞彙來描述選項,因為命名情境的標準(例如「付款後傳送登入資訊的共用帳號」)會對照到從未使用你政策用語的文字。對每個 noul,描述雙方,以便近似情況落在正確一側。為每個 choice 提供一個明確的 none,用於無任何符合項時,避免模型被迫隨意選擇。
對於 score,將每個等級描述為具體情況,因為等級構成尺度。並且像 prohibited 這樣的題目 ID 是程式碼讀取答案的關鍵,請勿期望模型將其解讀為指令。
以下列出客戶、條件尺度與禁售專案標準。noul 標準佔據請求本身。請在下一節進一步檢視。
// judge.ts
import { OpenRouter } from '@openrouter/sdk';
import { CATEGORIES, CONDITIONS, type Listing } from './listing';
import { priceSignal } from './rules';
const openrouter = new OpenRouter({
apiKey: process.env.OPENROUTER_API_KEY, // server-side only
});
export const PROHIBITED = {
none: 'An ordinary secondhand or new item that a general marketplace allows.',
weapon_or_weapon_part:
'A firearm, firearm part or accessory, ammunition, stun gun, or a knife or tool marketed for fighting or self-defense.',
medication_or_medical_claim:
'Prescription medication, a controlled substance, or any product sold with a claim that it treats, cures, or prevents a medical condition.',
counterfeit_or_replica:
'An item that carries a brand name, logo, or signature design without being made by that brand, including items called replica, AAA, 1:1, mirror, inspired by, or same factory. A brand-name item whose `price_signal` is far_below_typical with no reason given for the low price also fits here.',
recalled_or_unsafe_child_item:
'A car seat, crib, bassinet, or child helmet that has been in a crash, is missing parts or straps, is described as recalled, or has no visible manufacture date or label.',
account_or_digital_access:
'Login credentials, subscription or streaming accounts, gift card codes, license keys, or in-game currency.',
} as const;
export type ProhibitedKind = keyof typeof PROHIBITED;
// Score levels are ordered from worst to best, and each one describes a concrete situation.
export const CONDITION_LEVELS = [
'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.',
'Works fully. Light wear from normal use. Nothing broken or missing.',
'Works fully and looks unused or nearly unused. Original packaging or accessories may be included.',
'Brand new. Sealed, tagged, or never used.',
] as const;從 TypeScript 呼叫 Jev API
你可以透過 OpenRouter Decisions API 連結 Jev。請求包含三個元件:model、state 和 questions。回覆以 answers 為鍵,鍵值為你的題目 ID,每項都標記其 type,另外還有 usage 表示成本。
需要特殊 API 金鑰嗎?是否必須連結 TypeSafe 帳號?不!任何有效的 OpenRouter API 金鑰皆可。使用 Decisions API 無須 TypeSafe 帳號。只需兩個設定步驟:使用 bun add --exact @openrouter/sdk@1.3.17 安裝 SDK,然後將 OPENROUTER_API_KEY 環境變數設為你的金鑰。完整的 Decisions API 架構可參見 參考頁面。
此函式傳送市場狀態及所有五題,然後將回覆解析為政策可讀的純 Judgment。
// judge.ts, continued
export type Judgment = {
prohibited: { kind: ProhibitedKind; confidence: number; probabilities: Record<string, number> };
matchesCategory: number;
descriptionContradictsTitle: number;
offsiteTransaction: number;
describedCondition: { score: number; confidence: number };
costUsd: number;
};
function isProhibitedKind(value: string): value is ProhibitedKind {
return Object.hasOwn(PROHIBITED, value);
}
export async function judgeListing(listing: Listing): Promise<Judgment> {
const result = await openrouter.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: 'Which prohibited category, if any, does the item in `listing.title` and `listing.description` fall into? `price_signal` compares `listing.price_usd` with the typical price for its category.',
criteria: PROHIBITED,
},
matches_category: {
type: 'noul',
instructions: 'Does the item described in `listing.title` and `listing.description` belong in the category defined by `category_definition`?',
criteria: {
true: 'The item is the kind of thing the category definition describes.',
false: 'The item belongs in a different category, or the listing does not describe a physical item at all.',
},
},
description_contradicts_title: {
type: 'noul',
instructions: 'Does `listing.description` contradict `listing.title` about the brand, model, size, quantity, or whether the item works?',
criteria: {
true: 'The two disagree on at least one of those facts, such as a title that says one brand and a description that names another.',
false: 'The description adds detail or repeats the title without contradicting it.',
},
},
offsite_transaction: {
type: 'noul',
instructions: 'Does the listing ask the buyer to contact the seller, pay, or complete the sale outside the marketplace?',
criteria: {
true: 'The text gives a phone number, email, messaging app handle, external link, or asks for wire transfer, cash app, crypto, or gift card payment.',
false: 'The listing stays within the marketplace, including local pickup arranged through the marketplace.',
},
},
described_condition: {
type: 'score',
instructions: 'Based only on `listing.description`, which level best describes the physical condition of the item?',
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'
) {
throw new Error('Unexpected answer types in Decisions response');
}
if (!isProhibitedKind(prohibited.choice)) {
throw new Error(`Unknown prohibited kind ${prohibited.choice}`);
}
if (prohibited.confidence === undefined || prohibited.probabilities === undefined) {
throw new Error('Choice answer did not include confidence and probabilities');
}
if (described_condition.confidence === undefined) {
throw new Error('Score answer did not include confidence');
}
if (result.usage.cost === undefined) {
throw new Error('Response did not include 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,
};
}
export function declaredConditionIndex(listing: Listing): number {
return CONDITIONS.indexOf(listing.condition);
}在 offsite_transaction 標準中,本地取貨線使用雙邊規則。問題:如果這裡不使用雙邊規則會怎樣?這可能會讓字面閱讀者把「通過市場安排取貨」解讀為離平臺的聯絡。
Judgment 是介面中的輸出部分。它只包含證據,沒有其他內容,即勝者及其分佈、三個機率、一個分數及其置信度,以及成本。
throw 語句塊是驗證階段。SDK 的型別定義將 confidence 和 probabilities 設為可選,表示回應可能不包含它們。提供預設值會把錯誤回應隱藏在看似合理的決策後面。因此函式拒絕,協調器將拒絕轉為暫停,與網路失敗或 API 錯誤相同位置。
以純 HTTP 傳送相同請求
在此範例中,我們將看到一個 SDK 呼叫(本質上是一個包裝好的 POST https://openrouter.ai/api/alpha/decisions)。範例還包含兩個簡化問題及其輸出。第一個問題,即 noul,沒有其標準,這正是要展示它們是可選的。回應 id 將不符合您的回覆。
curl https://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.",
"Works fully. Light wear from normal use. Nothing broken or missing.",
"Works fully and looks unused or nearly unused. Original packaging or accessories may be included.",
"Brand new. Sealed, tagged, or never used."
]
}
}
}'{
"model": "typesafe/jev-1.13-20260917",
"answers": {
"offsite_transaction": { "type": "noul", "noul": 0.05 },
"described_condition": {
"type": "score",
"score": 1,
"legend": {
"0": "Does not work or is missing parts. Sold for parts or repair.",
"1": "Works, but has clear wear, damage, stains, or missing accessories that the buyer would notice immediately.",
"2": "Works fully. Light wear from normal use. Nothing broken or missing.",
"3": "Works fully and looks unused or nearly unused. Original packaging or accessories may be included.",
"4": "Brand new. Sealed, tagged, or never used."
},
"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 的 confidence docs 提供一般結構。對高置信度採取行動,將中等置信度交給人工,對低置信度則拒絕行動。這些線的位置取決於錯誤行動的成本,因此同一系統中的不同行動會有不同的閾值。由於問題獨立,政策亦可將它們結合。每個失敗的檢查都會新增暫停理由,單獨足夠強的檢查則會拒絕。
市場有三個行動。自動拒絕保留給證據足夠強,錯誤拒絕賣家比錯誤發布違規更稀少的情況。任何看似錯誤但缺乏該強度的情況將交給人工。發布是當沒有任何條件觸發時剩下的行動。
// moderate.ts
import { declaredConditionIndex, judgeListing, type Judgment } from './judge';
import type { Listing } from './listing';
import { hardRuleFailures } from './rules';
export type Action = 'publish' | 'hold' | 'reject';
export type Decision = {
action: Action;
reasons: string[];
judgment?: Judgment;
};
export const THRESHOLDS = {
rejectProhibitedConfidence: 0.8,
holdNoneConfidence: 0.5,
holdConditionConfidence: 0.3,
rejectOffsite: 0.8,
holdOffsite: 0.4,
holdContradiction: 0.6,
holdCategoryMismatch: 0.3,
holdConditionGap: 1.5,
};
// Pure policy: turns saved answers into an action, so thresholds can be replayed without new inference.
export function decide(listing: Listing, judgment: Judgment, thresholds = THRESHOLDS): Decision {
const reasons: 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)})`);
}
const conditionGap = declaredConditionIndex(listing) - describedCondition.score;
if (conditionGap >= thresholds.holdConditionGap) {
reasons.push(`declared ${listing.condition} but the description reads ${describedCondition.score.toFixed(1)} on the 0 to 4 scale`);
}
return { action: reasons.length > 0 ? 'hold' : 'publish', reasons, judgment };
}
export async function moderate(listing: Listing): Promise<Decision> {
const failures = hardRuleFailures(listing);
if (failures.length > 0) {
return { action: 'reject', reasons: failures };
}
try {
const judgment = await judgeListing(listing);
return decide(listing, judgment);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return { action: 'hold', reasons: [`judgment unavailable: ${message}`] };
}
}decide() 是純粹的。它接收一個列表和已儲存的判斷,並返回帶理由的行動,且不發出任何網路呼叫。這種純粹性使得後續調整成本低,因為你可以在已支付的答案上重新播放它。每個理由都標明觸發它的問題和數字,讓審查員能看到系統所見。
拒絕檢查先行執行並提前返回。暫停檢查是累積性的,因此含有所有適用理由的模糊列表將進入佇列。
停用檢查純粹基於 confidence 而非檢視贏家答案的機率。原因是贏家答案不一定等同於自信答案。具體檢查如下:若 none 贏得,且剩餘機率分佈於多個停用選項,則此情況屬於「fine」答案的多數。這不應該導致發布。條件底線低於停用值的原因是,score 若落在兩個等級之間,可能將機率傳播至相鄰等級,即使描述清晰。條件缺口是用宣告等級索引減去 Jev 分數計算的。這就是為何此機制在程式碼中實作且僅在單向觸發的原因。例如,若賣家宣稱 like_new 但描述為 fair,則會記錄保留。相反地,若賣家低估好商品,則不會記錄保留,因為保護的是買家。
moderate() 擁有流程,先執行硬規則,然後判斷,最後政策。沒有照片的刊登永遠不會到達 Jev,且 catch 將任何判斷失敗轉為保留,並以錯誤為原因。
這種結構有兩個特性。首先,模型路徑上的任何失敗都足以將刊登放入保留佇列,並阻止其進一步發布。其次,政策純粹是已儲存答案的函式,因此改變閾值只會重複已付費的判斷,且不會產生新的 Jev 呼叫。
THRESHOLDS 中的每個數字都是起點。若保留佇列被無害刊登填滿,請提高將它們放入的閾值;若違規專案漏過,則降低閾值。
從已標記樣本設定自信閾值
choice 或 score 的自信度衡量機率分佈的尖銳程度。TypeSafe 的自信度檔案直接指出它不說明你領域的準確率。因此,閾值是根據你的資料與成本作出的判斷。拒絕無辜賣家會失去一位賣家;發布偽造品則買家會蒙受損失,且你可能會收到律師來信。
調校流程對每個 Jev 整合相同。取出人類已決定的範例,對每個判斷一次,儲存答案,並在其上重播候選閾值。對於市場,我將 24 個刊登標記為 publish、hold 或 reject,涵蓋乾淨刊登、明確違規以及真實佇列產生的邊緣案例(如將廚師刀列為傢俱、褪黑素糖果、氣槍玩具)。
// labeled.ts
import type { Listing } from './listing';
export type Labeled = { listing: Listing; expected: 'publish' | 'hold' | 'reject' };
// Hand-labeled for this post. In production, pull listings your moderators already decided on.
export const LABELED: Labeled[] = [
{
expected: 'publish',
listing: {
id: 'T-01', category: 'furniture', condition: 'good', priceUsd: 35, photoCount: 3,
title: 'IKEA KALLAX shelf, 4x2, white',
description: 'Assembled, a few scratches on the top surface. Sturdy. Pickup only, second floor with elevator.',
},
},
{
expected: 'publish',
listing: {
id: 'T-02', category: 'electronics', condition: 'like_new', priceUsd: 240, photoCount: 6,
title: 'Nintendo Switch OLED with two games',
description: 'Bought in 2024, works perfectly. Includes dock, two Joy-Cons, Zelda TOTK and Mario Kart 8. Screen has no scratches.',
},
},
{
expected: 'publish',
listing: {
id: 'T-03', category: 'clothing', condition: 'fair', priceUsd: 60, photoCount: 4,
title: 'Patagonia Nano Puff jacket, women’s M',
description: 'Worn two seasons, small snag on the left sleeve that I patched with tenacious tape. Zipper works fine. Smoke-free home.',
},
},
{
expected: 'publish',
listing: {
id: 'T-04', category: 'sporting_goods', condition: 'good', priceUsd: 380, photoCount: 5,
title: 'Trek Marlin 5 mountain bike, size L',
description: 'Ridden about 300 miles. New brake pads last month. Some chips in the paint on the down tube. Shifts clean through all gears.',
},
},
{
expected: 'publish',
listing: {
id: 'T-05', category: 'toys_and_baby', condition: 'new', priceUsd: 420, photoCount: 3,
title: 'LEGO Technic Bugatti Chiron 42083, sealed',
description: 'Sealed in the original box, never opened. Box has a small dent on one corner from storage. Retired set.',
},
},
{
expected: 'hold',
listing: {
id: 'T-06', category: 'furniture', condition: 'good', priceUsd: 25, photoCount: 2,
title: 'Kitchen chef’s knife, 8 inch, Victorinox Fibrox',
description: 'Used in my home kitchen for a year, sharpened twice. No chips in the blade. Handle is in great shape.',
},
},
{
expected: 'publish',
listing: {
id: 'T-07', category: 'electronics', condition: 'good', priceUsd: 1150, photoCount: 7,
title: 'Canon EOS R6 body only',
description: 'Shutter count around 18k. Works flawlessly. Comes with battery, charger, and strap. No lens included. Selling because I moved to Sony.',
},
},
{
expected: 'hold',
listing: {
id: 'T-08', category: 'toys_and_baby', condition: 'new', priceUsd: 12, photoCount: 1,
title: 'Melatonin gummies, 2 sealed bottles',
description: 'Bought too many during a sale. Two unopened bottles, 60 gummies each, best before 2027. Standard over-the-counter sleep supplement.',
},
},
{
expected: 'reject',
listing: {
id: 'T-09', category: 'sporting_goods', condition: 'new', priceUsd: 75, photoCount: 2,
title: 'Glock 19 magazines, 3 pack',
description: 'Three factory 15-round magazines, never loaded. Local meetup only.',
},
},
{
expected: 'reject',
listing: {
id: 'T-10', category: 'toys_and_baby', condition: 'new', priceUsd: 300, photoCount: 1,
title: 'Ozempic pens, 2 left',
description: 'Prescribed to me but I stopped using them. 2 unopened 1mg pens, kept refrigerated. Message for details.',
},
},
{
expected: 'reject',
listing: {
id: 'T-11', category: 'electronics', condition: 'new', priceUsd: 20, photoCount: 1,
title: 'Netflix premium account, 1 year',
description: 'Private 4K profile on a shared premium account. Login details sent after payment. Warranty for 12 months.',
},
},
{
expected: 'reject',
listing: {
id: 'T-12', category: 'clothing', condition: 'new', priceUsd: 95, photoCount: 4,
title: 'Nike Air Jordan 1 Chicago, size 10',
description: 'Top tier batch, UA quality, comes with box and tags. Not retail but you will not be able to tell. Can ship worldwide.',
},
},
{
expected: 'reject',
listing: {
id: 'T-13', category: 'toys_and_baby', condition: 'good', priceUsd: 40, photoCount: 2,
title: 'Chicco KeyFit 30 infant car seat',
description: 'Base included. No idea when it was made, the label came off. Straps have some fraying near the buckle but still clip in.',
},
},
{
expected: 'reject',
listing: {
id: 'T-14', category: 'electronics', condition: 'like_new', priceUsd: 600, photoCount: 1,
title: 'MacBook Pro 14 M3, barely used',
description: 'Selling fast, paying with Zelle or Venmo only, I will ship after payment clears. Email me at quicksale.mbp@example.com.',
},
},
{
expected: 'reject',
listing: {
id: 'T-15', category: 'sporting_goods', condition: 'new', priceUsd: 30, photoCount: 3,
title: 'Tactical push dagger, boot knife',
description: 'Compact fixed blade for self-defense, fits in a boot or waistband. Kydex sheath included. Razor sharp out of the box.',
},
},
{
expected: 'reject',
listing: {
id: 'T-16', category: 'toys_and_baby', condition: 'new', priceUsd: 45, photoCount: 1,
title: 'Turmeric extract capsules',
description: 'Cures joint inflammation and reverses early arthritis in 30 days. Doctors do not want you to know about this. 3 bottles.',
},
},
{
expected: 'hold',
listing: {
id: 'T-17', category: 'electronics', condition: 'good', priceUsd: 350, photoCount: 2,
title: 'Samsung 55 inch 4K TV',
description: 'It is actually an LG C1 48 inch OLED, I reused an old listing title. Works great, remote included.',
},
},
{
expected: 'hold',
listing: {
id: 'T-18', category: 'electronics', condition: 'like_new', priceUsd: 150, photoCount: 3,
title: 'Dyson V11 cordless vacuum',
description: 'Battery only holds a charge for about 5 minutes and the motor makes a grinding noise. Selling for parts or if you want to fix it.',
},
},
{
expected: 'hold',
listing: {
id: 'T-19', category: 'clothing', condition: 'good', priceUsd: 400, photoCount: 4,
title: 'West Elm mid-century dining table',
description: 'Solid wood, seats six. One leg is a bit wobbly and there is a water ring on the top. Ships freight or local pickup.',
},
},
{
expected: 'hold',
listing: {
id: 'T-20', category: 'sporting_goods', condition: 'good', priceUsd: 90, photoCount: 2,
title: 'Wilson Pro Staff tennis racket',
description: 'Great racket, restrung last month. Grip is fresh. Frame has no cracks. I can also do the deal by phone if that is easier, whatever works.',
},
},
{
expected: 'publish',
listing: {
id: 'T-21', category: 'clothing', condition: 'like_new', priceUsd: 40, photoCount: 3,
title: 'Coach leather crossbody bag',
description: 'Got it as a gift from a friend who travels a lot, not sure where she bought it. Leather feels a little stiff. Comes with dust bag.',
},
},
{
expected: 'publish',
listing: {
id: 'T-22', category: 'toys_and_baby', condition: 'good', priceUsd: 45, photoCount: 3,
title: 'Airsoft M4 replica, spring powered',
description: 'Toy airsoft rifle, orange tip intact, shoots plastic BBs. Comes with 2 magazines and safety glasses. For ages 16 and up.',
},
},
{
expected: 'publish',
listing: {
id: 'T-23', category: 'toys_and_baby', condition: 'like_new', priceUsd: 50, photoCount: 3,
title: 'Graco Pack n Play playard',
description: 'Used at grandma’s house a handful of times. All parts and the mattress pad included, manufacture label from 2024 still attached.',
},
},
{
expected: 'publish',
listing: {
id: 'T-24', category: 'clothing', condition: 'good', priceUsd: 30, photoCount: 2,
title: 'Prescription glasses frames, Warby Parker',
description: 'Frames only, lenses removed. No scratches on the frame. Case included. Take them to any optician for new lenses.',
},
},
];調校指令碼掃描最高風險閾值,即對停用專案的自動拒絕自信度。對每個候選值,它會列印將被自動拒絕的專案、其中與標籤相符的數量以及進入審核的數量。它還會列印標記為 publish 的刊登中最不自信的答案,因為這是僅以自信度為基準的保留必須保持在其以下的底線。對於數千筆刊登,請從 分類食譜 借用受限併發。
// tune.ts
import { judgeListing } from './judge';
import { LABELED } from './labeled';
import { decide } from './moderate';
// Judge every labeled listing once, then replay candidate thresholds over the saved answers.
const judged = await Promise.all(
LABELED.map(async ({ listing, expected }) => ({ listing, expected, judgment: await judgeListing(listing) })),
);
type Judged = (typeof judged)[number];
function leastConfident(rows: Judged[], confidenceOf: (row: Judged) => number): Judged | undefined {
return rows.reduce<Judged | undefined>((min, row) => (min === undefined || confidenceOf(row) < confidenceOf(min) ? row : min), undefined);
}
const totalCost = 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 (const threshold of [0.5, 0.6, 0.7, 0.8, 0.9, 0.95]) {
const flagged = judged.filter((row) => row.judgment.prohibited.kind !== 'none');
const autoRejected = flagged.filter((row) => row.judgment.prohibited.confidence >= threshold);
const correct = autoRejected.filter((row) => row.expected === 'reject').length;
const wrong = autoRejected.length - correct;
const sentToReview = 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 } of judged) {
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 } of judged) {
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:');
const publishable = judged.filter((row) => row.expected === 'publish');
const shakiestNone = leastConfident(
publishable.filter((row) => row.judgment.prohibited.kind === 'none'),
(row) => row.judgment.prohibited.confidence,
);
console.log(shakiestNone ? ` none pick: ${shakiestNone.listing.id} @${shakiestNone.judgment.prohibited.confidence.toFixed(2)}` : ' none pick: no publish-labeled listing came back none');
const shakiestCondition = 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:');
let agreed = 0;
for (const { listing, expected, judgment } of judged) {
const decision = 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`);以下是 2026 年 9 月 22 日 Jev 1.13 的執行結果。
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從此類執行結果中可以讀出的三件事,適用於任何領域。
首先,清楚違規的情況相對於邊緣案例的位置。這裡每個實際違規的分數都接近 1.0,而兩個邊緣清單(褪黑素軟糖和一個氣動玩具)則遠低於此,因而在它們之間的任何閾值都能在此樣本上運作。選擇一個在兩側都有邊際的閾值,因為個別機率會在不同跑中變動,而閾值落在邊緣案例上會導致翻轉。在此樣本中,0.8 是最低且沒有錯誤拒絕的閾值,這就是實際使用的值。
第二,確認信心底線是否能通過您的乾淨樣本。最後一個區塊會列印出最不自信的 none 以及標記為 publish 的清單中最不自信的條件分數。將每個底線設在這些值以下,並預期條件底線將是兩者中較低的那個。
第三,爭議對問題的說明。Jev 未將其標記為禁止的那個拒絕案例是一個要求 Zelle 的 MacBook 清單,而單獨的 offsite_transaction 問題捕捉到了它,這證明瞭原子問題原則有效。標籤不同意的保持案例是氣動玩具,我將保留該保持。讓人類在上線前審查一支複製步槍是一筆值得支付的成本。
整個流程,24 個清單,每個有五個問題,成本約為十分之一分。當前定價請參閱 Jev 模型頁面。
將其整合在一起
入口點很簡短。對於每個清單,它會呼叫 moderate() 並列印動作、原因以及原始數字。請在正式環境中保留此日誌,否則審查者會看到裸露的 HOLD,卻不知道是哪個問題被觸發。
// run.ts
import type { Listing } from './listing';
import { moderate } from './moderate';
const SAMPLES: 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 car seat',
description:
'Our kid outgrew it. Was in a minor fender bender last year but looks totally fine. One of the chest clip straps is missing but you can order it online.',
category: 'toys_and_baby',
condition: 'like_new',
priceUsd: 60,
photoCount: 2,
},
{
id: 'L-104',
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.',
category: 'sporting_goods',
condition: 'like_new',
priceUsd: 450,
photoCount: 5,
},
];
let total = 0;
for (const listing of SAMPLES) {
const decision = await moderate(listing);
total += decision.judgment?.costUsd ?? 0;
console.log(`${listing.id} ${decision.action.toUpperCase()}`);
for (const reason of decision.reasons) console.log(` - ${reason}`);
if (decision.judgment) {
const j = 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 金鑰。接著將程式碼加入專案(每個程式碼區塊的第一行為檔名),並使用 bun run run.ts 在您自己的佇列中測試清單。
- Jev 文件中心 提供存取權、定價、SDK 與示範。
- Decisions API 參考 包含請求與回應的資料結構。
- 《tool-call gating cookbook》將相同的批准、阻擋與審核結構套用於代理行動。
- 《classification cookbook》將 Jev 應用於大量待處理專案,並符合速率限制。
- What Is Jev? 解釋模型、三種問題型別,以及如何閱讀機率。
- Is Jev as Accurate as Frontier Models at Classification? 以 77 意圖基準測試 Jev 與 Claude Opus 5 的表現,並包含信心級聯。
常見問題
如何使用 Jev 與 OpenRouter?
建立一個 OpenRouter API 金鑰,安裝 @openrouter/sdk,並使用 model typesafe/jev-1.13、state 物件以及一個或多個已型別化的問題(choice、noul 或 score)呼叫 openrouter.alpha.decisions.create()。Jev 會回傳每個問題的型別化答案及其機率,您的程式碼將這些數值轉換為動作。相同的請求亦可透過純 HTTP 在 POST https://openrouter.ai/api/alpha/decisions 執行。
Jev 的 API 端點和模型 ID 是什麼?
Jev 透過 OpenRouter 的 Decisions API 在 POST https://openrouter.ai/api/alpha/decisions 服務。model ID 為 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 無法生成文字,因此涉及寫作的任務仍需使用生成式模型。
來源:openrouter blog · openrouter.ai