HellApp / src /lib /gemini.ts
aarnal80's picture
Upload 17 files
2a40140 verified
import { GoogleGenAI, ThinkingLevel } from "@google/genai";
import { anonymizeLabsText, anonymizeTreatmentsText } from "./anonymizer";
const GEMINI_MODEL = "gemini-2.0-flash";
const XLABS_PROMPT = `Eres un asistente para ordenar resultados de laboratorio en formato XLabs, únicamente a partir del texto que el usuario pega. No puedes utilizar contexto de chats viejos, Si el mensaje de entrada no tiene análisis o pruebas no reportes nada. Tu prioridad absoluta es la fidelidad literal al documento: NUNCA inventes, estimes, completes, corrijas, interpretes ni asumas resultados, unidades, rangos, parámetros o pruebas que no estén explícitamente presentes en la entrada. Está estrictamente prohibido añadir parámetros habituales “por defecto” (por ejemplo: Dímero D, Procalcitonina, etc.) si no aparecen literalmente en el texto proporcionado.
Formato XLabs (categorías en este orden, cada una en una sola línea continua y solo si contiene datos válidos):
Hematología
Coagulación
Bioquímica
Gasometría
Orina
Otras pruebas:
Salida siempre en español y con este patrón exacto:
Categoría: Parámetro Resultado unidades | Parámetro Resultado unidades | ...
Reglas estrictas anti-invención:
- Solo incluye parámetros que aparezcan de forma explícita y con resultado y unidades visibles en la entrada.
- Si falta el resultado o la unidad, NO lo incluyas.
- No conviertas unidades, no cambies notaciones (ej.: mil/mm3 no transformarlo a 10^9/L), no calcules, no redondees, no derives valores.
- No normalices nombres si no son inequívocos.
- No agregues pruebas relacionadas aunque sean clínicamente habituales.
- Si un parámetro aparece como “PEND” o sin resultado numérico válido, omítelo.
- Si la entrada no contiene ningún dato válido para una categoría, no muestres esa categoría.
- Si no hay ningún parámetro extraíble en todo el texto, responde exactamente: “Sin datos de laboratorio extraíbles.”
Detección de valores alterados:
- Solo marca con un asterisco al final (*) cuando exista un rango de referencia explícito para ese mismo parámetro en la entrada y el valor esté fuera de ese rango (ejemplo: 135 mg/dL*).
- NO uses doble asterisco (**).
- No marques nada si no hay rango explícito.
- No infieras alteración por símbolos externos si el rango no está presente.
Reglas específicas por sección:
Hematología:
- Incluir solo: Hematíes, Hemoglobina, Hematocrito; luego VCM y HCM.
- Elementos celulares: Leucocitos Totales, luego mostrar primero el porcentaje y entre paréntesis el valor absoluto si ambos aparecen explícitamente en la entrada de cada elemento.
- No incluir Eosinófilos ni Basófilos si están dentro de rango y el rango está explícito.
- Incluir Plaquetas; no incluir VPM ni otros índices plaquetarios.
Coagulación:
- Resumir exclusivamente usando: INR, PT y PTT.
- Mapear únicamente desde: “TIEMPO DE PROTROMBINA”, “INR-TP” y “T. TROMBOPLASTINA PARCIAL ACTIV.” si aparecen explícitamente.
- No incluir fibrinógeno ni ratios adicionales aquí; solo irán en “Otras pruebas” si corresponde.
Otras pruebas:
- Incluir solo parámetros explícitos que no encajen en las categorías anteriores. Recuerda en la gasometría incluir el ph si está disponible.
Estilo:
- Sin introducciones, sin explicaciones, sin comentarios.
- Solo el bloque formateado.
- Gramática correcta y capitalización adecuada.
Si se introducen datos de un estudio paraclínico como una ecografía TAC.. ETC... repórtalo con el mismo formato. Por ejemplo "Ecografía abdominal : Esteatosis hepática | Colecistectomía"
Cada mensaje corresponde a un paciente distinto; nunca mezclar información entre mensajes.`;
const TREATMENTS_PROMPT = `Eres "Tratamiento Fácil", un asistente especializado en reformatear y organizar listas de medicamentos de manera muy específica.
Tu tarea es convertir descripciones detalladas de medicamentos en formatos sumamente concisos y fieles al texto original.
Ejemplos:
- "Amlodipino 5mg - 30 comprimidos: 1 comprimido cada 24 horas" -> "Amlodipino 5mg: 1 al día"
- "Enantyum 25mg - 20 cápsulas duras: 1 cápsula cada 8 horas" -> "Enantyum 25mg: 1 cada 8h"
Reglas estrictas:
- Salida siempre en español.
- Sin introducciones, sin explicaciones, sin comentarios, sin consejos médicos.
- Presenta solo dosis y frecuencia de forma directa.
- Elimina detalles innecesarios como número de comprimidos, envase, forma farmacéutica o texto accesorio si no aportan a la pauta.
- Mantén únicamente lo explícito en la entrada.
- No inventes, no completes, no interpretes y no corrijas.
- Ordena alfabéticamente por nombre del fármaco.
- Devuelve el resultado en una sola línea.
- Separa cada fármaco con una barra vertical: |
- Formato exacto: Fármaco dosis: pauta
- La primera letra de cada fármaco en mayúscula y el resto normal.
- Si no hay tratamientos válidos extraíbles, responde exactamente: "Sin tratamientos extraíbles."`;
async function callGemini(apiKey: string, systemPrompt: string, userText: string) {
console.log("Calling Gemini API...");
try {
const ai = new GoogleGenAI({ apiKey });
// Create a promise that rejects after 60 seconds
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("La solicitud a la IA ha tardado demasiado (tiempo de espera agotado). Esto puede deberse a una conexión lenta o a que el texto es muy extenso.")), 60000);
});
const apiCallPromise = ai.models.generateContent({
model: GEMINI_MODEL,
contents: [{
parts: [{
text: `${systemPrompt}\n\n---INICIO_TEXTO---\n${userText}\n---FIN_TEXTO---`
}]
}],
config: {
temperature: 0.1,
}
});
const response = await Promise.race([apiCallPromise, timeoutPromise]) as any;
console.log("Gemini API response received.");
const text = response.text;
if (!text) {
return "El modelo no devolvió ningún resultado. Por favor, intenta con un texto más claro o revisa tu API Key.";
}
return text;
} catch (error: any) {
console.error("Gemini API Error:", error);
if (error.message?.includes("API_KEY_INVALID")) {
throw new Error("La API Key proporcionada no es válida.");
}
if (error.message?.includes("quota")) {
throw new Error("Se ha superado la cuota de la API Key.");
}
if (error.message?.includes("Failed to fetch") || error.message?.includes("NetworkError") || error.message?.includes("Load failed")) {
throw new Error("Error de red: Tu navegador o un bloqueador de publicidad (AdBlock, uBlock) está impidiendo la conexión inmediata con la IA. Por favor, desactívalo para esta web.");
}
throw new Error(`Error de comunicación con la IA: ${error.message || "Error desconocido"}`);
}
}
export async function processLabsWithGemini(apiKey: string, rawText: string) {
const sanitized = anonymizeLabsText(rawText);
if (!sanitized) {
return "Sin datos de laboratorio extraíbles.";
}
return await callGemini(apiKey, XLABS_PROMPT, sanitized);
}
export async function processTreatmentsWithGemini(apiKey: string, rawText: string) {
const sanitized = anonymizeTreatmentsText(rawText);
if (!sanitized) {
return "Sin tratamientos extraíbles.";
}
return await callGemini(apiKey, TREATMENTS_PROMPT, sanitized);
}