import { AIProcessor } from "@helpers/ai_processor";
import { Metadata } from "@models/Metadata.model";
import { Prompt } from "@models/Prompt.model";
import { toInvestigationResponseDto } from "@typez/investigation";
import { getLogger } from "@utils/asyncLocalStorage";
import yamlModule from "js-yaml";
import set from "lodash/set";
import { AssistantContradictionDetectionFormat, AssistantContradictionResolutionFormat, } from "schemas/assistant.validation";
/**
* Contradiction Detector Service
* @category Services
*/
export class ContradictionDetectorService {
aiProcessor;
/**
* Constructor
* @category Services
*/
constructor() {
this.aiProcessor = new AIProcessor();
}
/**
* Detects contradictions between investigation fields.
* @category Services
* @param {ICreateInvestigationDto} investigation - The investigation in which to detect contradictions.
* @returns {Promise<IAssistantContradictionDetectionFormat>} - A detected contradiction for the given investigation.
*/
async detectContradiction(investigation) {
const logger = getLogger();
try {
logger.info(`Started to detecting contradiction`);
const prompt = await Prompt.findOne({ name: "detect_contradiction" });
if (!prompt) {
throw new Error("Contradiction detection prompt not found.");
}
const unit = investigation.unitNumberAndTitle?.split(":")[0]?.split(" ")[1];
const lesson = investigation.lessonNumberAndTitle?.split(":")[0]?.split(" ")[1] || "0";
const lessonNumber = parseInt(lesson, 10);
const metadata = await Metadata.findOne({
unit: unit,
lessonNumber: Number.isNaN(lessonNumber) ? undefined : lessonNumber,
});
let metadataYaml;
if (metadata) {
const investigationMetadataObject = metadata.toObject({
versionKey: false,
transform: (doc, ret) => {
delete ret._id;
delete ret.hasInvestigation;
return ret;
},
});
const yaml = yamlModule;
metadataYaml = yaml.dump(investigationMetadataObject);
}
else {
metadataYaml = " ";
}
const promptTemplate = prompt.template
.replace("{investigation}", JSON.stringify(investigation))
.replace("{guide}", metadataYaml);
let retry = 0;
let response = null;
while (retry < 3) {
response = (await this.aiProcessor.fetchLLMResponse(promptTemplate, AssistantContradictionDetectionFormat));
if (response.detectedContradiction === undefined ||
response.detectedContradiction === null ||
response.isContradictionDetected === undefined ||
response.isContradictionDetected === null) {
retry += 1;
}
else {
break;
}
}
if (retry === 3 || !response) {
throw new Error(`Failed to detect contradiction. Response is: ${JSON.stringify(response)}.`);
}
logger.info(`Has contradiction been detected: ${JSON.stringify(response)}`);
return response;
}
catch (error) {
logger.error(`An error occurred during contradiction detection: ${String(error)}`);
throw error;
}
}
/**
* Gets the resolution of contradictions in an investigation by requesting the LLM.
* @category Services
* @param {IInvestigationResponseDto} investigation - The investigation in which to resolve contradictions.
* @param {string} [fieldName] - The field name for which to resolve contradictions. If not provided, resolves all contradictions.
* @returns {Promise<IAssistantContradictionResolutionFormat>} - The resolution of the contradiction.
*/
async getContradictionResolution(investigation, fieldName) {
const logger = getLogger();
try {
logger.info(`Started to get contradiction resolution${fieldName ? ` for field: ${fieldName}` : " for all fields"}`);
const prompt = await Prompt.findOne({ name: "resolve_contradiction" });
if (!prompt) {
throw new Error("Resolve contradiction prompt not found.");
}
const unit = investigation.unitNumberAndTitle.value?.split(":")[0]?.split(" ")[1];
const lesson = investigation.lessonNumberAndTitle.value?.split(":")[0]?.split(" ")[1] || "0";
const lessonNumber = parseInt(lesson, 10);
const metadata = await Metadata.findOne({
unit: unit,
lessonNumber: Number.isNaN(lessonNumber) ? undefined : lessonNumber,
});
let metadataYaml;
if (metadata) {
const investigationMetadataObject = metadata.toObject({
versionKey: false,
transform: (doc, ret) => {
delete ret._id;
delete ret.hasInvestigation;
return ret;
},
});
const yaml = yamlModule;
metadataYaml = yaml.dump(investigationMetadataObject);
}
else {
metadataYaml = " ";
}
let promptTemplate = prompt.template
.replace("{investigation}", JSON.stringify(investigation))
.replace("{guide}", metadataYaml);
// Replace {fieldName} placeholder if it exists in the template
if (fieldName) {
promptTemplate = promptTemplate.replace("{fieldName}", fieldName);
}
else {
// If no fieldName provided, replace with empty string or remove the placeholder
promptTemplate = promptTemplate.replace("{fieldName}", "");
}
let retry = 0;
let response = null;
while (retry < 3) {
response = (await this.aiProcessor.fetchLLMResponse(promptTemplate, AssistantContradictionResolutionFormat));
if (response.resolvedContradiction === undefined ||
response.resolvedContradiction === null ||
response.message === undefined ||
response.message === null) {
retry += 1;
}
else {
break;
}
}
if (retry === 3 || !response) {
throw new Error(`Failed to get contradiction resolution. Response is: ${JSON.stringify(response)}.`);
}
logger.info(`Response for getting contradiction resolution is: ${JSON.stringify(response)}`);
return response;
}
catch (error) {
logger.error(`An error occurred during getting contradiction resolution: ${String(error)}`);
throw error;
}
}
/**
* Resolves contradictions in an investigation.
* @category Services
* @param {IInvestigation} investigation - The investigation in which to resolve contradictions.
* @param {string} [fieldName] - The field name for which to resolve contradictions. If not provided, resolves all contradictions.
* @returns {Promise<string>} - A message describing the resolved contradictions.
*/
async resolveContradiction(investigation, fieldName) {
const logger = getLogger();
try {
logger.info(`Started to resolving contradiction${fieldName ? ` for field: ${fieldName}` : " for all fields"}`);
const investigationWithContradictions = toInvestigationResponseDto(investigation);
const contradictionResolution = await this.getContradictionResolution(investigationWithContradictions, fieldName);
logger.info({ contradictionResolution });
try {
for (const resolvedContradiction of contradictionResolution.resolvedContradiction ?? []) {
if (resolvedContradiction.fieldName != "" && resolvedContradiction.value != "") {
const fieldName = resolvedContradiction.fieldName.includes(".value")
? resolvedContradiction.fieldName.split(".").slice(0, -1).join(".")
: resolvedContradiction.fieldName;
logger.info({ fieldName, resolvedContradiction });
set(investigation, `${fieldName}.value`, resolvedContradiction.value);
set(investigation, `${fieldName}.aiGeneratedValue`, resolvedContradiction.value);
set(investigation, `${fieldName}.isContradicting`, false);
set(investigation, `${fieldName}.contradictionReason`, null);
set(investigation, `${fieldName}.targetFieldName`, null);
}
}
set(investigation, "metadata.dateModified", new Date());
}
catch (error) {
logger.error(`An error occurred during applying contradiction resolution: ${String(error)}`);
throw error;
}
return contradictionResolution.message;
}
catch (error) {
logger.error(`An error occurred during resolving contradiction: ${String(error)}`);
throw error;
}
}
}
Source