@@ -127,3 +127,123 @@ export function writeLiveEvalReport(summaries: LiveScenarioSummary[], reportPath
127127 writeFileSync ( reportPath , `${ JSON . stringify ( report , null , 2 ) } \n` )
128128 writeFileSync ( reportPath . replace ( / \. j s o n $ / , '.md' ) , renderLiveMarkdown ( report ) )
129129}
130+
131+ /** One trial of one model on one scenario, for the comparison report. */
132+ export interface LiveModelRun {
133+ provider : string
134+ model : string
135+ scenarioId : string
136+ result : AgentToolUseResult
137+ }
138+
139+ /** Aggregate behavior of one model across the suite. */
140+ export interface LiveModelSummary {
141+ provider : string
142+ model : string
143+ label : string
144+ trials : number
145+ passed : number
146+ passRate : number
147+ avgIterations : number
148+ avgLatencyMs : number
149+ totalTokens : number
150+ }
151+
152+ /** Side-by-side comparison of several models on the same scenarios. */
153+ export interface LiveComparisonReport {
154+ suite : 'agent-tool-use-compare'
155+ generatedAt : string
156+ scenarios : string [ ]
157+ models : LiveModelSummary [ ]
158+ /** scenarioId -> model label -> pass rate. */
159+ matrix : Record < string , Record < string , number > >
160+ }
161+
162+ function modelLabel ( run : LiveModelRun ) : string {
163+ return `${ run . provider } /${ run . model } `
164+ }
165+
166+ export function buildLiveComparisonReport ( runs : LiveModelRun [ ] ) : LiveComparisonReport {
167+ const byModel = new Map < string , LiveModelRun [ ] > ( )
168+ for ( const run of runs ) {
169+ const key = modelLabel ( run )
170+ const list = byModel . get ( key ) ?? [ ]
171+ list . push ( run )
172+ byModel . set ( key , list )
173+ }
174+
175+ const scenarios = [ ...new Set ( runs . map ( ( run ) => run . scenarioId ) ) ] . sort ( )
176+ const models : LiveModelSummary [ ] = [ ]
177+ const matrix : Record < string , Record < string , number > > = { }
178+
179+ for ( const [ label , modelRuns ] of byModel ) {
180+ const results = modelRuns . map ( ( run ) => run . result )
181+ const passed = results . filter ( ( result ) => result . passed ) . length
182+ const [ provider , model ] = label . split ( '/' )
183+ models . push ( {
184+ provider,
185+ model,
186+ label,
187+ trials : results . length ,
188+ passed,
189+ passRate : results . length === 0 ? 0 : passed / results . length ,
190+ avgIterations : average ( results . map ( ( result ) => result . metrics . iterations ) ) ,
191+ avgLatencyMs : average ( results . map ( ( result ) => result . metrics . latencyMs ) ) ,
192+ totalTokens : results . reduce ( ( sum , result ) => sum + result . metrics . totalTokens , 0 ) ,
193+ } )
194+
195+ for ( const scenario of scenarios ) {
196+ const scenarioRuns = modelRuns . filter ( ( run ) => run . scenarioId === scenario )
197+ const scenarioPassed = scenarioRuns . filter ( ( run ) => run . result . passed ) . length
198+ matrix [ scenario ] ??= { }
199+ matrix [ scenario ] [ label ] = scenarioRuns . length === 0 ? 0 : scenarioPassed / scenarioRuns . length
200+ }
201+ }
202+
203+ models . sort ( ( a , b ) => b . passRate - a . passRate )
204+ return {
205+ suite : 'agent-tool-use-compare' ,
206+ generatedAt : new Date ( ) . toISOString ( ) ,
207+ scenarios,
208+ models,
209+ matrix,
210+ }
211+ }
212+
213+ function renderComparisonMarkdown ( report : LiveComparisonReport ) : string {
214+ const labels = report . models . map ( ( model ) => model . label )
215+ const lines = [
216+ '# Agent tool-use model comparison' ,
217+ '' ,
218+ `Generated: ${ report . generatedAt } ` ,
219+ '' ,
220+ '| Model | Pass rate | Trials | Avg iterations | Avg latency | Tokens |' ,
221+ '| --- | ---: | ---: | ---: | ---: | ---: |' ,
222+ ...report . models . map (
223+ ( model ) =>
224+ `| ${ model . label } | ${ ( model . passRate * 100 ) . toFixed ( 0 ) } % (${ model . passed } /${ model . trials } ) | ${ model . trials } | ${ model . avgIterations . toFixed ( 1 ) } | ${ Math . round ( model . avgLatencyMs ) } ms | ${ model . totalTokens } |`
225+ ) ,
226+ '' ,
227+ `| Scenario | ${ labels . join ( ' | ' ) } |` ,
228+ `| --- | ${ labels . map ( ( ) => '---:' ) . join ( ' | ' ) } |` ,
229+ ...report . scenarios . map (
230+ ( scenario ) =>
231+ `| ${ escapeCell ( scenario ) } | ${ labels
232+ . map ( ( label ) => `${ ( ( report . matrix [ scenario ] ?. [ label ] ?? 0 ) * 100 ) . toFixed ( 0 ) } %` )
233+ . join ( ' | ' ) } |`
234+ ) ,
235+ '' ,
236+ ]
237+ return lines . join ( '\n' )
238+ }
239+
240+ /**
241+ * Writes the model-comparison JSON report and a sibling Markdown table.
242+ * Unlike the single-model report, this is a matrix: scenario × model pass rates.
243+ */
244+ export function writeLiveComparisonReport ( runs : LiveModelRun [ ] , reportPath : string ) : void {
245+ const report = buildLiveComparisonReport ( runs )
246+ mkdirSync ( dirname ( reportPath ) , { recursive : true } )
247+ writeFileSync ( reportPath , `${ JSON . stringify ( report , null , 2 ) } \n` )
248+ writeFileSync ( reportPath . replace ( / \. j s o n $ / , '.md' ) , renderComparisonMarkdown ( report ) )
249+ }
0 commit comments