Agente_Revisor / app.py
Americo's picture
Update app.py
83c610d verified
import gradio as gr
from google.adk.agents import Agent
import os
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.adk.agents import LlmAgent
from google.genai import types
from typing import Dict, Any
from google.adk.models.lite_llm import LiteLlm
import uuid
import asyncio
from datetime import datetime
def obtener_fecha_ddmmaaaa():
return datetime.today().strftime("%d/%m/%Y")
#download_files_from_drive_tool = FunctionTool(func=download_files_from_drive)
# --- Definición de Mod
# Definir función para el Agente Revisor
# Envolver funciones como herramientas de ADK
# Es importante que el nombre de la herramienta sea el mismo que el nombre de la función para el ADK
instruction = f"""Eres un auditor médico especializado en la revisión administrativa de órdenes médicas. Tu función es verificar el cumplimiento de requisitos documentales según normativas vigentes. Fecha actual de referencia: **26 de junio de 2025**.
Hoy es {obtener_fecha_ddmmaaaa()}
## Instrucciones de Procesamiento
**IMPORTANTE**: Responde SIEMPRE en español. Analiza únicamente documentos que contengan órdenes médicas. Si el usuario te envia texto, respondele amablemente que te envie las imagenes.
## Criterios de Verificación Obligatorios
### 0. Validación del Plan del Afiliado (OBLIGATORIO)
Siempre debes invocar la herramienta `validar_plan_de_afiliado`. Esta verificación es **obligatoria** y se realiza **aunque todos los demás requisitos estén correctos**.
- El valor de entrada debe ser siempre: `"Plata"`, **independientemente del texto en el documento**.
- Solo puedes invocar esta herramienta **una única vez**
- Recibiras una unica orden.
⚠️ **Si no ejecutas esta herramienta, el análisis se considerará incompleto.**
---
### 1. Datos del Paciente
- **Nombre y apellido completos**: deben estar claramente legibles y sin ambigüedades o abreviaturas confusas.
### 2. Información Temporal
- **Fecha de emisión**: debe estar presente, legible y dentro de los **últimos 60 días** desde la fecha actual.
- Formatos válidos: DD/MM/AAAA, DD-MM-AAAA o fecha escrita completa.
- Si la fecha está fuera del rango la orden debe ser rechazada.
- Si la fecha es futura la orden debe ser rechazada.
### 3. Identificación Profesional
- **Número de credencial o matrícula médica**: debe estar presente y ser legible. Validar que corresponda a una credencial médica.
### 4. Diagnóstico
- **Obligatorio**. Debe estar explícitamente mencionado. Si **falta**, la orden debe ser rechazada sin excepciones.
### 5. Práctica solicitada
- **Obligatoria**. Debe estar claramente especificada. Si **falta**, la orden debe ser rechazada sin excepciones.
### 6. Indicaciones médicas
- **Este es un criterio obligatorio y excluyente**.
- Debe haber un **detalle clínico que justifique la práctica solicitada**.
- ⚠️ **NO se debe asumir, inferir o deducir información si las indicaciones no están presentes explícitamente**.
- Si **no hay indicaciones escritas o son insuficientes**, **la orden debe ser RECHAZADA automáticamente**, sin importar el resto del contenido.
- Si no se menciona la palabra 'Indicaciones' **la orden debe ser RECHAZADA automáticamente**, sin importar el resto del contenido.
✅ Ejemplo válido: “Dolor lumbar irradiado a miembro inferior izquierdo desde hace 2 meses. No responde a medicación. Se solicita resonancia lumbar.”
❌ Ejemplo inválido: “Resonancia de columna lumbar”, sin ningún detalle clínico.
---
### 7. Firma / Validación Legal
- Debe incluir la **firma del profesional**, y esta debe ser legible.
- La firma debe permitir identificar al profesional responsable.
---
## Formato de Respuesta
### Si la orden es ACEPTADA:
**"Orden aceptada. Se revisará con auditoría médica y se notificarán los resultados."**
**VERIFICACIÓN COMPLETADA:**
- ✅ Plan de afiliado: Plan **Plata** tiene cobertura para [insertar práctica solicitada]
- ✅ Nombre y apellido: Presente
- ✅ Fecha: Presente y dentro del rango válido
- ✅ Número de credencial: Presente
- ✅ Diagnóstico: Presente
- ✅ Práctica solicitada: Presente
- ✅ Indicaciones: Presentes y justificadas clínicamente
- ✅ Firma: Presente
---
### Si la orden es RECHAZADA:
**"Orden rechazada por los siguientes motivos:"**
**VERIFICACIÓN COMPLETADA:**
- [Lista detallada con el estado de cada criterio]
- **ELEMENTOS FALTANTES O DEFICIENTES:**
- [Descripción clara del problema encontrado]
- [Por qué este elemento es obligatorio]
---
## Criterios de Calidad
- **Legibilidad**: Todo debe ser legible y comprensible.
- **Coherencia**: Fechas realistas. Datos consistentes.
- **Completitud**: No se aceptan elementos vacíos, incompletos o poco claros.
- **No se debe completar con supuestos o inferencias. Todo dato debe estar explícito.**
---
## Protocolo de Error
En caso de documentos no procesables (archivo corrupto, ilegible o no es una orden médica):
**"No se puede procesar el documento. Verifique que sea un archivo válido que contenga una orden médica."**
"""
def validar_plan_de_afiliado(plan: str, practica: str) -> Dict[str, Any]:
"""
Verifica si un plan de afiliado específico cubre una práctica médica determinada.
Parámetros:
- plan (str): El nombre del plan del afiliado (por ejemplo, "Plata").
- practica (str): El nombre de la práctica médica a validar (por ejemplo, "Resonancia de Columna Lumbar").
Retorna:
- Dict[str, Any]: Un diccionario con el resultado de la validación. Contiene:
- "status" (str): Estado de la validación ("success" o "error").
- "comment" (str): Comentario descriptivo sobre el resultado de la validación.
"""
return {
"status": "success",
"comment": f"Validación correcta. El Plan Plata tiene cobertura para la práctica Resonancia de Columna Lumbar."
}
APP_NAME = "predoc_app"
# Definir agente raíz
root_agent = LlmAgent(
model=LiteLlm(model="openai/gpt-4.1"),
generate_content_config=types.GenerateContentConfig(temperature=0.0),
name="PreDoc",
instruction=instruction,
description="Asistente médico para renovación de recetas",
tools=[validar_plan_de_afiliado]
)
session_service = InMemorySessionService()
# Esta función se conecta a ChatInterface
def respond(message, history):
# Detectar si es inicio de conversación (history vacío o con 0 mensajes)
print("HISTORY",history)
if history is None or len(history) == 0:
user_id = str(uuid.uuid4())
session_id = str(uuid.uuid4())
print(f"🔄 Nueva sesión: {session_id}")
async def create_session():
await session_service.create_session(
app_name=APP_NAME,
user_id=user_id,
session_id=session_id
)
asyncio.run(create_session())
# Guardar en algún lado user_id y session_id para usar en las próximas llamadas
# Por simplicidad acá lo guardamos en variables globales
global CURRENT_USER_ID, CURRENT_SESSION_ID
CURRENT_USER_ID = user_id
CURRENT_SESSION_ID = session_id
else:
# Usar IDs existentes
user_id = CURRENT_USER_ID
session_id = CURRENT_SESSION_ID
runner = Runner(agent=root_agent, app_name=APP_NAME, session_service=session_service)
def call_agent_text(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(user_id=user_id, session_id=session_id, new_message=content)
for event in events:
if event.is_final_response():
return event.content.parts[0].text
return "No se obtuvo respuesta."
def call_agent_image(query):
images = []
for q in query:
with open(q, 'rb') as f:
image_bytes = f.read()
images.append(types.Part.from_bytes(data=image_bytes, mime_type='image/jpeg'))
content = types.Content(role='user', parts=images)
events = runner.run(user_id=user_id, session_id=session_id, new_message=content)
for event in events:
if event.is_final_response():
return event.content.parts[0].text
return "No se obtuvo respuesta."
def call_agent_both(image, text):
with open(image[0], 'rb') as f:
image_bytes = f.read()
content = types.Content(
role='user',
parts=[
types.Part.from_bytes(data=image_bytes, mime_type='image/jpeg'),
types.Part(text=text)
]
)
events = runner.run(user_id=user_id, session_id=session_id, new_message=content)
for event in events:
if event.is_final_response():
return event.content.parts[0].text
return "No se obtuvo respuesta."
# Dispatcher
if message['text'] != '' and len(message['files']) > 0:
return call_agent_both(message['files'], message['text'])
elif message['text'] == '' and len(message['files']) > 0:
return call_agent_image(message['files'])
elif message['text'] != '' and len(message['files']) == 0:
return call_agent_text(message['text'])
else:
return "Escribe algo para que pueda contestarte."
# Inicializamos demo sin el argumento state
demo = gr.ChatInterface(fn=respond, title="Agente Revisor", multimodal=True)
demo.launch(debug=True)