Vieval
[![npm version][npmx-version-src]][npmx-version-href]
[![npm downloads][npmx-downloads-src]][npmx-downloads-href]
[![bundle][bundle-src]][bundle-href]
[![License][license-src]][license-href]
[![JSDocs][jsdocs-src]][jsdocs-href]
에이전트, 모델 및 태스크 파이프라인을 위한 Vitest 스타일 평가 프레임워크입니다.
vieval은 반복 가능한 태스크 발견(repeatable task discovery), 매트릭스 스케줄링(matrix scheduling), 실시간 CLI 출력, JSON 아티팩트, 그리고 리포트 명령 기능을 제공하면서도 eval 작성 과정을 제품 코드와 가깝게 유지할 수 있도록 합니다.
Vieval을 사용해야 하는 이유 (Why Vieval)
describeTask, caseOf, casesFromInputs, expect를 사용하여 친숙한 평가 파일 작성이 가능합니다.
- 모델, 시나리오, 루브릭(rubric), 데이터셋 변형에 대한 프로젝트, 평가, 태스크 매트릭스 레이어를 제공합니다.
ChatModels를 통한 내장 채팅 모델 등록 기능과 비(non-)채팅 워크로드를 위한 사용자 정의 프로젝트 실행기(custom project executors)를 지원합니다.
- 동일한 CLI에서 사람이 읽을 수 있는 터미널 출력과 기계가 읽을 수 있는 JSON/리포트 아티팩트를 제공합니다.
- 고급 통합을 위해 가져올 수 있는 러너(runner), 스케줄러(scheduler), 단언(assertion), 설정(config), 플러그인(plugin), 테스트 진입점(testing entrypoints)이 있습니다.
빠른 시작 (Quick Start)
1단계. 설정 파일 생성
import { cwd } from 'node:process'
...
2단계. 평가 태스크 생성
import { caseOf, describeTask, expect } from 'vieval'
...
3단계. 실행
pnpm -F vieval eval:run -- --config ./vieval.config.ts
배포된 바이너리 형태는 다음과 같습니다:
vieval run --config ./vieval.config.ts
작성 API (Authoring API)
일반적인 Vitest와 같은 작성 경로를 위해 describeTask를 사용하세요:
import { caseOf, describeTask, expect } from 'vieval'
import { modelFromRun } from 'vieval/plugins/chat-models'
...
입력 배치(batch of inputs)를 로드할 때는 빌더 스타일을 사용하세요:
import { describeTask, expect } from 'vieval'
const arithmeticCases = [
...
describeEval은 여전히 describeTask의 별칭(alias)으로 내보내지지만, 태스크/케이스 의미론(task/case semantics)이 주요 런타임 모델이기 때문에 새로운 예제에서는 describeTask를 선호해야 합니다.
평가 파일 내의 모든 describeTask 호출은 자체적인 하나의 태스크입니다:
- 엔트리 이름은
describeTask의 이름을 따릅니다.
- 엔트리 ID는 프로젝트 루트를 기준으로 한 파일 경로이며,
.eval.* 확장자를 제외하고, 이름이 파일명과 다를 경우 #<name>을 추가합니다. 예를 들어, evals/speech.eval.ts에서 zh와 en을 등록하면 각각 evals/speech#zh와 evals/speech#en이 되고, speech만 등록하면 evals/speech가 됩니다.
- 하나의 파일 내에서 동일한 이름을 가진 두 개의 태스크는 오류입니다.
include 없이 프로젝트는 **/*.eval.{ts,mts,cts,js,mjs,cjs}를 수집합니다.
Matrix Model (행렬 모델)
vieval은 다음 순서로 행렬 범위를 확장합니다:
vieval.config.*에서 프로젝트 설정.
defineEval(...)에서 평가 정의(Eval definition).
defineTask(...)에서 태스크 정의(Task definition).
각 범위 내에서, 행렬 레이어는 다음 순서로 해결됩니다:
disable
extend
override
runMatrix와 evalMatrix 모두 프로젝트, 평가, 태스크 범위에서 지원됩니다. runMatrix: { scenario: ['baseline'] }와 같은 평면 객체는 runMatrix.extend로 정규화되며, 계층적 형태가 새로운 문서 및 예제에 선호됩니다.
각 예약된 태스크는 안정적인 행렬 메타데이터를 받습니다:
task.matrix.run
task.matrix.eval
task.matrix.meta.runRowId
task.matrix.meta.evalRowId
caseOf(..., { input }) 및 casesFromInputs(...)를 위한 task.matrix.inputs
Orchestration Model (오케스트레이션 모델)
vieval은 벤치마크 관리와 실행 신뢰성을 분리합니다. 실행 계층 구조는 comparison → workspace → project → task → case입니다.
comparison: 가장 바깥쪽 레벨입니다. 여러 에이전트, 메모리, 모델, 논문 또는 백엔드 구현에 걸쳐 수평적 평가를 위해 workspaces 목록을 나열하고 각 워크스페이스를 개별 프로세스로 병렬 실행합니다. 워크스페이스는 완벽한 프로젝트/케이스 정렬이 필요하지 않습니다. Workspaces and Comparisons를 참조하세요.
workspace: projects를 목록으로 나열하는 설정입니다. 이는 인라인으로 지정되거나 다른 설정에서 수집될 수 있습니다. 이는 프로젝트를 선택하고 스케줄링하며 환경을 제공합니다. workspaces가 없는 설정 자체가 워크스페이스입니다.
project: 작업의 완전한 단위입니다: 디스커버리 규칙 (include, exclude, root), runMatrix / evalMatrix, models, inferenceExecutors, 선택적 executor, plugins, reporters, 그리고 scheduler.
experiment: 명시적인 --experiment로부터 파생된 실행 메타데이터이거나, 생략된 경우 안정적인 매트릭스 행 메타데이터입니다. 이는 추가적인 스케줄러 계층을 생성하지 않습니다.
task: 파일에서 발견되고 추론 실행기 및 실행/평가 매트릭스 행에 걸쳐 확장되는 하나의 평가 정의입니다.
case: 태스크 내부의 점수 및 증거 출처입니다. context.recordScore(...)는 정규화된 점수 증거를 기여하고; context.recordMetric(...)은 보고서용 벤치마크 메타데이터를 방출합니다.
attempt: 신뢰도를 추정하는 데 사용되는 전체 태스크 재실행입니다. autoAttempt와 함께, 각 전체 시도는 증거를 기여하므로 실패 후 성공 쌍은 이전의 실패를 대체하기보다는 성공률로 점수화됩니다.
retry: 케이스 내부의 재시도입니다. autoRetry와 함께, 하나의 시도 내에서 케이스가 복구될 수 있으며; 재시도 패스는 여전히 해당 시도가 통과한 것으로 계산됩니다.
설정 상속은 동일한 외부에서 내부로(outside-in) 모델을 따릅니다: 워크스페이스의 최상위 models와 reporters는 아무것도 설정하지 않은 프로젝트의 기본값으로 사용되며, 최상위 plugins는 구성이 로드될 때 설정을 변환하고, 프로젝트별 플러그인은 형제 프로젝트로 누수되지 않으면서 프로젝트별 모델/리포터/동시성(concurrency)을 추가하거나 재정의할 수 있습니다. env, scheduler, 그리고 reporting은 전체 워크스페이스에 적용됩니다. 스케줄러는 상속 예외입니다: 최상위 scheduler(동시성 풀 및 미들웨어)는 프로젝트로 복사되지 않습니다. 이는 실행을 구성하며, 각 프로젝트의 scheduler가 그 아래의 해당 프로젝트를 구성합니다.
설정 예시
import { cwd } from 'node:process'
import { defineConfig, loadEnv, requiredEnvFrom } from 'vieval'
...
사용자 정의 실행기 (Custom Executor)
프로젝트가 executor를 제공하지 않아도, vieval run은 여전히 평가(eval) 파일을 발견하고, 작업을 예약하며, 모듈로 정의된 작업 콜백을 실행합니다. ASR, TTS, 이미지, 모션, 호스팅 에이전트 또는 다른 도메인 런타임에 대해 프로젝트가 사용자 정의 실행기가 필요할 때 projects[].executor를 제공하십시오.
import { defineConfig } from 'vieval'
export default defineConfig({
...
워크스페이스 및 비교 (Workspaces and Comparisons)
vieval run은 하나의 구성을 읽고 그 구성이 설명하는 레벨에서 실행합니다. workspaces를 나열하는 구성은 비교(comparison)이며, 다른 모든 구성은 projects를 나열하는 워크스페이스입니다.
comparison 작업 공간들을 나열하며, 각각의 프로세스에서 실행함
└─ workspace 프로젝트들을 나열하며, 하나의 프로세스에서 실행함
└─ project 평가 파일(eval files), 행렬(matrices), 모델(models), 실행기(executors), 플러그인(plugins), 리포터(reporters), 스케줄러(scheduler)
...
워크스페이스: 다른 구성에서 프로젝트 수집하기
projects 항목은 인라인 프로젝트 객체이거나 다른 구성을 참조하는 방식입니다: 경로 또는 glob 문자열, 또는 { from, env?, scheduler? } 형태일 수 있습니다.
import { defineConfig } from 'vieval'
...
from은 설정 파일이거나, vieval.config.*를 포함하는 디렉터리 또는 해당 디렉터리의 glob 패턴이며, 워크스페이스 설정에 상대적입니다.
- 수집된 프로젝트는 자체 설정(plugins, models, reporters, root)으로 로드됩니다. 따라서 자신의 설정에서 실행됩니다.
- 하나의 워크스페이스에 속한 프로젝트들은 하나의 프로세스를 공유하므로
env는 프로세스 전체 범위입니다. 즉, 워크스페이스의 env, 참조된 각 설정의 env, 그리고 참조 자체의 env가 병합됩니다. 두 개의 다른 값을 가진 키는 오류를 발생시키므로, 이러한 프로젝트는 비교(comparison) 워크스페이스로 실행해야 합니다.
- 참조에 지정된
scheduler는 수집된 프로젝트들의 스케줄러를 대체합니다.
- 프로젝트 이름은 워크스페이스 내에서 고유해야 하며, 참조 순환(reference cycle)은 오류입니다.
비교(Comparison): 여러 워크스페이스를 나란히 실행하기
import { defineConfig, loadEnv } from 'vieval'
...
workspaces 항목은 vieval.config.*를 포함하는 디렉터리 경로 또는 glob 패턴(비교 설정에 상대적), 디렉터리를 제외하기 위한 '!<glob>', 또는 { root, id?, env? }입니다. 워크스페이스 ID는 기본적으로 해당 디렉터리 이름으로 지정됩니다. 중복된 ID는 오류입니다.
id는 비교 설정의 디렉터리 이름을 기본값으로 사용합니다.
- 비교(comparison)에는
models, plugins, 또는 reporters가 없습니다. 각 워크스페이스는 자체 설정을 가지고 실행됩니다.
env (및 항목의 env)가 각 워크스페이스 설정의 env보다 우선합니다. 이는 워크스페이스 플러그인이 실행되기 전에 적용되므로, 예를 들어 모델 API 키를 읽는 플러그인도 이를 확인할 수 있습니다.
cache: { namespace }를 사용하면 모든 워크스페이스가 비교 설정 옆에 있는 .vieval/cache/<comparison id>/<namespace>/를 공유하게 됩니다. 이것을 사용하지 않으면 각 프로젝트는 자체 루트 아래에 캐시합니다.
concurrency: { workspace } (또는 --workspace-concurrency)는 한 번에 실행되는 워크스페이스의 수를 제한합니다. 기본값은 모두 동시에 실행하는 것입니다.
vieval run --config evals/agent-memory/vieval.config.ts는 각 워크스페이스를 자체 프로세스로 실행하며, 해당 워크스페이스가 해결하는 vieval을 사용합니다. 따라서 이 vieval은 동일한 버전이어야 하므로, 워크스페이스의 의존성들이 설치되어 있어야 합니다.
- 하나의 라이브 터미널 창에 모든 워크스페이스에 대한
Workspaces, Projects, Tasks, Cases, Concurrency, 그리고 Time 행이 표시됩니다.
- 한 워크스페이스가 실패하더라도 다른 워크스페이스의 실행을 막지 않습니다.
- 마지막에는 하이브리드 점수(hybrid score) 순으로 각 워크스페이스와 프로젝트별로 하나의 리더보드가 출력되며, 그 뒤에 실패한 워크스페이스들이 각각의 stderr 마지막 줄과 함께 나열됩니다.
- 어떤 워크스페이스라도 실패하거나 테스트 케이스가 실패하면 종료 코드는
1이 됩니다.
--json을 사용하면 비교 결과를 JSON 형식으로 출력합니다.
- 보고서는 비교 설정 파일 옆에 있는
.vieval/reports 디렉터리로 저장됩니다. 실험은 기본적으로 비교 ID를 사용하며, 모든 워크스페이스가 하나의 시도를 공유합니다.
실행할 항목 선택 (Select what runs)
vieval run의 셀렉터(Selectors)는 반복 가능합니다. *를 포함하는 값은 글로브(glob)입니다.
--workspace <id>: 비교 대상 워크스페이스들입니다. 이는 워크스페이스 설정에서 오류가 발생할 수 있습니다.
--project <name>: 프로젝트들입니다.
--task <name>: describeTask 이름 또는 엔트리 ID로 지정하는 태스크(tasks)입니다.
--case <name>: 케이스 이름으로 지정하는 케이스(cases)입니다.
아무것도 일치시키지 못하는 셀렉터는 무엇이 존재하는지 목록을 보여주는 오류를 발생시킵니다. 비교의 경우, --project, --task, 그리고 --case는 적어도 하나의 워크스페이스에서 일치해야 합니다.
vieval run --config evals/agent-memory/vieval.config.ts --workspace mem9 --with-viewer --open
vieval run --task zh --case 'case #1*'
CLI
vieval run [--config <path>] [--workspace <id>] [--project <name>] [--task <name>] [--case <name>] [--json] [--report-out <path> | --no-report]
vieval run --with-viewer [--port <port>] [--host <host>] [--open]
vieval viewer [reportRoot...] [--port <port>] [--host <host>] [--open]
...
vieval run --config <비교 설정>은 비교를 실행하며, 별도의 vieval compare 명령어는 없습니다. vieval report compare는 두 개의 기존 보고서를 비교(diff)합니다.
vieval run은 기본적으로 설정 파일 옆의 .vieval/reports 디렉토리에 보고서를 작성합니다. --report-out <path>를 사용하면 루트 경로를 변경할 수 있으며, --no-report는 아무것도 작성하지 않습니다. 모든 프로젝트는 <root>/<workspace>/<project>/<experiment>/<attempt>/<run>/ 아래에 독립적인 실행을 가집니다.
일반 워크스페이스 명령어:
pnpm install
pnpm -F vieval eval:run
pnpm -F vieval eval:run -- --config ./vieval.config.ts
...
동시성(Concurrency) 플래그는 vieval run에서 사용할 수 있습니다. 각 플래그는 전체 실행을 제한하며, 이는 최상위 scheduler.concurrency 설정의 동일한 키와 더 작은 값을 취하여 결합되므로, 플래그가 구성된 값을 초과하는 경우는 없습니다:
--workspace-concurrency: 한 번에 비교되는 워크스페이스의 최대 개수 (기본값: 모두 동시에). 이는 비교(comparison) 설정에서 concurrency.workspace와 같습니다.
--project-concurrency: 한 번에 실행되는 프로젝트의 최대 개수. 전역적인 task 또는 case 풀이 없는 경우(설정 파일이나 CLI에서), 프로젝트는 하나씩 순차적으로 실행됩니다. 이러한 풀이 있는 경우, 다음 프로젝트는 다른 프로젝트의 느린 케이스가 여전히 실행 중인 동안에도 시작될 수 있도록, 오직 해당 풀에 의해서만 제한됩니다.
--task-concurrency: 전체 실행에서 한 번에 예약되는 작업(task)의 최대 개수. 전역적인 task 또는 case 값이 없고 프로젝트의 task 값도 없는 경우, 각 프로젝트는 하나의 작업을 순차적으로 실행합니다.
--case-concurrency: 전체 실행에서 한 번에 실행되는 케이스(case)의 최대 개수.
동시성 값은 선언된 작업 범위와 공유됩니다: 최상위 scheduler.concurrency와 CLI 값은 전체 실행을 제한하고, 프로젝트의 scheduler.concurrency는 해당 프로젝트를 제한하며, describeTask(..., { concurrency: { case } })는 해당 작업을 제한하고, casesFromInputs(..., { concurrency })는 해당 그룹을 제한합니다. 케이스는 모든 레벨에서 슬롯을 확보한 후에만 실행되므로, 최상위에 scheduler: { concurrency: { case: 7 } }가 설정되어 있어도, 동시에 여러 작업이 실행될지라도 전체 실행은 7개의 케이스로 유지됩니다.
Public Entrypoints