Skip to content

SDK:TypeScript、Python 和 preworld eval ​

开发中

SDK 这种接法正在开发,暂时不能在平台上选用(见 SDK 快速开始)。已经用它接好的项目照常可用,这里的说明照旧适用。

SDK 在你们自己的进程里跑评测,结果作为实验传上来,并用 OpenTelemetry 记录模型调用。TypeScript:@preworld/sdk(Node 20 及以上,ESM 和 CommonJS)。Python:preworld(Python 3.10 及以上)。两者都不在 npm 和 PyPI 上:平台自己在 /downloads/ 提供,每次发版一个版本(sdk.json 写着当前的文件名),目标 →「SDK」卡片给出安装命令,见从平台装 SDK。一步一步的做法见 SDK 快速开始。

配置 ​

变量意思
PREWORLD_API_KEY有上传权限(「允许上传日志与实验」)的项目令牌 pj_…
PREWORLD_PROJECT项目编号 prj_…
PREWORLD_BASE_URL平台地址(默认 https://review.nice2.io)
PREWORLD_ENVIRONMENTdev、staging、prod 或 ci;设了 CI 时默认 ci。别的值会提醒一次并忽略。
PREWORLD_LANGzh 让摘要用中文(没设时看 LC_ALL、LANG)
PREWORLD_GATE_WAITEval 等平台评分器打完分、再读发布门槛,最多等几分钟(默认 10;0 只读一次)
OTEL_SERVICE_NAME追踪的 service.name

也可以在代码里设:init({ project, apiKey?, baseUrl?, environment? })(TypeScript)或 preworld.init(project=..., api_key=None, base_url=None, environment=None)(Python),优先于环境变量。SDK 从不读 .env 文件。git 信息来自 GITHUB_SHA、GITHUB_HEAD_REF、GITHUB_REF_NAME 和 GITHUB_REF(PR 编号从 refs/pull/N/… 里取),没有就问 git。

Eval ​

ts
Eval<I, O, E>(name: string, options: {
  data: Row[] | Iterable<Row> | AsyncIterable<Row> | Dataset | (() => …),   // Row = { input, expected?, metadata?, tags?, id? }
  task: (input: I, ctx: { expected?, metadata?, tags, trial, rowKey, experimentId?, span? }) => O | Promise<O>,
  scores?: Array<ScorerFn | string>,
  metadata?: Record<string, unknown>,
  trialCount?: number,        // 默认 1
  maxConcurrency?: number,    // 默认 10
  experimentName?: string,    // 默认 "<name> · <提交号前 7 位>",没有 git 时 "<name> · YYYY-MM-DD HH:MM"
  baseline?: string,          // exp_…;默认:项目的基线,没有就是这个 Eval(按名字)上一次跑完的实验
}): Promise<EvalResult>
python
Eval(name, *, data, task, scores=(), metadata=None, trial_count=1, max_concurrency=10,
     experiment_name=None, baseline=None) -> EvalResult
await EvalAsync(...)          # 一样的参数,在已经运行的事件循环里用
  • data:一组行、任意(同步或异步)可迭代对象、返回它们的函数,或用 dataset("<名字或 ds_…>", { version }) / dataset(name, version=None) 读平台上的数据集(当前的行,或那一版)。平台数据集的行只把 input、expected、metadata 和 tags 交给任务。
  • task:你们的代码。Python 的函数有第二个参数时传入上下文;普通函数在工作线程上跑,async 的在事件循环上跑。
  • scores:本地评分函数,用 {input, output, expected, metadata} 调用(Python 按参数名传),返回 0 到 1 的数、布尔值、null/None,或 {name?, score, metadata?};以及按编号写的平台评分器,builtin:<slug> 或 sc_…(可以带 @版本),上传后在平台上打分。评分器的列名是函数名(没名字的是 score_N),存作 local:<名字>,门槛规则也这样写它;metadata.rationale 和 metadata.label 会保留。
  • 未判定:任务抛错的行记 task_error;评分函数抛错记 scorer_error,什么都没返回记 skipped,不在 0–1 之间记 bad_score。一行的分数是判定了的那几次的平均。
  • 行的键:平台数据集的行是 <数据集行编号>#normal,否则是行的 id,再没有就是输入的哈希;重复的加 #2、#3。同样的输入在两种 SDK 里得到同样的键,所以多次运行之间能和基线逐行对上。

Eval 做的事:建实验(POST /v2/projects/:p/experiments),按最多 100 行或 4 MB 一批追加行(POST /v2/experiments/:id/rows,每批有自己的幂等键),结束实验,等发布门槛定下来——平台评分器(builtin:…、sc_…)在结束之后才给这些行打分,门槛用它的长轮询读(GET /v2/experiments/:id/gate?wait=60,没定就再问,最多 PREWORLD_GATE_WAIT 分钟)——读基线的行来比较,打印摘要并返回结果。摘要里平台评分器的数字就是平台的,和实验页一样;到时没打完,就仍显示「待平台评分」,不读门槛(退出码 2),不会判没通过。遇到网络错误和 408、425、429、5xx 会重试(最多 5 次,Retry-After 最多等 60 秒;每次请求 60 秒超时)。平台拒收某一批时,剩下的行不再跑。

摘要:

text
Preworld · support-bot
  实验:support-bot · 4f2c9e1 (exp_…)
  20 行 · 20 次 · 任务出错 0 · 14.2 秒
  评分器                 平均       基线       变化            改善       回归
  exact               85%      80%      +5.0 个百分点   2        1
  builtin:factuality  90%      85%      +5.0 个百分点   1        0
  发布门槛:通过 ✓
    ✓ local:exact mean >= 0.8 (实得 0.85)
  https://<平台地址>/p/prj_…/experiments/exp_…

EvalResult 有 name、project、experiment({id, name, url})、baselineId、rows、trials、taskErrors、durationMs、scores(每个评分器:mean、decided、undecided、pending、direction、baseline_mean、delta、improved、regressed)、gate、gateError、error、exitCode 和 results(Python 用下划线命名)。Eval 从不退出进程:exitCode(results) / exit_code(results) 给出 preworld eval 会用的退出码。

追踪 ​

TypeScriptPython记录什么
traced(fn, { name?, kind? })@traced 或 @traced(name=None, kind="chain")一个函数:参数作为输入,返回值作为输出,抛错记为出错
wrapOpenAI(client)wrap_openai(client)chat.completions.create、responses.create、embeddings.create,包括流式(TypeScript 返回一个包装;Python 直接改传进去的客户端并返回它)
wrapAnthropic(client)wrap_anthropic(client)messages.create,包括 stream: true
aiSdkTelemetry({ functionId?, recordInputs?, recordOutputs? })—Vercel AI SDK 5、6、7:experimental_telemetry: aiSdkTelemetry()
currentSpan()、flush()、shutdownTracing()current_span()、flush()、shutdown()短命进程退出前把没发的发出去(Eval 和命令行会自己做)

种类:agent、llm、tool、retrieval、embedding、chain(默认)、other。跨度带 OpenTelemetry GenAI 属性(gen_ai.request.model、gen_ai.usage.*、gen_ai.input.messages、gen_ai.output.messages……),以 OTLP/HTTP protobuf 发到 <平台地址>/otel/v1/traces,带上令牌和 x-preworld-environment。SDK 用自己的 tracer,从不替换全局的 OpenTelemetry provider。在 Eval 里,每个跨度都带 preworld.experiment_id、preworld.row_key 和 preworld.trial,每一行都能连到它的追踪。没有 API key 或用了 --no-send 时不记录追踪。

命令行:preworld eval ​

bash
npx preworld eval [文件或目录…] [--no-send] [--json] [--first N]     # TypeScript
preworld eval [文件或目录…] [--no-send] [--json] [--first N]         # Python(或 python -m preworld eval)
文件TypeScript:*.eval.ts、.mts、.cts、.js、.mjs、.cjs(TypeScript 要装 tsx)。Python:eval_*.py 和 *_eval.py。默认在当前目录下找;跳过 node_modules、__pycache__、虚拟环境和点开头的目录。
--no-send只在本地跑和打分;什么都不上传,也不需要令牌。
--jsonJSON 打到标准输出,摘要打到标准错误:{version: 1, exit_code, evals: [{file, name, project, experiment, baseline_id, rows, trials, task_errors, duration_ms, scores, gate, gate_error, error, exit_code}]}。
--first N每个 Eval 只跑前 N 行。

退出码:0 发布门槛通过或没设规则 · 1 发布门槛没通过 · 2 读不到门槛(平台评分器在 PREWORLD_GATE_WAIT 内没打完,或平台没回答) · 3 出错(没找到文件、某个文件出错、参数不对)。有几个 Eval 时:有一个 3 就是 3,否则有 1 就是 1,否则有 2 就是 2。

pytest ​

Python 包同时是一个 pytest 插件。标了 @pytest.mark.preworld(可以带 tags=[…])的测试成为同一个实验的行,按节点编号做键;输入是测试的参数(没有就是测试名)。

python
import pytest
from preworld.pytest_plugin import check, log_outputs

@pytest.mark.preworld(tags=["math"])
@pytest.mark.parametrize("q,expected", [("2+2", "4")])
def test_answer(q, expected):
    out = agent(q)
    log_outputs(out)
    check("short", len(out) < 100)     # 一条断言,同时记成它自己的分数
    assert out == expected
  • 每个测试的 pass 分是 1 或 0,没过的断言写进理由。check(name, condition, message=None)、log_score(name, value)、log_inputs、log_outputs 和 log_expected 往这一行里补充内容。跳过的和 xfail 的测试不算行。
  • 设了 PREWORLD_API_KEY 和 PREWORLD_PROJECT 才上传。--preworld-no-send 只在本地;--preworld-experiment 名字 给实验起名(默认 <目录名> · <提交号前 7 位>)。
  • 结束时实验被结束,读门槛(定下来之后)和基线,打印一段「preworld」。pytest 自己的退出码从不改变。

下一步 ​

本站的法律文件都是草稿,待律师审阅,尚未生效。