"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleHallucination = void 0;
exports.createHalloumiPrompt = createHalloumiPrompt;
exports.getClaimsFromResponse = getClaimsFromResponse;
const invariant_1 = __importDefault(require("../util/invariant"));
const defaults_1 = require("../providers/defaults");
const defaults_2 = require("../providers/defaults");
/**
 * Splits a given text into sentences using sentence-splitter.
 * @param text The input string to split.
 * @returns An array of sentence strings.
 */
function splitIntoSentences(text) {
    const SegmenterCtor = Intl.Segmenter;
    const segmenter = new SegmenterCtor('en', { granularity: 'sentence' });
    const segments = segmenter.segment(text);
    const finalSentences = [];
    let shortSentenceString = "";
    for (const { segment } of segments) {
        // Assume that a sentence is more than 8 characters.
        if (segment.length > 8) {
            finalSentences.push(shortSentenceString + segment);
            shortSentenceString = "";
        }
        else {
            shortSentenceString += segment;
        }
    }
    return finalSentences;
}
/**
 * Annotate a set of sentences with a given annotation character.
 * @param sentences A list of sentences to annotate.
 * @param annotationChar The character to use for annotation.
 * @returns The annotated string with annotation characters + sentence number.
 */
function annotate(sentences, annotationChar) {
    const annotatedSentences = [];
    let sentenceNumber = 0;
    for (const sentence of sentences) {
        sentenceNumber++;
        const annotatedSentence = `<|${annotationChar}${sentenceNumber}|><${sentence}><end||${annotationChar}>`;
        annotatedSentences.push(annotatedSentence);
    }
    return annotatedSentences.join("");
}
function getOffsets(originalString, sentences) {
    const offsets = new Map();
    let stringProgressPointer = 0;
    let sentenceId = 1;
    for (const sentence of sentences) {
        const stringToSearch = originalString.slice(stringProgressPointer);
        const startOffset = stringToSearch.indexOf(sentence) + stringProgressPointer;
        const endOffset = startOffset + sentence.length;
        stringProgressPointer = endOffset;
        offsets.set(sentenceId, { startOffset, endOffset });
        sentenceId++;
    }
    return offsets;
}
/**
 * Creates a Halloumi prompt from a given context, request and response.
 * @param context The context or document to reference.
 * @param response The response to the request.
 * @param request The request or question that was used to produce the response.
 * @returns The Halloumi prompt.
 */
function createHalloumiPrompt(context, response, request = "Make one or more claims about information in the documents.") {
    const contextSentences = splitIntoSentences(context);
    const contextOffsets = getOffsets(context, contextSentences);
    const annotatedContextSentences = annotate(contextSentences, "s");
    const annotatedContext = `<|context|>${annotatedContextSentences}<end||context>`;
    const annotatedRequest = `<|request|><${request.trim()}><end||request>`;
    const responseSentences = splitIntoSentences(response);
    const responseOffsets = getOffsets(response, responseSentences);
    const annotatedResponseSentences = annotate(responseSentences, "r");
    const annotatedResponse = `<|response|>${annotatedResponseSentences}<end||response>`;
    const prompt = `${annotatedContext}${annotatedRequest}${annotatedResponse}`;
    const halloumiPrompt = {
        prompt,
        contextOffsets,
        responseOffsets
    };
    return halloumiPrompt;
}
/**
 * Gets the claim id from a subsegment.
 * @param subsegment A subsegment string of the form "<|r1|".
 * @returns The numeric claim id.
 */
function getClaimIdFromSubsegment(subsegment) {
    const textClaimId = subsegment.split("|")[1];
    const integerClaimId = Number.parseInt(textClaimId.split("r")[1]);
    return integerClaimId;
}
/**
 * Gets the citations from a subsegment.
 * @param subsegment A subsegment string of the form "|s1|,|s2|,|s3|,|s4|".
 * @returns The list of numeric citations.
 */
