OpenRouter 集成与实践

OpenRouter 集成与实践

Ori Eval

3 分钟阅读

Ori Eval

通过在真实提示词上测试你的智能体,为项目找出最合适的模型;每次运行使用一套测试框架和一个模型

Ori Eval(评测)会告诉你哪个模型最适合你的项目,并给出说明原因的分数。用日常语言提出你的问题即可。你的编程智能体随后会在项目中查找测试材料、编写评测、运行你想比较的模型,并给出结果。评测是一个 *.eval.ts 文件。若需要更多控制,可以自己编写该文件,但不必从这一步开始。

从你的编程智能体开始

你不必自己安装或运行 Ori。把以下指令交给你的编程智能体:

运行 curl -fsSL https://openrouter.ai/skills/spawn-ori-eval,并按照其输出中的说明开始使用

spawn-ori-eval 技能会逐步指导智能体。智能体会安装 Ori,并确认你已登录。随后它会带着你的请求启动 ori code -p。运行过程中,Ori 的 create-eval 技能会提出决定评测如何设计的问题:要测试程序的哪一部分、该部分必须做好什么、你是否有真实数据,以及费用上限。Ori 随后会编写评测、运行模型,并给出推荐。

spawn-ori-eval 技能在临时目录中完成工作,不会把评测文件放进你的项目。 若希望把评测文件留在项目中,请使用下文的手动步骤。

为何用 Ori 运行评测

评测中最困难的部分是稳定的测试台。Ori 是一套测试框架,而不仅仅是一项技能。Ori 会为一次运行解析出一套测试框架和一个模型,并在该次运行的每项测试中保持不变。提示词无法改动它们。因此,同一批评测文件的两次运行会使用相同的配置。

Ori 通过 OpenRouter 发送请求。因此一次对比可以包含来自多家提供商的模型。仅针对某一厂商的测试框架,则只能测试该厂商的模型。

手动执行这些步骤

以下步骤可手工完成同样的工作。需要更多控制,或想查看智能体写了什么时,使用这些步骤。

开始之前

安装命令行界面(CLI):

curl -fsSL https://openrouter.ai/labs/ori/install.sh | bash

登录一次:

ori login

评测会向真实模型发送请求,因此 Ori 必须已登录。ori login 命令会通过浏览器为你登录,并保存凭证供后续运行使用。

Ori 使用 Bun 运行评测文件。若尚未安装 Bun,ori eval 会征求你的同意再安装。在非交互式终端中,或当 CI 为 true 时,ori eval 不会询问,而是停止并告诉你如何安装 Bun。

你的项目不必是 TypeScript 项目,你也不需要安装 TypeScript。评测文件测试的是智能体,而不是你的代码。

从一个问题开始

进入项目目录,然后让 Ori 创建评测:

cd my-project
ori code -p "我的客服智能体最适合用哪个模型?"
# 较长的请求:
ori code --prompt-file /tmp/ori-task.txt

ori code -p 命令不需要终端。它将输出写入 stdout,并在提示词完成后退出。较长的请求请使用 --prompt-file。不能同时使用 -p--prompt-file。Ori 也会拒绝未带任何标志的提示词。

你不必了解评测方法或模型名称。智能体会完成这些步骤:

  1. 在项目中查找测试材料。 它会查找提示词、工具定义、数据文件(.jsonl.csv、聊天记录)以及已知正确答案。其他语言的测试也可以提供测试用例,但评测文件始终是 TypeScript。
  2. 询问什么最重要。 例如「更看重准确率,还是速度和成本?」,仅在答案会改变评测时才会提问。
  3. 编写评测,然后运行。 它会生成 evals/<feature>/<name>.eval.ts。它会从 OpenRouter 的实时目录中,按你的最高价格筛选候选模型,然后运行它们。
  4. 推荐一个模型。 它会给出支撑该推荐的分数、耗时和费用。

评测文件会作为普通代码留在你的项目中。当某家提供商发布新模型、你提高标准的严格程度,或在持续集成(CI)中,都可以再次运行它们。

评测文件

想自己编写评测,或想查看智能体写了什么时,阅读本节。评测文件看起来就像普通的 bun test 文件:

evals/support/recommends.eval.ts
import { test } from 'bun:test';
import { setupAgent } from 'ori/eval';

const agent = setupAgent();

test('recommends restaurants using the search tool', async () => {
  const run = await agent.run('里斯本晚餐该去哪吃?');

  run.tool('search').toBeCalled();
  run.tool('delete_file').toNotBeCalled();
  run.toComplete();
  run.toCostAtMost(0.01);
  run.toFinishWithin(30_000);
});

不带参数的 setupAgent() 会返回工作区解析出的测试框架和模型。这就是你已经在运行的智能体。然后使用以下命令之一:

ori eval
ori eval --report eval-report.md
ori eval --baseline best

Ori 会在当前目录下查找每个 *.eval.ts 文件。它不会查看 node_modules.ori.git。随后 Ori 会启动临时运行时,并把这些文件交给 bun testori eval 的退出码就是 bun test 的退出码。因此评测失败会使你的 CI 任务失败。

