Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion packages/shared/src/research/claim-verifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { JudgeClient } from '../evaluation/judge-client.ts';
import {
CLAIM_VERIFIER_VERSION,
createClaimVerifier,
verifyReportClaims,
type ClaimVerificationInput,
} from './claim-verifier.ts';

function input(
claimText: string,
evidence: ClaimVerificationInput['evidence'],
Expand Down Expand Up @@ -143,4 +143,33 @@ describe('claim verifier', () => {
expect(result.evidenceIds).toEqual(['source-evidence']);
expect(result.evidenceIds).not.toContain('Explanation only.');
});

it('verifies all claims across research report sections end to end', async () => {
const judge = judgeReturning('{"status":"supported","reason":"Confirmed by filing excerpt."}');
const verifier = createClaimVerifier(judge);
const report = {
id: 'report-123',
sections: [
{
evidence: [
{ claim: 'Revenue rose 10%', runId: 'sec-edgar-run-1', summary: 'SEC 10-Q filing notes 10% revenue increase' },
{ claim: 'Gross margin expanded', runId: 'run-2', summary: 'Margin up 150bps' },
],
},
{
evidence: [
{ claim: 'Revenue rose 10%', runId: 'sec-edgar-run-1', summary: 'Same claim in conclusion' },
],
},
],
};
const summary = await verifyReportClaims(report, verifier);

expect(summary.reportId).toBe('report-123');
expect(summary.totalClaims).toBe(2); // Deduplicated identical claim statement
expect(summary.supported).toBe(2);
expect(summary.contradicted).toBe(0);
expect(summary.insufficientEvidence).toBe(0);
expect(summary.results).toHaveLength(2);
});
});
58 changes: 58 additions & 0 deletions packages/shared/src/research/claim-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,64 @@ export interface ClaimVerifier {
verify(input: ClaimVerificationInput, signal?: AbortSignal): Promise<ClaimVerificationResult>;
}

export interface ReportClaimVerificationSummary {
reportId: string;
verifiedAt: number;
totalClaims: number;
supported: number;
contradicted: number;
insufficientEvidence: number;
results: ClaimVerificationResult[];
}

/**
* Verify all atomic claims within a ResearchReport against their associated evidence.
* Extracts EvidenceRef claims across all sections and checks each using the ClaimVerifier.
*/
export async function verifyReportClaims(
report: { id: string; sections?: Array<{ evidence?: Array<{ claim: string; runId?: string; summary?: string }> }> },
verifier: ClaimVerifier,
signal?: AbortSignal
): Promise<ReportClaimVerificationSummary> {
const seenClaims = new Map<string, ClaimVerifierEvidence[]>();
for (const section of report.sections ?? []) {
for (const ref of section.evidence ?? []) {
if (!ref.claim) continue;
const existing = seenClaims.get(ref.claim) ?? [];
if (ref.summary) {
existing.push({ id: ref.runId ?? `evidence-${existing.length + 1}`, content: ref.summary });
}
seenClaims.set(ref.claim, existing);
}
}

const results: ClaimVerificationResult[] = [];
let supported = 0;
let contradicted = 0;
let insufficientEvidence = 0;

let idx = 0;
for (const [claimText, evidenceList] of seenClaims.entries()) {
idx += 1;
const claimId = `claim-${idx}`;
const res = await verifier.verify({ claimId, claimText, evidence: evidenceList }, signal);
results.push(res);
if (res.status === 'supported') supported += 1;
else if (res.status === 'contradicted') contradicted += 1;
else insufficientEvidence += 1;
}

return {
reportId: report.id,
verifiedAt: Date.now(),
totalClaims: results.length,
supported,
contradicted,
insufficientEvidence,
results,
};
}

interface JudgeResult {
status: ClaimVerificationStatus;
reason: string;
Expand Down
11 changes: 11 additions & 0 deletions packages/shared/src/research/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,14 @@ export {
type SanitizePolicy,
type SanitizeResult,
} from './sanitize.ts';
export {
CLAIM_VERIFIER_VERSION,
createClaimVerifier,
verifyReportClaims,
type ClaimVerificationInput,
type ClaimVerificationResult,
type ClaimVerificationStatus,
type ClaimVerifier,
type ClaimVerifierEvidence,
type ReportClaimVerificationSummary,
} from './claim-verifier.ts';