快速开始:用 SDK 跑一个 Eval(开发中)
开发中
这种接法正在开发,暂时不能在平台上选用,现在请连 GitHub 仓库或用本地测试机。已经用它接好的项目照常可用,下面的说明照旧适用。
在你们自己的进程里,让 Agent 跑几条输入,给每条输出打分,把结果作为一个实验传上来。大约 5 分钟。
任务在你们的机器或 CI 上用你们自己的密钥跑,这种接法里平台从不调用你们的 Agent。平台收到的是每一行的输入、输出和分数,以及你们包装过的调用的追踪。
1. 从平台装 SDK
TypeScript 包 @preworld/sdk 和 Python 包 preworld 不在 npm 和 PyPI 上:平台自己提供,在 https://<平台地址>/downloads/,每次发版一个新版本。项目的目标 →「SDK」卡片上有下面两行,平台地址和当前版本都已填好,从那里复制就行。也可以自己查当前版本:
curl -fsS https://<平台地址>/downloads/sdk.json # "version",以及 "npm" 和 "python" 下的文件名然后在你们项目的目录里:
curl -fLO https://<平台地址>/downloads/preworld-sdk-<版本>.tgz
npm install -D ./preworld-sdk-<版本>.tgz tsx # tsx 用来跑 *.eval.ts 文件curl -fLO https://<平台地址>/downloads/preworld-<版本>.tar.gz
pip install ./preworld-<版本>.tar.gz # 或:uv add ./preworld-<版本>.tar.gz把下载的文件和代码一起提交:package.json(或你们的依赖清单)指向的就是它,CI 里的 npm ci、pip install 装的是同一个文件,平台出了新版本也装得上。要升级,就下载新文件,照样装一次。每个文件旁边有它的校验值(<文件名>.sha256)。SDK 依赖的开源包(OpenTelemetry)由 npm 和 pip 从各自的仓库下载。
2. 建一个能上传的项目令牌
- 建一个项目(侧栏的「新建项目」,选「会调用工具的 Agent」),或者打开一个已有的。
- 打开自动化 →「API 令牌」。填名称,比如
laptop,勾上**「允许上传日志与实验」,点「新建令牌」**。 - 复制令牌(
pj_…)。它只显示这一次。
然后在运行 Eval 的终端里设四个变量:
export PREWORLD_API_KEY=pj_你的令牌 # 刚建的令牌
export PREWORLD_PROJECT=prj_你的项目编号 # 项目地址里的那一段:/p/<这里>/…
export PREWORLD_BASE_URL=https://<平台地址>
export PREWORLD_ENVIRONMENT=dev # dev、staging、prod 或 ci(设了 CI 时默认 ci)SDK 自己从不读取 .env 文件。项目的「目标」→ SDK 那张卡片上有这几行,项目的值已经填好。
3. 写一个 Eval
import { Eval } from '@preworld/sdk';
// 你们的 Agent:这里用一个查表回答的替身
async function answer(question: string): Promise<string> {
const known: Record<string, string> = { '退款多久到账?': '退款在 5 个工作日内到账。' };
return known[question] ?? '我不知道。';
}
Eval('support-bot', {
data: [
{ input: '退款多久到账?', expected: '退款在 5 个工作日内到账。' },
{ input: '能改收货地址吗?', expected: '可以,发货之前都能改。' },
],
task: answer,
scores: [
function exact({ output, expected }) { return output === expected; }, // 在你们这边打分:0 到 1 的数,或 true / false
'builtin:factuality', // 在平台上由评审模型打分
],
});from preworld import Eval
def answer(question):
known = {"退款多久到账?": "退款在 5 个工作日内到账。"}
return known.get(question, "我不知道。")
def exact(output, expected): # 按参数名拿到 input、output、expected、metadata
return output == expected
Eval(
"support-bot",
data=[
{"input": "退款多久到账?", "expected": "退款在 5 个工作日内到账。"},
{"input": "能改收货地址吗?", "expected": "可以,发货之前都能改。"},
],
task=answer,
scores=[exact, "builtin:factuality"],
)data是一组行(input,可选expected、metadata、tags、id)、任意可迭代对象,或者用dataset("<名字>")取平台上存的数据集。task是你们的代码。它拿到这一行的输入(第二个参数是这次的序号、行的键和期望值)。scores里可以混着放:你们写的函数在你们的进程里跑;平台评分器写编号,内置库的是builtin:<slug>,项目自己建的是sc_…。函数请起名,名字就是这个评分器的列名。
4. 运行
npx preworld eval # TypeScript:这个目录下所有 *.eval.ts / *.eval.js
preworld eval # Python:这个目录下所有 eval_*.py / *_eval.py平台用它的评分器(这里是 builtin:factuality)给这些行打分时,它会等着,打完再打印摘要和链接(PREWORLD_LANG=zh 时是中文):
preworld: 平台评分器还在打分:打完再读发布门槛…
Preworld · support-bot
实验:support-bot · 4f2c9e1 (exp_…)
2 行 · 2 次 · 任务出错 0 · 9.4 秒
评分器 平均
exact 50%
builtin:factuality 100%
发布门槛:通过 ✓
– 相对基线回归 ≤ 0 行 (实得 no baseline) · 仅提示
https://<平台地址>/p/prj_…/experiments/exp_…这条规则是项目默认的发布门槛:和基线比,不能有哪一行变差,只提示。第一次跑还没有基线,所以这条不适用(–)。打开链接:实验里有每一行的输入、输出和分数,和终端里一样。改完再跑一次,第二个实验会和第一个逐行比较:它的基线是项目设的基线,没设就是同一个 Eval(Eval('support-bot', …) 里同一个名字)上一次跑完的实验,不管提交和时间是否一样。
--no-send 只在本地跑和评分,什么都不上传;--first 5 每个 Eval 只跑前 5 行。
5. 在 CI 里卡住
这条命令在发布门槛通过或没设规则时退出码是 0,没通过是 1,读不到门槛是 2,出错是 3。在设置 →「发布门槛」里定规则(比如「factuality 平均 ≥ 0.8」),然后在 CI 里跑同样的命令:没过门槛的版本这一步就失败。平台评分器给每一行都打完分才读发布门槛,所以按平台评分器定的规则不会因为分数还没出来就判没过。10 分钟内没打完(PREWORLD_GATE_WAIT,单位分钟),就不读门槛,退出码是 2。在 PR 上评论表格的 GitHub Action 见 CI 与 GitHub Action。
记录任务里的调用
包装一下模型客户端,每一行就能连到它在日志里的追踪:
import OpenAI from 'openai';
import { wrapOpenAI, traced } from '@preworld/sdk';
const openai = wrapOpenAI(new OpenAI()); // chat、responses 和 embeddings 调用都记成跨度
const search = traced(async (q: string) => db.search(q), { name: 'search', kind: 'retrieval' });Python 用 wrap_openai(OpenAI())、wrap_anthropic(Anthropic()) 和 @traced。所有选项见 SDK 参考。