MCPcopy Create free account
hub / github.com/VectifyAI/OpenKB / run_eval

Function run_eval

openkb/skill/evaluator.py:389–462  ·  view source on GitHub ↗

Run trigger-accuracy + body-alignment evaluation. Args: skill_dir: `` /output/skills/ `` model: LiteLLM model string from KB config eval_set: pre-generated prompts; if None, generate fresh count: how many should-trigger + should-not prompts to generate

(
    skill_dir: Path,
    *,
    model: str,
    eval_set: list[EvalPrompt] | None = None,
    count: int = EVAL_DEFAULT_COUNT,
)

Source from the content-addressed store, hash-verified

387
388
389async def run_eval(
390 skill_dir: Path,
391 *,
392 model: str,
393 eval_set: list[EvalPrompt] | None = None,
394 count: int = EVAL_DEFAULT_COUNT,
395) -> EvalResult:
396 """Run trigger-accuracy + body-alignment evaluation.
397
398 Args:
399 skill_dir: ``<kb>/output/skills/<name>``
400 model: LiteLLM model string from KB config
401 eval_set: pre-generated prompts; if None, generate fresh
402 count: how many should-trigger + should-not prompts to generate
403 """
404 if eval_set is None:
405 eval_set = await generate_eval_set(skill_dir, model=model, count=count)
406
407 desc = _read_description(skill_dir)
408 content = _skill_content_block(skill_dir)
409 result = EvalResult(prompts=eval_set)
410
411 # Run grading concurrently. Each prompt is independent — graders read
412 # the same `desc`/`content` strings and produce results that are then
413 # appended to `result` in eval_set order below, so concurrent
414 # execution is correctness-preserving. A semaphore caps simultaneous
415 # LLM calls to avoid hitting provider rate limits.
416 sem = asyncio.Semaphore(EVAL_CONCURRENCY)
417
418 async def _trigger(p: EvalPrompt) -> Literal["trigger", "no-trigger"]:
419 async with sem:
420 return await grade_one(desc, p.question, model=model)
421
422 async def _coverage(
423 p: EvalPrompt,
424 ) -> tuple[Literal["supported", "unsupported", "ambiguous"], str]:
425 async with sem:
426 return await grade_coverage(content, p.question, model=model)
427
428 trigger_tasks = [_trigger(p) for p in eval_set]
429 # Body alignment only meaningful on questions the skill claims to
430 # handle — for should-not questions the body is correctly empty of
431 # relevant material.
432 coverage_prompts = [p for p in eval_set if p.expected == "trigger"]
433 coverage_tasks = [_coverage(p) for p in coverage_prompts]
434
435 # return_exceptions=True so one failed grader doesn't discard the
436 # other ~29 successful gradings. Errored prompts are surfaced
437 # separately on the result and excluded from rate denominators.
438 trigger_results, coverage_results = await asyncio.gather(
439 asyncio.gather(*trigger_tasks, return_exceptions=True),
440 asyncio.gather(*coverage_tasks, return_exceptions=True),
441 )
442
443 # Walk inputs in original order so `result.*` lists are deterministic
444 # even though the gather() above completed out of order.
445 for prompt, graded in zip(eval_set, trigger_results):
446 if isinstance(graded, BaseException):

Calls 8

generate_eval_setFunction · 0.85
_read_descriptionFunction · 0.85
_skill_content_blockFunction · 0.85
EvalResultClass · 0.85
_triggerFunction · 0.85
_coverageFunction · 0.85
CoverageMissClass · 0.85
EvalMissClass · 0.85