function getClaimCitationsFromSubsegment(subsegment) {
    const citationSegments = subsegment.split(",");
    const citations = [];
    for (const citationSegment of citationSegments) {
        const citation = citationSegment.replace(/\|/g, "").replace(/s/g, "");
        if (citation.includes("-")) {
            const citationRange = citation.split("-");
            for (let i = Number.parseInt(citationRange[0].trim()); i <= Number.parseInt(citationRange[1].trim()); i++) {
                citations.push(i);
            }
        }
        else if (citation.includes("to")) {
            const citationRange = citation.split("to");
            for (let i = Number.parseInt(citationRange[0].trim()); i <= Number.parseInt(citationRange[1].trim()); i++) {
                citations.push(i);
            }
        }
        else {
            const citationInt = Number.parseInt(citation);
            if (!Number.isNaN(citationInt)) {
                citations.push(Number.parseInt(citation));
            }
        }
    }
    return citations;
}
/**
 * Gets the support status from a subsegment.
 * @param subsegment A subsegment string of the form "|supported|" or "|unsupported|".
 * @returns True if the claim is supported, false otherwise.
 */
function getSupportStatusFromSubsegment(subsegment) {
    return subsegment.startsWith("|supported|");
}
/**
 * Gets a claim from a segment string.
 * @param segment A segment string containing all information for claim verification.
 * @returns The claim object with all relevant information.
 */
