File size: 9,779 Bytes
e861faf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189a6fd
e861faf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756f908
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e861faf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1ec8dc4
e861faf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cae0efb
e861faf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cae0efb
e861faf
 
 
 
 
 
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import os
from dotenv import load_dotenv
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.adk.agents import LlmAgent
from google.adk.tools import FunctionTool
from google.adk.models.lite_llm import LiteLlm
from typing import Dict, Any
from datetime import date, datetime
from google import genai
from google.genai import types
import requests

import google.generativeai as genai
from pydantic import BaseModel
import json
import uuid
import asyncio
import gradio as gr


# Cargar variables de entorno desde .env
load_dotenv()

from datetime import datetime

def obtener_fecha():
    return datetime.now().strftime("%Y-%m-%d")

# Establecer variables de entorno necesarias
#os.environ['GOOGLE_API_KEY'] = os.getenv("GOOGLE_API_KEY")
#os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = os.getenv("GOOGLE_GENAI_USE_VERTEXAI")
#genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))

# Inicializar cliente de BigQuery - ACTUALIZA CON TU PROJECT ID
#client_bq = bigquery.Client(project="uma-datascience-dev")  # Cambia por tu project ID

class SQLResult(BaseModel):
    consulta_sql: str

# Definir función para generar SQL
def generar_sql(pregunta: str) -> Dict[str, Any]:
    """
    Genera una consulta SQL a partir de una pregunta en lenguaje natural usando un modelo de lenguaje (Gemini).

    Esta función toma una pregunta sobre datos médicos/administrativos y utiliza un LLM configurado con un prompt
    que describe el contexto, el esquema de la tabla y reglas de generación para producir una consulta SQL válida
    y segura que pueda ejecutarse sobre BigQuery.

    Parámetros:
    -----------
    pregunta : str
        Pregunta del usuario en lenguaje natural relacionada con la tabla 
        `uma-datascience-dev.Bigquery_Dataset.evaluaciones_auditoria`.

    Retorna:
    --------
    Dict[str, Any]
        Un diccionario con al menos la clave `consulta_sql`, que contiene la consulta SQL generada.
        Ejemplo:
        {
            "consulta_sql": "SELECT COUNT(*) FROM ..."
        }

    Requiere:
    ---------
    - Que esté configurada correctamente la API de Gemini (`google.generativeai`) y la clave esté cargada.
    - Que exista un esquema de Pydantic `SQLResult` para validar la estructura esperada.

    Nota:
    -----
    - Usa el modelo `gemini-2.0-flash-lite`.
    - Aplica reglas estrictas de formato y nombres de campos según el schema de BigQuery.
    - La consulta es generada en base al contexto, fecha actual y convenciones de codificación establecidas.
    """
    prompt = f"""
Sos un asistente experto en análisis de datos médicos/administrativos que responde preguntas en lenguaje natural
transformándolas en consultas SQL para BigQuery.

Tabla disponible: uma-datascience-dev.Bigquery_Dataset.evaluaciones_auditoria

Columnas disponibles:
- Fecha (DATE): Fecha del registro
- Practica_solicitada (STRING): Descripción de la práctica médica solicitada
- DNI (INTEGER): Documento Nacional de Identidad del paciente
- Profesional (STRING): Nombre del profesional médico
- Estado (STRING): Estado actual del registro/solicitud

🔎 Valores posibles para la columna `Estado`:
- "aprobado"
- "rechazado"

(No existen otros estados. No inventes valores como "pendiente", "en revisión", etc.)

Algunos valores de la columna 'practica_solicitada' son:

Análisis de sangre
Colonoscopia
Ecografía abdominal
Electrocardiograma
Endoscopia digestiva alta
Mamografía
Radiografía de cráneo
Radiografía de rodilla
Resonancia de cerebro
Resonancia de columna lumbar
Tomografía de tórax


⚠️ Importante:
- Siempre usá nombres de tabla completamente calificados: uma-datascience-dev.Bigquery_Dataset.evaluaciones_auditoria
- Usá comillas invertidas para referenciar tablas y campos cuando sea necesario
- Para fechas, usá el formato DATE en las consultas (ej: DATE('2024-01-01'))
- Si la pregunta es ambigua, asumí una interpretación razonable y explícita
- No inventes campos que no existen en el schema

📘 Ejemplos de preguntas y sus consultas SQL:

- Pregunta: ¿Cuántas prácticas se solicitaron este mes?

Esperamos una respuesta tipo:

SELECT COUNT(*) AS total_practicas FROM uma-datascience-dev.Bigquery_Dataset.evaluaciones_auditoria WHERE EXTRACT(MONTH FROM Fecha) = EXTRACT(MONTH FROM CURRENT_DATE()) AND EXTRACT(YEAR FROM Fecha) = EXTRACT(YEAR FROM CURRENT_DATE())


- Pregunta: ¿Cuántas prácticas se aprobaron este año?

Esperamos una respuesta tipo:

SELECT COUNT(*) AS total_aprobadas FROM uma-datascience-dev.Bigquery_Dataset.evaluaciones_auditoria WHERE Estado = 'aprobado' AND EXTRACT(YEAR FROM Fecha) = EXTRACT(YEAR FROM CURRENT_DATE())

---

Ejemplos de consultas típicas:
- "¿Cuántas prácticas se solicitaron este mes?" 
- "¿Qué profesionales han atendido más pacientes?"
- "¿Cuáles son los estados más comunes?"
- "¿Qué prácticas se solicitan más frecuentemente?"

La fecha de hoy es: {obtener_fecha()}


---


Consulta del usuario: {pregunta}

Genera una consulta SQL precisa y eficiente.


"""

    model = genai.GenerativeModel(
        model_name="gemini-2.0-flash-lite",
        generation_config={
            "response_mime_type": "application/json",
            "response_schema": SQLResult
        }
    )   
    response = model.generate_content(prompt)
    consulta_sql = json.loads(response.text)
    print("Consulta SQL generada:", consulta_sql)
    return consulta_sql

