Self-hosted evals para sa AI agents
Gumawa ng eval loop sa sarili mong repo: golden cases mula sa real traces, deterministic checks muna, LLM judge kasunod, at pass rate bawat commit.
Ano ang self-hosted evals para sa AI agents
Ang self-hosted evals para sa AI agents ay apat na bagay na pinapanatili mo sa sarili mong repository: isang file ng mga naka-save na case, isang script na nagpapatakbo ng agent sa mga ito, isang set ng checks na nagga-grade sa bawat sagot, at isang table ng mga resultang maaari mong i-query. Walang vendor na kailangan sa listahang ito. Ilang daang linya lang ng Python at isang SQLite file ang kailangan para sa buong loop.
Gumana ang agent sa demo dahil ikaw mismo ang pumili sa limang input. Pumalya ito sa ikalawang linggo dahil may nagbago sa isang linya ng prompt, sa model, o sa description ng tool, at walang measurement na sumaklaw sa alinman sa mga ito. Ginagawang partikular ng eval loop ang “parang mas masama na ngayon” bilang “bumaba ang pass rate mula 58 sa 60 tungo sa 51 sa 60 sa commit 4f1c9ab”.
May apat na hakbang ang loop, at tig-isang section sa gabay na ito para sa bawat hakbang: mangolekta ng mga totoong trace, gawing cases ang mga kawili-wiling trace, i-grade ang bawat case sa bawat pagbabago, at i-store ang pass rate kasama ng commit na gumawa nito. Gumagana ang parehong loop anuman ang pinapatakbo mo sa agent, at ang mga self-hosted agent framework na sulit patakbuhin ay pangunahing nagkakaiba sa dami ng trace na awtomatiko nilang ibinibigay sa iyo.
Bakit pumapalya ang agent sa ikalawang linggo
Ang agent ay binubuo ng prompt, model, mga tool definition, at anumang context na kinukuha sa runtime. Maaaring magbago ang lahat ng ito nang hindi binabago ang application code, kaya walang makikitang dapat tutulan sa karaniwang code review.
Ang pinakakaraniwang sanhi ay pag-edit sa prompt. Nagdagdag ka ng isang pangungusap para pigilan ang bastos na tugon. Binabago ng pangungusap na iyon ang behavior sa mga input na hindi muling sinubukan, at malinaw itong makikita sa mga trace: ang trace noong nakaraang linggo para sa parehong tanong ay may create_refund tool call, samantalang wala nito ang trace ngayong linggo at sa halip ay magalang na paghingi ng paumanhin ang tugon. Walang nagkaroon ng error, kaya walang alert na na-trigger.
Ang ikalawang sanhi ay ang model. Itala ang eksaktong model string na ipinadala mo sa bawat run, claude-haiku-4-5-20251001 sa halip na shorthand na nasa isip mo lang, dahil malalaman mo lamang ang dahilan ng pagbaba ng pass rate noong araw na nagpalit ka ng model kung kasama sa row ang model.
Ang ikatlong sanhi ay ang mga tool. Kapag binago ang pagkakasulat ng tool description, nagbabago kung kailan nagpapasyang tawagin ito ng model. Kung dumarating ang mga tool mo mula sa MCP server na tumatakbo sa isang VPS, nasa ibang process ang schema, kaya maaari itong magbago nang walang anumang diff sa repository mo. Ang ikaapat na sanhi ay retrieval: ang parehong tanong ay tumatama sa index na muling binuo nang magdamag, at sinusunod ng sagot ang bagong document.
Buuin ang golden set mula sa mga trace na kinokolekta mo na
Huwag mag-imbento ng mga eval case. Kunin ang mga ito mula sa traffic. Kung nagpapatakbo ka na ng self-hosted Langfuse tracing para sa agent mo, naka-store ang bawat request kasama ang input, tool calls, at output nito. Iyan mismo ang raw material na kailangan ng isang case.
Mag-export ng isang window ng root observations gamit ang public API. Gumagamit ito ng basic authentication. Ang public key ang username, at ang secret key ang password.
export LF_HOST="https://langfuse.example.com"
curl -sS -u "$LF_PUBLIC_KEY:$LF_SECRET_KEY" \
"$LF_HOST/api/public/v2/observations?limit=50&isRootObservation=true&fromStartTime=2026-07-01T00:00:00Z" \
| jq '.data[0]'Basahin muna ang isang record bago magsulat ng anumang parsing. Lumalabas ang mga row sa ilalim ng data, pero nakadepende sa paraan ng pag-instrument ng agent mo sa mga span ang mga field name na naglalaman ng tanong at sagot. I-map ang aktuwal mong nakikita, hindi ang inaasahan mong makikita. Pagkatapos, manu-manong isulat ang mga case bilang tig-isang JSON object bawat linya sa evals/cases.jsonl:
{"id": "refund-double-charge", "tags": ["smoke"], "input": "I was charged twice for order 41822.", "must_call": ["lookup_order", "create_refund"], "must_not_include": ["I cannot help"], "rubric": "The reply confirms exactly one refund for order 41822 and states the amount."}Nakakatulong ang limang panuntunan para manatiling kapaki-pakinabang ang set:
- Sapat na ang 40 hanggang 80 case para magsimula. Kapag mas mababa sa 20, kayang baguhin ng isang flaky case ang pass rate nang 5 puntos, kaya binabalewala ang numerong biglang nagbabago nang walang malinaw na dahilan.
- Bawat production bug na inaayos mo ay dapat maging case sa araw ng pag-aayos nito. Ang kaugaliang ito ang nagtitiyak na lumalaki ang set sa tamang direksiyon.
- Isang behavior bawat case. Kapag sabay na sinusuri ng isang case ang halaga ng refund at tono, wala itong malinaw na ipinapakitang resulta kapag nag-fail ito.
- Hindi kailanman nagbabago ang
id, dahil ginagamit ang id para ikumpara ang run ngayong araw sa run noong nakaraang buwan. - Mag-redact bago mag-commit. Mapupunta ang file na ito sa git, kaya alisin ang mga pangalan ng customer at anumang order number na hindi mo pagmamay-ari.
Unahin ang pag-grade gamit ang deterministic checks dahil libre ang mga ito
Ang anumang may tamang sagot ay lagyan ng plain assertion. Walang model call, gastos, o ambiguity. Nahuhuli ng deterministic checks ang mga structural regression. Ang mga ito ang nakasisira sa mga system sa paligid ng iyong agent: hindi ma-parse ang JSON, hindi kailanman tinawag ang tool, bumalik ang ipinagbabawal na parirala, o walang source na binanggit ang sagot.
Isang function lang ang kailangang may alam tungkol sa iyong agent. Generic ang lahat ng iba pa sa harness.
import json, os, urllib.request
def run_agent(case):
req = urllib.request.Request(
os.environ["AGENT_URL"],
data=json.dumps({"input": case["input"]}).encode(),
headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.load(resp)
def deterministic(case, result):
text = result.get("output", "")
called = [c["name"] for c in result.get("tool_calls", [])]
failures = []
for tool in case.get("must_call", []):
if tool not in called:
failures.append(f"tool not called: {tool}")
for phrase in case.get("must_not_include", []):
if phrase.lower() in text.lower():
failures.append(f"forbidden phrase: {phrase}")
if len(called) > case.get("max_tool_calls", 12):
failures.append(f"too many tool calls: {len(called)}")
return failuresIsama sa listahang iyon ang tool budget. Nagkaroon ng regression ang isang agent na nakakalutas ng case sa 3 calls ngayon pero nangangailangan ng 11 bukas, kahit tama ang final answer, dahil binabayaran mo ang bawat call na ginagawa nito.
LLM bilang judge, at ang apat na paraan kung paano ito nagkakamali
Ang anumang pumasa sa assertions ay nangangailangan ng grader na nagbabasa. Ang LLM judge ay isang second model call: natatanggap nito ang tanong, sagot ng agent, at isang criterion, pagkatapos ay nagbabalik ng verdict. Ito ang tanging praktikal na paraan upang i-grade kung “sinasagot ba ng reply ang hinihingi ng user”.
Apat na panuntunan ang nagpapagamit nang maayos sa judge:
- Binary verdict, hindi score na 1 hanggang 10. Sa scale, karaniwang 7 at 8 ang ibinabalik nito sa halos lahat, kaya hindi nagbabago ang numero at wala kang natututuhan mula rito.
- Isang criterion bawat call. Itanong kung tama ang refund amount, o kung tama ang tone, pero huwag pareho nang sabay.
- Ibigay sa judge ang inaasahang sagot kapag mayroon nito ang case. Mas madaling i-grade laban sa reference kaysa mag-grade nang walang tiyak na batayan.
- Pilitin ang output shape at mahigpit itong i-parse.
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
def judge_prompt(case, output):
return (
"You grade one answer against one criterion.\n"
"Reply with JSON only, in this exact shape:\n"
'{"verdict": "pass", "confidence": "high", "reason": "one short sentence"}\n'
f"Criterion: {case['rubric']}\n"
f"Question: {case['input']}\n"
f"Answer: {output}\n"
"Length is not a criterion. Judge only the criterion above."
)
def judge(case, output, model):
msg = client.messages.create(
model=model,
max_tokens=200,
messages=[{"role": "user", "content": judge_prompt(case, output)}],
)
return json.loads(msg.content[0].text)Narito ang failure modes. May test para sa bawat isa na maaari mong patakbuhin ngayong hapon. Mahalaga ang pagpapatakbo ng mga ito dahil ang judge na hindi nasusuri ay gumagawa ng mga numerong mukhang eksakto pero walang saysay.
Length bias. Mas madalas pumapasa ang mas mahahabang sagot. Subukan ito: kumuha ng sampung sagot na binigo ng judge, dagdagan ang bawat isa ng dalawang paragraph ng kumpiyansang filler na walang bagong fact, at i-judge muli ang mga ito. Kung may verdict na nagbago at naging pass, length bias iyon, at ang rubric ang kailangang ayusin.
Self-preference. Madalas na mas mahinahon ang pag-grade ng judge sa output mula sa sarili nitong model family kaysa sa output mula sa ibang family. Subukan ito: i-grade ang parehong 30 sagot gamit ang judges mula sa dalawang magkaibang family at ihambing ang mga verdict case by case. Sa mga hindi pagkakasunduan, basahin mo mismo ang case.
Position bias. Kung ginagamit mo ang judge upang maghambing ng dalawang sagot, A at B, baligtarin ang pagkakasunod-sunod at patakbuhin itong muli. Kung nagbabago ang verdict kapag binaligtad ang order, hindi pa ligtas gamitin ang pairwise comparison para sa rubric na iyon.
Rubric drift. Ang malalabong criterion ay gumagawa ng judges na madaling sumang-ayon. Ang “Nakatutulong ba ang sagot?” ay pumapasa sa halos kahit ano. Ang “Sinasabi ba ng sagot ang refund amount sa dollars?” ay pumapasa lamang sa talagang tinutukoy mo. Isulat muli ang bawat criterion hanggang malinaw nitong tukuyin ang fact na sinusuri.
Isang guard ang sumasaklaw sa lahat ng apat. Magtabi ng 30 case na mano-mano mong nilagyan ng label, at i-score ang judge laban sa mga label mo sa tuwing babaguhin mo ang judge model o judge prompt. Kung hindi ito sumasang-ayon sa iyo sa mahigit isang case sa bawat sampu, ayusin muna ang rubric bago magtiwala sa anumang pass rate na ginagawa nito. Ang judge ay code, kaya dapat itong i-version at i-review gaya ng code.
Gamitin ang murang grader, at mag-escalate sa frontier model
Ang paggamit ng pinakamahal na model sa bawat kaso at bawat commit ang dahilan kung bakit maaaring lumampas ang gastos sa eval sa agent na sinusuri nito. Ayusin ang mga grader ayon sa presyo, at huminto agad kapag malinaw na ang sagot.
The data behind this chart
[
{
"label": "Haiku 4.5, Batch API",
"usd_per_1000_judge_calls": "0.90"
},
{
"label": "Haiku 4.5",
"usd_per_1000_judge_calls": "1.80"
},
{
"label": "Sonnet 5",
"usd_per_1000_judge_calls": "3.60"
},
{
"label": "Opus 5",
"usd_per_1000_judge_calls": "9.00"
}
]Ipinapalagay ng mga halagang ito ang humigit-kumulang 1,200 input token at 120 output token sa bawat judge call. Makatotohanang laki ito para sa isang tanong, isang sagot, at isang criterion. Ang paghusga sa 1,000 kaso ay nagkakahalaga ng 1.80 US dollars sa Claude Haiku 4.5 at 9.00 sa Claude Opus 5. Mukhang maliit ang agwat hanggang i-multiply mo ito. Ang set na may 60 kaso, na hinuhusgahan sa bawat commit at may 40 commit bawat linggo, ay nagreresulta sa 2,400 judge call bawat linggo bago pa man patakbuhin ng sinuman ang nightly job.
Dalawang discount ang direktang naaangkop sa eval work, at maaaring pagsabayin ang mga ito. Hindi interactive ang eval run, kaya hinahati ng Batch API ang presyo ng input at output kapalit ng asynchronous delivery. Ito ang unang row ng chart. Eksaktong magkapareho ang rubric at instructions sa bawat call, kaya angkop ang prompt caching: ang cache read ay nagkakahalaga ng isang-sampu ng base input price, at ang five-minute cache write ay nagkakahalaga ng 1.25 beses ng base input price. Dahil dito, nababawi ng cache ang gastos pagkatapos ng isang hit. Ito ang list price ng Anthropic noong August 2026. Nasa introductory pricing ang Sonnet 5 hanggang 31 August 2026, kaya tataas ang ikatlong bar pagkatapos ng petsang iyon.
Ang ladder, ayon sa pagkakasunod:
- Deterministic check sa bawat kaso. Walang API cost.
- Maliit na model judge para sa mga kasong nakalampas sa mga check na iyon.
- Frontier judge lamang kapag sinabi ng maliit na judge na fail, o nagsabing pass nang may mababang confidence.
- Human review sa maliit na sample, isang beses bawat linggo.
CHEAP = "claude-haiku-4-5-20251001"
STRICT = "claude-opus-5"
def grade(case, result):
hard = deterministic(case, result)
if hard:
return False, "deterministic", "; ".join(hard)
first = judge(case, result["output"], CHEAP)
if first["verdict"] == "pass" and first["confidence"] == "high":
return True, CHEAP, first["reason"]
second = judge(case, result["output"], STRICT)
return second["verdict"] == "pass", STRICT, second["reason"]Ipinagpapalit nito ang bahagi ng grading accuracy kapalit ng mas mababang gastos, kaya sukatin ang trade-off sa halip na ipagpalagay ito. Isang beses bawat buwan, i-grade rin ang buong set gamit ang strict judge at ihambing ang dalawang column. Kung hindi magkatugma ang mga ito sa higit sa ilang kaso, masyadong maluwag ang rubric para sa maliit na model. Ang rubric ang dapat mong ayusin. Hiwalay na gawain ang pagkontrol sa gastos mismo ng agent, at saklaw ito sa pagkontrol sa gastos ng AI agent sa isang VPS.
Subaybayan ang pass rate sa paglipas ng panahon sa isang system na pagmamay-ari mo
Ang pass rate na hindi mo maiuugnay sa isang commit ay pakiramdam lamang. Mag-imbak ng isang row para sa bawat case sa bawat run, kasama ang commit at model sa loob ng row.
CREATE TABLE IF NOT EXISTS results (
run_id TEXT NOT NULL,
ran_at TEXT NOT NULL,
git_sha TEXT NOT NULL,
agent_model TEXT NOT NULL,
case_id TEXT NOT NULL,
passed INTEGER NOT NULL,
graded_by TEXT NOT NULL,
reason TEXT
);SELECT run_id, git_sha, agent_model,
count(*) AS cases,
round(100.0 * sum(passed) / count(*), 1) AS pass_pct
FROM results
GROUP BY run_id
ORDER BY ran_at DESC
LIMIT 10;I-load ang schema gamit ang sqlite3 evals/results.db < evals/schema.sql, pagkatapos basahin ang trend gamit ang sqlite3 -box evals/results.db < evals/passrate.sql. Ang isang taon ng daily run para sa 60 case ay humigit-kumulang 22,000 row, kaya hindi magiging hiwalay na proyekto ang store. Sinasaklaw ng Pagpapatakbo ng SQLite sa production sa isang VPS ang mga setting na nagiging mahalaga kapag ibinabahagi ang file sa pagitan ng mga machine.
Inililimbag ng runner ang parehong impormasyon para sa isang tao:
run 2026-08-05T09:14:22Z sha 4f1c9ab model claude-sonnet-5 58/60 pass (96.7%)
FAIL refund-double-charge deterministic: tool not called: create_refund
FAIL pto-policy-question judge(opus): reply gives no dollar amountPatakbuhin ang suite sa mga pagbabagong maaaring makasira sa isang agent. Kabilang dito ang prompt edits, pagbabago ng model, at pagbabago ng tool, sa halip na bawat commit saanman sa repository. Sapat ang isang pre-push hook para sa mabilis na subset:
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
python3 evals/run.py --set smoke || exit 1
EOF
chmod +x .git/hooks/pre-pushMas mabagal ang full run at dapat itong ilagay sa isang schedule. Isinasagawa ng nightly systemd service at timer sa VPS ang buong set laban sa deployed prompt. Ito ang nakakakita sa mga pagbabagong nagmumula sa labas ng repository, gaya ng hosted tool na nagbago ng behavior.
Pagsusuri ng tao, may sample sa halip na kumpletong pagsusuri
Naka-calibrate ang judge batay sa mga label ng tao, kaya kailangang may gumawa ng mga ito. Suriin ang isang sample bawat linggo: lahat ng case na hindi naipasa ng judge, kasama ang sampung pass na random na pinili. Mahalaga ang random na mga pass dahil maaaring mukhang perpekto sa dashboard ang judge na tahimik nang nagpapasa ng mga maling sagot kung sarili nitong verdict ang ginagamit na batayan.
Labinlimang case na tig-tatlong minuto ay 45 minuto bawat linggo. Bilang resulta, nakakakuha ka ng mga correction sa rubric kapag hindi kayo nagkakasundo ng judge, pati ng mga bagong case para sa mga uri ng failure na hindi pa naisip ng sinuman. Isulat ang verdict ng tao sa parehong table, na nakatakda ang graded_by sa human, upang maging query ang pagkakasundo ng judge at tao sa halip na umasa sa memorya.
Ano ang nasisira sa mismong eval harness
anthropic.RateLimitError sa unang buong run. Ang sabay-sabay na pagproseso ng 60 kaso ay lumalampas sa request o token limit ng tier mo. Itakda sa apat na worker ang maximum concurrency, at ilipat ang nightly run sa Batch API.
json.JSONDecodeError: Expecting value: line 1 column 1 (char 0) mula sa judge. Nag-reply ang model gamit ang prose, o binalot nito sa code fence ang JSON nito. Mag-retry nang isang beses, pagkatapos ay itala ang kaso bilang error. Huwag kailanman ituring na pass ang parse failure, dahil ang suite na ginagawang pass ang mga error ay aabot sa 100% habang lalong humihina ang agent.
Mga flaky case. Pumapasa ang parehong input sa isang run at bumabagsak sa kasunod dahil nagsa-sample ang agent ng output nito. Patakbuhin ang flaky case nang 3 beses at itala ang fraction sa halip na tanggalin ang kaso. Ang kasong pumapasa sa 2 sa 3 run ay tunay na robustness bug, at matutuklasan ito ng customer.
Pagkaluma ng golden set. May nag-e-edit ng expected answer para maging green ang suite. Suriin ang mga diff sa evals/cases.jsonl nang kasing-ingat ng mga diff sa agent, dahil ang file na iyon ang nakasulat na depinisyon mo ng tama.
Suite na hindi kailanman bumabagsak. Ang pass rate na nananatili sa 100% sa loob ng isang buwan ay nangangahulugang hindi na nasusubaybayan ng set ang produkto. Kumuha ng 10 kamakailang trace, hanapin ang mga kasong hindi mahusay na nahawakan ng agent, at idagdag ang mga ito.
FAQ
Ilang kaso ang kailangan ng isang AI agent eval set?
Magsimula sa 40 hanggang 80, at dagdagan ang set batay sa mga totoong failure. Kapag mas mababa sa humigit-kumulang 20 kaso, maaaring magpabago ang isang flaky result ng pass rate nang 5 puntos, kaya nawawalan ng saysay ang bilang. Kapag lumampas sa ilang daang kaso, nagkakaroon ng aktuwal na gastos at oras sa bawat run, habang kaunti na lamang ang nadaragdag na coverage ng bawat bagong kaso. Ang mahalaga ay hindi ang bilang. Ito ay ang bahagi ng mga kilalang production failure type na lumilitaw kahit isang beses sa set.
Mapagkakatiwalaan ko ba ang isang LLM judge na mag-grade ng agent ko?
Oo, ngunit pagkatapos lamang itong masukat gamit ang sarili mong labels. Magtabi ng 30 kaso na mano-mano mong na-grade, at ikumpara ang score ng judge sa mga ito tuwing binabago mo ang judge model o judge prompt. Nagpapakita ang mga judge ng length bias, kung saan mas madalas pumapasa ang mga sagot na dinagdagan ng hindi kailangang teksto. Nagpapakita rin sila ng self-preference, kung saan mas mahinahong bina-grade ang output mula sa sarili nilang model family. Parehong maaaring i-test ang mga ito: dagdagan ng padding ang isang failed answer at i-re-judge ito, o ipa-grade ang parehong mga sagot sa judge mula sa ibang family. Kung hindi sumasang-ayon ang judge sa iyong mga label sa mahigit isang kaso sa bawat sampu, masyadong malabo ang rubric para gamitin.
Aling model ang dapat mag-grade ng evals?
Magsimula sa murang opsyon at mag-escalate kapag kailangan. Walang gastos ang deterministic assertions, kaya tumatakbo muna ang mga ito sa bawat kaso. Isang maliit na model ang humahawak sa mga malinaw na pass. Ang mga fail at verdict na mababa ang confidence lamang ang ipinapasa sa frontier model. Sa list prices noong August 2026, ang pag-grade ng 1,000 kaso ay nagkakahalaga ng humigit-kumulang 1.80 US dollars gamit ang Claude Haiku 4.5 at humigit-kumulang 9.00 gamit ang Claude Opus 5. Dahil asynchronous ang eval runs, hinahati ng Batch API sa kalahati ang alinman sa dalawang halagang ito.
Pinapalitan ba ng evals ang production monitoring?
Hindi, dahil magkaibang tanong ang sinasagot ng mga ito. Sinasabi ng eval suite kung pinabubuti o pinasasama ng isang pagbabagong ilalabas mo ang isang fixed set ng mga kaso. Sinasabi naman ng tracing at monitoring kung ano ang aktuwal na nararanasan ng mga user ngayon, kabilang ang mga input na walang katumbas na kaso. Nagtutulungan ang mga ito: nagbibigay ang traces ng mga bagong kaso, at tinutukoy ng eval suite kung talagang gumana ang iyong fix.