@@ -754,6 +754,41 @@ function buildGraderTelemetry(graderOutput, eventTimeMs) {
754754 return { attributes, events } ;
755755}
756756
757+ /**
758+ * Build summary attributes and per-result events from BinEval JSONL records.
759+ * Only bounded, structured fields are included; free-form questions are excluded.
760+ *
761+ * @param {unknown[] } evalResults
762+ * @param {number } eventTimeMs
763+ * @returns {{attributes: Array<{key: string, value: object}>, events: Array<{timeUnixNano: string, name: string, attributes: Array<{key: string, value: object}>}>} }
764+ */
765+ function buildEvalTelemetry ( evalResults , eventTimeMs ) {
766+ if ( ! Array . isArray ( evalResults ) ) {
767+ return { attributes : [ ] , events : [ ] } ;
768+ }
769+ const results = evalResults . filter ( result => result && typeof result === "object" && typeof result . id === "string" && result . id ) ;
770+ if ( results . length === 0 ) {
771+ return { attributes : [ ] , events : [ ] } ;
772+ }
773+
774+ const normalizedAnswers = results . map ( result => {
775+ const answer = typeof result . answer === "string" ? result . answer . toUpperCase ( ) : "UNKNOWN" ;
776+ return answer === "YES" || answer === "NO" ? answer : "UNKNOWN" ;
777+ } ) ;
778+ const countAnswer = answer => normalizedAnswers . filter ( value => value === answer ) . length ;
779+ const attributes = [ buildAttr ( "gh-aw.evals.count" , results . length ) , buildAttr ( "gh-aw.evals.yes" , countAnswer ( "YES" ) ) , buildAttr ( "gh-aw.evals.no" , countAnswer ( "NO" ) ) , buildAttr ( "gh-aw.evals.unknown" , countAnswer ( "UNKNOWN" ) ) ] ;
780+ const timeUnixNano = toNanoString ( eventTimeMs ) ;
781+ const events = results . map ( ( result , index ) => {
782+ const resultAttributes = [ buildAttr ( "gh-aw.eval.id" , result . id ) , buildAttr ( "gh-aw.eval.answer" , normalizedAnswers [ index ] ) ] ;
783+ if ( typeof result . model === "string" && result . model ) {
784+ resultAttributes . push ( buildAttr ( "gh-aw.eval.model" , result . model ) ) ;
785+ }
786+ return { timeUnixNano, name : "eval.result" , attributes : resultAttributes } ;
787+ } ) ;
788+
789+ return { attributes, events } ;
790+ }
791+
757792// ---------------------------------------------------------------------------
758793// Custom OTLP attributes (GH_AW_OTLP_ATTRIBUTES)
759794// ---------------------------------------------------------------------------
@@ -2376,6 +2411,19 @@ async function sendJobConclusionSpan(spanName, options = {}) {
23762411 }
23772412
23782413 const graderTelemetry = jobName === "agent" ? buildGraderTelemetry ( readJSONIfExists ( "/tmp/gh-aw/agent/graders/grader_results.json" ) , endMs ) : { attributes : [ ] , events : [ ] } ;
2414+ const evalTelemetry =
2415+ jobName === "evals"
2416+ ? buildEvalTelemetry (
2417+ ( ( ) => {
2418+ try {
2419+ return parseJsonlContent ( fs . readFileSync ( "/tmp/gh-aw/evals.jsonl" , "utf8" ) ) ;
2420+ } catch {
2421+ return [ ] ;
2422+ }
2423+ } ) ( ) ,
2424+ endMs
2425+ )
2426+ : { attributes : [ ] , events : [ ] } ;
23792427
23802428 const resourceAttributes = buildGitHubActionsResourceAttributes ( {
23812429 repository,
@@ -2435,7 +2483,7 @@ async function sendJobConclusionSpan(spanName, options = {}) {
24352483 } ) ;
24362484 } ;
24372485
2438- const spanEvents = [ ...buildSpanEvents ( endMs ) , ...graderTelemetry . events ] ;
2486+ const spanEvents = [ ...buildSpanEvents ( endMs ) , ...graderTelemetry . events , ... evalTelemetry . events ] ;
24392487
24402488 // Prefer the timestamp written at the very beginning of the Execute Agent CLI step
24412489 // (captures true step start on the host, before the AWF container launches) so the
@@ -2534,6 +2582,7 @@ async function sendJobConclusionSpan(spanName, options = {}) {
25342582 // conclusion span, rather than its dedicated child span or downstream jobs
25352583 // which may have downloaded the agent artifact.
25362584 attributes . push ( ...graderTelemetry . attributes ) ;
2585+ attributes . push ( ...evalTelemetry . attributes ) ;
25372586
25382587 // Only attach token-usage attributes to jobs that actually executed model usage.
25392588 // Most downstream jobs (conclusion, safe_outputs) may have agent_usage.json on
@@ -2620,6 +2669,7 @@ module.exports = {
26202669 appendToOTLPJSONL,
26212670 buildExperimentAttributes,
26222671 buildGraderTelemetry,
2672+ buildEvalTelemetry,
26232673 parseOTLPCustomAttributes,
26242674 buildCustomOTLPAttributes,
26252675} ;
0 commit comments