@indemn/cli 1.0.0 → 1.1.0
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/.claude-plugin/marketplace.json +28 -0
- package/README.md +29 -23
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +20 -0
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +38 -0
- package/dist/cli/eval.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +40 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/report/agent-card/cover.d.ts +20 -0
- package/dist/report/agent-card/cover.d.ts.map +1 -0
- package/dist/report/agent-card/cover.js +58 -0
- package/dist/report/agent-card/cover.js.map +1 -0
- package/dist/report/agent-card/document.d.ts +7 -0
- package/dist/report/agent-card/document.d.ts.map +1 -0
- package/dist/report/agent-card/document.js +144 -0
- package/dist/report/agent-card/document.js.map +1 -0
- package/dist/report/agent-card/types.d.ts +77 -0
- package/dist/report/agent-card/types.d.ts.map +1 -0
- package/dist/report/agent-card/types.js +3 -0
- package/dist/report/agent-card/types.js.map +1 -0
- package/dist/report/assets/Indemn_PrimaryLogo_Iris.png +0 -0
- package/dist/report/assets/assets/Indemn_PrimaryLogo_Iris.png +0 -0
- package/dist/report/assets/assets/fonts/Barlow-Bold.ttf +0 -0
- package/dist/report/assets/assets/fonts/Barlow-Medium.ttf +0 -0
- package/dist/report/assets/assets/fonts/Barlow-Regular.ttf +0 -0
- package/dist/report/assets/assets/fonts/Barlow-SemiBold.ttf +0 -0
- package/dist/report/assets/fonts/Barlow-Bold.ttf +0 -0
- package/dist/report/assets/fonts/Barlow-Medium.ttf +0 -0
- package/dist/report/assets/fonts/Barlow-Regular.ttf +0 -0
- package/dist/report/assets/fonts/Barlow-SemiBold.ttf +0 -0
- package/dist/report/eval-report/cover.d.ts +16 -0
- package/dist/report/eval-report/cover.d.ts.map +1 -0
- package/dist/report/eval-report/cover.js +8 -0
- package/dist/report/eval-report/cover.js.map +1 -0
- package/dist/report/eval-report/document.d.ts +19 -0
- package/dist/report/eval-report/document.d.ts.map +1 -0
- package/dist/report/eval-report/document.js +32 -0
- package/dist/report/eval-report/document.js.map +1 -0
- package/dist/report/eval-report/example-page.d.ts +11 -0
- package/dist/report/eval-report/example-page.d.ts.map +1 -0
- package/dist/report/eval-report/example-page.js +18 -0
- package/dist/report/eval-report/example-page.js.map +1 -0
- package/dist/report/eval-report/item-page.d.ts +9 -0
- package/dist/report/eval-report/item-page.d.ts.map +1 -0
- package/dist/report/eval-report/item-page.js +72 -0
- package/dist/report/eval-report/item-page.js.map +1 -0
- package/dist/report/eval-report/matrix-page.d.ts +8 -0
- package/dist/report/eval-report/matrix-page.d.ts.map +1 -0
- package/dist/report/eval-report/matrix-page.js +31 -0
- package/dist/report/eval-report/matrix-page.js.map +1 -0
- package/dist/report/eval-report/types.d.ts +69 -0
- package/dist/report/eval-report/types.d.ts.map +1 -0
- package/dist/report/eval-report/types.js +7 -0
- package/dist/report/eval-report/types.js.map +1 -0
- package/dist/report/eval-report/v2-cover.d.ts +9 -0
- package/dist/report/eval-report/v2-cover.d.ts.map +1 -0
- package/dist/report/eval-report/v2-cover.js +32 -0
- package/dist/report/eval-report/v2-cover.js.map +1 -0
- package/dist/report/export.d.ts +17 -0
- package/dist/report/export.d.ts.map +1 -0
- package/dist/report/export.js +327 -0
- package/dist/report/export.js.map +1 -0
- package/dist/report/generate.d.ts +7 -0
- package/dist/report/generate.d.ts.map +1 -0
- package/dist/report/generate.js +30 -0
- package/dist/report/generate.js.map +1 -0
- package/dist/report/scoring.d.ts +90 -0
- package/dist/report/scoring.d.ts.map +1 -0
- package/dist/report/scoring.js +174 -0
- package/dist/report/scoring.js.map +1 -0
- package/dist/report/styles.d.ts +111 -0
- package/dist/report/styles.d.ts.map +1 -0
- package/dist/report/styles.js +134 -0
- package/dist/report/styles.js.map +1 -0
- package/docs/plans/2026-03-13-cli-exports.md +819 -0
- package/package.json +14 -4
- package/skills/eval-analysis/SKILL.md +178 -0
- package/skills/eval-analysis/references/data-shapes.md +279 -0
- package/src/cli/agents.ts +22 -0
- package/src/cli/eval.ts +42 -0
- package/src/mcp/tools.ts +50 -0
- package/src/report/agent-card/cover.tsx +232 -0
- package/src/report/agent-card/document.tsx +582 -0
- package/src/report/agent-card/types.ts +86 -0
- package/src/report/assets/Indemn_PrimaryLogo_Iris.png +0 -0
- package/src/report/assets/fonts/Barlow-Bold.ttf +0 -0
- package/src/report/assets/fonts/Barlow-Medium.ttf +0 -0
- package/src/report/assets/fonts/Barlow-Regular.ttf +0 -0
- package/src/report/assets/fonts/Barlow-SemiBold.ttf +0 -0
- package/src/report/eval-report/cover.tsx +69 -0
- package/src/report/eval-report/document.tsx +105 -0
- package/src/report/eval-report/example-page.tsx +121 -0
- package/src/report/eval-report/item-page.tsx +211 -0
- package/src/report/eval-report/matrix-page.tsx +124 -0
- package/src/report/eval-report/types.ts +77 -0
- package/src/report/eval-report/v2-cover.tsx +102 -0
- package/src/report/export.ts +394 -0
- package/src/report/generate.tsx +37 -0
- package/src/report/scoring.ts +281 -0
- package/src/report/styles.ts +138 -0
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Eval report types
|
|
2
|
+
|
|
3
|
+
export type Severity = 'High' | 'Medium' | 'Low';
|
|
4
|
+
|
|
5
|
+
export interface EvaluatorInfo {
|
|
6
|
+
key: string;
|
|
7
|
+
name: string;
|
|
8
|
+
severity: Severity;
|
|
9
|
+
prompt?: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface MatrixCell {
|
|
14
|
+
score: number;
|
|
15
|
+
comment: string | null;
|
|
16
|
+
passed: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface MatrixRow {
|
|
20
|
+
resultId: string;
|
|
21
|
+
testCaseId: string;
|
|
22
|
+
question: string;
|
|
23
|
+
response: string;
|
|
24
|
+
cells: Record<string, MatrixCell>;
|
|
25
|
+
avgScore: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MatrixData {
|
|
29
|
+
evaluators: EvaluatorInfo[];
|
|
30
|
+
rows: MatrixRow[];
|
|
31
|
+
columnAverages: Record<string, number>;
|
|
32
|
+
overallAverage: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface WeightConfig {
|
|
36
|
+
High: number;
|
|
37
|
+
Medium: number;
|
|
38
|
+
Low: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const DEFAULT_WEIGHTS: WeightConfig = {
|
|
42
|
+
High: 2,
|
|
43
|
+
Medium: 1,
|
|
44
|
+
Low: 0.5,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// V2 result types
|
|
48
|
+
export interface CriterionScore {
|
|
49
|
+
criterion: string;
|
|
50
|
+
passed: boolean;
|
|
51
|
+
score: number;
|
|
52
|
+
reasoning: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RubricRuleScore {
|
|
56
|
+
rule_id: string;
|
|
57
|
+
rule_name: string;
|
|
58
|
+
severity: string;
|
|
59
|
+
passed: boolean;
|
|
60
|
+
score: number;
|
|
61
|
+
reasoning: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface EvaluationResult {
|
|
65
|
+
result_id: string;
|
|
66
|
+
test_case_id: string;
|
|
67
|
+
input: { message?: string } & Record<string, unknown>;
|
|
68
|
+
output: { response?: string } & Record<string, unknown>;
|
|
69
|
+
scores: Record<string, { score: number; comment: string | null }>;
|
|
70
|
+
item_name?: string;
|
|
71
|
+
item_type?: 'single_turn' | 'scenario' | 'transcript' | 'voice_simulation';
|
|
72
|
+
criteria_scores?: CriterionScore[];
|
|
73
|
+
criteria_passed?: boolean;
|
|
74
|
+
rubric_scores?: RubricRuleScore[];
|
|
75
|
+
rubric_passed?: boolean;
|
|
76
|
+
passed?: boolean;
|
|
77
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// V2 eval report cover — ported from EvalReportV2Cover.tsx
|
|
2
|
+
import { Page, View, Text, Image } from '@react-pdf/renderer';
|
|
3
|
+
import { styles, colors, logoPath } from '../styles.js';
|
|
4
|
+
import { getResultsScoreMetrics, getScoreHexColor } from '../scoring.js';
|
|
5
|
+
import type { EvaluationResult } from './types.js';
|
|
6
|
+
|
|
7
|
+
interface EvalReportV2CoverProps {
|
|
8
|
+
customerName: string;
|
|
9
|
+
reportDate: string;
|
|
10
|
+
results: EvaluationResult[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function EvalReportV2Cover({ customerName, reportDate, results }: EvalReportV2CoverProps) {
|
|
14
|
+
const metrics = getResultsScoreMetrics(results);
|
|
15
|
+
const criteriaRate = metrics.criteriaRate !== null ? Math.round(metrics.criteriaRate * 100) : 0;
|
|
16
|
+
const rubricRate = metrics.rubricRate !== null ? Math.round(metrics.rubricRate * 100) : null;
|
|
17
|
+
|
|
18
|
+
const singleTurnCount = results.filter(r => r.item_type === 'single_turn' || r.item_type === undefined).length;
|
|
19
|
+
const scenarioCount = results.filter(r => r.item_type === 'scenario').length;
|
|
20
|
+
const transcriptCount = results.filter(r => r.item_type === 'transcript').length;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Page size="A4" style={[styles.page, { justifyContent: 'center' }]}>
|
|
24
|
+
{/* Logo */}
|
|
25
|
+
<View style={{ alignItems: 'center', marginBottom: 48 }}>
|
|
26
|
+
<Image src={logoPath} style={{ width: 200, height: 46 }} />
|
|
27
|
+
</View>
|
|
28
|
+
|
|
29
|
+
{/* Title */}
|
|
30
|
+
<View style={{ alignItems: 'center', marginBottom: 48 }}>
|
|
31
|
+
<Text style={{ fontSize: 28, fontWeight: 700, color: colors.eggplant, marginBottom: 8 }}>
|
|
32
|
+
Evaluation Report
|
|
33
|
+
</Text>
|
|
34
|
+
<Text style={{ fontSize: 16, fontWeight: 500, color: colors.muted }}>
|
|
35
|
+
{customerName}
|
|
36
|
+
</Text>
|
|
37
|
+
<Text style={{ fontSize: 14, fontWeight: 400, color: colors.muted, marginTop: 4 }}>
|
|
38
|
+
{reportDate}
|
|
39
|
+
</Text>
|
|
40
|
+
</View>
|
|
41
|
+
|
|
42
|
+
{/* Key Highlights */}
|
|
43
|
+
<View style={[styles.card, { marginHorizontal: 40 }]}>
|
|
44
|
+
<Text style={[styles.sectionTitle, { textAlign: 'center', marginBottom: 16 }]}>
|
|
45
|
+
Key Highlights
|
|
46
|
+
</Text>
|
|
47
|
+
|
|
48
|
+
<View style={{ gap: 8 }}>
|
|
49
|
+
{/* Scores - side by side */}
|
|
50
|
+
<View style={{ flexDirection: 'row', justifyContent: 'center', gap: 32, marginBottom: 8 }}>
|
|
51
|
+
{metrics.criteriaTotal > 0 && (
|
|
52
|
+
<View style={{ alignItems: 'center' }}>
|
|
53
|
+
<Text style={{
|
|
54
|
+
fontSize: 40,
|
|
55
|
+
fontWeight: 700,
|
|
56
|
+
color: getScoreHexColor(metrics.criteriaRate),
|
|
57
|
+
}}>
|
|
58
|
+
{criteriaRate}%
|
|
59
|
+
</Text>
|
|
60
|
+
<Text style={{ fontSize: 12, fontWeight: 500, color: colors.muted }}>
|
|
61
|
+
Criteria ({metrics.criteriaPassed}/{metrics.criteriaTotal} checks)
|
|
62
|
+
</Text>
|
|
63
|
+
</View>
|
|
64
|
+
)}
|
|
65
|
+
{rubricRate !== null && (
|
|
66
|
+
<View style={{ alignItems: 'center' }}>
|
|
67
|
+
<Text style={{
|
|
68
|
+
fontSize: 40,
|
|
69
|
+
fontWeight: 700,
|
|
70
|
+
color: getScoreHexColor(metrics.rubricRate),
|
|
71
|
+
}}>
|
|
72
|
+
{rubricRate}%
|
|
73
|
+
</Text>
|
|
74
|
+
<Text style={{ fontSize: 12, fontWeight: 500, color: colors.muted }}>
|
|
75
|
+
Rubric ({metrics.rubricRulesPassed}/{metrics.rubricRulesTotal} rules)
|
|
76
|
+
</Text>
|
|
77
|
+
</View>
|
|
78
|
+
)}
|
|
79
|
+
</View>
|
|
80
|
+
|
|
81
|
+
{/* Item type breakdown */}
|
|
82
|
+
<Text style={styles.bodyText}>
|
|
83
|
+
{'\u2022'} {results.length} test items evaluated
|
|
84
|
+
{(() => {
|
|
85
|
+
const parts: string[] = [];
|
|
86
|
+
if (singleTurnCount > 0) parts.push(`${singleTurnCount} single-turn`);
|
|
87
|
+
if (scenarioCount > 0) parts.push(`${scenarioCount} scenario${scenarioCount > 1 ? 's' : ''}`);
|
|
88
|
+
if (transcriptCount > 0) parts.push(`${transcriptCount} transcript${transcriptCount > 1 ? 's' : ''}`);
|
|
89
|
+
return parts.length > 1 ? ` (${parts.join(', ')})` : parts.length === 1 ? ` (all ${parts[0]})` : '';
|
|
90
|
+
})()}
|
|
91
|
+
</Text>
|
|
92
|
+
</View>
|
|
93
|
+
</View>
|
|
94
|
+
|
|
95
|
+
{/* Footer */}
|
|
96
|
+
<View style={[styles.footer, { position: 'absolute', bottom: 40 }]}>
|
|
97
|
+
<Text style={styles.caption}>Powered by Indemn Evals</Text>
|
|
98
|
+
<Text style={styles.caption}>Page 1</Text>
|
|
99
|
+
</View>
|
|
100
|
+
</Page>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
// Shared export logic used by both CLI commands and MCP tools
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { IndemnClient } from '../sdk/client.js';
|
|
5
|
+
import { EvalSDK } from '../sdk/eval.js';
|
|
6
|
+
import { RubricsSDK } from '../sdk/rubrics.js';
|
|
7
|
+
import { TestSetsSDK } from '../sdk/test-sets.js';
|
|
8
|
+
import { AgentsSDK } from '../sdk/agents.js';
|
|
9
|
+
import { generateAgentCardPDF, generateEvalReportPDF } from './generate.js';
|
|
10
|
+
import { getRunScoreMetrics, getRunCriteriaScore, getRunRubricScore, getResultsScoreMetrics } from './scoring.js';
|
|
11
|
+
import type { AgentCardData, AgentCardTool, AgentCardKnowledgeBase } from './agent-card/types.js';
|
|
12
|
+
import type { EvalReportData } from './eval-report/document.js';
|
|
13
|
+
import type { EvaluationResult } from './eval-report/types.js';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Markdown export
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
export async function exportEvalMarkdown(
|
|
20
|
+
runId: string,
|
|
21
|
+
outputPath?: string,
|
|
22
|
+
client?: IndemnClient,
|
|
23
|
+
): Promise<{ file_path: string; file_name: string; file_type: string }> {
|
|
24
|
+
const c = client || new IndemnClient();
|
|
25
|
+
const evalSdk = new EvalSDK(c);
|
|
26
|
+
const rubricsSdk = new RubricsSDK(c);
|
|
27
|
+
const testSetsSdk = new TestSetsSDK(c);
|
|
28
|
+
|
|
29
|
+
const [run, results] = await Promise.all([
|
|
30
|
+
evalSdk.getRunStatus(runId),
|
|
31
|
+
evalSdk.getRunResults(runId),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const [rubric, testSet, botContext] = await Promise.all([
|
|
35
|
+
run.rubric_id ? rubricsSdk.getRubric(run.rubric_id).catch(() => null) : null,
|
|
36
|
+
run.test_set_id ? testSetsSdk.getTestSet(run.test_set_id).catch(() => null) : null,
|
|
37
|
+
run.agent_id ? evalSdk.getBotContext(run.agent_id).catch(() => null) : null,
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const markdown = formatEvalMarkdown(run, results, rubric, testSet, botContext);
|
|
41
|
+
const agentName = (botContext as any)?.bot_name || run.agent_id || 'Unknown';
|
|
42
|
+
const date = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
43
|
+
const cleanName = agentName.replace(/[^a-zA-Z0-9\s]/g, '').replace(/\s+/g, '_');
|
|
44
|
+
const cleanDate = date.replace(/[^a-zA-Z0-9]/g, '_');
|
|
45
|
+
const defaultFilename = `Indemn_Evaluation_${cleanName}_${cleanDate}.md`;
|
|
46
|
+
|
|
47
|
+
const resolvedPath = path.resolve(outputPath || defaultFilename);
|
|
48
|
+
fs.writeFileSync(resolvedPath, markdown);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
file_path: resolvedPath,
|
|
52
|
+
file_name: path.basename(resolvedPath),
|
|
53
|
+
file_type: 'markdown',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function formatEvalMarkdown(
|
|
58
|
+
run: any,
|
|
59
|
+
results: any[],
|
|
60
|
+
rubric: any,
|
|
61
|
+
testSet: any,
|
|
62
|
+
botContext: any,
|
|
63
|
+
): string {
|
|
64
|
+
const lines: string[] = [];
|
|
65
|
+
const agentName = botContext?.bot_name || run.agent_id || 'Unknown';
|
|
66
|
+
const orgName = botContext?.organization_name || '';
|
|
67
|
+
const model = run.bot_llm_model ? `${run.bot_llm_provider || ''}/${run.bot_llm_model}` : 'N/A';
|
|
68
|
+
const date = run.completed_at || run.started_at || new Date().toISOString();
|
|
69
|
+
|
|
70
|
+
// Header
|
|
71
|
+
lines.push(`# Evaluation Results — ${agentName}`);
|
|
72
|
+
lines.push('');
|
|
73
|
+
lines.push(`**Run:** ${run.run_id}`);
|
|
74
|
+
lines.push(`**Date:** ${new Date(date).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}`);
|
|
75
|
+
lines.push(`**Agent:** ${agentName} (${run.agent_id})`);
|
|
76
|
+
if (orgName) lines.push(`**Organization:** ${orgName}`);
|
|
77
|
+
lines.push(`**Model:** ${model}`);
|
|
78
|
+
lines.push(`**Status:** ${run.status}`);
|
|
79
|
+
lines.push('');
|
|
80
|
+
|
|
81
|
+
// Scores
|
|
82
|
+
if (run.criteria_total !== undefined) {
|
|
83
|
+
lines.push(`**Criteria:** ${run.criteria_passed ?? 0}/${run.criteria_total} checks passed (${run.criteria_total > 0 ? Math.round(((run.criteria_passed ?? 0) / run.criteria_total) * 100) : 0}%)`);
|
|
84
|
+
}
|
|
85
|
+
if (run.rubric_rules_total !== undefined && run.rubric_rules_total > 0) {
|
|
86
|
+
lines.push(`**Rubric:** ${run.rubric_rules_passed ?? 0}/${run.rubric_rules_total} rules passed (${Math.round(((run.rubric_rules_passed ?? 0) / run.rubric_rules_total) * 100)}%)`);
|
|
87
|
+
}
|
|
88
|
+
if (run.total !== undefined) {
|
|
89
|
+
lines.push(`**Items:** ${run.passed ?? 0}/${run.total} passed, ${run.failed ?? 0} failed`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Component scores
|
|
93
|
+
if (run.component_scores) {
|
|
94
|
+
lines.push('');
|
|
95
|
+
lines.push('**Component Scores:**');
|
|
96
|
+
for (const [scope, data] of Object.entries(run.component_scores as Record<string, any>)) {
|
|
97
|
+
if (data && data.total > 0) {
|
|
98
|
+
lines.push(`- ${scope}: ${data.passed}/${data.total} (${Math.round((data.passed / data.total) * 100)}%)`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Rubric info
|
|
104
|
+
if (rubric) {
|
|
105
|
+
lines.push('');
|
|
106
|
+
lines.push(`**Rubric:** ${rubric.name} (v${rubric.version || 1}, ${rubric.rules?.length || 0} rules)`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Test set info
|
|
110
|
+
if (testSet) {
|
|
111
|
+
lines.push(`**Test Set:** ${testSet.name} (v${testSet.version || 1}, ${testSet.items?.length || 0} items)`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lines.push('');
|
|
115
|
+
lines.push('---');
|
|
116
|
+
lines.push('');
|
|
117
|
+
|
|
118
|
+
// Per-result details
|
|
119
|
+
for (let i = 0; i < results.length; i++) {
|
|
120
|
+
const r = results[i];
|
|
121
|
+
const itemName = r.item_name || r.input?.message || `Test Item ${i + 1}`;
|
|
122
|
+
const itemType = r.item_type || 'single_turn';
|
|
123
|
+
const passed = r.passed ? 'PASS' : 'FAIL';
|
|
124
|
+
|
|
125
|
+
// Match test item from test set
|
|
126
|
+
let testItem: any = null;
|
|
127
|
+
if (testSet?.items) {
|
|
128
|
+
testItem = testSet.items.find((t: any) => t.item_id === r.test_case_id);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
lines.push(`## ${i + 1}. ${itemName}`);
|
|
132
|
+
lines.push('');
|
|
133
|
+
lines.push(`**Type:** ${itemType} | **Result:** ${passed}${r.duration_ms ? ` | **Duration:** ${(r.duration_ms / 1000).toFixed(1)}s` : ''}`);
|
|
134
|
+
lines.push('');
|
|
135
|
+
|
|
136
|
+
// Conversation / response
|
|
137
|
+
if (r.output?.messages && Array.isArray(r.output.messages)) {
|
|
138
|
+
lines.push('### Conversation');
|
|
139
|
+
lines.push('');
|
|
140
|
+
for (const msg of r.output.messages) {
|
|
141
|
+
lines.push(`> **${msg.role === 'user' ? 'User' : 'Agent'}:** ${msg.content}`);
|
|
142
|
+
lines.push('');
|
|
143
|
+
}
|
|
144
|
+
} else if (r.output?.response) {
|
|
145
|
+
lines.push('### Response');
|
|
146
|
+
lines.push('');
|
|
147
|
+
lines.push(`> ${r.output.response}`);
|
|
148
|
+
lines.push('');
|
|
149
|
+
} else if (r.output?.transcript) {
|
|
150
|
+
lines.push('### Transcript');
|
|
151
|
+
lines.push('');
|
|
152
|
+
lines.push('```');
|
|
153
|
+
lines.push(r.output.transcript);
|
|
154
|
+
lines.push('```');
|
|
155
|
+
lines.push('');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Criteria scores
|
|
159
|
+
if (r.criteria_scores && r.criteria_scores.length > 0) {
|
|
160
|
+
lines.push('### Criteria Scores');
|
|
161
|
+
lines.push('');
|
|
162
|
+
lines.push('| Criterion | Result | Score | Reasoning |');
|
|
163
|
+
lines.push('|-----------|--------|-------|-----------|');
|
|
164
|
+
for (const c of r.criteria_scores) {
|
|
165
|
+
const reasoning = (c.reasoning || '').replace(/\|/g, '\\|').replace(/\n/g, ' ').slice(0, 200);
|
|
166
|
+
lines.push(`| ${c.criterion.replace(/\|/g, '\\|')} | ${c.passed ? 'PASS' : 'FAIL'} | ${Math.round(c.score * 100)}% | ${reasoning} |`);
|
|
167
|
+
}
|
|
168
|
+
lines.push('');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Rubric scores
|
|
172
|
+
if (r.rubric_scores && r.rubric_scores.length > 0) {
|
|
173
|
+
lines.push('### Rubric Scores');
|
|
174
|
+
lines.push('');
|
|
175
|
+
lines.push('| Rule | Severity | Result | Score | Reasoning |');
|
|
176
|
+
lines.push('|------|----------|--------|-------|-----------|');
|
|
177
|
+
for (const rule of r.rubric_scores) {
|
|
178
|
+
const reasoning = (rule.reasoning || '').replace(/\|/g, '\\|').replace(/\n/g, ' ').slice(0, 200);
|
|
179
|
+
lines.push(`| ${rule.rule_name.replace(/\|/g, '\\|')} | ${rule.severity} | ${rule.passed ? 'PASS' : 'FAIL'} | ${Math.round(rule.score * 100)}% | ${reasoning} |`);
|
|
180
|
+
}
|
|
181
|
+
lines.push('');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// V1 scores
|
|
185
|
+
if (r.scores && Object.keys(r.scores).length > 0 && !r.criteria_scores) {
|
|
186
|
+
lines.push('### Scores');
|
|
187
|
+
lines.push('');
|
|
188
|
+
lines.push('| Evaluator | Score | Comment |');
|
|
189
|
+
lines.push('|-----------|-------|---------|');
|
|
190
|
+
for (const [key, val] of Object.entries(r.scores as Record<string, any>)) {
|
|
191
|
+
const comment = (val.comment || '').replace(/\|/g, '\\|').replace(/\n/g, ' ').slice(0, 200);
|
|
192
|
+
lines.push(`| ${key} | ${val.score} | ${comment} |`);
|
|
193
|
+
}
|
|
194
|
+
lines.push('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (i < results.length - 1) {
|
|
198
|
+
lines.push('---');
|
|
199
|
+
lines.push('');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Eval report PDF
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
export async function exportEvalReport(
|
|
211
|
+
runId: string,
|
|
212
|
+
outputPath?: string,
|
|
213
|
+
client?: IndemnClient,
|
|
214
|
+
): Promise<{ file_path: string; file_name: string; file_type: string }> {
|
|
215
|
+
const c = client || new IndemnClient();
|
|
216
|
+
const evalSdk = new EvalSDK(c);
|
|
217
|
+
|
|
218
|
+
const [run, results] = await Promise.all([
|
|
219
|
+
evalSdk.getRunStatus(runId),
|
|
220
|
+
evalSdk.getRunResults(runId),
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
const botContext = run.agent_id ? await evalSdk.getBotContext(run.agent_id).catch(() => null) : null;
|
|
224
|
+
|
|
225
|
+
const isV2 = results.length > 0 && (results[0] as any).criteria_scores != null;
|
|
226
|
+
const customerName = (botContext as any)?.organization_name || (botContext as any)?.bot_name || run.agent_id || 'Unknown';
|
|
227
|
+
const reportDate = new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
|
228
|
+
|
|
229
|
+
let reportData: EvalReportData;
|
|
230
|
+
if (isV2) {
|
|
231
|
+
reportData = { customerName, reportDate, v2Results: results as unknown as EvaluationResult[] };
|
|
232
|
+
} else {
|
|
233
|
+
// V1 — for now throw a clear error since V1 matrix transform requires evaluator config
|
|
234
|
+
throw new Error('V1 report format not yet supported for PDF export. Use "eval export" for markdown output.');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const filePath = await generateEvalReportPDF(reportData, outputPath);
|
|
238
|
+
return {
|
|
239
|
+
file_path: filePath,
|
|
240
|
+
file_name: path.basename(filePath),
|
|
241
|
+
file_type: 'pdf',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Agent card PDF
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
export async function exportAgentCard(
|
|
250
|
+
agentId: string,
|
|
251
|
+
outputPath?: string,
|
|
252
|
+
client?: IndemnClient,
|
|
253
|
+
): Promise<{ file_path: string; file_name: string; file_type: string }> {
|
|
254
|
+
const c = client || new IndemnClient();
|
|
255
|
+
const agentsSdk = new AgentsSDK(c);
|
|
256
|
+
const evalSdk = new EvalSDK(c);
|
|
257
|
+
|
|
258
|
+
const agent = await agentsSdk.getAgent(agentId);
|
|
259
|
+
|
|
260
|
+
// Fetch config and functions from copilot server
|
|
261
|
+
let config: any = null;
|
|
262
|
+
let functions: any[] = [];
|
|
263
|
+
try {
|
|
264
|
+
config = await c.copilotGet(`/organization/:org_id/ai-studio/bots/${agentId}/configurations`);
|
|
265
|
+
} catch { /* config may not be available */ }
|
|
266
|
+
try {
|
|
267
|
+
functions = await c.copilotGet(`/organization/:org_id/ai-studio/bots/${agentId}/functions`) as any[];
|
|
268
|
+
} catch { /* functions may not be available */ }
|
|
269
|
+
|
|
270
|
+
// Fetch bot context from eval service for additional data
|
|
271
|
+
let botContext: any = null;
|
|
272
|
+
try {
|
|
273
|
+
botContext = await evalSdk.getBotContext(agentId);
|
|
274
|
+
} catch { /* eval service may not have this agent */ }
|
|
275
|
+
|
|
276
|
+
// Fetch eval runs
|
|
277
|
+
let evalRuns: any[] = [];
|
|
278
|
+
try {
|
|
279
|
+
evalRuns = await evalSdk.listRuns(agentId);
|
|
280
|
+
} catch { /* evals may not be available */ }
|
|
281
|
+
|
|
282
|
+
const cardData = mapToAgentCardData(agent, config, functions, evalRuns, botContext);
|
|
283
|
+
const filePath = await generateAgentCardPDF(cardData, outputPath);
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
file_path: filePath,
|
|
287
|
+
file_name: path.basename(filePath),
|
|
288
|
+
file_type: 'pdf',
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function mapToAgentCardData(
|
|
293
|
+
agent: any,
|
|
294
|
+
config: any,
|
|
295
|
+
functions: any[],
|
|
296
|
+
evalRuns: any[],
|
|
297
|
+
botContext: any,
|
|
298
|
+
): AgentCardData {
|
|
299
|
+
const generatedDate = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
300
|
+
|
|
301
|
+
// Map tools from bot context (richer) or functions
|
|
302
|
+
const tools: AgentCardTool[] = (botContext?.tools || functions || []).map((f: any) => ({
|
|
303
|
+
id: f.id || f._id || '',
|
|
304
|
+
name: f.name || f.tool_name || '',
|
|
305
|
+
tool_name: f.tool_name || f.name || '',
|
|
306
|
+
type: f.type || 'unknown',
|
|
307
|
+
description: f.description,
|
|
308
|
+
}));
|
|
309
|
+
|
|
310
|
+
// Map knowledge bases from bot context
|
|
311
|
+
const knowledge_bases: AgentCardKnowledgeBase[] = (botContext?.knowledge_bases || []).map((kb: any) => ({
|
|
312
|
+
id: kb.id || kb._id || '',
|
|
313
|
+
name: kb.name || '',
|
|
314
|
+
type: kb.type || 'unknown',
|
|
315
|
+
document_count: kb.document_count || 0,
|
|
316
|
+
}));
|
|
317
|
+
|
|
318
|
+
// System prompt
|
|
319
|
+
const system_prompt = botContext?.system_prompt || config?.ai_config?.system_prompt;
|
|
320
|
+
|
|
321
|
+
// LLM config
|
|
322
|
+
const llm_config = botContext?.llm_config ? {
|
|
323
|
+
provider: botContext.llm_config.provider,
|
|
324
|
+
model: botContext.llm_config.model,
|
|
325
|
+
parameters: botContext.llm_config.parameters,
|
|
326
|
+
} : undefined;
|
|
327
|
+
|
|
328
|
+
// Map eval runs
|
|
329
|
+
const completedRuns = evalRuns.filter((r: any) => r.status === 'completed');
|
|
330
|
+
const evaluationRuns = completedRuns.map((r: any) => {
|
|
331
|
+
const metrics = getRunScoreMetrics(r);
|
|
332
|
+
return {
|
|
333
|
+
run_id: r.run_id,
|
|
334
|
+
status: r.status,
|
|
335
|
+
created_at: r.created_at || r.started_at || '',
|
|
336
|
+
completed_at: r.completed_at,
|
|
337
|
+
bot_llm_model: r.bot_llm_model,
|
|
338
|
+
bot_llm_provider: r.bot_llm_provider,
|
|
339
|
+
criteriaScore: getRunCriteriaScore(r),
|
|
340
|
+
rubricScore: getRunRubricScore(r),
|
|
341
|
+
criteriaPassed: metrics.criteriaPassed,
|
|
342
|
+
criteriaTotal: metrics.criteriaTotal,
|
|
343
|
+
rubricRulesPassed: metrics.rubricRulesPassed,
|
|
344
|
+
rubricRulesTotal: metrics.rubricRulesTotal,
|
|
345
|
+
total: r.total || 0,
|
|
346
|
+
completed: r.completed || 0,
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Evaluation summary across all completed runs
|
|
351
|
+
let evaluationSummary = undefined;
|
|
352
|
+
if (completedRuns.length > 0) {
|
|
353
|
+
let totalCriteriaPassed = 0, totalCriteriaTotal = 0;
|
|
354
|
+
let totalRubricPassed = 0, totalRubricTotal = 0;
|
|
355
|
+
for (const r of completedRuns) {
|
|
356
|
+
const m = getRunScoreMetrics(r);
|
|
357
|
+
totalCriteriaPassed += m.criteriaPassed;
|
|
358
|
+
totalCriteriaTotal += m.criteriaTotal;
|
|
359
|
+
totalRubricPassed += m.rubricRulesPassed;
|
|
360
|
+
totalRubricTotal += m.rubricRulesTotal;
|
|
361
|
+
}
|
|
362
|
+
evaluationSummary = {
|
|
363
|
+
criteriaRate: totalCriteriaTotal > 0 ? totalCriteriaPassed / totalCriteriaTotal : null,
|
|
364
|
+
rubricRate: totalRubricTotal > 0 ? totalRubricPassed / totalRubricTotal : null,
|
|
365
|
+
criteriaPassed: totalCriteriaPassed,
|
|
366
|
+
criteriaTotal: totalCriteriaTotal,
|
|
367
|
+
rubricRulesPassed: totalRubricPassed,
|
|
368
|
+
rubricRulesTotal: totalRubricTotal,
|
|
369
|
+
totalRuns: completedRuns.length,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Component scores from latest run
|
|
374
|
+
const latestRun = completedRuns[0];
|
|
375
|
+
const componentScores = latestRun?.component_scores;
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
id: agent._id,
|
|
379
|
+
name: agent.name || 'Unnamed Agent',
|
|
380
|
+
description: agent.description,
|
|
381
|
+
channels: (agent.channels || ['WEB']).map((c: any) =>
|
|
382
|
+
typeof c === 'string' ? c : c.name || c.type || String(c)
|
|
383
|
+
),
|
|
384
|
+
organization_id: agent.id_project || (botContext as any)?.organization_id,
|
|
385
|
+
system_prompt,
|
|
386
|
+
tools,
|
|
387
|
+
knowledge_bases,
|
|
388
|
+
llm_config,
|
|
389
|
+
generatedDate,
|
|
390
|
+
evaluationSummary,
|
|
391
|
+
componentScores,
|
|
392
|
+
evaluationRuns,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// PDF rendering driver — generates agent card and eval report PDFs
|
|
2
|
+
import { renderToBuffer } from '@react-pdf/renderer';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { AgentCardDocument } from './agent-card/document.js';
|
|
6
|
+
import { EvalReportDocument } from './eval-report/document.js';
|
|
7
|
+
import type { AgentCardData } from './agent-card/types.js';
|
|
8
|
+
import type { EvalReportData } from './eval-report/document.js';
|
|
9
|
+
|
|
10
|
+
function generateFilename(prefix: string, name: string, date: string, ext: string): string {
|
|
11
|
+
const cleanName = name.replace(/[^a-zA-Z0-9\s]/g, '').replace(/\s+/g, '_');
|
|
12
|
+
const cleanDate = date.replace(/[^a-zA-Z0-9]/g, '_');
|
|
13
|
+
return `${prefix}_${cleanName}_${cleanDate}.${ext}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function generateAgentCardPDF(data: AgentCardData, outputPath?: string): Promise<string> {
|
|
17
|
+
const filename = outputPath || generateFilename('Indemn_Agent_Card', data.name, data.generatedDate, 'pdf');
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
const buffer = await renderToBuffer(<AgentCardDocument data={data} /> as any);
|
|
20
|
+
const resolvedPath = path.resolve(filename);
|
|
21
|
+
fs.writeFileSync(resolvedPath, buffer);
|
|
22
|
+
return resolvedPath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function generateEvalReportPDF(data: EvalReportData, outputPath?: string): Promise<string> {
|
|
26
|
+
const customerName = data.customerName;
|
|
27
|
+
const filename = outputPath || generateFilename('Indemn_Evaluation', customerName, data.reportDate, 'pdf');
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
const buffer = await renderToBuffer(<EvalReportDocument data={data} /> as any);
|
|
30
|
+
const resolvedPath = path.resolve(filename);
|
|
31
|
+
fs.writeFileSync(resolvedPath, buffer);
|
|
32
|
+
return resolvedPath;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Re-export types for convenience
|
|
36
|
+
export type { AgentCardData } from './agent-card/types.js';
|
|
37
|
+
export type { EvalReportData, EvalReportDataV1, EvalReportDataV2 } from './eval-report/document.js';
|