OpenRouter 教程:如何在 CI 中用 LLM eval 门禁拦截 Pull Request
TL;DR · AI 摘要
OpenRouter æç¨ï¼å¦ä½å¨ CI ä¸ç¨ LLM eval é¨ç¦æ¦æª Pull Request · AIHOT OpenRouterï¼Announcements · 2026-1...
核心要点
- 主题聚焦:OpenRouter 教程:如何在 CI 中用 LLM eval 门禁拦截 Pull Reque
- 来源:AI HOT 精选,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
OpenRouter æç¨ï¼å¦ä½å¨ CI ä¸ç¨ LLM eval é¨ç¦æ¦æª Pull Request · AIHOT
OpenRouterï¼Announcements
·
2026-10-01 08:00
12 å°æ¶å
ç²¾é
AI è¯å
67
OpenRouter æç¨ï¼å¦ä½å¨ CI ä¸ç¨ LLM eval é¨ç¦æ¦æª Pull Request
How to Gate Pull Requests on LLM Evals in CI
AI 导读
OpenRouter å叿ç¨ï¼è®²è§£å¦ä½ç¨åºå®ç eval éå¨ CI ä¸é¨ç¦ pull requestï¼å½éè¿çä½äºé弿¶èæ¬ä»¥éé¶éåºç 黿¢åå¹¶ï¼åæ³ä¸åå æµè¯é¨ç¦ä¸è´ã
æ¨èçç±
åæç»åºäºä»èæ¬å° GitHub Actions ç宿´å¯å¤ç°æµç¨ï¼å¹¶å¼ºè°äºè·¯å¾è¿æ»¤é¡»æ¾ job å±ãéå¼åºå®æµçæéç¹ã
æ£æ · AI ç¿»è¯
卿¯æä»£ççç³»ç»æç¤ºä¸æ¹ä¸è¡ï¼å°±å¯è½åå¸ä¸ä¸ªåè¯å®¢æ·é款çªå£æ¯ 30 天ç代çï¼èä½ çæ¿çè§å®æ¯ 14 å¤©ãæ£å¸¸ç CI æµæ°´çº¿ä¸ä¼æ£æ¥æ¨¡å说äºä»ä¹ï¼æä»¥æå»ºéè¿ï¼ç¬¬ä¸ä¸ªçå°éè¯¯çæ¡ç人æ¯å®¢æ·ã
ç¨åºå®çè¯ä¼°éæ¥é¨æ§æå请æ±ï¼å°±åç¨å¤±è´¥çåå æµè¯æ¥é¨æ§ä¸æ ·ãä½ ææµè¯ç¨ä¾ä¿åå¨ä»åºä¸ï¼å¨æç¤ºæ´æ¹æ¶è¿è¡å®ä»¬ï¼å¹¶å¨å¤ªå¤å¤±è´¥æ¶é»æ¢åå¹¶ã
卿¬æåä¸ï¼ä½ ä¸ºæ¯æä»£çç¼åä¸ä¸ªè¯ä¼°éï¼å¹¶ç¨ä¸ä¸ªè°ç¨ OpenRouter çèæ¬æ¥è¯åãä½ æµé卿²¡æä»»ä½æ´æ¹çæ åµä¸å¤æ¬¡è¿è¡ä¹é´ç»æçæ³¢å¨ç¨åº¦ï¼ç¶åå°è¯¥èæ¬æ¥å ¥ GitHub Actions ä½ä¸ºå¿ éçç¶ææ£æ¥ã
ç®èè¨ä¹
- åºå®çè¯ä¼°éæ¯æäº¤å°ä»åºä¸çæµè¯ç¨ä¾å表ãå®ä» éè¿ç»è¿å®¡æ¥çæå请æ±è¿è¡æ´æ¹ã
- å¨ä½ä¸çº§å«è䏿¯å·¥ä½æµçº§å«è¿è¡è¿æ»¤ãGitHub å°å if æ¡ä»¶èè·³è¿çä½ä¸æ¥å为éè¿çæ£æ¥ï¼èå è·¯å¾è¿æ»¤å¨è·³è¿ç工使µä¼çä¸å¾ å¤ççå¿ éæ£æ¥å¹¶é»æ¢åå¹¶ã
- å½éè¿çä½äºé弿¶ï¼è¯ä¼°èæ¬ä»¥éé¶ç¶æéåºï¼è该éåºä»£ç æ£æ¯å¯¼è´ä½ä¸å¤±è´¥çåå ã
- éè¿å¯¹æªæ´æ¹åæ¯çéå¤è¿è¡æ¥æµééå¼ï¼è䏿¯éæ©ä¸ä¸ªä¸¥æ ¼çæ°åã
- temperature å seed ä» å¨å°å ¶åå¨ supported_parameters ä¸ç模å䏿叮å©ã使ç¨å¤æ°æç¥¨çéå¤éæ ·éç¨äºæææ¨¡åã
CI ä¸ç LLM è¯ä¼°æå³çä»ä¹
è¯ä¼°æ¯ä»£ççæµè¯ç¨ä¾ã宿ä¸ä¸ªè¾å ¥åä¸ä¸ªç¨äºå¤ææ¨¡åçæ¡æ¯å¦å¯æ¥åçè§åãåºå®çè¯ä¼°éæ¯æäº¤å°ä»åºä¸çè¿äºç¨ä¾çå表ï¼å¹¶ä¸ä» éè¿ç»è¿å®¡æ¥çæå请æ±è¿è¡æ´æ¹ãæµæ°´çº¿é¨åæ¯ä½ ç CI 设置ãå®å³å®ç¨ä¾ä½æ¶è¿è¡ï¼ä»¥åå½å¤ªå¤ç¨ä¾å¤±è´¥æ¶æå请æ±ä¼åçä»ä¹ã
å¤ææ¨¡åçæ¡æ¯å¦è¯å¥½æ¯ä¸ä¸ªåç¬çé®é¢ï¼ä¸ç§éæ©æ¯å°å ¶äº¤ç»å¦ä¸ä¸ªå å½è¯åè çæ¨¡åãè¿ç§ææ¯ç§°ä¸º LLM-as-a-judgeï¼æä»¬å¨ æä»¬ç LLM-as-a-judge æå ä¸ä»ç»äºå®ã æä»¬çå·¥å ·è°ç¨å¾ªç¯æå æ¶µçäºæå»ºä»£çæ¬èº«ãæ¬æåæ¯å ³äºå®ä»¬ä¹é´çæµæ°´çº¿ã
å¨ä½ è½é¨æ§ä»»ä½ä¸è¥¿ä¹åéè¦ä»ä¹
å¨é¨æ§è½åè¯ä½ 任使ç¨ä¿¡æ¯ä¹åï¼éè¦åå¤å¥½ä¸ä»¶äºã
- ä¸ä¸ªåºå®çãçæ¬åçè¯ä¼°éã Anthropic ç代çè¯ä¼°æåå»ºè®®å° ä»çå®å¤±è´¥ä¸æåç 20 å° 50 个ç®åä»»å¡ ä½ä¸ºèµ·å§éãæä»¬è¿é使ç¨ä¸ä¸ªï¼ä»¥ä¾¿ç¤ºä¾ä¿æç®çã
- ä¸ç§è¯åæ¹æ³åä¸ä¸ªéå¼ã è¯åæ¹æ³å°ä¸ä¸ªçæ¡è½¬åä¸ºä½ å¯ä»¥è®¡æ°çéè¿æå¤±è´¥ãéå¼éç¨äºæ´ä¸ªè¿è¡ãæ¬æå使ç¨å符串æè¨ãè¯åæ åæè¯å¤æ¨¡åä¹å¯ä»¥ã
- è¿è¡è¶³å¤å¯éå¤ä»¥å¼å¾ä¿¡èµã 鍿§åºåºäºåå½è䏿¯åªå£°è¿è¡é»æ¢ã使ç¨å¤æ°æç¥¨çéå¤éæ ·éç¨äºæææ¨¡åã temperature å seed ä» å¨ååºå®ä»¬ç模å䏿叮å©ï¼å æ¤å¨ä¾èµå®ä»¬ä¹åè¯·æ£æ¥ 模åç supported_parameters ã
ä½ è¿éè¦ä¸ä¸ªå¯¼åºä¸º OPENROUTER_API_KEY ç OpenRouter API å¯é¥ ãNode 20 ææ´æ°çæ¬ï¼ä»¥å jq ã
å忥æå»ºé¨æ§
宿è¿åæ¥åï¼è§¦åä½ çæç¤ºçæå请æ±ä¼èªå¨è¿è¡ä½ çè¯ä¼°éï¼å¹¶ä¸å¦æåæ°ä¸éåæ æ³åå¹¶ã
- ä» å¨å¯è½ç ´åä½ ç代ççæ´æ¹ä¸è§¦åè¯ä¼°ã
- å°è¯ä¼°éæ¾å¨ä»åºä¸ï¼ä¸å ¶æµè¯çæç¤ºæ¾å¨ä¸èµ·ã
- ç¼å CI ä½ä¸è¿è¡çèæ¬ã
- æµéå³å®åå¹¶æ¯å¦è¢«é»æ¢çéå¼ã
第 1 æ¥ï¼ä» å¨ç¸å ³åæ´æ¶è§¦å
å½æç¤ºè¯ãagent é»è¾ãå·¥å · schemaãè¯ä¼°éãè¯ä¼°èæ¬æå·¥ä½æµæ¬èº«åçååæ¶è¿è¡è¯ä¼°ãæå两项å¾å®¹æè¢«éæ¼ã妿å®ä»¬ä¸å¨è¿æ»¤æ¡ä»¶ä¸ï¼ä¸ä¸ªç ´åè¯åé»è¾æä¿®æ¹é¨ç¦ç pull request å°±ä¼è·³è¿è¯ä¼°å¹¶å¨æªç»æµè¯çæ åµä¸åå¹¶ã
ä½ å¯ä»¥ç¨ä¸¤ä¸ª job æ¥å®ç°ã第ä¸ä¸ª job å§ç»è¿è¡ã宿£æ¥ pull request æ´æ¹çæä»¶æ¯å¦å¹é ä¸ç»è·¯å¾ï¼å¹¶æ ¹æ®æ¯å¦æå¹é è¾åº true æ false ã第äºä¸ª job è¿è¡è¯ä¼°ï¼ä» å½ç¬¬ä¸ä¸ª job è¿å true æ¶æä¼å¯å¨ã
以工使µå¤´é¨å第ä¸ä¸ª job å¼å§ .github/workflows/eval-gate.yml ã agent: ä¸ååºçè·¯å¾æ¯ä½ è¦ä¸ºèªå·±çä»åºä¿®æ¹çè·¯å¾ã
name
:
eval-gate
on
:
pull_request
concurrency
:
group
:
eval-gate-${{ github.ref }}
cancel-in-progress
:
true
jobs
:
changes
:
runs-on
:
ubuntu-latest
outputs
:
agent
:
${{ steps.filter.outputs.agent }}
steps
:
-
uses
:
actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
# v7.0.1
-
uses
:
dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d
# v4.0.3
id
:
filter
with
:
filters
:
|
agent:
- '.github/workflows/eval-gate.yml'
- 'prompts/**'
- 'agents/**'
- 'tools/**/schema.json'
- 'eval-sets/**'
- 'scripts/run-evals.mjs'concurrency é å cancel-in-progress ä¼å¨æäººå次æ¨éå°åä¸åæ¯æ¶åæ¶ä¸ä¸æ¬¡è¿è¡ï¼å æ¤ä¸ä¸ªè¿ç»å¤æ¬¡æ¨éç pull request ä¸ä¼ä¸ºæ¯æ¬¡æ¨éé½ä»åºä¸æ¬¡è¯ä¼°è¿è¡ç代价ã
è¿éæä¸¤ä»¶äºå¯è½åºéã
ç¬¬ä¸æ¯ç»¿è²ç对å¾èåæ²¡æè¯ä¼°ãå½ç¬¬ä¸ä¸ª job è¿å false æ¶ï¼GitHub ä¼è·³è¿è¯ä¼° jobï¼è 被跳è¿ç job 伿»¡è¶³å¿ éçç¶ææ£æ¥ ãè¿æ£æ¯æ å ³ç pull request å¾ä»¥åå¹¶çåå ãè¿ä¹æå³çä¸ä¸ªæ¼éç glob ä¼å¨æ²¡æè¿è¡ä»»ä½ç¨ä¾çæ åµä¸éè¿æ£æ¥ï¼èä¸ä¸ªç´æ¥å¤±è´¥ç changes job ä¹æåæ ·çææï¼å 为 GitHub ä¼è·³è¿å ¶ needs ä¾èµå¤±è´¥ç jobã第 3 æ¥éè¿å° changes ä¹è®¾ä¸ºå¿ éæ£æ¥æ¥å µä½ç¬¬äºä¸ªæ¼æ´ã对äºç¬¬ä¸ä¸ªæ¼æ´ï¼å¨æç¤ºè¯åæ´é¦æ¬¡éè¿æ¶æå¼è¿è¡æ¥å¿ï¼ç¡®è®¤è¯ä¼°ç¡®å®è¿è¡äºã
ç¬¬äºæ¯è¿æ»¤æ¡ä»¶æ¾å¨åªéãä¸è¦æå®ä¸ç§»å°å·¥ä½æµçº§å«ä½ä¸º on.pull_request.paths ãGitHub å ³äº è·³è¿å·¥ä½æµè¿è¡ çææ¡£è¯´ï¼å½å·¥ä½æµå è·¯å¾è¿æ»¤èè¢«è·³è¿æ¶ï¼âä¸è¯¥å·¥ä½æµå ³èçæ£æ¥å°ä¿æâPendingâç¶æâï¼èè¦æ±è¿äºæ£æ¥ç pull request ä¼è¢«é»æ¢åå¹¶ã
第 2 æ¥ï¼å°è¯ä¼°éæ¾å ¥ä»åº
å°è¯ä¼°éä¸å®ææµè¯çæç¤ºè¯æ¾å¨åä¸ä¸ªä»åºä¸ãå½æäººç¼è¾æç¤ºè¯æ¶ï¼é对å®çæµè¯ä¼å¨åä¸ä¸ª pull request ä¸åæ´ï¼ä¸ä½å®¡æ¥è è½åæ¶çå°ä¸¤è ã
å个æä»¶å¹¶ææ¾ç½®ã
your-repo/
.github/workflows/eval-gate.yml -> the workflow from Step 1
prompts/support-agent.md -> the system prompt
eval-sets/support-agent.json -> the cases that test it
scripts/run-evals.mjs -> the script from Step 3å°è¯ä¼°éä¿å为 eval-sets/support-agent.json ãæ¯ä¸ªç¨ä¾å å«ä¸ä¸ªè¾å ¥ãçæ¡å¿ é¡»å å«çå符串ï¼ä»¥åçæ¡ä¸å¾å å«çå符串ã mustMention ä¸çæ¡ç®ä¹å¯ä»¥æ¯ä¸ä¸ªå表ï¼å¦ç¬¬ä¸ä¸ªç¨ä¾é£æ ·ï¼æ¤æ¶çæ¡åªéå å«å ¶ä¸çä¸ä¸ªå符串å³å¯ã彿¨¡åå¨ä½ ææâhumanâçå°æ¹åæâa personâæâour teamâæ¶ï¼å个åé¢å符串就ä¼å¤±è´¥ï¼å æ¤å¨æå¤ç§æªè¾å¯æ¥åçå°æ¹è¯·ä½¿ç¨å表ã
[
{
"id"
:
"refund-window"
,
"input"
:
"How long do I have to request a refund on a digital download?"
,
"mustMention"
: [
"14 days"
],
"mustNotMention"
: [
"30 days"
]
},
{
"id"
:
"refund-exception"
,
"input"
:
"I bought a download 60 days ago. Can I still get a refund?"
,
"mustMention"
: [
"14 days"
],
"mustNotMention"
: [
"yes, you can"
]
},
{
"id"
:
"escalation"
,
"input"
:
"Your product deleted my files and I want a lawyer."
,
"mustMention"
: [[
"human"
,
"person"
,
"our team"
,
"specialist"
]],
"mustNotMention"
: [
"14 days"
]
}
]è¿äºç¨ä¾ææµè¯çæç¤ºè¯å°±å¨å®æè¾¹ï¼ä½äº prompts/support-agent.md ä¸ã
You are a support agent for a digital downloads store.
The refund window is 14 days from purchase. There are no exceptions to it.
If a customer threatens legal action or reports data loss, hand off to a human
and do not quote the refund policy.
Answer in at most three sentences.ä»ä¸ä¸ªç¨ä¾å¼å§ãæ¯å½ agent å¨ç产ç¯å¢ä¸åºéæ¶ï¼å°±æ·»å ä¸ä¸ªã
å° prompts/ å eval-sets/ é½ç½®äº CODEOWNERS è§åä¹ä¸ï¼å¹¶å¨åæ¯ä¿æ¤è§åä¸å¼å¯âRequire review from Code Ownersâãå¦åï¼ç»è¿å¤±è´¥é¨ç¦çæå¿«æ¹æ³å°±æ¯æ¾å®½é£ä¸ªæè·äºåå½çæµè¯ãä» æä¸ä¸ª CODEOWNERS æä»¶åªä¼è¯·æ±å®¡æ¥ï¼å¹¶ä¸ä¼é»æ¢åå¹¶ã
第 3 æ¥ï¼ç¼å CI job è¿è¡çèæ¬
该 job è¿è¡ä¸ä¸ªèæ¬ï¼å½éè¿çä½äºé弿¶ä»¥éé¶ç¶æéåºãè¿ä¸ªéåºç å°±æ¯ CI 黿¢åå¹¶æéçå ¨é¨ã
è¯¥èæ¬å è½½è¯ä¼°éï¼å°æ¯ä¸ªç¨ä¾åéç»æ¨¡åï¼æ£æ¥çæ¡ï¼å¹¶ä»¥ä¸ä¸ªåç¥ CI åçäºä»ä¹çéåºç éåºãå®åªä½¿ç¨ Node å 置模åï¼å æ¤æ éå®è£ ä»»ä½ä¸è¥¿ãå°å ¶ä¿å为 scripts/run-evals.mjs ã
import
{ readFileSync }
from
"node:fs"
;
import
{ parseArgs }
from
"node:util"
;
const
{
values
}
=
parseArgs
({
options: {
set: { type:
"string"
, default:
"eval-sets/support-agent.json"
},
prompt: { type:
"string"
, default:
"prompts/support-agent.md"
},
model: { type:
"string"
, default:
"anthropic/claude-sonnet-5"
},
threshold: { type:
"string"
, default:
"0.9"
},
samples: { type:
"string"
, default:
"3"
},
concurrency: { type:
"string"
, default:
"8"
},
},
});
const
systemPrompt
=
readFileSync
(values.prompt,
"utf8"
);
const
cases
=
JSON
.
parse
(
readFileSync
(values.set,
"utf8"
));
const
threshold
=
Number
(values.threshold);
const
samples
=
Number
(values.samples);
const
concurrency
=
Number
(values.concurrency);
// Exit 2 for anything that stops the eval from running, so the job can tell
// "the agent got worse" apart from "the eval could not run".
function
abort
(
message
) {
console.
error
(
`::error::eval could not run: ${
message
}`
);
process.
exit
(
2
);
}
if
(
!
Array.
isArray
(cases)
||
cases.
length
===
0
)
abort
(
`${
values
.
set
} has no cases`
);
if
(
!
(threshold
>
0
&&
threshold
<=
1
))
abort
(
`--threshold must be greater than 0 and at most 1, got "${
values
.
threshold
}"`
);
if
(
!
Number.
isInteger
(samples)
||
samples
<
1
||
samples
%
2
===
0
)
abort
(
`--samples must be a positive odd integer, got ${
values
.
samples
}`
);
if
(
!
Number.
isInteger
(concurrency)
||
concurrency
<
1
)
abort
(
`--concurrency must be a positive integer, got ${
values
.
concurrency
}`
);
class
EvalDidNotRun
extends
Error
{}
async
function
callModel
(
input
) {
const
res
=
await
fetch
(
"https://openrouter.ai/api/v1/chat/completions"
, {
method:
"POST"
,
signal: AbortSignal.
timeout
(
60_000
),
headers: {
Authorization:
`Bearer ${
process
.
env
.
OPENROUTER_API_KEY
}`
,
"Content-Type"
:
"application/json"
,
},
body:
JSON
.
stringify
({
model: values.model,
// Route to one provider only. A different provider serving the same slug
// between runs would look like a prompt regression.
provider: { order: [
"anthropic"
], allow_fallbacks:
false
},
messages: [
{ role:
"system"
, content: systemPrompt },
{ role:
"user"
, content: input },
],
}),
});
if
(
!
res.ok)
throw
new
EvalDidNotRun
(
`${
res
.
status
} ${
await
res
.
text
()
}`
);
const
body
=
await
res.
json
();
return
{ text: body.choices?.[
0
]?.message?.content
??
""
, cost: body.usage?.cost
??
0
};
}
async
function
run
(
input
) {
for
(
let
attempt
=
1
; attempt
<=
3
; attempt
++
) {
try
{
return
await
callModel
(input);
}
catch
(err) {
if
(attempt
===
3
)
throw
new
EvalDidNotRun
(err.message);
await
new
Promise
((
resolve
)
=>
setTimeout
(resolve, attempt
*
2000
));
}
}
}
// A requirement is either a string, or an array meaning "any one of these".
function
matches
(
answer
,
requirement
) {
const
options
=
Array.
isArray
(requirement)
?
requirement
:
[requirement];
return
options.
some
((
option
)
=>
answer.
includes
(option.
toLowerCase
()));
}
function
score
(
text
,
testCase
) {
const
answer
=
text.
toLowerCase
();
const
must
=
testCase.mustMention
??
[];
const
mustNot
=
testCase.mustNotMention
??
[];
return
(
must.
every
((
r
)
=>
matches
(answer, r))
&&
!
mustNot.
some
((
r
)
=>
matches
(answer, r))
);
}
// One unit of work per sample, so the whole matrix runs with a fixed
// concurrency instead of one request at a time.
const
jobs
=
cases.
flatMap
((
testCase
)
=>
Array.
from
({ length: samples }, ()
=>
testCase),
);
const
results
=
new
Map
(cases.
map
((
c
)
=>
[c.id, []]));
let
spend
=
0
;
let
cursor
=
0
;
async
function
worker
() {
while
(cursor
<
jobs.
length
) {
const
testCase
=
jobs[cursor
++
];
const
{
text
,
cost
}
=
await
run
(testCase.input);
spend
+=
cost;
results.
get
(testCase.id).
push
(
score
(text, testCase));
}
}
const
started
=
Date.
now
();
try
{
await
Promise
.
all
(Array.
from
({ length: Math.
min
(concurrency, jobs.
length
) }, worker));
}
catch
(err) {
// A provider timeout is not a quality regression.
abort
(err.message);
}
let
passed
=
0
;
for
(
const
testCase
of
cases) {
const
verdicts
=
results.
get
(testCase.id);
const
majority
=
verdicts.
filter
(Boolean).
length
>
samples
/
2
;
if
(majority) passed
++
;
const
trace
=
verdicts.
map
((
v
)
=>
(v
?
"."
:
"x"
)).
join
(
""
);
console.
log
(
`${
majority
?
"PASS"
:
"FAIL"} ${
testCase
.
id
} ${
trace
}`
);
}
const
rate
=
passed
/
cases.
length
;
const
seconds
=
((Date.
now
()
-
started)
/
1000
).
toFixed
(
1
);
console.
log
(
`
\n
pass rate ${
rate
.
toFixed
(
2
)
} against threshold ${
threshold
}`
);
console.
log
(
`${
jobs
.
length
} calls in ${
seconds
}s, cost $${
spend
.
toFixed
(
4
)
} on ${
values
.
model
}`
);
if
(rate
<
threshold) {
console.
error
(
`::error::eval gate failed: ${
passed
}/${
cases
.
length
} cases passed`
);
process.
exit
(
1
);
}è¯¥èæ¬é¤äºè°ç¨æ¨¡åä¹å¤è¿åä¸ä»¶äºã
å½è¯ä¼°é为空ææä¸ªéé¡¹æ ¼å¼é误æ¶ï¼å®ä¼æç»è¿è¡ï¼å¹¶å¨åé请æ±å以éåºç 2 éåºãå¦ææ²¡æè¿äºæ£æ¥ï¼ä¸ä¸ªè¢«æå¤æ¿æ¢ä¸º [] çè¯ä¼°éä¼äº§ç NaN çéè¿çï¼è NaN < threshold 为åï¼å æ¤æ²¡æä»»ä½è¯ä¼°è½éè¿é¨ç¦ã --threshold 为 90% æè´ç --samples ä¹ä¼ä»¥åæ ·çæ¹å¼éè¿ï¼è空ç --threshold ä¼åæ 0 ï¼æ²¡æä»»ä½éè¿çè½ä½äºå®ï¼æä»¥è¯¥æ£æ¥ä¹ä¼æç» 0 ã
å®å¯¹æ¯ä¸ªç¨ä¾è¿è¡å¤æ¬¡ï¼æ¯ä¸æ¬¡è¿è¡é½æ¯ä¸ä¸ªæ ·æ¬ãå®åæææ ·æ¬ä¸ç夿°è£å³ï¼å 为ç¸åçæç¤ºå¹¶ä¸æ»æ¯äº§çç¸åççæ¡ã
å®è¿ä¼å°æ¯ä¸ªè¯·æ±è·¯ç±å°åä¸ä¸ªæä¾åãå anthropic/claude-sonnet-5 è¿æ ·ç模å slug ä¼éè¿ OpenRouter ç±å¤ä¸ªæä¾åæä¾æå¡ã卿°åæ¬ææ¶ï¼å ¶ç«¯ç¹å æ¬ AnthropicãAmazon BedrockãAzure å Googleãå¦ææ²¡æè·¯ç±å好ï¼åä¸è¯ä¼°ç两次è¿è¡å¯è½ä¼å°è¾¾ä¸¤ä¸ªä¸åçæä¾åï¼èå®ä»¬çæ¡ä¹é´çå·®å¼çèµ·æ¥å°±åæ¯æç¤ºåå½ã provider.order æ¯æä¾å slug çä¼å 级åè¡¨ï¼ allow_fallbacks: false åè¯æä»¬è¦å°è¯è¯¥å表ä¹å¤ç任使ä¾åã妿ååºçæä¾å失败ï¼è¯·æ±å°±ä¼å¤±è´¥ï¼è䏿¯è½¬ç§»å°å¤ç¨æä¾åï¼èæ¬ä¼ä»¥éåºç 2 éåºã æä¾åéæ©ææ¡£ æ¶µçäºè¿ä¸¤ä¸ªå段ã该åºå®ç»å®å°ä½ æå®ç模åãå¦æä½ å° --model æ´æ¹ä¸ºå ¶ä»ä¾åºåçæ¨¡åï¼ä¹è¦æ´æ¹ order çå¼ï¼å¦å没ææä¾åä¼å¹é ï¼æ¯ä¸ªè¯·æ±é½ä¼å¤±è´¥ã
æåä¸è¡æå°çææ¬æ¥èªæä»¬å¨æ¯ä¸ªéæµå¼ååºä¸å å«ç usage 对象ã ç¨éæ ¸ç®ææ¡£ æè¿°äºè¿äºå段ã
å 卿¬å°è¿è¡å®ã
export
OPENROUTER_API_KEY
=
"sk-or-..."
node
scripts/run-evals.mjs
--samples
3æ¯ä¸ªç¹æ¯ä¸ä¸ªéè¿çæ ·æ¬ï¼æ¯ä¸ª x æ¯ä¸ä¸ªå¤±è´¥çæ ·æ¬ï¼å æ¤ä½ å¯ä»¥çå°åªä¸ªç¨ä¾é´ææ§å¤±è´¥ï¼èä¸ä» ä» æ¯æç»çéè¿çã
PASS refund-window ...
PASS refund-exception ...
PASS escalation ...
pass rate 1.00 against threshold 0.9
9 calls in <seconds>s, cost $<cost> on anthropic/claude-sonnet-5å½æä¸ªç¨ä¾ç大夿°æ ·æ¬éè¿æ¶ï¼è¯¥ç¨ä¾å°±ç®ä½éè¿ï¼æä»¥ ..x ä»ç¶æ¯ PASS ã
ç°å¨å°ç¬¬äºä¸ªä½ä¸æ·»å å° .github/workflows/eval-gate.yml ä¸ï¼ä½äº changes ä½ä¸ä¸æ¹ã
eval-gate
:
needs
:
changes
if
:
needs.changes.outputs.agent == 'true'
runs-on
:
ubuntu-latest
timeout-minutes
:
15
steps
:
-
uses
:
actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
# v7.0.1
-
uses
:
actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
# v7.0.0
with
:
node-version
:
'24'
-
name
:
Run fixed eval set
env
:
OPENROUTER_API_KEY
:
${{ secrets.OPENROUTER_API_KEY }}
run
:
node scripts/run-evals.mjs --samples 3 --threshold 0.9if: è¡è¯»å changes ä½ä¸çè¾åºï¼æ£æ¯å®é»æ¢äºè¯ä¼°å¨ä¸å¯è½ç ´å代ççæå请æ±ä¸è¿è¡ã timeout-minutes: 15 黿¢æèµ·çæä¾åå ç¨è¿è¡å¨è¾¾ é»è®¤å å°æ¶ ãæ¯ä¸ªæä½é½åºå®å°å®æ´çæäº¤ SHAï¼å¹¶å¨æ«å°¾æ³¨é䏿³¨æå ¶å¯¹åºçåå¸çæ¬ãå v4 è¿æ ·çæ ç¾å¯ä»¥è¢«ç§»å¨ä»¥æåæ°ä»£ç ï¼èè¿ä¸ªä½ä¸ææä½ ç API å¯é¥ï¼å æ¤åºå®å° SHA æå³çè¿è¡ç代ç ä¸ä¼å¨æ²¡æä½ ä»åºä¸åæ´çæ åµä¸æ¹åã GitHub çå®å ¨å åºæå 乿¨èè¿æ ·åã
å¨é¨ç¦è½å¤é»æ¢ä»»ä½ä¸è¥¿ä¹åï¼åä¸ä»¶äºã
- å¨ Settings > Secrets and variables > Actions ä¸å°ä½ çå¯é¥æ·»å 为ä»åºæºå¯ï¼å½å为 OPENROUTER_API_KEY ã
- æå¼ä¸ä¸ªè§¦å prompts/ çæå请æ±ï¼ä»¥ä¾¿å·¥ä½æµè¿è¡ä¸æ¬¡ã
- å¨ä½ çåæ¯ä¿æ¤è§åä¸å° changes å eval-gate 齿·»å 为 å¿ éç¶ææ£æ¥ ã
ç¬¬ä¸æ¥ææ¯å°è¯ä¼°åæé¨ç¦çå ³é®ã没æå®ï¼å¤±è´¥çè¯ä¼°ä¼å¨æå请æ±ä¸æ¾ç¤ºä¸ä¸ªçº¢ Xï¼ä½æå请æ±ä»ç¶å¯ä»¥åå¹¶ãåæ¶è¦æ± changes å¯ä»¥è¦çè¿æ»¤ä½ä¸æ¬èº«å¤±è´¥çæ åµï¼ä¾å¦æ£åºé误ãç¶å GitHub ä¼è·³è¿ eval-gate ï¼è被跳è¿çä½ä¸ç®ä½éè¿ï¼æä»¥å¦æåªè¦æ± eval-gate ï¼æç¤ºæ´æ¹å¯è½ä¼å¨æªè¢«è¯ä¼°çæ åµä¸åå¹¶ã妿ä¹è¦æ± changes ï¼å¤±è´¥çè¿æ»¤ä½ä¸ä¼é»æ¢åå¹¶ãä¸ç¸å ³çæå请æ±ä»ç¶å¯ä»¥åå¹¶ï¼å 为 changes éè¿è eval-gate 被跳è¿ã
é¤ GITHUB_TOKEN å¤ï¼ GitHub ä¸ä¼å°å¯é¥ä¼ éç»ç± fork ä»åºè§¦åç工使µ ï¼å æ¤æ¥èª fork çæå请æ±ä¼å 缺å°å¯é¥è失败ãå¦æä½ çä»åºæ¥å fork è´¡ç®ï¼ä¹è¦å¨æ¨éå° main æ¶è¿è¡è¯ä¼°ï¼è¿æ ·éè¿ fork æäº¤çæ´æ¹ä»ä¼è¢«æ£æ¥ãå¦æä½ éè¦è®°å½ææ¬¡é»æåå¹¶çè¿è¡æ£æ¥äºä»ä¹ï¼è¯·å°è¿è¡è¾åºä½ä¸ºå·¥ä½æµäº§ç©ä¸ä¼ ã
ç°å¨è¿ä¸ªé¨ç¦ä¼å¨æ¯ä¸ªå¯è½ç ´å agent çæå请æ±ä¸è¿è¡ãå®ä»ç¶ä½¿ç¨ä¸ä¸ªæ²¡äººè½è¯æå ¶åçæ§çéå¼ï¼æä»¥ç¬¬ 4 æ¥è¦æµéåºä¸ä¸ªã
第 4 æ¥ï¼æµééå¼
éå¯¹æªæ¹å¨ç main 忝è¿è¡è¯ä¼°éå æ¬¡ã忬¡è¿è¡ä¹é´ä¸è¦å任使´æ¹ãè®°ä¸ä½ çå°çæä½éè¿çãä½ è¦çæ¯æå·®ç䏿¬¡è¿è¡ï¼è䏿¯å ¸åç飿¬¡ï¼æä»¥å¦ææ¡ä»¶å 许就å¤è¿è¡å 次ãé£ä¸ªæä½éè¿çå°±æ¯ä½ çä¸éï¼ä½ æéå¼è®¾å¨è¯¥å¼æä»¥ä¸ã
å¨ä¸ä¸ªç¨ä¾çæ åµä¸ï¼å ¶ä¸ä¸ä¸ªç¨ä¾å¨è¿å 次è¿è¡ä¸çæä¸æ¬¡å¤±è´¥ï¼ä¸éå°±æ¯ 0.67ãç¸å¯¹äº 0.9 çéå¼ï¼é£æ¬¡è¿è¡å°±æ¯ä¸ä¸ªè¢«é»æçæå请æ±ï¼èå ¶èå并没æåå½ã
å½ä½ çä¸éå¾ä½æ¶ï¼æé¡ºåºææ¥ä»¥ä¸ä¸é¡¹ã
é¦å ï¼æ£æ¥ä½ èªå·±çæè¨ãä¸é¢ç escalation ç¨ä¾æ¥å human ã person ã our team æ specialist ä¸çä»»æä¸ä¸ªã妿æä¸ªçæ¬è¦æ±åé¢å符串 human ï¼é£ä¹æ¯å½æ¨¡ååæâa personâæ¶å®é½ä¼å¤±è´¥ï¼èæç¤ºè¯å¹¶ä¸è½é»æ¢æ¨¡åè¿æ ·åãæ¯æ¬¡é½è¦å æ£æ¥ä½ çæè¨ï¼åæ£æ¥å ¶ä»ä»»ä½ä¸è¥¿ã
å ¶æ¬¡ï¼æ£æ¥ä½ çç¡®å®æ§è®¾ç½®æ¯å¦èµ·äºä½ç¨ãå° temperature 设为é¶å¹¶åºå®ä¸ä¸ª seed ï¼åªæå¨æ¨¡åæ¯æè¿äºåæ°æ¶ææå¸®å©ãä¸é¢çå½ä»¤ä¼æå°åºæ¨¡åæ¯æçåæ°ï¼è¿æ ·ä½ å°±è½çå° temperature å seed æ¯å¦å¨å表ä¸ã
curl
-s
"https://openrouter.ai/api/v1/models"
\
|
jq
-r
'.data[] | select(.id=="anthropic/claude-sonnet-5") | .supported_parameters'Claude Sonnet 5 两è 齿²¡æååºï¼è¿å°±æ¯ä¸é¢çèæ¬ä¸åéå®ä»¬çåå ãæä»¬ç Claude Sonnet 5 è¿ç§»æå æåºï¼å¯¹äºè¯¥æ¨¡åï¼ temperature ã top_p å top_k ä¼è¢«éé»å¿½ç¥ã2026 å¹´ 9 æ 18 æ¥ï¼ç®å½ä¸ååºäº 445 个模åï¼å ¶ä¸ 267 ä¸ªåæ¶ååºäº seed å temperature ãå ¶ä½ 178 个ï¼çº¦å äºåä¹äºï¼åªååºäºå ¶ä¸ä¹ä¸æä¸¤è 齿²¡æãè¿æ¡å½ä»¤ä¼ç»è®¡åæ¶ååºä¸¤è çæ¨¡åæ°éï¼ä½ å¯ä»¥éæ°è¿è¡å®ä»¥è·å¾å½åæ°åã
curl
-s
"https://openrouter.ai/api/v1/models"
|
jq
'
[.data[] | select(.supported_parameters | index("seed") and index("temperature"))] | length'å¦æä½ çæ¨¡åç¡®å®ååºäºå®ä»¬ï¼å°±æ temperature: 0 å seed: 42 å å ¥è¯·æ±ä½ï¼å¹¶å¨ order æè¾¹è®¾ç½® provider.require_parameters: true ã使ç¨é»è®¤ç require_parameters: false æ¶ï¼å³ä½¿æä¸ªæä¾å䏿¯æè¯·æ±ä¸çæ¯ä¸ªåæ°ï¼å®ä»å¯è½æ¶å°è¯·æ±å¹¶å¿½ç¥èªå·±ä¸è®¤è¯çåæ°ãä½¿ç¨ require_parameters: true æ¶ï¼è¯·æ±åªä¼è¢«è·¯ç±å°æ¯æææåæ°çæä¾åã
第ä¸ï¼ç»è¿åä¸¤é¡¹æ£æ¥åä»ç¶åå¨çæ³¢å¨å°±æ¯çå®çï¼ä½ éè¿éæ ·æ¥å¸æ¶å®ãæé« --samples ç´å°ä¸éä¸åååã3 æ¯ä¸ä¸ªåççé»è®¤å¼ï¼èä¸å®å·²ç»æ¯å次è¿è¡ææ¬çä¸åï¼æä»¥å¨æé«å° 5 ä¹åå æµéä¸ä¸ã
å°è§æ¨¡è¯ä¼°éæä¸ä¸ªå¼å¾äºè§£çç¹æ§ãå¨ä¸ä¸ªç¨ä¾çæ åµä¸ï¼éè¿çåªè½æ¯ 0ã0.33ã0.67 æ 1.00ï¼æä»¥ 0.9 çé弿å³çä¸ä¸ªç¨ä¾å¿ é¡»å ¨é¨éè¿ãè¿æ¯æè¯ä¼°éæ©å å° 20 ä¸ªææ´å¤ç¨ä¾çå¦ä¸ä¸ªçç±ã
å½æä¸ªæå请æ±ä» å ä¸ä¸ªç¨ä¾èæªéè¿é¨ç¦æ¶ï¼å¯¹ main è¿è¡åæ ·çè¯ä¼°ã妿 main ä¹å¤±è´¥ï¼é£è¿æ¬¡å¤±è´¥å°±æ¯åªå£°ï¼ä½ éè¦æ´å¤æ ·æ¬ææ´ä½çéå¼ã妿 main éè¿ï¼å°±æè¿æ¬¡å¤±è´¥å½ä½è¯¥æå请æ±ä¸çå彿¥å¤çã
è¿å°±æ¯å®æ´çé¨ç¦ãæ¬æåçå ¶ä½é¨åä»ç»å½åç¬¦ä¸²æ£æ¥ä¸åè¶³å¤æ¶è¯¥æä¹åï¼ä»¥å使¶å¼å¾ç¨ä¸ä¸ªè½åå¨è¿è¡åå²ç平尿¥æ¿æ¢æ®éèæ¬ã
ç¨ Ori Eval æµè¯è°ç¨å·¥å ·ç agent
ä¸é¢çèæ¬åé䏿¡æ¶æ¯å¹¶è¯»å䏿¡åå¤ãå¦æä½ ç agent è°ç¨å·¥å ·ï¼è¿è¿ä¸å¤ãåç¬¦ä¸²æ£æ¥æ æ³åè¯ä½ agent æ¯å¦è°ç¨äºæ£ç¡®çå·¥å ·ï¼æè æ¯å¦è°ç¨äºä¸ä¸ªæ¬åºé¿å çæè´µå·¥å ·ã
Ori Eval æ¯æä»¬ç¨äº agent ç eval æµè¯æ¡æ¶ãæµè¯æ¡æ¶æ¯è¿è¡ eval çç¨åºãå®è¿è¡ agentï¼è®°å½ agent åäºä»ä¹ï¼å¹¶æ ¹æ®ä½ çæè¨æ£æ¥ç»æãOri eval æ¯ .eval.ts æä»¶ï¼æè¨éå¯¹çæ¯ agent åäºä»ä¹ã
import
{ test }
from
'bun:test'
;
import
{ assertModelIsLive, setupAgent, setupJudge }
from
'ori/eval'
;
const
MODEL
=
'anthropic/claude-sonnet-5'
;
await
assertModelIsLive
(
MODEL
);
const
agent
=
setupAgent
({ model:
MODEL
});
// Grade with a different model family than the one under test.
const
judge
=
setupJudge
({
agent:
setupAgent
({ model:
'openai/gpt-5-mini'
}),
minScore:
0.8
,
});
test
(
'looks up the order before quoting the refund policy'
,
async
()
=>
{
const
run
=
await
agent.
run
(
'Can I refund order #1234? I bought it 60 days ago.'
);
run.
tool
(
'lookup_order'
).
toBeCalled
();
run.
tool
(
'issue_refund'
).
toNotBeCalled
();
run.
toComplete
();
await
judge.
autoEvals
({
criteria:
'Cites the 14-day window and does not invent exceptions.'
,
run,
});
});run.tool(...) æ¯æ®éèæ¬æ æ³åå°çé¨åã妿 slug 离å¼äºç®å½ï¼ assertModelIsLive ä¼ä»¥æ¸ æ°çæ¶æ¯ä½¿è¿è¡å¤±è´¥ï¼å æ¤æä»¶ä¼å¤§å£°å¤±è´¥ï¼è䏿¯æµè¯ä¸ä¸ªå·²ä¸åå¨ç模åãå¨ä½ 让è¯å¤å¨ä½¿æå»ºå¤±è´¥ä¹åï¼èªå·±ç»å䏿¹è¿è¡ä¸çæ ·æ¬æåï¼å¹¶å°ä½ çå¤å®ä¸è¯å¤å¨çå¤å®è¿è¡æ¯è¾ãAnthropic çæååºäºåæ ·çåå 建议 ç¨äººç±»ä¸å®¶æ ¡å LLM è¯å¤å¨ ã
Ori ä½¿ç¨ Bun è¿è¡ eval æä»¶ï¼å½ CI 为 true æ¶ï¼å®ä¸ä¼ä¸ºä½ å®è£ Bunãå¨å·¥ä½æµä¸ï¼ä½ å å®è£ Bunï¼ç¶åä¸è½½ä¸ä¸ªåºå®çæ¬ç Ori åå¸å å¹¶å¨è¿è¡åéªè¯å ¶æ ¡éªåï¼å ä¸ºè¯¥ä»»å¡ææä½ ç API keyã设置 OPENROUTER_API_KEY åï¼Ori å¨ CI ä¸ä¸éè¦ ori login ã
-
uses
:
oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
# v2.2.0
-
name
:
Install Ori
env
:
ORI_RELEASE
:
cli-0.15.0-531912d
ORI_SHA256
:
d2545db7a686f29ebae5bbf7e134d89a409cd00c760c1f24a5f8a88692c5947d
run
:
|
base="https://github.com/OpenRouterLabs/ori-releases/releases/download/$ORI_RELEASE"
curl -fsSL --proto '=https' -o ori "$base/ori-linux-x64"
echo "$ORI_SHA256 ori" | sha256sum -c -
mkdir -p "$HOME/.local/bin"
install -m 0755 ori "$HOME/.local/bin/ori"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
-
name
:
Run pinned agent eval
env
:
OPENROUTER_API_KEY
:
${{ secrets.OPENROUTER_API_KEY }}
run
:
ori eval --report eval-report.mdORI_RELEASE å ORI_SHA256 æå®äºæ°åæ¶ç稳å®çæ¬åå ¶ ori-linux-x64 æè¦ãå½ä½ è¿ç§»å°æ´æ°ççæ¬æ¶ï¼è¯·åæ¶æ´æ°ä¸¤è ãä»åä¸ä¸ªåå¸é¡µé¢ä¸è½½æ ¡éªåæä»¶ä¸ä¼å¢å ä»»ä½ä¸è¥¿ï¼å 为任ä½è½æ¿æ¢äºè¿å¶æä»¶ç人é½è½æ¿æ¢å®æè¾¹çæ ¡éªåãå°æè¦ä¿çå¨å·¥ä½æµä¸æå³çäºè¿å¶æä»¶ä¸æ¦æ¹åï¼ sha256sum æ£æ¥å°±ä¼å¤±è´¥ã
ori eval æ¥æ¾å½åç®å½ä¸çæ¯ä¸ª *.eval.ts æä»¶å¹¶å°å®ä»¬äº¤ç» bun test ãå®çéåºä»£ç å°±æ¯ bun test çéåºä»£ç ï¼å æ¤ eval 失败ä¼å¯¼è´ä»»å¡å¤±è´¥ã --report ä¼åä¸ä»½ Markdown æ¥åï¼ä½ å¯ä»¥å°å ¶ä½ä¸º artifact ä¸ä¼ æè¿½å å°ä»»å¡æè¦ä¸ã
æ¯æ¬¡ Ori è¿è¡é½ä¼åç宿¨¡ååé请æ±å¹¶äº§çè´¹ç¨ï¼å æ¤è¯·å°è¿äº eval æé¤å¨æ¯æ¬¡æäº¤é½ä¼è¿è¡çä»»å¡ä¹å¤ã Ori Eval ææ¡£ æ¶µçäºå¦ä½ä»ç±äººå¯å¨ç任塿æè®¡åè¿è¡å®ä»¬ã
æ¯è¾æ®éèæ¬ä¸ eval å¹³å°
ä¸é¢çèæ¬æ¯ä¸ä¸ªå®æ´ç eval é¨ç¦ãå¹³å°å¢å äºä»ªè¡¨çãå¯ç»å¶å¾è¡¨çè¿è¡åå²ï¼ä»¥å让工ç¨ä¹å¤ç人æ éæå¼ CI æ¥å¿å°±è½é è¯»ç»æçæ¹å¼ã
æ¹æ³
å¨ CI ä¸å¦ä½è¿è¡
éå®
æéå
æ®éèæ¬
ä½ èªå·±ç¼å CI æ¥éª¤åéåºä»£ç é»è¾
æ ï¼å ä¸ºè¿æ¯ä½ èªå·±ç代ç
ä¸å°ä¸¤ä¸ª eval éï¼ä¸å®å ¨æ§å¶è¯å
Ori Eval
å¨ä»»å¡ä¸è¿è¡
ori evalï¼å½ eval 失败æ¶ä»¥éé¶éåº
ä½ï¼å 为 eval æä»¶ä¿çå¨ä½ çä»åºä¸ï¼å¹¶é对ç®å½ä¸ç任使¨¡åè¿è¡
å·¥å ·è°ç¨ agentï¼æå¨ä½ èªå·±ç agent 䏿¯è¾æ¨¡å
DeepEval
å¨ pytest ä¸è¿è¡
deepeval test runï¼å½ä½äºæ¯ä¸ªææ çé弿¶
assert_test()伿åºå¼å¸¸
ä½ï¼å 为è¯ååºæ¯å¼æºç
ç°æçææ ï¼å¦çæ¡ç¸å ³æ§åä»»å¡å®æåº¦
Braintrust
ä¸ä¸ªå·²åå¸ç GitHub Actionï¼è¿è¡ eval å¹¶å¨ pull request ä¸å叿è¦è¯è®º
ä¸çï¼å 为è¯ååå²åå¨äºä»ä»¬çå¹³å°ä¸
å¨è¯å®¡ä¸èé CI æ¥å¿ä¸åç°åæ°åå
Arize
ä» SDK ä½ä¸ºæ®é Python æ¥éª¤è¿è¡
client.experiments.run()ï¼å ¶ææ¡£ä¸æä¸ä¸ªç¤ºä¾å·¥ä½æµ
ä¸çï¼å 为 experiments API æ¯ä»ä»¬ç
å·²ç»ä½¿ç¨ Arize è¿è¡å¯è§æµæ§çä»åº
Galileo
ä» SDK è¿è¡
run_experimentï¼æå¯¹å¤è½® agent 使ç¨
create_experimentä¸çï¼å ä¸ºææ åå岿¯å¹³å°åçç
åºäº eval åå²çæç®¡ä»ªè¡¨ç
ä»èæ¬å¼å§ãæ 论åªç§æ¹å¼ï¼ä½ çè¯ä¼°éåè¯åé»è¾é½çå¨ä½ çä»åºéï¼ä¹åä½ å¯ä»¥è®©å¹³å°æåå®ä»¬ã离å¼å¹³å°æå³çè¦ç§»æ¤éå¯¹å ¶ SDK ç¼åçè¯åé»è¾ï¼å¹¶ä¸¢å¤±åå¨å¨é£éçè¿è¡åå²ã
常è§å¤±è´¥æ¨¡å¼
第ä¸ä¸ªæ¯ææ¬ã宿¯ç¨ä¾æ°ä¹ä»¥æ ·æ¬æ°åä¹ä»¥ç¸å ³æå请æ±è¢«æå¼çé¢çãèæ¬ä¼ä» usage.cost åæ®µæå°æ¯æ¬¡è¿è¡çææ¬ï¼æä»¥å¨æé« --samples ææ©å¤§éåä¹åï¼å 读é£ä¸è¡ãé¿æç¤ºè¯åè¯å模åä¼å¤§å¹ æ¹åè¿ä¸ªæ°åï¼æä»¥è¦æµéä½ èªå·±çéåãå½åä»·æ ¼å¨ å®ä»·é¡µé¢ ä¸ã
第äºä¸ªæ¯é度ãé¨ç¦å¢å çæ¯ä¸åéé½ä¼å å°æ¯ä¸ªè§¦åæç¤ºè¯çæå请æ±ä¸ãå¹¶åè¿è¡æ ·æ¬ãèæ¬ç --concurrency æ å¿é»è®¤æ¯ 8ï¼æä»¥ç¤ºä¾ä¸ç乿¬¡è°ç¨å两波è¿è¡ï¼è䏿¯ä¹æ¬¡é¡ºåºè¯·æ±ï¼èä¸è¿ç§å·®å¼ä¼éçéåè§æ¨¡å¢å¤§èæ©å¤§ã
第ä¸ä¸ªæ¯é¨ç¦å é误åå è失败ãæä¾åè¶ æ¶ä¸æ¯åå½ï¼è¿å°±æ¯ä¸ºä»ä¹èæ¬å¨è¯ä¼°æ æ³è¿è¡æ¶ä»¥ 2 éåºï¼å¨æºè½ä½åå·®æ¶ä»¥ 1 éåºï¼å¹¶æå°ä¸æ¡ GitHub 注é说æåçäºåªç§æ åµã
第å个æ¯é«äºåºçº¿çéå¼ãä½ æ²¡ææµéè¿çéå¼ä¼é»æ¢æ²¡ææ¹åä»»ä½ä¸è¥¿çæå请æ±ï¼èç»è¿å®çå¯ä¸åæ³æ¯ç®¡çååå¹¶æå¨æ¶é´ååä¸è®¾ç½®æ´ä½çéå¼ãæ ¹æ®ä½ å¨ç¬¬ 4 æ¥æµå¾çåºçº¿æ¥è®¾ç½®éå¼ã
常è§é®é¢
å¦ä½å° LLM è¯ä¼°æ·»å å° CI/CD æµæ°´çº¿ï¼
å¨ä»åºä¸ä¿çä¸ä¸ªåºå®çãå¸¦çæ¬çè¯ä¼°éï¼å¨ CI ä½ä¸ä¸è¿è¡å®ï¼æ ¹æ®éå¼å¯¹è¾åºè¯åï¼å¹¶å°è¯¥ä½ä¸ä»¥åä¸ºå ¶æå ³çè·¯å¾è¿æ»¤ä½ä¸æ è®°ä¸ºåæ¯ä¿æ¤ä¸çå¿ éç¶ææ£æ¥ãä½ä¸çéåºç å³å®ç»æï¼å°±å失败çåå æµè¯ä¸æ ·ã
ä»ä¹æ¯åºå®è¯ä¼°éï¼ä¸ºä»ä¹å®éè¦ä¸ä»£ç ä¸èµ·ä¿æçæ¬åï¼
åºå®è¯ä¼°éæ¯ä¸ä»½å·²æ£å ¥çæµè¯è¾å ¥åè¯åæ åå表ï¼åªè½éè¿ç»è¿å®¡æ¥çæåè¯·æ±æ¥æ´æ¹ã妿å®ä½äºä»åºä¹å¤ï¼å°±ä¼ä¸æç¤ºè¯ä¸åæ¥ï¼ä¸åæµè¯å®é åå¸çå 容ã
ä½ è½æ ¹æ®è¯ä¼°åæ°é»æ¢æå请æ±åå¹¶åï¼
å¯ä»¥ãåªè¦è¿è¡å®çä½ä¸æ¯å¿ éç¶ææ£æ¥ï¼ä¸ä¸ªå¨ä½äºé弿¶ä»¥éé¶éåºçæ®éèæ¬å°±è¶³å¤äºãææç¤ºè¯åè¯ä¼°éæ¾å¨ CODEOWNERS è§åä¹åï¼å¹¶å¯ç¨âRequire review from Code Ownersâï¼è¿æ ·åå¼±æè·äºåå½çæµè¯ä¹éè¦ç»è¿å®¡æ¥ã
LLM-as-a-judge ä¸å¨ CI ä¸è¿è¡è¯ä¼°æä»ä¹åºå«ï¼
LLM-as-a-judge æ¯å次è¿è¡çè¯åæ¹æ³ï¼ç±ç¬¬äºä¸ªæ¨¡åå¯¹çæ¡è¯åãå¨ CI ä¸è¿è¡è¯ä¼°æ¯å´ç»ä»»ä½è¯åæ¹æ³çæµæ°´çº¿ãå®å³å®ç¨ä¾ä½æ¶è¿è¡ãé对åªä¸ªåºå®éåè¿è¡ï¼ä»¥åå½åæ°ä½æ¶æå请æ±ä¼ææ ·ã
è¯ä¼°éè¦æ¯æ¬¡æäº¤é½è¿è¡ï¼è¿æ¯åªå¨æç¤ºè¯åæºè½ä½é»è¾åæ´æ¶è¿è¡ï¼
åªå¨è§¦åæç¤ºè¯ãæºè½ä½é»è¾ãå·¥å ·æ¨¡å¼æè¯ä¼°éçåæ´æ¶è¿è¡ãå¨ä½ä¸çº§å«è䏿¯å·¥ä½æµçº§å«è¿è¡è¿æ»¤ãGitHub ä¼å°å if æ¡ä»¶èè·³è¿çä½ä¸æ¥å为éè¿çæ£æ¥ï¼èå è·¯å¾è¿æ»¤å¨è·³è¿ç工使µä¼è®©å¿ 鿣æ¥ä¿æå¾ å®å¹¶é»æ¢åå¹¶ã
卿¯ä¸ª PR ä¸è¿è¡ LLM è¯ä¼°å¨ token å CI åéæ¹é¢è¦è±å¤å°é±ï¼
ææ¬çäºç¨ä¾æ°éä¹ä»¥æ¯ä¸ªç¨ä¾çæ ·æ¬æ°ï¼åä¹ä»¥ç¸å ³æå请æ±è¢«æå¼çé¢çãæ¬æåä¸çèæ¬ä¼ä» API ååºä¸ç usage åæ®µæå°æ¯æ¬¡è¿è¡çææ¬ï¼å æ¤ä½ å¯ä»¥æµéèªå·±çéåãå½å模åä»·æ ¼è§ å®ä»·é¡µé¢ ã
æåªäºå·¥å ·æ¯æå¨åå¹¶ååºäºåºå®è¯ä¼°é对 PR è¿è¡é¨ç¦ï¼
ä» é ä¸ä¸ªå¸¦é弿£æ¥çæ®éèæ¬å°±è¶³å¤äºãOri EvalãDeepEvalãBraintrustãArize å Galileo å¨ç¸åçéåºç 模å¼ä¹ä¸å¢å äºæ¥åãè¿è¡å岿é对æºè½ä½çæè¨ã
å¦ä½å¤çä¸ç¨³å®æéç¡®å®æ§çè¯ä¼°é»å¡äºä¸ä¸ªå¥½ç PRï¼
å æ£æ¥ä½ èªå·±çæè¨ï¼å ä¸ºæ¨¡åæ¹è¿°çå个åé¢å符串æ¯å¸¸è§åå ãç¶å对æ¯ä¸ªç¨ä¾éæ ·å¤æ¬¡å¹¶å夿°å¤å®ãå¨ä¾èµ temperature æ seed ä¹åï¼å å¨ OpenRouter 模åç«¯ç¹ ä¸æ£æ¥æ¨¡åç supported_parameters ï¼å 为æªååºå®ä»¬ç模åä¼å¿½ç¥å®ä»¬ã
LLM ä½ä¸ºè¯å¤è æ¯å¦è¶³å¤å¯é ï¼å¯ä»¥æ®æ¤è®©æå»ºå¤±è´¥ï¼
å¦æä½ è¡¡éè¯å¤è è䏿¯å设å®å¯é ï¼é£å°±å¯ä»¥ãèªå·±ç»ä¸é¨åè¿è¡ç»ææåï¼å¹¶å°ä½ çå¤å®ä¸è¯å¤è çå¤å®è¿è¡æ¯è¾ï¼åæ¶å°è¯å¤è 䏿®éæè¨é 对ï¼è¿æ ·æå»ºå°±ä¸ä¼å 䏿¬¡æªç»éªè¯ç模åè°ç¨è失败ã
ç»è®º
卿¬æåä¸ï¼ä½ 为ä¸ä¸ªæ¯ææºè½ä½ç¼åäºè¯ä¼°éï¼ç¨ä¸ä¸ªå¨ä½äºé弿¶ä»¥éé¶ç¶æéåºçèæ¬å¯¹å ¶è¯åï¼å¨å¤æ¬¡éå¤è¿è¡ä¸æµéäºä½ èªå·±çä¸éï¼å¹¶å°è¯¥ä½ä¸è®¾ä¸ºå¿ 鿣æ¥ãåºå®çè¯ä¼°éãç»è¿æµéçéå¼åä½ä¸çº§è§¦åå¨ï¼ä¸ºä½ çæç¤ºè¯åæºè½ä½é»è¾æä¾äºä¸åå æµè¯ä¸ºä»£ç æä¾çç¸åä¿æ¤ãè¦å°é¨ç¦æ©å±å°è°ç¨å·¥å ·çæºè½ä½ï¼è¯·ä» Ori Eval ææ¡£ å¼å§ã
åèèµæ
- Ori Eval
- æä¾åéæ©
- 模åä¸ supported_parameters
- ç¨éæ ¸ç®
- Claude Sonnet 5 è¿ç§»æå
- æå¼ AI æºè½ä½è¯ä¼°çç¥ç§é¢çº±
- GitHub Actions ç工使µè¯æ³
- è·³è¿å·¥ä½æµè¿è¡
- ä½¿ç¨æ¡ä»¶æ§å¶ä½ä¸æ§è¡
- å¨ GitHub Actions ä¸ä½¿ç¨å¯é¥
- å ³äºåä¿æ¤åæ¯
- å ³äºä»£ç ææè
- dorny/paths-filter
æ¥æºï¼ OpenRouterï¼Announcements · openrouter.ai
#
æç¨/å®è·µ
é¨ç½²/å·¥ç¨
Agent
ç¼ç