具体来说,OpenAI Decisions API、Microsoft-Decision-1、Cloudflare Clef 等托管模型,RouterPlus、OpenRouter 等多模型网关,以及 Clef 的开源权重和 EdgeJev 这类兼容 POST /v1/systemone 接口格式的开源项目,都能提供 Choice、Score 和 Noul(是非题)这一类能力。
还不熟悉决策模型的话,可以先读 决策模型是什么?System One 模型、Jev 与 OpenAI Decisions API 对比。
决策模型选型地图(2026 年 10 月)
按“谁来跑模型、你写什么格式”,大致可以分成四类:
1. 托管单模型:厂商自己训练、自己托管,你调用它的 API。
- Jev(TypeSafe):
POST /v1/systemone,目前只接受文本。也可以通过 OpenRouter 调用。 - OpenAI Decisions API:
POST /v1/decisions,目前只有gpt-6-luna一个模型,处于公测阶段,支持文本和图片。 - Microsoft-Decision-1:在 Microsoft Foundry 中部署,也可以通过 OpenRouter 使用。微软说它是在 Qwen3.5-9B 的基础上后训练得到的。
- Cloudflare Clef / Clef-flash:托管在 Workers AI 上。Cloudflare 说它遵循
POST /v1/systemone接口格式,可以读取文本、JSON、图片或视频。
2. 多模型网关:一个密钥、一个端点,背后路由到多家决策模型。
- RouterPlus:
POST /v1/decisions。目录里有十个决策模型,其中九个使用同一套POST /v1/systemone接口格式;自有模型 Decider 2B 和 Kev 4B 标价为 $0。 - OpenRouter:Jev 可以走
POST /api/alpha/decisions,也可以走兼容 TypeSafe SDK 的POST /api/v1/systemone。OpenRouter 文档说不需要排队,也不需要单独的 TypeSafe 账户。
3. 开源权重与开源复现:模型可以下载到自己的机器上运行。
- Clef 开源权重:Cloudflare 以 Apache 2.0 许可在 Hugging Face 上开源了 Clef 和 Clef-flash 的权重。
- EdgeJev(Apache-2.0):把 laya、kev、NanoJev、PlayJev 等开源复现统一成“转换 → 量化 → 部署”的流程,在 CPU 上离线运行,并提供
POST /v1/systemone本地服务。项目自述“数据不出本机”。 - 还有 jev-clone 这类独立复现。它们都声明与 TypeSafe 无隶属关系。
4. SDK 抽象层:本身不是模型,而是在你的代码和上面这些服务之间加一层。
- System One SDK(本站):用 TypeScript 定义 Choice / Score / Noul 问题,通过适配器连接不同的模型服务。
要注意,SDK 开源不等于模型可以自托管。System One 开源的是 SDK;System One 本身不提供可下载的模型权重。SDK 能否直接连接你自建的模型服务,见下文“自托管要考虑什么”。
为什么会被锁定:请求格式、问题定义与阈值
锁定风险通常不在“调用了哪家 API”,而在于问题定义被写成了某一家的请求格式。对比官方文档,可以看到两套不同的写法:
| | TypeSafe Jev / Microsoft-Decision-1 / Cloudflare Clef | OpenAI Decisions API |
| --- | --- | --- |
| 输入字段 | state(字符串或 JSON) | input(字符串,或包含文本和图片的 user 消息) |
| 问题结构 | questions 是一个对象,键是你起的问题 ID | questions 是一个数组,每个问题用 name 标识 |
| 是非题 | type: "noul" | type: "predicate" |
| 选项与档位 | 都写在 criteria 里(Choice 用对象,Score 用有序数组) | Choice 写在 choices: [{ value, description }],Score 写在 levels: [{ label, description }] |
RouterPlus 的文档也写明,OpenAI 的 API 不使用 POST /v1/systemone 接口格式,它的网关需要在中间做一次翻译。此外,各家的 confidence 算法并不相同。RouterPlus 文档举的例子是:在 Jev 上调好的 confidence 阈值,不能直接搬到 Clef 或 GPT-6 Luna Decisions 上用。
所以要能随时换模型,需要守住三样东西:
- 问题定义:问题、选项、评分档位写在你自己的代码里,不散落在某家的控制台或请求体里。
- 阈值和业务规则:“低于多少分就转人工”这类策略属于应用代码,换模型时要重新验证。
- 调用层:换模型时,只改适配器和凭据。
还有一个现实情况:各家都发布了“自己最强”的自测结果。微软称 Microsoft-Decision-1 在其 36 项基准对比中准确率最高(微软自测);Cloudflare 称在 10 个决策基准中,Clef 系列有 7 个最高(Cloudflare 自测)。这些结论无法互相印证。能在你自己的标注数据上方便地对比几个后端,本身就是不被锁定的价值所在。
用 System One SDK 写一次、换后端
下面这段代码来自开发团队提供的示例(examples/decision-articles/02-swappable-provider.ts)。问题、评分量表和动作阈值都留在这一个文件里,切换后端时只换适配器和凭据:
hosted:System One 托管 API(@system-one-ai/adapter-system-one)。system-one.dev 提供 Jev(typesafe/jev)、Clef Flash(cloudflare/clef-flash)和 Clef(cloudflare/clef)模型服务。typesafe:同一个适配器,指向https://api.typesafe.ai/v1。openrouter:@system-one-ai/adapter-openrouter。cloudflare:@system-one-ai/adapter-cloudflare,需要 Cloudflare 账户 ID。
ts/**
* Article 2 — Keep the problem definition in your code so the provider is swappable.
*
* The SDK is an open-source TypeScript toolkit. Questions, rubrics, and
* action thresholds live in this file. Only the adapter / credentials change.
*
* Adapters this repository actually depends on:
* - @system-one-ai/adapter-system-one → System One hosted API or TypeSafe HTTP
* - @system-one-ai/adapter-openrouter → OpenRouter Decisions
* - @system-one-ai/adapter-cloudflare → Cloudflare REST (needs accountId)
*
* `@system-one-ai/adapter-vercel` is mentioned in docs but is not a dependency
* of this repo, so this file does not import it.
*
* Hosted credits apply only to POST /v1/systemone. TypeSafe, OpenRouter, and
* Cloudflare bill on their own accounts. Adapter support is not the hosted
* model catalog — use GET /v1/models for what this site currently hosts.
*/
import { createSystemOne, choice, score, booleanQuestion } from '@system-one-ai/core';
import type { EvaluationClient } from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { openRouterAdapter } from '@system-one-ai/adapter-openrouter';
import { cloudflareAdapter } from '@system-one-ai/adapter-cloudflare';
import { createFetchTransport } from '@system-one-ai/transport-fetch';
/** Application-owned problem definition. Do not move these labels into the provider dashboard. */
export const reviewQuestions = {
team: choice('Choose the team that should review this message.', {
billing: 'Payments and refunds',
support: 'Technical issues',
review: 'More information or a person is required',
}),
completeness: score('How completely does the draft reply address the message?', [
'Does not address the message',
'Partially addresses the message',
'Fully addresses the message',
]),
};
/** Application-owned policy. Unknown or missing scores are unresolved, not a pass. */
export const REVIEW_IF_SCORE_BELOW = 1.5;
export type ProviderKind = 'hosted' | 'typesafe' | 'openrouter' | 'cloudflare';
export function createProviderClient(kind: ProviderKind): EvaluationClient {
const transport = createFetchTransport();
if (kind === 'hosted') {
const apiKey = process.env.SYSTEM_ONE_API_KEY;
const baseURL = process.env.SYSTEM_ONE_BASE_URL;
const model = process.env.SYSTEM_ONE_MODEL;
if (!apiKey || !baseURL || !model) {
throw new Error('Hosted client needs SYSTEM_ONE_API_KEY, SYSTEM_ONE_BASE_URL, SYSTEM_ONE_MODEL.');
}
return createSystemOne({
apiKey, baseURL, model, adapter: systemOneAdapter, transport, maxRetries: 0, timeoutMs: 30_000,
});
}
if (kind === 'typesafe') {
const apiKey = process.env.TYPESAFE_API_KEY;
const model = process.env.TYPESAFE_MODEL;
if (!apiKey || !model) throw new Error('TypeSafe client needs TYPESAFE_API_KEY and TYPESAFE_MODEL.');
return createSystemOne({
apiKey,
baseURL: 'https://api.typesafe.ai/v1',
model,
adapter: systemOneAdapter,
transport,
maxRetries: 0,
timeoutMs: 30_000,
});
}
if (kind === 'openrouter') {
const apiKey = process.env.OPENROUTER_API_KEY;
const model = process.env.OPENROUTER_MODEL;
if (!apiKey || !model) throw new Error('OpenRouter client needs OPENROUTER_API_KEY and OPENROUTER_MODEL.');
return createSystemOne({
apiKey, model, adapter: openRouterAdapter, transport, maxRetries: 0, timeoutMs: 30_000,
});
}
const apiKey = process.env.CLOUDFLARE_API_TOKEN;
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
const model = process.env.CLOUDFLARE_MODEL;
if (!apiKey || !accountId || !model) {
throw new Error('Cloudflare REST client needs CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_MODEL.');
}
return createSystemOne({
apiKey,
model,
adapter: cloudflareAdapter({ accountId }),
transport,
maxRetries: 0,
timeoutMs: 30_000,
});
}
export type ReviewNext = 'revise_draft' | 'keep_draft' | 'needs_review';
export function applyReviewPolicy(completeness: number | null | undefined): ReviewNext {
if (completeness == null || Number.isNaN(completeness)) {
return 'needs_review';
}
return completeness < REVIEW_IF_SCORE_BELOW ? 'revise_draft' : 'keep_draft';
}
export async function reviewDraft(
client: EvaluationClient,
input: { message: string; draft: string },
) {
const result = await client.evaluate({
state: { message: input.message, answer: input.draft },
questions: reviewQuestions,
});
return {
team: result.answers.team.choice,
completeness: result.answers.completeness.score,
next: applyReviewPolicy(result.answers.completeness.score),
model: result.model,
};
}
async function main() {
const kind = (process.env.EXAMPLE_PROVIDER ?? 'hosted') as ProviderKind;
const allowed: ProviderKind[] = ['hosted', 'typesafe', 'openrouter', 'cloudflare'];
if (!allowed.includes(kind)) {
throw new Error(`EXAMPLE_PROVIDER must be one of ${allowed.join(', ')}.`);
}
const decision = await reviewDraft(createProviderClient(kind), {
message: 'I was charged twice for the same order.',
draft: 'Please send your order number so we can review both charges.',
});
console.log(JSON.stringify(decision, null, 2));
}
if (process.argv[1] && import.meta.url.endsWith(process.argv[1].split(/[\\/]/).pop() ?? '')) {
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});
}
示例里有几点值得注意(均出自示例注释和 README):
- 问题定义归应用所有:
reviewQuestions的注释写明,不要把这些标签挪到供应商的控制台里。 - 阈值归应用所有:
REVIEW_IF_SCORE_BELOW = 1.5是应用自己的策略。示例的applyReviewPolicy()分三种情况:分数缺失(undefined或null)或不是数字(NaN)时返回needs_review,交给人工审核;分数低于 1.5 返回revise_draft;其余返回keep_draft。也就是说,缺失或未知的分数按“未解决”处理,不算通过。 - 计费各自独立:System One 积分只用于 System One 托管 API;TypeSafe、OpenRouter、Cloudflare 由各自的账户计费。
- 适配器支持不等于托管目录:本站当前托管了哪些模型,请以
GET /v1/models为准。system-one.dev 提供 Jev(typesafe/jev)、Clef Flash(cloudflare/clef-flash)和 Clef(cloudflare/clef)模型服务。 - 有文档提到过
@system-one-ai/adapter-vercel,但它不是这个仓库的依赖,所以示例没有引入。
运行时用 EXAMPLE_PROVIDER=typesafe|openrouter|cloudflare 选择后端,并提供该服务自己的凭据。这些调用由对应服务计费,不消耗 System One 积分。
另外,@system-one-ai/core 的 npm 说明里提到,请求可以携带图片,但只有显式声明 supportsImages: true 的适配器才接受;不支持图片的适配器会直接报错,不会悄悄把图片降级成文本。
自托管要考虑什么
想让“数据不出内网”,需要的是可以自己运行的模型,而不只是开源的 SDK。目前能核实的选择有 Clef 的开源权重,以及 EdgeJev 这类开源复现。评估时建议看这几点:
- 数据驻留:EdgeJev 自述推理过程不发任何网络请求、没有遥测、state 和问题都不离开本机(项目自述)。托管服务的数据处理以各家条款为准:OpenAI 文档提到对符合条件的客户支持 ZDR,Jev 文档说不用客户请求训练模型。
- 准确率与校准:开源复现和官方模型差多少,目前只有项目自报的数字。比如 EdgeJev 在 README 里公布了自测成绩,并附有测试条件说明。jev-clone 在 README 中写道:已有的公开复现开源了“接口形状”,但没有一份做过校准评估(项目自述)。上线前请用自己的标注数据测一遍。
- 语言:TypeSafe 文档说 Jev 以英语为主要训练语言,中日韩文字可以处理,但效果不一定一样好。中文场景无论选哪家,都建议先测。
- 延迟:EdgeJev 自报在 4 核 CPU 上单题 15.6 ms(项目自报,测试条件见其 README)。厂商公布的延迟同样是自测数字。
- 运维成本:模型转换、量化、部署、监控和版本升级都要自己负责。比如 EdgeJev 的转换步骤需要 torch 并且要联网下载权重,之后才能离线运行。
- 和 SDK 怎么接:EdgeJev 提供
POST /v1/systemone,System One 托管端也使用{baseURL}/systemone这个路径。System One SDK 能否直接连接这类自建服务,目前还没有验证,本文不作承诺。
怎么选:一个简单的判断顺序
- 数据能不能出内网? 不能,就看开源权重或开源复现,并为自己做校准验证留出时间。
- 需不需要图片? 按各家官方文档,Jev 目前只接受文本;OpenAI Decisions API 和 Clef 支持图片。
- 想不想同时试几家? 用网关或 SDK 抽象层,在同一份问题定义上对比。
- 无论选哪家:把问题定义和阈值留在自己的代码里,换后端后用同一份标注数据重新验证阈值。
表中不收录延迟和准确率数字。厂商与开源项目公布的这类数字都是各自自测,本文没有实测。