@lunora/testing 1.0.0-alpha.111 → 1.0.0-alpha.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +81 -2
- package/dist/index.d.ts +81 -2
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/containsScorer-gv0Sdd86.mjs +2 -0
- package/dist/packem_shared/groundednessScorer-CznJBIjn.mjs +3 -0
- package/package.json +4 -4
- package/dist/packem_shared/containsScorer-DDK7WEuL.mjs +0 -2
package/dist/index.d.mts
CHANGED
|
@@ -578,6 +578,20 @@ interface Scorer {
|
|
|
578
578
|
name: string;
|
|
579
579
|
score: (sample: ScorerSample) => Promise<ScoreResult | number> | ScoreResult | number;
|
|
580
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* What a `produce` runner may return: the output text alone, or the text plus
|
|
583
|
+
* metadata describing what the run actually did.
|
|
584
|
+
*
|
|
585
|
+
* The metadata form exists for scorers that judge more than the final string —
|
|
586
|
+
* a retrieval scorer needs the ranked ids the run retrieved, which only the run
|
|
587
|
+
* can know. It is merged OVER the case's own metadata before scoring.
|
|
588
|
+
*/
|
|
589
|
+
interface ProducedOutput {
|
|
590
|
+
/** Merged over the case's `metadata`, then handed to every scorer. */
|
|
591
|
+
metadata?: Record<string, unknown>;
|
|
592
|
+
/** The output text under test. */
|
|
593
|
+
output: string;
|
|
594
|
+
}
|
|
581
595
|
/** One dataset case: an input and its optional gold answer/metadata. */
|
|
582
596
|
interface EvalCase {
|
|
583
597
|
expected?: string;
|
|
@@ -628,5 +642,70 @@ declare const scoreSample: (sample: ScorerSample, scorers: ReadonlyArray<Scorer>
|
|
|
628
642
|
* give each its own thread/key inside `produce` if the producer is stateful.
|
|
629
643
|
* Returns per-case results plus the mean of their averages.
|
|
630
644
|
*/
|
|
631
|
-
declare const evaluate: (cases: ReadonlyArray<EvalCase>, produce: (input: string) => Promise<string> | string, scorers: ReadonlyArray<Scorer>) => Promise<EvalResult>;
|
|
632
|
-
|
|
645
|
+
declare const evaluate: (cases: ReadonlyArray<EvalCase>, produce: (input: string) => Promise<ProducedOutput | string> | ProducedOutput | string, scorers: ReadonlyArray<Scorer>) => Promise<EvalResult>;
|
|
646
|
+
/** Where a retrieval scorer reads its two id lists from. */
|
|
647
|
+
interface RetrievalScorerOptions {
|
|
648
|
+
/** Metadata key holding the gold relevant ids. Default `"relevant"`. */
|
|
649
|
+
relevantKey?: string;
|
|
650
|
+
/** Metadata key holding the run's ranked retrieved ids. Default `"retrieved"`. */
|
|
651
|
+
retrievedKey?: string;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Recall@k scorer — what fraction of the gold passages made it into the top
|
|
655
|
+
* `k`.
|
|
656
|
+
*
|
|
657
|
+
* This is the ceiling on everything downstream: a passage retrieval never
|
|
658
|
+
* returned is one no reranker can promote and no prompt can cite. Omit `k` to
|
|
659
|
+
* score the whole retrieved list.
|
|
660
|
+
*/
|
|
661
|
+
declare const recallAtK: (k?: number, options?: RetrievalScorerOptions) => Scorer;
|
|
662
|
+
/**
|
|
663
|
+
* Precision@k scorer — what fraction of the top `k` retrieved passages are
|
|
664
|
+
* gold.
|
|
665
|
+
*
|
|
666
|
+
* This is the counterweight to recall: padding `topK` raises recall for free
|
|
667
|
+
* while burying the answer in noise the model has to read past, and pay for.
|
|
668
|
+
* Omit `k` to score the whole retrieved list.
|
|
669
|
+
*/
|
|
670
|
+
declare const precisionAtK: (k?: number, options?: RetrievalScorerOptions) => Scorer;
|
|
671
|
+
/**
|
|
672
|
+
* Mean Reciprocal Rank scorer — `1 / rank` of the first gold passage: 1 if it
|
|
673
|
+
* is first, 0.5 if second, 0 if absent.
|
|
674
|
+
*
|
|
675
|
+
* This is the metric that notices ordering, which recall cannot. A gold passage
|
|
676
|
+
* at rank 20 counts the same as rank 1 for recall@20, but only one of those
|
|
677
|
+
* survives a context-window trim or a model that skims the top of its prompt.
|
|
678
|
+
*/
|
|
679
|
+
declare const mrrScorer: (options?: RetrievalScorerOptions) => Scorer;
|
|
680
|
+
/**
|
|
681
|
+
* Normalized Discounted Cumulative Gain scorer — relevance discounted
|
|
682
|
+
* logarithmically by rank, divided by the best achievable arrangement.
|
|
683
|
+
*
|
|
684
|
+
* This is the one to gate on when comparing retrieval strategies. Unlike recall
|
|
685
|
+
* it is sensitive to order, and unlike MRR it credits every gold passage rather
|
|
686
|
+
* than only the first — so it is the metric that can actually say whether a
|
|
687
|
+
* reranker or a hybrid leg helped.
|
|
688
|
+
*
|
|
689
|
+
* Relevance is binary: an id is gold or it is not, which is what a gold-id set
|
|
690
|
+
* expresses. Graded relevance would need per-id weights.
|
|
691
|
+
*/
|
|
692
|
+
declare const ndcgAtK: (k?: number, options?: RetrievalScorerOptions) => Scorer;
|
|
693
|
+
/**
|
|
694
|
+
* Groundedness scorer — does the answer assert only what the retrieved context
|
|
695
|
+
* supports?
|
|
696
|
+
*
|
|
697
|
+
* This is the generation-side counterpart to the metrics above: perfect
|
|
698
|
+
* retrieval still fails if the model answers from its own weights. It scores
|
|
699
|
+
* the output against the context via an injected `judge`, the same shape
|
|
700
|
+
* `llmScorer` takes, so this stays model-agnostic and mockable.
|
|
701
|
+
*
|
|
702
|
+
* Reads the context from `metadata.context` by default — the `context` string
|
|
703
|
+
* `retrieve()` already returns. Fails closed: no context means nothing could
|
|
704
|
+
* have grounded the answer.
|
|
705
|
+
*/
|
|
706
|
+
declare const groundednessScorer: (options: {
|
|
707
|
+
contextKey?: string;
|
|
708
|
+
judge: (prompt: string) => Promise<string>;
|
|
709
|
+
name?: string;
|
|
710
|
+
}) => Scorer;
|
|
711
|
+
export { type AgentHarness, type AgentHarnessOptions, type AgentRunOverrides, type EvalCase, type EvalItemResult, type EvalResult, type EvaluationAttributeValue, type EvaluationMetrics, type EvaluationSpanHandle, type FakeScheduledJob, type FakeSchedulerControls, type FunctionRegistry, type HarnessDispatch, type HarnessMessage, type HarnessThread, type LunoraTestOptions, type ProducedOutput, type RecordEvaluationInput, type RetrievalScorerOptions, type ScheduledJobFailure, type ScoreResult, type Scorer, type ScorerSample, type SweepOptions, type TestHarness, type TestIdentity, type TestSubscription, agentHarness, containsScorer, evaluate, evaluationAttributes, exactMatchScorer, finalTurn, groundednessScorer, keywordScorer, llmScorer, lunoraTest, mrrScorer, ndcgAtK, precisionAtK, recallAtK, recordEvaluation, regexScorer, scoreSample, toolCallTurn };
|
package/dist/index.d.ts
CHANGED
|
@@ -578,6 +578,20 @@ interface Scorer {
|
|
|
578
578
|
name: string;
|
|
579
579
|
score: (sample: ScorerSample) => Promise<ScoreResult | number> | ScoreResult | number;
|
|
580
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* What a `produce` runner may return: the output text alone, or the text plus
|
|
583
|
+
* metadata describing what the run actually did.
|
|
584
|
+
*
|
|
585
|
+
* The metadata form exists for scorers that judge more than the final string —
|
|
586
|
+
* a retrieval scorer needs the ranked ids the run retrieved, which only the run
|
|
587
|
+
* can know. It is merged OVER the case's own metadata before scoring.
|
|
588
|
+
*/
|
|
589
|
+
interface ProducedOutput {
|
|
590
|
+
/** Merged over the case's `metadata`, then handed to every scorer. */
|
|
591
|
+
metadata?: Record<string, unknown>;
|
|
592
|
+
/** The output text under test. */
|
|
593
|
+
output: string;
|
|
594
|
+
}
|
|
581
595
|
/** One dataset case: an input and its optional gold answer/metadata. */
|
|
582
596
|
interface EvalCase {
|
|
583
597
|
expected?: string;
|
|
@@ -628,5 +642,70 @@ declare const scoreSample: (sample: ScorerSample, scorers: ReadonlyArray<Scorer>
|
|
|
628
642
|
* give each its own thread/key inside `produce` if the producer is stateful.
|
|
629
643
|
* Returns per-case results plus the mean of their averages.
|
|
630
644
|
*/
|
|
631
|
-
declare const evaluate: (cases: ReadonlyArray<EvalCase>, produce: (input: string) => Promise<string> | string, scorers: ReadonlyArray<Scorer>) => Promise<EvalResult>;
|
|
632
|
-
|
|
645
|
+
declare const evaluate: (cases: ReadonlyArray<EvalCase>, produce: (input: string) => Promise<ProducedOutput | string> | ProducedOutput | string, scorers: ReadonlyArray<Scorer>) => Promise<EvalResult>;
|
|
646
|
+
/** Where a retrieval scorer reads its two id lists from. */
|
|
647
|
+
interface RetrievalScorerOptions {
|
|
648
|
+
/** Metadata key holding the gold relevant ids. Default `"relevant"`. */
|
|
649
|
+
relevantKey?: string;
|
|
650
|
+
/** Metadata key holding the run's ranked retrieved ids. Default `"retrieved"`. */
|
|
651
|
+
retrievedKey?: string;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Recall@k scorer — what fraction of the gold passages made it into the top
|
|
655
|
+
* `k`.
|
|
656
|
+
*
|
|
657
|
+
* This is the ceiling on everything downstream: a passage retrieval never
|
|
658
|
+
* returned is one no reranker can promote and no prompt can cite. Omit `k` to
|
|
659
|
+
* score the whole retrieved list.
|
|
660
|
+
*/
|
|
661
|
+
declare const recallAtK: (k?: number, options?: RetrievalScorerOptions) => Scorer;
|
|
662
|
+
/**
|
|
663
|
+
* Precision@k scorer — what fraction of the top `k` retrieved passages are
|
|
664
|
+
* gold.
|
|
665
|
+
*
|
|
666
|
+
* This is the counterweight to recall: padding `topK` raises recall for free
|
|
667
|
+
* while burying the answer in noise the model has to read past, and pay for.
|
|
668
|
+
* Omit `k` to score the whole retrieved list.
|
|
669
|
+
*/
|
|
670
|
+
declare const precisionAtK: (k?: number, options?: RetrievalScorerOptions) => Scorer;
|
|
671
|
+
/**
|
|
672
|
+
* Mean Reciprocal Rank scorer — `1 / rank` of the first gold passage: 1 if it
|
|
673
|
+
* is first, 0.5 if second, 0 if absent.
|
|
674
|
+
*
|
|
675
|
+
* This is the metric that notices ordering, which recall cannot. A gold passage
|
|
676
|
+
* at rank 20 counts the same as rank 1 for recall@20, but only one of those
|
|
677
|
+
* survives a context-window trim or a model that skims the top of its prompt.
|
|
678
|
+
*/
|
|
679
|
+
declare const mrrScorer: (options?: RetrievalScorerOptions) => Scorer;
|
|
680
|
+
/**
|
|
681
|
+
* Normalized Discounted Cumulative Gain scorer — relevance discounted
|
|
682
|
+
* logarithmically by rank, divided by the best achievable arrangement.
|
|
683
|
+
*
|
|
684
|
+
* This is the one to gate on when comparing retrieval strategies. Unlike recall
|
|
685
|
+
* it is sensitive to order, and unlike MRR it credits every gold passage rather
|
|
686
|
+
* than only the first — so it is the metric that can actually say whether a
|
|
687
|
+
* reranker or a hybrid leg helped.
|
|
688
|
+
*
|
|
689
|
+
* Relevance is binary: an id is gold or it is not, which is what a gold-id set
|
|
690
|
+
* expresses. Graded relevance would need per-id weights.
|
|
691
|
+
*/
|
|
692
|
+
declare const ndcgAtK: (k?: number, options?: RetrievalScorerOptions) => Scorer;
|
|
693
|
+
/**
|
|
694
|
+
* Groundedness scorer — does the answer assert only what the retrieved context
|
|
695
|
+
* supports?
|
|
696
|
+
*
|
|
697
|
+
* This is the generation-side counterpart to the metrics above: perfect
|
|
698
|
+
* retrieval still fails if the model answers from its own weights. It scores
|
|
699
|
+
* the output against the context via an injected `judge`, the same shape
|
|
700
|
+
* `llmScorer` takes, so this stays model-agnostic and mockable.
|
|
701
|
+
*
|
|
702
|
+
* Reads the context from `metadata.context` by default — the `context` string
|
|
703
|
+
* `retrieve()` already returns. Fails closed: no context means nothing could
|
|
704
|
+
* have grounded the answer.
|
|
705
|
+
*/
|
|
706
|
+
declare const groundednessScorer: (options: {
|
|
707
|
+
contextKey?: string;
|
|
708
|
+
judge: (prompt: string) => Promise<string>;
|
|
709
|
+
name?: string;
|
|
710
|
+
}) => Scorer;
|
|
711
|
+
export { type AgentHarness, type AgentHarnessOptions, type AgentRunOverrides, type EvalCase, type EvalItemResult, type EvalResult, type EvaluationAttributeValue, type EvaluationMetrics, type EvaluationSpanHandle, type FakeScheduledJob, type FakeSchedulerControls, type FunctionRegistry, type HarnessDispatch, type HarnessMessage, type HarnessThread, type LunoraTestOptions, type ProducedOutput, type RecordEvaluationInput, type RetrievalScorerOptions, type ScheduledJobFailure, type ScoreResult, type Scorer, type ScorerSample, type SweepOptions, type TestHarness, type TestIdentity, type TestSubscription, agentHarness, containsScorer, evaluate, evaluationAttributes, exactMatchScorer, finalTurn, groundednessScorer, keywordScorer, llmScorer, lunoraTest, mrrScorer, ndcgAtK, precisionAtK, recallAtK, recordEvaluation, regexScorer, scoreSample, toolCallTurn };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{agentHarness as o,finalTurn as t,toolCallTurn as a}from"./packem_shared/agentHarness-DA9onGVO.mjs";import{evaluationAttributes as
|
|
1
|
+
import{agentHarness as o,finalTurn as t,toolCallTurn as a}from"./packem_shared/agentHarness-DA9onGVO.mjs";import{evaluationAttributes as l,recordEvaluation as n}from"./packem_shared/evaluationAttributes-BA1uUP-z.mjs";import{lunoraTest as s}from"./packem_shared/lunoraTest-CC-mCvor.mjs";import{groundednessScorer as p,mrrScorer as u,ndcgAtK as x,precisionAtK as S,recallAtK as f}from"./packem_shared/groundednessScorer-CznJBIjn.mjs";import{containsScorer as g,evaluate as A,exactMatchScorer as v,keywordScorer as K,llmScorer as M,regexScorer as T,scoreSample as k}from"./packem_shared/containsScorer-gv0Sdd86.mjs";import{extractLink as C,listCapturedMail as b,waitForMail as h}from"@lunora/mail/testing";export{o as agentHarness,g as containsScorer,A as evaluate,l as evaluationAttributes,v as exactMatchScorer,C as extractLink,t as finalTurn,p as groundednessScorer,K as keywordScorer,b as listCapturedMail,M as llmScorer,s as lunoraTest,u as mrrScorer,x as ndcgAtK,S as precisionAtK,f as recallAtK,n as recordEvaluation,T as regexScorer,k as scoreSample,a as toolCallTurn,h as waitForMail};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{LunoraError as g}from"@lunora/errors";const l=/^\s*(-?\d+(?:\.\d+)?)/u,i=e=>Number.isFinite(e)?Math.min(1,Math.max(0,e)):0,h=e=>typeof e=="number"?{score:i(e)}:{score:i(e.score),...e.reason===void 0?{}:{reason:e.reason}},u=e=>e.length===0?0:e.reduce((t,o)=>t+o,0)/e.length,y=(e,t={})=>({name:`contains:${e}`,score:({output:o})=>{const a=t.caseSensitive?o:o.toLowerCase(),r=t.caseSensitive?e:e.toLowerCase();return a.includes(r)?1:0}}),v=(e,t="regex")=>({name:t,score:({output:o})=>e.test(o)?1:0}),$=()=>({name:"exact-match",score:({expected:e,output:t})=>t.trim()===e?.trim()?1:0}),L=e=>{if(e.length===0)throw new g("BAD_REQUEST","@lunora/testing: keywordScorer requires at least one keyword");return{name:"keyword-coverage",score:({output:t})=>{const o=t.toLowerCase(),a=e.filter(r=>o.includes(r.toLowerCase())).length;return{reason:`${String(a)}/${String(e.length)} keywords present`,score:a/e.length}}}},S=(e,t)=>[`Rate the ASSISTANT OUTPUT against this criterion: ${e}`,"Respond with a single number from 0 (fails) to 1 (fully meets), then a dash and a one-line reason.",...t.input===void 0?[]:["",`Input: ${t.input}`],...t.expected===void 0?[]:["",`Reference answer: ${t.expected}`],"",`Assistant output: ${t.output}`].join(`
|
|
2
|
+
`),w=e=>{const t=l.exec(e);return{reason:e.trim(),score:t?i(Number(t[1])):0}},b=e=>({name:e.name??"llm-judge",score:async t=>w(await e.judge(S(e.criteria,t)))}),f=async(e,t)=>{const o=await Promise.all(t.map(async n=>({name:n.name,result:h(await n.score(e))}))),a={},r=new Map;for(const{name:n,result:s}of o){const c=r.get(n)??0;r.set(n,c+1),a[c===0?n:`${n}#${String(c+1)}`]=s}return{average:u(o.map(({result:n})=>n.score)),scores:a}},A=async(e,t,o)=>{const a=await Promise.all(e.map(async r=>{const n=await t(r.input),s=typeof n=="string"?n:n.output,c=typeof n=="string"||n.metadata===void 0?r.metadata:{...r.metadata,...n.metadata},m={input:r.input,output:s,...r.expected===void 0?{}:{expected:r.expected},...c===void 0?{}:{metadata:c}},{average:d,scores:p}=await f(m,o);return{average:d,input:r.input,output:s,scores:p}}));return{average:u(a.map(r=>r.average)),items:a}};export{y as containsScorer,A as evaluate,$ as exactMatchScorer,L as keywordScorer,b as llmScorer,w as parseJudgeScore,v as regexScorer,f as scoreSample};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{LunoraError as g}from"@lunora/errors";import{parseJudgeScore as h}from"./containsScorer-gv0Sdd86.mjs";const w="retrieved",v="relevant",l=(e,t)=>{const n=e.metadata?.[t];return Array.isArray(n)?n.filter(r=>typeof r=="string"&&r.length>0):[]},s=(e,t)=>{const n=l(e,t?.relevantKey??v);if(n.length!==0)return{relevant:new Set(n),retrieved:l(e,t?.retrievedKey??w)}},a=e=>({reason:`no gold ids under metadata.${e?.relevantKey??v} — cannot score retrieval`,score:0}),c=(e,t)=>{if(e!==void 0&&(!Number.isInteger(e)||e<1))throw new g("BAD_REQUEST",`@lunora/testing: ${t} \`k\` must be a positive integer`)},u=(e,t)=>t===void 0?e.retrieved:e.retrieved.slice(0,t),f=(e,t)=>{const n=new Set;for(const r of e)t.has(r)&&n.add(r);return n.size},S=(e,t)=>{const n=new Set;let r=0;for(const[o,i]of e.entries())t.has(i)&&!n.has(i)&&(n.add(i),r+=1/Math.log2(o+2));return r},m=e=>{let t=0;for(let n=0;n<e;n+=1)t+=1/Math.log2(n+2);return t},p=(e,t)=>(c(e,"recallAtK"),{name:e===void 0?"recall":`recall@${String(e)}`,score:n=>{const r=s(n,t);if(r===void 0)return a(t);const o=f(u(r,e),r.relevant);return{reason:`${String(o)}/${String(r.relevant.size)} gold ids retrieved`,score:o/r.relevant.size}}}),x=(e,t)=>(c(e,"precisionAtK"),{name:e===void 0?"precision":`precision@${String(e)}`,score:n=>{const r=s(n,t);if(r===void 0)return a(t);const o=u(r,e);if(o.length===0)return{reason:"nothing retrieved",score:0};const i=f(o,r.relevant);return{reason:`${String(i)}/${String(o.length)} retrieved ids are gold`,score:i/o.length}}}),y=e=>({name:"mrr",score:t=>{const n=s(t,e);if(n===void 0)return a(e);const r=n.retrieved.findIndex(o=>n.relevant.has(o));return r===-1?{reason:"no gold id retrieved",score:0}:{reason:`first gold id at rank ${String(r+1)}`,score:1/(r+1)}}}),$=(e,t)=>(c(e,"ndcgAtK"),{name:e===void 0?"ndcg":`ndcg@${String(e)}`,score:n=>{const r=s(n,t);if(r===void 0)return a(t);const o=u(r,e);if(o.length===0)return{reason:"nothing retrieved",score:0};const i=S(o,r.relevant),d=m(e===void 0?r.relevant.size:Math.min(r.relevant.size,e));return{reason:`dcg ${i.toFixed(3)} / ideal ${d.toFixed(3)}`,score:d===0?0:i/d}}}),K=e=>{if(typeof e.judge!="function")throw new g("BAD_REQUEST","@lunora/testing: groundednessScorer requires an injected `judge` function");const t=e.contextKey??"context";return{name:e.name??"groundedness",score:async n=>{const r=n.metadata?.[t];if(typeof r!="string"||r.trim().length===0)return{reason:`no retrieved context under metadata.${t}`,score:0};const o=await e.judge(["Rate how well the ASSISTANT ANSWER is supported by the RETRIEVED CONTEXT below.","Score 1 if every claim in the answer is supported by the context, 0 if the answer asserts","anything the context does not support. Judge support only — do NOT reward correctness","the context does not contain.","Respond with a single number from 0 to 1, then a dash and a one-line reason.","",`Retrieved context:
|
|
2
|
+
${r}`,"",`Assistant answer: ${n.output}`].join(`
|
|
3
|
+
`));return h(o)}}};export{K as groundednessScorer,y as mrrScorer,$ as ndcgAtK,x as precisionAtK,p as recallAtK};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/testing",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.112",
|
|
4
4
|
"description": "Testing toolkit for Lunora: an in-memory harness for queries, mutations, and actions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@lunora/agent": "1.0.0-alpha.
|
|
53
|
+
"@lunora/agent": "1.0.0-alpha.58",
|
|
54
54
|
"@lunora/errors": "1.0.0-alpha.22",
|
|
55
55
|
"@lunora/mail": "1.0.0-alpha.50",
|
|
56
|
-
"@lunora/server": "1.0.0-alpha.
|
|
57
|
-
"@lunora/shard-engine": "1.0.0-alpha.
|
|
56
|
+
"@lunora/server": "1.0.0-alpha.77",
|
|
57
|
+
"@lunora/shard-engine": "1.0.0-alpha.30"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@playwright/test": "^1.61.1"
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{LunoraError as d}from"@lunora/errors";const g=/^\s*(-?\d+(?:\.\d+)?)/u,i=e=>Number.isFinite(e)?Math.min(1,Math.max(0,e)):0,p=e=>typeof e=="number"?{score:i(e)}:{score:i(e.score),...e.reason===void 0?{}:{reason:e.reason}},u=e=>e.length===0?0:e.reduce((t,n)=>t+n,0)/e.length,x=(e,t={})=>({name:`contains:${e}`,score:({output:n})=>{const a=t.caseSensitive?n:n.toLowerCase(),r=t.caseSensitive?e:e.toLowerCase();return a.includes(r)?1:0}}),f=(e,t="regex")=>({name:t,score:({output:n})=>e.test(n)?1:0}),v=()=>({name:"exact-match",score:({expected:e,output:t})=>t.trim()===e?.trim()?1:0}),y=e=>{if(e.length===0)throw new d("BAD_REQUEST","@lunora/testing: keywordScorer requires at least one keyword");return{name:"keyword-coverage",score:({output:t})=>{const n=t.toLowerCase(),a=e.filter(r=>n.includes(r.toLowerCase())).length;return{reason:`${String(a)}/${String(e.length)} keywords present`,score:a/e.length}}}},l=(e,t)=>[`Rate the ASSISTANT OUTPUT against this criterion: ${e}`,"Respond with a single number from 0 (fails) to 1 (fully meets), then a dash and a one-line reason.",...t.input===void 0?[]:["",`Input: ${t.input}`],...t.expected===void 0?[]:["",`Reference answer: ${t.expected}`],"",`Assistant output: ${t.output}`].join(`
|
|
2
|
-
`),h=e=>{const t=g.exec(e);return{reason:e.trim(),score:t?i(Number(t[1])):0}},$=e=>({name:e.name??"llm-judge",score:async t=>h(await e.judge(l(e.criteria,t)))}),S=async(e,t)=>{const n=await Promise.all(t.map(async o=>({name:o.name,result:p(await o.score(e))}))),a={},r=new Map;for(const{name:o,result:s}of n){const c=r.get(o)??0;r.set(o,c+1),a[c===0?o:`${o}#${String(c+1)}`]=s}return{average:u(n.map(({result:o})=>o.score)),scores:a}},L=async(e,t,n)=>{const a=await Promise.all(e.map(async r=>{const o=await t(r.input),s={input:r.input,output:o,...r.expected===void 0?{}:{expected:r.expected},...r.metadata===void 0?{}:{metadata:r.metadata}},{average:c,scores:m}=await S(s,n);return{average:c,input:r.input,output:o,scores:m}}));return{average:u(a.map(r=>r.average)),items:a}};export{x as containsScorer,L as evaluate,v as exactMatchScorer,y as keywordScorer,$ as llmScorer,f as regexScorer,S as scoreSample};
|