create-eval 技能会把新的评测文件放在顶层 evals/<feature>/ 目录中。Ori 也能在其他目录中找到评测文件。

使用 --baseline last|best|model:<slug> 可将本次运行与先前运行比较。Ori 将先前运行保存在 .ori/eval/history.jsonl。只有包含完全相同评测文件的运行之间才能比较。使用 --report <path> 可为其他人写出 Markdown 报告。

当对比中的某个模型在给出回答之前就停止时,报告会将该模型显示为 unmeasured。报告不会移除该模型,也不会将费用显示为零。

比较模型

从 OpenRouter 的实时目录获取候选模型,而不是自己手写一组 slug。设定最高价格,然后用每个模型运行同一份评测:

evals/support/model.eval.ts
import { test } from 'bun:test';
import { candidateModels, setupAgent } from 'ori/eval';

const candidates = await candidateModels({
  limit: 5,
  maxPromptPrice: 0.000005,
});

for (const model of candidates) {
  test(`handles a refund request on ${model}`, async () => {
    const run = await setupAgent({ model }).run(
      '一位顾客想为订单 #1234 申请退款。你会怎么做?',
    );
    run.tool('lookup_order').toBeCalled();
    run.toComplete();
  });
}

candidateModels 函数还可以按 maxCompletionPriceminContextLength、质量指数、必需参数、输入模态以及 excludeExpiring 进行筛选。如果目录未提供某项数值,该模型就无法通过相应限制。

在文件中点名某个具体模型时,使用 assertModelIsLive(slug)。若该模型离开实时目录,评测会以明确的错误信息失败。

为开放性回答打分

有些问题没有唯一正确答案。使用 LLM 评判器为回答打分。setupJudge() 会在自己的评分模型上创建独立的智能体。因此分数与你正在测试的模型无关。你也可以把自定义智能体传给 setupJudge(),以使用不同的评判器。

evals/support/quality.eval.ts
import { test } from 'bun:test';
import { setupAgent, setupJudge } from 'ori/eval';

const agent = setupAgent();
const judge = setupJudge({ minScore: 0.8 });

test('gives an accurate, grounded answer', async () => {
  const run = await agent.run('我们对数字商品的退款政策是什么?');
  await judge.autoEvals({
    criteria: '引用 14 天窗口期,并且不编造例外情况。',
    run,
  });
});

startingCriteria 对象包含可按常见维度编辑的评分细则:accuracycompletenessinstructionFollowingsafetystructuredOutputtoneAndVoice。将其中一项作为标准传给 judge.autoEvals

使用你自己的数据

来自用户的真实数据,比你编造的提示词更好。对聊天记录,或问答对,使用 test.each

import supportPairs from './support-pairs.json';

test.each(supportPairs)(
  'answers: $question',
  async ({ question, mustMention }) => {
    const run = await agent.run(question);
    run.toMention(mustMention);
    run.toComplete();
  },
);

在 CI 中运行评测

评测会向真实模型发送请求,因此会产生费用。把评测放在单独的任务中。由人工启动该任务,或按计划运行。不要把它放进常规的单元测试任务。

OPENROUTER_API_KEY 变量中为该任务提供 OpenRouter API 密钥。把密钥保存在仓库 Secret 中。有了该变量,Ori 在 CI 中就不需要 ori login

评测失败会返回非零退出码并使任务失败。若发布依赖此任务,表现变差的智能体会阻止发布。

你的常规测试任务可以检查评测文件是否存在:

ori eval --list --allow-no-key

该命令只查找评测文件。它不需要密钥,也不会调用任何模型。

GitHub Actions

Ori 在 CI 中需要 Bun。当 CI 为 true 时,ori eval 不会替你安装 Bun。

Ori 安装脚本会把 ori 二进制文件放到 $HOME/.local/bin。安装脚本对 PATH 的更改只作用于运行该脚本的 shell。把该目录加入 $GITHUB_PATH,以便后续步骤能找到 ori

.github/workflows/eval.yml
name: eval

on:
  workflow_dispatch:
  schedule:
    - cron: '0 9 1 * *'

jobs:
  eval:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v6
      - uses: oven-sh/setup-bun@v2
      - name: Install Ori
        run: |
          curl -fsSL https://openrouter.ai/labs/ori/install.sh | bash
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
      - name: Run the evals
        run: ori eval --report eval-report.md
        env:
          OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
      - name: Add the report to the job summary
        if: always()
        run: |
          if [ -f eval-report.md ]; then
            cat eval-report.md >> "$GITHUB_STEP_SUMMARY"
          fi

workflow_dispatch 事件允许人工启动该任务。schedule 事件每月运行一次评测。计划运行可以显示是否有新模型更适合你的项目。

已初始化的 Ori 工作区包含 package.json 和本地 ori 依赖。当工作区还没有生成的 .ori/sdk 包或其依赖时,ori eval 会在运行测试之前创建或安装它们。你不需要单独的 bun install 步骤。