File size: 7,376 Bytes
2a40140 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
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);
}
|