# Definir función para ejecutar SQL

    

import requests

def ejecutar_sql(consulta_sql: str) -> dict:
    """
    Ejecuta una consulta SQL en un endpoint de Cloud Run que expone acceso a BigQuery.

    Parámetros:
    -----------
    consulta_sql : str
        Consulta SQL a ejecutar.

    Retorna:
    --------
    dict
        Diccionario con dos claves:
        - "resultados": lista de filas devueltas por la consulta (cada fila como dict).
        - "cantidad_de_filas": cantidad total de filas en la respuesta.
    
    Lanza:
    ------
    Exception si la llamada al endpoint falla o devuelve un error HTTP.
    """
    url = "https://bigquery-tool-145741847476.us-central1.run.app"
    payload = {"sql": consulta_sql}

    response = requests.post(url, json=payload)

    if response.status_code == 200:
        data = response.json()
        resultados = data.get("results", [])
        return {
            "resultados": resultados,
            "cantidad_de_filas": len(resultados)
        }
    else:
        raise Exception(
            f"Error al llamar Cloud Run: {response.status_code} - {response.text}"
        )

APP_NAME = "predoc_app"
# Envolver funciones como herramientas de ADK
tool_generar_sql = FunctionTool(func=generar_sql)
tool_ejecutar_sql = FunctionTool(func=ejecutar_sql)

# Definir el agente
root_agent = LlmAgent(
    name="analista_datos_medicos",
    model='gemini-2.5-flash',
    instruction="""
Eres un asistente experto en análisis de datos médicos/administrativos. Tu trabajo consiste en interpretar preguntas en lenguaje natural y responderlas con datos reales almacenados en BigQuery.

Tu tabla contiene información sobre:
- Fechas de solicitudes médicas
- Prácticas médicas solicitadas
- DNI de pacientes
- Profesionales médicos
- Estados de las solicitudes

Para responder preguntas:
1. Usa la herramienta `generar_sql` para transformar la pregunta del usuario en una consulta SQL válida
2. Luego usa la herramienta `ejecutar_sql` para ejecutar esa consulta y obtener los resultados
3. Interpreta y presenta los resultados de manera clara y útil

Hazlo automáticamente sin pedir confirmación al usuario. Si encuentras errores, explica qué ocurrió y sugiere alternativas.

Ejemplos de preguntas que puedes responder:
- "¿Cuántas prácticas se solicitaron esta semana?"
- "¿Qué profesional ha atendido más casos?"
- "¿Cuáles son las prácticas más solicitadas?"
- "¿Cómo se distribuyen los estados de las solicitudes?"
- "¿Cuántos pacientes únicos hay en el sistema?"
""",
    tools=[generar_sql, ejecutar_sql],
    generate_content_config=types.GenerationConfig(
        temperature=0.0
    )
)


session_service = InMemorySessionService()

# Esta función se conecta a ChatInterface
def respond(text, 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."



    return call_agent_text(text)


# Inicializamos demo sin el argumento state
demo = gr.ChatInterface(fn=respond, title="Agente Analista", multimodal=False)

demo.launch(debug=True)