function getClaimFromSegment(segment) {
    const claim_segments = segment.split("><");
    const claimId = getClaimIdFromSubsegment(claim_segments[0]);
    const claimString = claim_segments[1];
    const subclaims = [];
    let claimProgressIndex = 3; // Start at 3 to skip the claim id, claim string and the subclaims tag
    for (let i = claimProgressIndex; i < claim_segments.length; i++) {
        const subsegment = claim_segments[i];
        if (subsegment.startsWith("end||subclaims")) {
            claimProgressIndex = i + 1;
            break;
        }
        else {
            subclaims.push(subsegment);
        }
    }
    let citation_index = -1;
    let explanation_index = -1;
    let label_index = -1;
    for (let i = claimProgressIndex; i < claim_segments.length; i++) {
        const subsegment = claim_segments[i];
        if (subsegment.startsWith("|cite|")) {
            citation_index = i + 1;
        }
        else if (subsegment.startsWith("|explain|")) {
            explanation_index = i + 1;
        }
        else if (subsegment.startsWith("|supported|") || subsegment.startsWith("|unsupported|")) {
            label_index = i;
        }
    }
    const citations = getClaimCitationsFromSubsegment(claim_segments[citation_index]);
    const explanation = claim_segments[explanation_index];
    const supported = getSupportStatusFromSubsegment(claim_segments[label_index]);
    const claim = {
        claimId,
        claimString,
        subclaims,
        citations,
        explanation,
        supported,
        probabilities: new Map()
    };
    return claim;
}
function getClaimsFromResponse(response) {
    // Example response: <|r1|><There is no information about the average lifespan of a giant squid in the deep waters of the Pacific Ocean in the provided document.><|subclaims|><The document contains information about the average lifespan of a giant squid.><The information about giant squid lifespan is related to the Pacific Ocean.><end||subclaims><|cite|><|s1 to s49|><end||cite><|explain|><Upon reviewing the entire document, there is no mention of giant squid or any related topic, including their average lifespan or the Pacific Ocean. The document is focused on international relations, diplomacy, and conflict resolution.><end||explain><|supported|><end||r><|r2|><The document is focused on international relations, diplomacy, and conflict resolution, and does not mention giant squid or any related topic.><|subclaims|><The document is focused on international relations, diplomacy, and conflict resolution.><The document does not mention giant squid or any related topic.><end||subclaims><|cite|><|s1|,|s2|,|s3|,|s4|><end||cite><|explain|><The first four sentences clearly establish the document's focus on international relations, diplomacy, and conflict resolution, and there is no mention of giant squid or any related topic throughout the document.><end||explain><|supported|><end||r><|r3|><The document mentions cats.><|subclaims|><The document makes some mention of cats.><end||subclaims><|cite|><None><end||cite><|explain|><There is no mention of cats anywhere in the document.><end||explain><|unsupported|><end||r>
    const segments = response.split("<end||r>");
    const claims = [];
    for (const segment of segments) {
        if (segment.length === 0) {
            continue;
        }
        const claim = getClaimFromSegment(segment);
        claims.push(claim);
    }
    return claims;
}
const handleHallucination = async ({ assertion, outputString, test, output, context, prompt, workspaceId, }) => {
    const vars = { ...test?.vars ?? {} };
    if (assertion?.context && typeof assertion?.context === 'string') {
        vars.context = assertion?.context;
    }
    if (typeof output === 'object') {
        const parsedOutput = output;
        if (typeof parsedOutput.trusys === 'object' && parsedOutput.trusys.context) {
            vars.context = parsedOutput.trusys.context;
            outputString = parsedOutput.trusys.output;
        }
    }
    (0, invariant_1.default)(typeof prompt === 'string' && prompt.trim() !== '', 'Prompt must be non-empty string');
    (0, invariant_1.default)(typeof vars.context === 'string' && vars.context.trim() !== '', 'Context must be non-empty string');
    const halloumiPrompt = createHalloumiPrompt(vars.context, outputString, prompt);
    const llmprompt = [
        {
            role: 'user',
            content: halloumiPrompt.prompt,
        },
    ];
    const defaultProviders = await (0, defaults_1.getDefaultProviders)(undefined, workspaceId || '');
    const hallucinationProvider = defaultProviders.hallucinationProvider;
    const resp = await hallucinationProvider.callApi(JSON.stringify(llmprompt));
    const parsedResponse = getClaimsFromResponse(resp.output);
    // Score is calculated as the fraction (0..1) of claims that are supported, rounded to two decimals.
    const totalClaims = parsedResponse.length;
    const supportedCount = parsedResponse.filter(c => Boolean(c.supported)).length;
    const fraction = totalClaims === 0 ? 0 : supportedCount / totalClaims;
    const scoreFraction = Math.round(fraction * 100) / 100; // two decimal places
    const pass = assertion.threshold ? scoreFraction >= assertion.threshold : scoreFraction >= 0.7;
    const gradingResult = {
        assertion,
        pass,
        score: scoreFraction,
        reason: parsedResponse.map(c => `*Claim ${c.claimId}*: ${c.claimString} <Result>: ${c.supported ? "Supported" : "Unsupported"}. <Citation>: Sentences from context - ${JSON.stringify(c.citations)}. <Explanation>: ${c.explanation}`).join("\n\n\n"),
        tokensUsed: { ...resp.tokenUsage, model: defaults_2.HALLUCINATION_MODEL_NAME },
    };
    return gradingResult;
    /*  invariant(typeof prompt === 'string' && prompt.trim() !== '', 'Prompt must be non-empty string');
    
      const pythonScriptPath = path.join(__dirname, 'python', 'oumi_judge.py');
      const pythonScriptOutput = await runPython(pythonScriptPath, 'get_assert', [
        outputString,
        context,
      ]);
    
      const result = JSON.parse(pythonScriptOutput) as {
        helpful: boolean;
        honest: boolean;
        safe: boolean;
        score: number;
        reason: string;
      };
    
      const pass = result.helpful && result.honest && result.safe;
      
      const gradingResult: GradingResult = {
        assertion,
        pass,
        score: result.score,
        reason: result.reason,
        namedScores: {
          helpful: result.helpful ? 1 : 0,
          honest: result.honest ? 1 : 0,
          safe: result.safe ? 1 : 0,
        }
      };
    
      return gradingResult;*/
};
exports.handleHallucination = handleHallucination;
//# sourceMappingURL=hallucination.js.map