@@ -6,6 +6,7 @@ import org.evomaster.client.java.instrumentation.shared.ObjectiveNaming
66import org.evomaster.core.EMConfig
77import org.evomaster.core.output.service.PartialOracles
88import org.evomaster.core.problem.httpws.HttpWsCallResult
9+ import org.evomaster.core.problem.rest.service.AIResponseClassifier
910import org.evomaster.core.remote.service.RemoteController
1011import org.evomaster.core.search.Solution
1112import org.evomaster.core.utils.IncrementalAverage
@@ -57,6 +58,9 @@ class Statistics : SearchListener {
5758 @Inject
5859 private lateinit var oracles: PartialOracles
5960
61+ @Inject(optional = true )
62+ private lateinit var aiResponseClassifier: AIResponseClassifier
63+
6064 /* *
6165 * How often test executions did timeout
6266 */
@@ -126,25 +130,24 @@ class Statistics : SearchListener {
126130 }
127131 }
128132
129- val headers = " interval," + snapshots.values.first().map { it.header }.joinToString(" ," )
133+ // All headers, including AI-related ones
134+ val headers = " interval," + snapshots.values.first().joinToString(" ," ) { it.header }
130135
131136 val path = Paths .get(config.snapshotStatisticsFile).toAbsolutePath()
132-
133137 Files .createDirectories(path.parent)
134138
135- if (! Files .exists(path) or ! config.appendToStatisticsFile) {
139+ if (! Files .exists(path) || ! config.appendToStatisticsFile) {
136140 Files .deleteIfExists(path)
137141 Files .createFile(path)
138-
139142 path.toFile().appendText(" $headers \n " )
140143 }
141144
142145 snapshots.entries.stream()
143- .sorted { o1, o2 -> o1.key.compareTo(o2.key) }
144- .forEach {
145- val elements = it.value.map { it.element }.joinToString( " , " )
146- path.toFile().appendText(" ${it. key} ,$elements \n " )
147- }
146+ .sorted { o1, o2 -> o1.key.compareTo(o2.key) }
147+ .forEach { (key, pairs) ->
148+ val elements = pairs.joinToString( " , " ) { it.element }
149+ path.toFile().appendText(" $key ,$elements \n " )
150+ }
148151 }
149152
150153
@@ -339,9 +342,60 @@ class Statistics : SearchListener {
339342 }
340343 addConfig(list)
341344
345+ // Adding AI data
346+ list.addAll(getAIData())
347+
342348 return list
343349 }
344350
351+ // For building AI metric pairs
352+ fun aiMetricsAsPairs (
353+ enabled : Boolean ,
354+ type : String ,
355+ accuracy : Double ,
356+ precision : Double ,
357+ recall : Double ,
358+ f1 : Double ,
359+ mcc : Double
360+ ): List <Pair > = listOf (
361+ Pair (" ai_model_enabled" , enabled.toString()),
362+ Pair (" ai_model_type" , type),
363+ Pair (" ai_accuracy" , " %.4f" .format(accuracy)),
364+ Pair (" ai_precision400" , " %.4f" .format(precision)),
365+ Pair (" ai_recall400" , " %.4f" .format(recall)),
366+ Pair (" ai_f1Score400" , " %.4f" .format(f1)),
367+ Pair (" ai_mcc400" , " %.4f" .format(mcc))
368+ )
369+
370+ fun getAIData (): List <Pair > {
371+ // AI model is unable
372+ if (! config.isEnabledAIModelForResponseClassification()) {
373+ return aiMetricsAsPairs(
374+ enabled = false ,
375+ type = " NONE" ,
376+ accuracy = 0.0 ,
377+ precision = 0.0 ,
378+ recall = 0.0 ,
379+ f1 = 0.0 ,
380+ mcc = 0.0
381+ )
382+ }
383+
384+ // Compute metrics
385+ val metrics = aiResponseClassifier.viewInnerModel().estimateOverallMetrics()
386+
387+ return aiMetricsAsPairs(
388+ enabled = true ,
389+ type = config.aiModelForResponseClassification.name,
390+ accuracy = metrics.accuracy,
391+ precision = metrics.precision400,
392+ recall = metrics.recall400,
393+ f1 = metrics.f1Score400,
394+ mcc = metrics.mcc
395+ )
396+ }
397+
398+
345399 private fun distinctActions () : Int {
346400 if (sampler == null ){
347401 return 0
0 commit comments