Skip to content

Commit 67f9099

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
fix: GenAI Client(evals) - fix visualization
PiperOrigin-RevId: 825084384
1 parent 458fa3f commit 67f9099

File tree

1 file changed

+68
-51
lines changed

1 file changed

+68
-51
lines changed

vertexai/_genai/_evals_visualization.py

Lines changed: 68 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -283,26 +283,27 @@ def _get_evaluation_html(eval_result_json: str) -> str:
283283
let traceHtml = `<div class="trace-event-row"><div class="name"><span class="icon">🏃</span>agent_run</div></div>`;
284284
eventsArray.forEach(event => {{
285285
if (event.content && event.content.parts && event.content.parts.length > 0) {{
286-
const part = event.content.parts[0];
287-
if (part.function_call) {{
288-
traceHtml += `<div class="trace-details-wrapper"><details><summary><div class="trace-event-row"><div class="name trace-l1"><span class="icon">🛠️</span>function_call</div></div></summary>`;
289-
traceHtml += `<div class="trace-details details-l1">function name: ${{part.function_call.name}}</div>`;
290-
traceHtml += `<div class="trace-details details-l1">function args: ${{formatDictVals(part.function_call.args)}}</div></details></div>`;
291-
}} else if (part.text && event.content.role === 'model') {{
292-
traceHtml += `<div class="trace-details-wrapper"><details><summary><div class="trace-event-row"><div class="name trace-l1"><span class="icon">💬</span>call_llm</div></div></summary>`;
293-
traceHtml += `<div class="trace-details details-l1">model response: ${{part.text}}</div></details></div>`;
294-
}} else if (part.function_response) {{
295-
traceHtml += `<div class="trace-details-wrapper"><details><summary><div class="trace-event-row"><div class="name trace-l1"><span class="icon">🛠️</span>function_response</div></div></summary>`;
296-
traceHtml += `<div class="trace-details details-l1">function name: ${{part.function_response.name}}</div>`;
297-
let response_val = part.function_response.response;
298-
if(typeof response_val === 'object' && response_val !== null && response_val.result !== undefined) {{
299-
response_val = response_val.result;
286+
event.content.parts.forEach(part => {{
287+
if (part.function_call) {{
288+
traceHtml += `<div class="trace-details-wrapper"><details><summary><div class="trace-event-row"><div class="name trace-l1"><span class="icon">🛠️</span>function_call</div></div></summary>`;
289+
traceHtml += `<div class="trace-details details-l1">function name: ${{part.function_call.name}}</div>`;
290+
traceHtml += `<div class="trace-details details-l1">function args: ${{formatDictVals(part.function_call.args)}}</div></details></div>`;
291+
}} else if (part.text && event.content.role === 'model') {{
292+
traceHtml += `<div class="trace-details-wrapper"><details><summary><div class="trace-event-row"><div class="name trace-l1"><span class="icon">💬</span>call_llm</div></div></summary>`;
293+
traceHtml += `<div class="trace-details details-l1">model response: ${{part.text}}</div></details></div>`;
294+
}} else if (part.function_response) {{
295+
traceHtml += `<div class="trace-details-wrapper"><details><summary><div class="trace-event-row"><div class="name trace-l1"><span class="icon">🛠️</span>function_response</div></div></summary>`;
296+
traceHtml += `<div class="trace-details details-l1">function name: ${{part.function_response.name}}</div>`;
297+
let response_val = part.function_response.response;
298+
if(typeof response_val === 'object' && response_val !== null && response_val.result !== undefined) {{
299+
response_val = response_val.result;
300+
}}
301+
traceHtml += `<div class="trace-details details-l1">function response: ${{formatDictVals(response_val)}}</div></details></div>`;
302+
}} else {{
303+
// Skipping user messages and other parts in trace view
304+
return;
300305
}}
301-
traceHtml += `<div class="trace-details details-l1">function response: ${{formatDictVals(response_val)}}</div></details></div>`;
302-
}} else {{
303-
// Skipping user messages and other parts in trace view
304-
return;
305-
}}
306+
}});
306307
}}
307308
}});
308309
return traceHtml;
@@ -313,16 +314,17 @@ def _get_evaluation_html(eval_result_json: str) -> str:
313314
const role = event.content.role;
314315
let contentHtml = '';
315316
if (event.content && event.content.parts && event.content.parts.length > 0) {{
316-
const part = event.content.parts[0];
317-
if (part.text) {{
318-
contentHtml = DOMPurify.sanitize(marked.parse(String(part.text)));
319-
}} else if (part.function_call) {{
320-
contentHtml = `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(part.function_call, null, 2))}}</pre>`;
321-
}} else if (part.function_response) {{
322-
contentHtml = `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(part.function_response, null, 2))}}</pre>`;
323-
}} else {{
324-
contentHtml = `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(event.content, null, 2))}}</pre>`;
325-
}}
317+
event.content.parts.forEach(part => {{
318+
if (part.text) {{
319+
contentHtml += DOMPurify.sanitize(marked.parse(String(part.text)));
320+
}} else if (part.function_call) {{
321+
contentHtml += `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(part.function_call, null, 2))}}</pre>`;
322+
}} else if (part.function_response) {{
323+
contentHtml += `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(part.function_response, null, 2))}}</pre>`;
324+
}} else {{
325+
contentHtml += `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(part, null, 2))}}</pre>`;
326+
}}
327+
}});
326328
}} else {{
327329
contentHtml = `<pre class="raw-json-container">${{DOMPurify.sanitize(JSON.stringify(event.content, null, 2))}}</pre>`;
328330
}}
@@ -456,28 +458,43 @@ def _get_evaluation_html(eval_result_json: str) -> str:
456458
457459
if (name.startsWith('hallucination') && val.explanation) {{
458460
try {{
459-
const explanationData = JSON.parse(val.explanation);
460-
if (Array.isArray(explanationData) && explanationData.length > 0 && explanationData[0].sentence) {{
461-
bubbles += '<div class="rubric-bubble-container" style="margin-top: 8px;">';
462-
explanationData.forEach(item => {{
463-
let sentence = item.sentence || 'N/A';
464-
const label = item.label ? item.label.toLowerCase() : '';
465-
const isPass = label === 'no_rad' || label === 'supported';
466-
const verdictText = isPass ? '<span class="pass">Pass</span>' : '<span class="fail">Fail</span>';
467-
if (isPass) {{
468-
sentence = `"${{sentence}}" is grounded`;
469-
}}
470-
const rationale = item.rationale || 'N/A';
471-
const itemJson = JSON.stringify(item, null, 2);
472-
bubbles += `
473-
<details class="rubric-details">
474-
<summary class="rubric-bubble">${{verdictText}}: ${{DOMPurify.sanitize(sentence)}}</summary>
475-
<div class="explanation" style="padding: 10px 0 0 20px;">${{DOMPurify.sanitize(rationale)}}</div>
476-
<pre class="raw-json-container">${{DOMPurify.sanitize(itemJson)}}</pre>
477-
</details>`;
478-
}});
479-
bubbles += '</div>';
480-
explanationHandled = true;
461+
const explanationData = typeof val.explanation === 'string' ? JSON.parse(val.explanation) : val.explanation;
462+
if (Array.isArray(explanationData) && explanationData.length > 0) {{
463+
let sentenceGroups = [];
464+
if (explanationData[0].explanation && Array.isArray(explanationData[0].explanation)) {{
465+
explanationData.forEach(item => {{
466+
if(item.explanation && Array.isArray(item.explanation)) {{
467+
sentenceGroups.push(item.explanation);
468+
}}
469+
}});
470+
}} else if (explanationData[0].sentence) {{
471+
sentenceGroups.push(explanationData);
472+
}}
473+
474+
if(sentenceGroups.length > 0) {{
475+
sentenceGroups.forEach(sentenceList => {{
476+
bubbles += '<div class="rubric-bubble-container" style="margin-top: 8px;">';
477+
sentenceList.forEach(item => {{
478+
let sentence = item.sentence || 'N/A';
479+
const label = item.label ? item.label.toLowerCase() : '';
480+
const isPass = label === 'no_rad' || label === 'supported';
481+
const verdictText = isPass ? '<span class="pass">Pass</span>' : '<span class="fail">Fail</span>';
482+
if (isPass) {{
483+
sentence = `"${{sentence}}" is grounded`;
484+
}}
485+
const rationale = item.rationale || 'N/A';
486+
const itemJson = JSON.stringify(item, null, 2);
487+
bubbles += `
488+
<details class="rubric-details">
489+
<summary class="rubric-bubble">${{verdictText}}: ${{DOMPurify.sanitize(sentence)}}</summary>
490+
<div class="explanation" style="padding: 10px 0 0 20px;">${{DOMPurify.sanitize(rationale)}}</div>
491+
<pre class="raw-json-container">${{DOMPurify.sanitize(itemJson)}}</pre>
492+
</details>`;
493+
}});
494+
bubbles += '</div>';
495+
}});
496+
explanationHandled = true;
497+
}}
481498
}}
482499
}} catch (e) {{
483500
console.error("Failed to parse hallucination explanation:", e);

0 commit comments

Comments
 (0)