#Python

0 Seguidores · 129 Publicaciones

Python es un lenguaje de programación interpretado de alto nivel para propósitos generales de programación. Creado por Guido van Rossum y publicado por primera vez en 1991, Python tiene una filosofía de diseño que hace hincapié en la legibilidad del código, en particular mediante el uso significativo de espacios en blanco.

Sitio oficial.

InterSystems Python Binding Documentatión.

Anuncio Sergio Farago · oct 23, 2025

Hola, comunidad:

¿Sois desarrolladores de Python? Si es así, ¡ya podéis empezar a crear aplicaciones con InterSystems IRIS sin necesidad de aprender un nuevo lenguaje de programación!

Use Python with InterSystems IRIS. Try the exercise. 

👨‍💻 Probad este ejercicio para empezar rápidamente a usar la interfaz DB-API de Python y conectar con una base de datos de InterSystems IRIS para ejecutar consultas SQL.

💬 ¿Cuál fue vuestra experiencia con el ejercicio? ¡Contádmelo en los comentarios!

0
0 19
Artículo Alberto Fuentes · sep 12, 2025 5m read

En el artículo anterior vimos cómo construir un agente de IA de atención al cliente con smolagents e InterSystems IRIS, combinando SQL, RAG con búsquedas vectoriales e interoperabilidad.

En ese caso, utilizamos modelos en la nube (OpenAI) para el LLM y los embeddings.

En esta ocasión daremos un paso más: ejecutar el mismo agente, pero con modelos locales gracias a Ollama.


¿Por qué ejecutar modelos en local?

El uso de LLMs en la nube es la opción más sencilla para empezar:

  • ✅ Modelos ya optimizados y mantenidos
  • ✅ Fácil acceso con una simple API
  • ✅ Servicio serverless: no necesitas preocuparte por el hardware ni mantenimiento
  • ❌ Costes por uso
  • ❌ Dependencia de servicios externos
  • ❌ Restricciones de privacidad al enviar datos

En cambio, al correr modelos en local conseguimos:

  • ✅ Control total sobre los datos y el entorno
  • ✅ Sin costes variables por uso
  • ✅ Posibilidad de hacer fine tuning o adaptar modelos con técnicas como LoRA (Low-Rank Adaptation), que permite entrenar algunas capas del modelo para ajustarlo a tu dominio concreto sin necesidad de reentrenarlo entero
  • ❌ Mayor consumo de recursos en tu servidor
  • ❌ Limitaciones en tamaño/modelos dependiendo del hardware

Ahí es donde entra en juego Ollama.


¿Qué es Ollama?

Ollama es una herramienta que facilita la ejecución de modelos de lenguaje y embeddings en tu propio ordenador con una experiencia muy simple:

  • Descargas modelos con un ollama pull
  • Los ejecutas de forma local, expuestos como una API HTTP
  • Los puedes integrar directamente en tus aplicaciones, igual que harías con OpenAI

En resumen: la misma API que usarías en la nube, pero corriendo en tu portátil o servidor.


Configuración básica de Ollama

Primero, instala Ollama desde su web y verifica que funciona:

ollama --version

Después, descarga un par de modelos:

# Descargar un modelo de embeddings
ollama pull nomic-embed-text:latest

# Descargar un modelo de lenguaje
ollama pull llama3.1:8b

# Ver todos los modelos disponibles
ollama list

Puedes probar embeddings directamente con un curl:

curl http://localhost:11434/api/embeddings -d '{
  "model": "nomic-embed-text:latest",
  "prompt": "Ollama hace sencillo ejecutar LLMs en local."
}'

Usar Ollama en el agente de IRIS

El repositorio Customer Support Agent Demo incluye ya la configuración para Ollama. Sólo necesitas:

  1. Descargar los modelos necesarios para ejecutarlos en Ollama Yo he utilizado nomic-embed-text para los embeddings de las búsquedas vectoriales y devstral como LLM.

  2. Configurar IRIS para utilizar embeddings de Ollama con el modelo local:

INSERT INTO %Embedding.Config (Name, Configuration, EmbeddingClass, VectorLength, Description)
  VALUES ('ollama-nomic-config', 
          '{"apiBase":"http://host.docker.internal:11434/api/embeddings", 
            "modelName": "nomic-embed-text:latest"}',
          'Embedding.Ollama', 
          768,  
          'embedding model in Ollama');
  1. Ajustar el tamaño de las columnas para almacenar vectores en las tablas del ejemplo (el modelo local tiene un tamaño diferente de vectores que el de OpenAI original).
ALTER TABLE Agent_Data.Products DROP COLUMN Embedding;
ALTER TABLE Agent_Data.Products ADD COLUMN Embedding VECTOR(FLOAT, 768);

ALTER TABLE Agent_Data.DocChunks DROP COLUMN Embedding;
ALTER TABLE Agent_Data.DocChunks ADD COLUMN Embedding VECTOR(FLOAT, 768);
  1. Configurar el fichero de entorno .env para indicar los modelos que queremos utilizar:
OPENAI_MODEL=devstral:24b-small-2505-q4_K_M
OPENAI_API_BASE=http://localhost:11434/v1
EMBEDDING_CONFIG_NAME=ollama-nomic-config
  1. Actualizar los embeddings

Como tenemos un modelo de embedding diferente del original, debemos actualizar los embeddings utilizando el modelo local nomic-embed-text

python scripts/embed_sql.py
  1. Ejecutar el agente para que haga uso de la nueva configuración

El código ya utilizará la configuración para que tanto los embeddings como el LLM se sirvan desde el endpoint local.

Con esta configuración podrás hacer preguntas como:

  • “¿Dónde está mi pedido #1001?”
  • “¿Cuál es el plazo de devolución?"

Y el agente utilizará:

  • SQL de IRIS para datos estructurados
  • Búsquedas vectoriales con embeddings de Ollama (local)
  • Interoperabilidad para simular llamadas a APIs externas
  • Un LLM local para planificar y generar código que llame a las herramientas necesarias para obtener la respuesta

Conclusión

Gracias a Ollama, podemos ejecutar nuestro Agente de Atención al Cliente con IRIS sin depender de la nube:

  • Privacidad y control de los datos
  • Coste cero por token
  • Flexibilidad total para probar y adaptar modelos (LoRA)

¿El reto? Necesitas una máquina con suficiente memoria y CPU/GPU para correr modelos grandes. Pero para prototipos y pruebas, es una opción muy potente y práctica.


Referencias útiles

0
0 29
Artículo Alberto Fuentes · sep 1, 2025 7m read

Las preguntas de atención al cliente pueden abarcar datos estructurados (pedidos, productos 🗃️), conocimiento no estructurado (docs/FAQs 📚) y otros sistemas integrados (actualizaciones de envío 🚚). En este post vamos a construir un agente de IA compacto que cubre los tres—usando:

  • 🧠 Python + smolagents para orquestar el “cerebro” del agente
  • 🧰 InterSystems IRIS para SQL, Búsqueda Semántica (RAG) e Interoperabilidad (una API de seguimiento de envío simulada)

⚡ TL;DR (versión express)

  • Construye un Agente de Atención al Cliente funcional con Python + smolagents orquestando herramientas sobre InterSystems IRIS (SQL, Búsqueda Semántica/RAG, Interoperabilidad con una API simulada).
  • Responde preguntas reales (p. ej., “¿Se entregó el pedido #1001?”“¿Cuál es el plazo de devolución?”) combinando tablas, documentos y llamadas de interoperabilidad.
  • Inicia IRIS en Docker, carga el esquema y datos de ejemplo, prepara los embeddings de los documentos para RAG, registra herramientas (SQL/RAG/API) y ejecuta el agente por línea de comando o interfaz gráfica.

image


🧭 Lo que vas a construir

Un Agente de Atención al Cliente capaz de:

  • 🔎 Consultar datos estructurados (clientes, pedidos, productos, envíos) vía SQL
  • 📚 Recuperar conocimiento no estructurado (FAQs y documentos) con RAG sobre IRIS Vector Search (Búsqueda Vectorial)
  • 🔌 Llamar a una API de seguimiento envío (simulada) mediante el framework de Interoperabilidad de IRIS, con Visual Trace para inspeccionar cada llamada

Arquitectura (resumida)

Usuario ➜ Agente (smolagents CodeAgent)
               ├─ Herramienta SQL ➜ Tablas en IRIS
               ├─ Herramienta RAG ➜ Búsqueda Vectorial en IRIS (embeddings + chunks)
               └─ Herramienta Seguimiento Envío ➜ Interoperabilidad IRIS (simulada) ➜ Visual Trace

¿Conoces smolagents? Es un framework ligero de agentes de Hugging Face en el que el modelo planifica y utiliza tus herramientas; como alternativas, puedes considerar LangGraph y LlamaIndex.


🧱 Prerrequisitos

  • 🐍 Python 3.9+
  • 🐳 Docker para ejecutar IRIS en un contenedor
  • 🧑‍💻 VS Code para revisar el código
  • 🔑 Clave de API de OpenAI (para el LLM y los embeddings) — o ejecútalo localmente con Ollama si lo prefieres

1) 🧩 Clona el repositorio y prepara un entorno Python

git clone https://github.com/intersystems-ib/customer-support-agent-demo
cd customer-support-agent-demo

python -m venv .venv
# macOS/Linux
source .venv/bin/activate
# Windows (PowerShell)
# .venv\Scripts\Activate.ps1

pip install -r requirements.txt
cp .env.example .env   # añade tu clave de OpenAI

2) 🐳 Arranca InterSystems IRIS (Docker)

docker compose build
docker compose up -d

Abre el Management Portal (http://localhost:52773 en esta demo).


3) 🗃️ Carga los datos estructurados (SQL)

Desde el SQL Explorer de IRIS o tu cliente SQL favorito:

LOAD SQL FROM FILE '/app/iris/sql/schema.sql' DIALECT 'IRIS' DELIMITER ';';
LOAD SQL FROM FILE '/app/iris/sql/load_data.sql' DIALECT 'IRIS' DELIMITER ';';

Este es el esquema que acabas de cargar: image

Ejecuta algunas consultas para familiarizarte con los datos. El agente usará estas tablas para resolver preguntas:

-- Listado de clientes
SELECT * FROM Agent_Data.Customers;

-- Pedidos de un cliente
SELECT o.OrderID, o.OrderDate, o.Status, p.Name AS Product
FROM Agent_Data.Orders o
JOIN Agent_Data.Products p ON o.ProductID = p.ProductID
WHERE o.CustomerID = 1;

-- Info de envío de un pedido
SELECT * FROM Agent_Data.Shipments WHERE OrderID = 1001;

✅ Si ves filas, tu parte estructurada está lista.


4) 📚 Añade conocimiento no estructurado con Vector Search (RAG)

Crea una configuración de embeddings (el ejemplo usa un modelo de OpenAI; ajústalo a tu gusto):

INSERT INTO %Embedding.Config
  (Name, Configuration, EmbeddingClass, VectorLength, Description)
VALUES
  ('my-openai-config',
   '{"apiKey":"YOUR_OPENAI_KEY","sslConfig":"llm_ssl","modelName":"text-embedding-3-small"}',
   '%Embedding.OpenAI',
   1536,
   'a small embedding model provided by OpenAI');

¿Quieres los pasos y opciones exactos? Consulta la documentación

Después, genera los embeddings del contenido de ejemplo:

python scripts/embed_sql.py

Comprueba que los embeddings están en las tablas:

SELECT COUNT(*) AS ProductChunks FROM Agent_Data.Products;
SELECT COUNT(*) AS DocChunks     FROM Agent_Data.DocChunks;

🔎 Bonus: Búsqueda híbrida + vectorial directamente desde SQL con EMBEDDING()

Una gran ventaja de IRIS es que puedes hacer búsqueda semántica (vectorial) dentro de SQL y mezclarla con filtros clásicos—sin microservicios extra. La función EMBEDDING() de SQL crea al vuelo el vector de tu texto de búsqueda y puedes compararlo con vectores almacenados usando operaciones como VECTOR_DOT_PRODUCT.

Ejemplo A — Búsqueda híbrida de productos (filtro por precio + ranking semántico):

SELECT TOP 3
    p.ProductID,
    p.Name,
    p.Category,
    p.Price,
    VECTOR_DOT_PRODUCT(p.Embedding, EMBEDDING('headphones with ANC', 'my-openai-config')) score
FROM Agent_Data.Products p
WHERE p.Price < 200
ORDER BY score DESC

Ejemplo B — Búsqueda semántica de fragmentos de documentos (ideal para alimentar RAG):

SELECT TOP 3
    c.ChunkID  AS chunk_id,
    c.DocID      AS doc_id,
    c.Title         AS title,
    SUBSTRING(c.ChunkText, 1, 400) AS snippet,
    VECTOR_DOT_PRODUCT(c.Embedding, EMBEDDING('warranty coverage', 'my-openai-config')) AS score
FROM Agent_Data.DocChunks c
ORDER BY score DESC

¿Por qué es potente? Puedes pre-filtrar por precio, categoría, idioma, tenant, fechas, etc., y luego ordenar por similitud semántica—todo en una sola sentencia SQL.


5) 🔌 Conecta una API de envíos (mock) con Interoperability

El proyecto expone un pequeño endpoint /api/shipping/status a través de la Interoperabilidad de IRIS — perfecto para simular llamadas “del mundo real”:

curl -H "Content-Type: application/json" \
  -X POST \
  -d '{"orderStatus":"Processing","trackingNumber":"DHL7788"}' \
  http://localhost:52773/api/shipping/status

Ahora abre Visual Trace en el Portal para ver el flujo de mensajes paso a paso (como el radar de un aeropuerto ✈️).


6) 🤖 Conoce al agente (smolagents + tools)

Echa un vistazo a estos archivos:

  • agent/customer_support_agent.py — inicia un CodeAgent y registra las herramientas
  • agent/tools/sql_tool.py — utilidades SQL parametrizadas
  • agent/tools/rag_tool.py — búsqueda vectorial + recuperación de documentos
  • agent/tools/shipping_tool.py — llamadas al endpoint de Interoperability

El CodeAgent planifica qué pasos dar y genera código que llama a tus herramientas. Tú pones las herramientas; él pone el cerebro con un modelo LLM.


7) ▶️ Ejecútalo

Modo one-shot (pruebas rápidas)

python -m cli.run --email alice@example.com --message "Where is my order #1001?"
python -m cli.run --email alice@example.com --message "Show electronics that are good for travel"
python -m cli.run --email alice@example.com --message "Was my headphones order delivered, and what’s the return window?"

CLI interactivo

python -m cli.run --email alice@example.com

Web UI (Gradio)

python -m ui.gradio
# open http://localhost:7860

🛠️ ¿Cómo funciona?

Flujo del agente (simplificado):

  1. 🧭 Planifica cómo resolver la pregunta y qué herramientas usar: p. ej., “comprobar estado del pedido → buscar política de devoluciones”.

  2. 🛤️ Llama a las herramientas según se necesite

    • 🗃️ SQL para clientes/pedidos/productos
    • 📚 RAG sobre embeddings para FAQs/docs (recuerda que puedes prototipar RAG directamente en SQL con EMBEDDING() + operaciones vectoriales como arriba)
    • 🔌 Interoperability API para estado de envíos
  3. 🧩 Sintetiza: compone una respuesta clara y precisa.

Añade o cambia herramientas según crezca tu caso de uso: promociones, garantías, inventario, etc.


🎁 Resumen

Ya tienes un Agente de Atención al Cliente compacto que combina:

  • 🧠 Razonamiento LLM (smolagents CodeAgent)
  • 🗃️ Datos estructurados (IRIS SQL)
  • 📚 Conocimiento no estructurado (IRIS Vector Search + RAG) — con el plus de que EMBEDDING() te permite búsqueda híbrida + vectorial directamente desde SQL
  • 🔌 Llamadas a sistemas en vivo (IRIS Interoperability + Visual Trace)
1
1 60
Artículo Guillaume Rongier · jul 24, 2025 2m read

img

Este será un artículo breve sobre PEP 8, la guía de estilo de Python.

¿Qué es PEP 8?

En pocas palabras, PEP 8 proporciona directrices y buenas prácticas sobre cómo escribir código Python.

  • los nombres de variables deben estar en snake_case

  • los nombres de clases deben estar en CamelCase

  • los nombres de funciones deben estar en snake_case

  • las constantes deben estar en UPPER_CASE

  • la indentación debe ser de 4 espacios (sin tabs)

  • las variables/funciones privadas deben comenzar con un guion bajo (_)

    • porque en Python no existen variables ni funciones privadas; es solo una convención
  • vuestro script no debería ejecutarse al ser importado

    • recuerda que al importar vuestro script, el código se ejecuta; consultad el primer artículo
  • ...

No es necesario mencionarlas todas, pero tenedlas en cuenta: os ayudarán a entender el código de otros y a que otros entiendan vuestro código ^^

También puede que hayáis oído hablar del término "pythonic". Seguir PEP 8 es una forma de escribir código Python que se considera "pythonic" (no es lo único, pero forma parte de ello).

¿Por qué PEP 8 es importante y relevante para los desarrolladores de Python en IRIS?

En IRIS, y especialmente en ObjectScript, también tenemos una guía de estilo, que se basa principalmente en camelCase para los nombres de variables y PascalCase para los nombres de clases.

Desafortunadamente, PEP 8 recomienda usar snake_case para nombres de variables y funciones.

Y como ya sabéis, en ObjectScript el guion bajo se usa para concatenar, lo cual obviamente no nos conviene.

¿Cómo superar este problema? Usad comillas dobles para llamar a nombres de variables o funciones en Python desde código ObjectScript.

Ejemplo:

Class Article.PEP8Example Extends %RegisteredObject
{

ClassMethod Run()
{
    Set sys = ##class(%SYS.Python).Import("sys")
    do sys.path.append("/irisdev/app/src/python/article")
    set pep8Example = ##class(%SYS.Python).Import("pep8_example")
    do pep8Example."my_function"() // Notice the double quotes around the function name
}

}

Esto llamará a la función my_function en el archivo pep8_example.py, que está definida de la siguiente manera:

# src/python/article/pep8_example.py
def my_function():
    print("Hello, World!")

Cuando ejecutéis el método Run de la clase Article.PEP8Example, mostrará:

iris session iris -U IRISAPP '##class(Article.PEP8Example).Run()'
Hello, World!

¡Eso es todo!

0
0 25
Artículo Alberto Fuentes · jul 4, 2025 3m read

Estoy escribiendo esta publicación principalmente para recopilar un consenso informal sobre cómo los desarrolladores están utilizando Python junto con IRIS, ¡así que por favor responded a la encuesta al final del artículo! En el cuerpo del artículo, daré un poco de contexto sobre cada una de las opciones proporcionadas, así como sus ventajas, pero si lo preferís, podéis hacer una lectura rápida y simplemente responder la encuesta.

0
0 36
Artículo Kurro Lopez · jun 30, 2025 16m read

Aprenda a diseñar agentes de IA escalables y autónomos que combinen razonamiento, búsqueda vectorial e integración de herramientas utilizando LangGraph.

cover

Demasiado Largo; No lo Leí

  • Los agentes de IA son sistemas proactivos que combinan memoria, contexto e iniciativa para automatizar tareas que van más allá de los chatbots tradicionales.
  • LangGraph es un framework que nos permite crear flujos de trabajo complejos de IA, utilizando nodos (tareas) y aristas (conexiones) con gestión de estado integrada.
  • Esta guía te guiará en la creación de un agente de atención al cliente basado en IA que clasifica prioridades, identifica temas relevantes y determina si escalar o responder automáticamente.

Entonces, ¿Qué son Exactamente los Agentes de Inteligencia Artificial?

Seamos realistas: los "agentes de IA" pueden parecer los robots que tomarán el control de su sala de juntas. En realidad, son sus aliados proactivos que pueden optimizar flujos de trabajo complejos y eliminar tareas repetitivas. Piense en ellos como el siguiente paso evolutivo más allá de los chatbots: no se limitan a esperar indicaciones; inician acciones, coordinan múltiples pasos y se adaptan sobre la marcha.

Antes, crear un sistema "inteligente" significaba hacer malabarismos con distintos modelos para la comprensión del lenguaje, la generación de código, la búsqueda de datos, etc., y luego unirlos con cinta adhesiva. La mitad del tiempo se perdía en el infierno de la integración, mientras que la otra mitad se dedicaba a depurar el pegamento.

Los agentes dan la vuelta al guión. Combinan contexto, iniciativa y adaptabilidad en un único flujo orquestado. No se trata sólo de automatización, sino de inteligencia con una misión. Y gracias a marcos de trabajo como LangGraph, crear tu propio equipo de agentes puede ser... ¿me atrevería a decir que divertido?

image

¿Qué es LangGraph, Exactamente?

LangGraph es un framework innovador que revoluciona la forma de crear aplicaciones complejas con grandes modelos lingüísticos (LLM).

Imagina que diriges una orquesta: cada instrumento (o "nodo") necesita saber cuándo tocar, con qué volumen y en qué secuencia. LangGraph, en este caso**,** es tu batuta, dándote lo siguiente:

  • Estructura gráfica: Emplea una estructura de tipo gráfico con nodos y aristas, lo que permite a los desarrolladores diseñar flujos de trabajo flexibles y no lineales que admiten ramas y bucles. Refleja procesos complejos de toma de decisiones que se asemejan al funcionamiento de las vías neuronales.
  • Gestión de estados: LangGraph ofrece herramientas integradas para la persistencia del estado y la recuperación de errores, lo que simplifica el mantenimiento de los datos contextuales a través de diversas etapas dentro de una aplicación. Puede alternar eficazmente entre la memoria a corto y a largo plazo, mejorando la calidad de la interacción gracias a herramientas como Zep.
  • Integración de herramientas: Con LangGraph, los agentes LLM pueden colaborar fácilmente con servicios externos o bases de datos para obtener datos del mundo real, mejorando la funcionalidad y capacidad de respuesta de sus aplicaciones.
  • Human-in-the-Loop: Más allá de la automatización, LangGraph da cabida a las intervenciones humanas en los flujos de trabajo, que son cruciales para los procesos de toma de decisiones que requieren supervisión analítica o consideración ética.

Tanto si está construyendo un chatbot con memoria real, un motor de historias interactivo o un equipo de agentes que abordan un problema complejo, LangGraph convierte la fontanería que provoca dolores de cabeza en una máquina de estados limpia y visual.

Primeros Pasos

Para empezar con LangGraph, necesitarás una configuración básica que normalmente implica la instalación de bibliotecas esenciales como langgraph y langchain-openai. A partir de ahí, puedes definir los nodos (tareas) y las aristas (conexiones) dentro del grafo, implementando de forma efectiva puntos de control para la memoria a corto plazo y utilizando Zep para necesidades de memoria más persistentes.

Cuando utilices LangGraph, ten en cuenta lo siguiente:

  • Diseñar con flexibilidad: Aproveche la potente estructura gráfica para tener en cuenta las posibles ramificaciones e interacciones del flujo de trabajo que no sean estrictamente lineales.
  • Interactuar con herramientas cuidadosamente: Mejore, pero no sustituya, las capacidades de LLM con herramientas externas. Proporcione a cada herramienta descripciones completas para permitir un uso preciso.
  • Emplear soluciones de memoria enriquecida: Utilice la memoria de forma eficiente, tenga en cuenta la ventana de contexto del LLM y considere la posibilidad de integrar soluciones externas para la gestión automática de hechos.

Ahora que hemos cubierto los fundamentos de LangGraph, vamos a sumergirnos en un ejemplo práctico. Para ello, desarrollaremos un agente de IA diseñado específicamente para la atención al cliente.

Este agente recibirá solicitudes por correo electrónico, analizará la descripción del problema en el cuerpo del mensaje y determinará la prioridad de la solicitud y el tema/categoría/sector apropiado.

Así que abróchense los cinturones y ¡adelante!

buckle up

Para empezar, tenemos que definir qué es una "Herramienta" (Tool). Puedes pensar en ella como un "gestor asistente" especializado para tu agente, que le permite interactuar con funcionalidades externas.

El decorador @tool es esencial aquí. LangChain simplifica la creación de herramientas personalizadas, lo que significa que primero se define una función de Python y luego se aplica el decorador @tool.

tools

Ilustremos esto creando nuestra primera herramienta. Esta herramienta ayudará al agente a clasificar la prioridad de un ticket de soporte de TI basándose en su contenido de correo electrónico:

    from langchain_core.tools import tool
    
    @tool
    def classify_priority(email_body: str) -> str:
        """Classify the priority of an IT support ticket based on email content."""
        prompt = ChatPromptTemplate.from_template(
            """Analyze this IT support email and classify its priority as High, Medium, or Low.
            
            High: System outages, security breaches, critical business functions down
            Medium: Non-critical issues affecting productivity, software problems
            Low: General questions, requests, minor issues
            
            Email: {email}
            
            Respond with only: High, Medium, or Low"""
        )
        chain = prompt | llm
        response = chain.invoke({"email": email_body})
        return response.content.strip()

Excelente. Ahora tenemos un aviso que ordena a la IA recibir el cuerpo del correo electrónico, analizarlo y clasificar su prioridad como Alta, Media o Baja.

¡Ya está!. ¡Acaba de componer una herramienta que su agente puede llamar!

A continuación, creemos una herramienta similar para identificar el tema principal (o categoría) de la solicitud de asistencia:


    @tool
    def identify_topic(email_body: str) -> str:
        """Identify the main topic/category of the IT support request."""
        prompt = ChatPromptTemplate.from_template(
            """Analyze this IT support email and identify the main topic category.
            
            Categories: password_reset, vpn, software_request, hardware, email, network, printer, other
            
            Email: {email}
            
            Respond with only the category name (lowercase with underscores)."""
        )
        chain = prompt | llm
        response = chain.invoke({"email": email_body})
        return response.content.strip()

Ahora tenemos que crear un estado, y en LangGraph esta pequeña pieza es, más o menos, una gran cosa.

Considéralo el sistema nervioso central de tu gráfico. Es la forma en que los nodos se comunican entre sí, pasándose notas como si fueran alumnos aventajados en clase.

Según los documentos:

"Un estado es una estructura de datos compartida que representa la instantánea actual de tu aplicación".

¿En la práctica? El estado es un mensaje estructurado que se mueve entre nodos. Transporta la salida de un paso como entrada para el siguiente. Básicamente, es el pegamento que mantiene unido todo el flujo de trabajo.

Por lo tanto, antes de construir el gráfico, debemos definir la estructura de nuestro estado. En este ejemplo, nuestro estado incluirá lo siguiente:

  • La solicitud del usuario (cuerpo del correo electrónico)
  • La prioridad asignada
  • El tema identificado (categoría)

Es sencillo y limpio, para que puedas moverte por el gráfico como un profesional.

    from typing import TypedDict

    # Define the state structure
    class TicketState(TypedDict):
        email_body: str
        priority: str
        topic: str
        
    
    # Initialize state
    initial_state = TicketState(
        email_body=email_body,
        priority="",
        topic=""
    )

Nodos vs. Aristas: Componentes Clave de LangGraph

Los elementos fundamentales de LangGraph son los nodos y las aristas.

  • Nodos: Son las unidades operativas dentro del grafo, que realizan el trabajo real. Un nodo suele consistir en código Python que puede ejecutar cualquier lógica, desde cálculos hasta interacciones con modelos lingüísticos (LLM) o integraciones externas. Esencialmente, los nodos son como funciones individuales o agentes en la programación tradicional.
  • Aristas: Las aristas definen el flujo de ejecución entre nodos, determinando lo que ocurre a continuación. Actúan como conectores que permiten la transición del estado de un nodo a otro basándose en condiciones predefinidas. En el contexto de LangGraph, las aristas son cruciales para orquestar la secuencia y el flujo de decisiones entre nodos.

Para comprender la funcionalidad de las aristas, consideremos una simple analogía de una aplicación de mensajería:

  • Los nodos se asemejan a los usuarios (o sus dispositivos) que participan activamente en una conversación.
  • Las aristas simbolizan los hilos de conversación o las conexiones entre usuarios que facilitan la comunicación.

Cuando un usuario selecciona un hilo de chat para enviar un mensaje, se crea efectivamente un borde que lo vincula a otro usuario. Cada interacción, ya sea el envío de un mensaje de texto, voz o vídeo, sigue una secuencia predefinida, comparable al esquema estructurado del estado de LangGraph. Esto garantiza la uniformidad y la interpretabilidad de los datos transmitidos a lo largo del borde.

A diferencia de la naturaleza dinámica de las aplicaciones basadas en eventos, LangGraph emplea un esquema estático que se mantiene constante durante toda la ejecución. Simplifica la comunicación entre nodos, permitiendo a los desarrolladores confiar en un formato de estado estable, garantizando así una comunicación de borde sin fisuras.

Diseño de un Flujo de Trabajo Básico

La ingeniería de flujos en LangGraph puede conceptualizarse como el diseño de una máquina de estados. En este paradigma, cada nodo representa un estado o paso de procesamiento distinto, mientras que las aristas definen las transiciones entre esos estados. Este enfoque es particularmente beneficioso para los desarrolladores que buscan un equilibrio entre las secuencias de tareas deterministas y las capacidades de toma de decisiones dinámicas de la IA. Empecemos a construir nuestro flujo inicializando un StateGraph con la clase TicketState que definimos anteriormente.

    from langgraph.graph import StateGraph, START, END
    
    workflow = StateGraph(TicketState)

Adición de Nodos: Los nodos son bloques de construcción fundamentales, definidos para ejecutar tareas tan específicas como clasificar la prioridad de un ticket o identificar su tema.

Cada función de nodo recibe el estado actual, realiza su operación y devuelve un diccionario para actualizar el estado:

   def classify_priority_node(state: TicketState) -> TicketState:
        """Node to classify ticket priority."""
        priority = classify_priority.invoke({"email_body": state["email_body"]})
        return {"priority": priority}

    def identify_topic_node(state: TicketState) -> TicketState:
        """Node to identify ticket topic."""
        topic = identify_topic.invoke({"email_body": state["email_body"]})
        return {"topic": topic}
        
        
    workflow.add_node("classify_priority", classify_priority_node)
    workflow.add_node("identify_topic", identify_topic_node)

Los métodos classify_priority_node e identify_topic_node cambiarán el TicketState y enviarán la entrada de parámetros.

Creación de Aristas: Definir aristas para conectar nodos:


    workflow.add_edge(START, "classify_priority")
    workflow.add_edge("classify_priority", "identify_topic")
    workflow.add_edge("identify_topic", END)

El classify_priority establece el inicio, mientras que el identify_topic determina el final de nuestro flujo de trabajo hasta el momento.

Compilación y Ejecución: Una vez configurados los nodos y las aristas, compile el flujo de trabajo y ejecútelo.


    graph = workflow.compile()
    result = graph.invoke(initial_state)

¡Genial! También puede generar una representación visual de nuestro flujo LangGraph.

graph.get_graph().draw_mermaid_png(output_file_path="graph.png")

Si ejecutara el código hasta este punto, observaría un gráfico similar al siguiente:

first_graph.png

Esta ilustración visualiza una ejecución secuencial: inicio, seguido de clasificación de la prioridad, luego identificación del tema y, por último, finalización.

Uno de los aspectos más potentes de LangGraph es su flexibilidad, que nos permite crear flujos y aplicaciones más complejos. Por ejemplo, podemos modificar el flujo de trabajo para añadir aristas desde START a ambos nodos con la siguiente línea:

    workflow.add_edge(START, "classify_priority")
    workflow.add_edge(START, "identify_topic")

Este cambio implicará que el agente ejecute classify_priority e identify_topic simultáneamente.

Otra característica muy valiosa de LangGraph es la posibilidad de utilizar aristas condicionales. Permiten que el flujo de trabajo se ramifique en función de la evaluación del estado actual, permitiendo el enrutamiento dinámico de las tareas.

Vamos a mejorar nuestro flujo de trabajo. Crearemos una nueva herramienta que analice el contenido, la prioridad y el tema de la solicitud para determinar si se trata de un problema de alta prioridad que requiere escalado (es decir, la apertura de un ticket para un equipo humano). Si no es así, se generará una respuesta automática para el usuario.


    @tool
    def make_escalation_decision(email_body: str, priority: str, topic: str) -> str:
        """Decide whether to auto-respond or escalate to IT team."""
        prompt = ChatPromptTemplate.from_template(
            """Based on this IT support ticket, decide whether to:
            - "auto_respond": Send an automated response for simple/common or medium priority issues
            - "escalate": Escalate to the IT team for complex/urgent issues
            
            Email: {email}
            Priority: {priority}
            Topic: {topic}
            
            Consider: High priority items usually require escalation, while complex technical issues necessitate human review.
            
            Respond with only: auto_respond or escalate"""
        )
        chain = prompt | llm
        response = chain.invoke({
            "email": email_body,
            "priority": priority,
            "topic": topic
        })
        return response.content.strip()
        

Además, si se determina que la solicitud es de prioridad baja o media (lo que lleva a una decisión de "auto_responder"), realizaremos una búsqueda vectorial para recuperar respuestas históricas. Esta información se utilizará entonces para generar una respuesta automática adecuada. Sin embargo, se necesitarán dos herramientas adicionales:


    @tool
    def retrieve_examples(email_body: str) -> str:
        """Retrieve relevant examples from past responses based on email_body."""
        try:
            examples = iris.cls(__name__).Retrieve(email_body)
            return examples if examples else "No relevant examples found."
        except:
            return "No relevant examples found."

    @tool
    def generate_reply(email_body: str, topic: str, examples: str) -> str:
        """Generate a suggested reply based on the email, topic, and RAG examples."""
        prompt = ChatPromptTemplate.from_template(
            """Generate a professional IT support response based on:
            
            Original Email: {email}
            Topic Category: {topic}
            Example Response: {examples}
            
            Create a helpful, professional response that addresses the user's concern.
            Keep it concise and actionable."""
        )
        chain = prompt | llm
        response = chain.invoke({
            "email": email_body,
            "topic": topic,
            "examples": examples
        })
        return response.content.strip()

Ahora, definamos los nodos correspondientes a esas nuevas herramientas:

    
    def decision_node(state: TicketState) -> TicketState:
        """Node to decide on escalation or auto-response."""
        decision = make_escalation_decision.invoke({
            "email_body": state["email_body"],
            "priority": state["priority"],
            "topic": state["topic"]
        })
        return {"decision": decision}
        
    
    def rag_node(state: TicketState) -> TicketState:
        """Node to retrieve relevant examples using RAG."""
        examples = retrieve_examples.invoke({"email_body": state["email_body"]})
        return {"rag_examples": examples}

    def generate_reply_node(state: TicketState) -> TicketState:
        """Node to generate suggested reply."""
        reply = generate_reply.invoke({
            "email_body": state["email_body"],
            "topic": state["topic"],
            "examples": state["rag_examples"]
        })
        return {"suggested_reply": reply}
        
    
    def execute_action_node(state: TicketState) -> TicketState:
        """Node to execute final action based on decision."""
        if state["decision"] == "escalate":
            action = f"&#x1f6a8; ESCALATED TO IT TEAM\nPriority: {state['priority']}\nTopic: {state['topic']}\nTicket created in system."
            print(f"[SYSTEM] Escalating ticket to IT team - Priority: {state['priority']}, Topic: {state['topic']}")
        else:
            action = f"&#x2705; AUTO-RESPONSE SENT\nReply: {state['suggested_reply']}\nTicket logged for tracking."
            print(f"[SYSTEM] Auto-response sent to user - Topic: {state['topic']}")
        
        return {"final_action": action}
        
        
        
    workflow.add_node("make_decision", decision_node)
    workflow.add_node("rag", rag_node)
    workflow.add_node("generate_reply", generate_reply_node)
    workflow.add_node("execute_action", execute_action_node)

El borde condicional utilizará entonces la salida del nodo make_decision para dirigir el flujo:

    workflow.add_conditional_edges(
        "make_decision",
        lambda x: x.get("decision"),
        {
            "auto_respond": "rag",
            "escalate": "execute_action"
        }
    )

Si la herramienta make_escalation_decision (a través de decision_node) da como resultado "auto_respond", el flujo de trabajo procederá a través del nodo rag (para recuperar ejemplos), luego a generate_reply (para elaborar la respuesta), y finalmente a execute_action (para registrar la auto-respuesta).

Por el contrario, si la decisión es "escalar", el flujo pasará por alto el RAG y tomará pasos de generación, moviéndose directamente a execute_action para manejar la escalada. Para completar el gráfico añadiendo las aristas estándar restantes, haga lo siguiente:

    workflow.add_edge("rag", "generate_reply")
    workflow.add_edge("generate_reply", "execute_action")
    workflow.add_edge("execute_action", END)

Nota sobre el Dataset: Para este proyecto, el dataset que utilizamos para alimentar la generación mejorada de recuperación (RAG) procedía del dataset Customer Support Tickets dataset on Hugging Face. El dataset se filtró para incluir exclusivamente los elementos categorizados como "Asistencia técnica" y se restringió a entradas en inglés. De este modo, se garantizaba que el sistema RAG sólo recuperara ejemplos muy relevantes y específicos del ámbito de las tareas de asistencia técnica.

Llegados a este punto, nuestro gráfico debería parecerse al siguiente:

graph.png

Cuando ejecute este gráfico con un correo electrónico que resulte en una clasificación de alta prioridad y una decisión de "escalar", verá la siguiente respuesta:

image.png

Al mismo tiempo, una solicitud clasificada como de baja prioridad y que dé lugar a una decisión "auto_respuesta" desencadenará una respuesta parecida a la que se muestra a continuación:

image.png

Entonces... ¿Esto es todo Solete?

No del todo. Hay que tener cuidado con algunos puntos débiles:

  • Privacidad de los datos: Tenga cuidado con la información sensible - estos agentes requieren barandillas.
  • Costes de computación: Algunas configuraciones avanzadas requieren recursos importantes.
  • Alucinaciones: Los LLMs pueden ocasionalmente inventar cosas (aunque son más inteligentes que la mayoría de los internos).
  • No determinismo: La misma entrada puede devolver diferentes salidas, lo que es genial para la creatividad, pero complicado para los procesos estrictos.

Sin embargo, la mayoría de estos puntos débiles pueden gestionarse con una buena planificación, las herramientas adecuadas y -lo has adivinado- un poco de reflexión.

LangGraph hace que los agentes de IA dejen de ser palabras de moda para convertirse en soluciones reales y operativas. Tanto si desea automatizar la atención al cliente, gestionar incidencias informáticas o crear aplicaciones autónomas, este marco lo hace posible y, de hecho, agradable.

¿Tienes alguna pregunta o comentario? Hablemos. La revolución de la IA necesita constructores como tú.

2
0 71
Artículo Julio Esquerdo · jun 5, 2025 18m read

Uso de Python en InterSystems Iris

Hola

En este artículo veremos el uso de python como lenguaje de programación en InterSystems Iris. Para ello, utilizaremos como referencia la versión de Community 2025.1 que está disponible para ser descargada en https:// download.intersystems.com iniciando sesión en el entorno. Para obtener más información sobre cómo descargar e instalar Iris, consulte el enlace de la comunidad https://community.intersystems.com/post/how-download-and-install-intersystems-iris

0
0 49
Artículo Daniela Echenique · jun 2, 2025 5m read

Objetivo

Explorar un enfoque de validación estructural adaptativa para mantener estables las pruebas automatizadas frente a cambios en la estructura del JSON generado a partir de datos expuestos desde una base de datos InterSystems IRIS.

Se busca minimizar falsos positivos ante cambios no críticos, siempre que la respuesta siga siendo coherente con la estructura real de la base. En estos casos, la ejecución continúa y se deja registro del evento en un archivo estructurado, que permite trazabilidad y análisis evolutivo.

Además de validar la idea central, este proyecto abre camino a posibles extensiones técnicas, como la validación dinámica contra el esquema real de la base, el seguimiento histórico de cambios estructurales, y el uso de técnicas de machine learning para distinguir entre ajustes legítimos y errores funcionales, comprendiendo no solo el cambio, sino también su intención.

Desafíos actuales

Diseñar una Arquitectura de Automatización de Pruebas sólida es fundamental para garantizar la calidad en entornos donde las APIs son el nexo entre sistemas.

Un desafío que enfrentan los equipos de testing es la fragilidad de las pruebas automatizadas ante cambios menores en la estructura del JSON que devuelven los endpoints.

Estos cambios, aunque no afecten el comportamiento funcional de la API, pueden romper los tests al no coincidir con las estructuras esperadas, lo que genera:

  • Tiempo y esfuerzo en mantenimiento.
  • Interrupciones de pipelines CI/CD.
  • Dificultad para identificar defectos reales.
  • Desconfianza en la suite de regresión.

La fragilidad estructural no es un detalle técnico: es un obstáculo estratégico, el avance es hacia resiliencia estructural, con pruebas que evolucionen con el sistema, no que se quiebren con él.

Enfoque propuesto

Frente a estos desafíos, se propone una estrategia de validación estructural adaptativa, que incorpora una capa intermedia entre la respuesta de la API y la lógica del test, capaz de detectar diferencias estructurales y responder de manera flexible según el tipo de cambio.

Cuando se detecta una discrepancia entre el esquema recibido y el esperado, el sistema debería poder:

  1. Identificar la diferencia estructural (claves nuevas o faltantes).
  2. Validar si el cambio es coherente con la evolución del modelo de datos, utilizando una fuente confiable como la base de datos real.
  3. Clasificar según reglas predefinidas
    • Si es aceptable, continuar la ejecución y registrar un warning.
    • Si es crítico, detener la prueba.
  4. Dejar registro del evento en un archivo .ndjson, lo que habilita posibles evoluciones:
    • Dashboards de trazabilidad.
    • Entrenamiento de modelos para anticipar cambios.
    • Pull requests automáticos para adaptar tests validados.

MVP

Se desarrolló un MVP funcional que implementa este enfoque y permite validar la adaptación estructural ante cambios en la respuesta JSON generada desde InterSystems IRIS.

Componentes principales

  • Base de datos: InterSystems IRIS Community Edition ejecutada en contenedor Docker.
  • Exposición de datos: Clase ObjectScript que expone información como JSON.
  • Cliente de validacion: Python 3.10 con el módulo irisnative.
  • Pruebas: Pytest
  • Lógica de validación:
    • Modo estricto: falla ante cualquier discrepancia.
    • Modo adaptativo: acepta campos adicionales no esperados y emite un warning si no faltan campos críticos.
  • Trazabilidad: eventos estructurales detectados se almacenan en formato .ndjson.
  • IDE: Visual Studio Code (Ubuntu)
  • Repositorio: https://github.com/danielaeche/resilient-api-test.git

Limitación del MVP

La validación estructural no se hace contra el esquema activo de la base en tiempo real, sino contra una clase intermediaria. Esto significa que si la base cambia, se debe actualizar manualmente la clase.

Escalar esta solución requiere usar la Python DB API de InterSystems para consultar directamente la estructura actual de la base de datos, permitiendo una validación verdaderamente dinámica y autónoma, sin necesidad de actualizar manualmente la clase que expone los datos.

Ejecución de prueba

  1. Estructura esperada: Se entrega este JSON desde IRIS:
[
  { "id": 1, "name": "Virginia", "age": 38 },
  { "id": 2, "name": "Daniela", "age": 17 }
]
  • Test estricto (test_api_strict.py) : ✅ Resultado: la prueba pasa.
  1. Cambio estructural simulado : Se agrega un campo nuevo (coverage):
[
  { "id": 1, "name": "Virginia", "age": 38, "coverage": "premium" },
  { "id": 2, "name": "Daniela", "age": 17, "coverage": "basic" }
]
  • Test estricto: 🛑 Resultado: la prueba falla por estructura inesperada.
  • Test adaptativo (test_api_adaptive.py) : ⚠️ Resultado: la prueba pasa con warning[WARNING] Nuevos campos detectados: {'coverage'}

Conclusión

Este artículo propone un enfoque frente a un problema: cómo evitar que pequeños cambios estructurales rompan pruebas que no deberían fallar.

El MVP demuestra que es posible incorporar tolerancia estructural sin perder capacidad de detección. La solución es escalable y propone una evolución hacia modelos donde la calidad de la prueba no dependa exclusivamente del script, sino que sea respaldada por una capa de validación contextual que no solo detecta un cambio, sino que evalúa su impacto antes de decidir.

También plantea una dirección de valor: mecanismos resilientes que puedan integrarse de forma nativa en herramientas como las que InterSystems desarrolla, como parte de una estrategia de calidad continua para entornos donde el cambio es constante.

El futuro de la automatización no está en tener más scripts que se ejecuten una y otra vez, sino en construir pruebas capaces de entender por qué fallan los sistemas, incluso cuando parecen funcionar.

Automatizar con resiliencia es evitar que el esfuerzo se consuma en lo repetible, y entregarle a quienes diseñan la calidad el espacio para identificar riesgos emergentes, desafiar lo previsto y diseñar validaciones con sentido estratégico.

0
0 66
InterSystems Official Mario Sanchez Macias · abr 7, 2025

Resumen de alertas

ID de la Alerta Productos y versiones afectadas Requisitos explícitos
DP-439207 InterSystems IRIS® data platform 2024.3 (AIX) Instalaciones AIX Uso del procesamiento JSON y conjuntos de caracteres Unicode no-Latin-1 
DP-439280 InterSystems IRIS 2024.3 (containers with IntegratedML) Contenedores integradosML usando TensorFlow

Detalle de las alertas

DP-439207 - Problema de análisis JSON Unicode en AIX

0
0 45
Artículo Luis Angel Pérez Ramos · mar 21, 2025 5m read

Después de tantos años de espera, por fin tenemos un controlador oficial disponible en Pypi

Además, encontré el driver JDBC finalmente disponible en Maven desde hace ya 3 meses, y el driver .Net en Nuget desde hace más de un mes.

Como autor de multitud de implementaciones de librerías de Python con soporte de IRIS, quería probarlo. La implementación de DB-API significa que debe ser reemplazable e implementar las funciones definidas en el estándar. La única diferencia entre las diferentes bases de datos debería ser únicamente el SQL.

Y la belleza de usar librerías existentes que ya implementaron la conectividad con otras bases de datos usando el estándar DB-API, es que estas librerías deberían ser totalmente compatibles con el controlador.

Decidí probar el driver oficial de InterSystems implementando su soporte en la librería SQLAlchemy-iris.

0
0 40
Artículo Rodolfo Pscheidt Jr · mar 18, 2025 2m read

 

En este artículo, discutiré el uso de un LLM alternativo para la IA generativa. OpenAI es comúnmente utilizado, pero en este artículo os mostraré cómo usarlo y las ventajas de utilizar Ollama.

En el modelo de uso de IA generativa al que estamos acostumbrados, seguimos el siguiente flujo:

  • Tomamos textos de una fuente de datos (un archivo, por ejemplo) y los transformamos en vectores.
  • Almacenamos los vectores en una base de datos IRIS.
  • Llamamos a un LLM (Large Language Model) que accede a estos vectores como contexto para generar respuestas en lenguaje humano.
0
0 91
Artículo Ricardo Paiva · mar 17, 2025 6m read

InterSystems ha estado a la vanguardia de la tecnología de bases de datos desde su creación, siendo pionera en innovaciones que superan constantemente a competidores como Oracle, IBM y Microsoft. Al centrarse en un diseño eficiente del núcleo y adoptar un enfoque sin concesiones en el rendimiento de los datos, InterSystems se ha hecho un hueco en las aplicaciones de misión crítica, garantizando fiabilidad, velocidad y escalabilidad.

Una historia de excelencia técnica

0
0 62
Artículo Alberto Fuentes · jul 16, 2024 4m read

wsgi_logo

Contexto

La Interfaz de Pasarela de Servidor Web (WSGI, por sus siglas en inglés) es una convención de llamada para que los servidores web puedan reenviar solicitudes a aplicaciones o frameworks web escritos en el lenguaje de programación Python. WSGI es un estándar de Python descrito en detalle en PEP 3333.

🤔 Ok, gran definición, ¿y qué tiene que ver con IRIS?

IRIS 2024.2+ tiene una nueva característica que os permite ejecutar aplicaciones WSGI directamente en IRIS. Esta característica es una excelente manera de integrar IRIS con otros frameworks y bibliotecas de Python.

Esto sigue la tendencia de la experiencia Python primero, donde podéis usar Python para interactuar con IRIS, y ahora también podéis ejecutar aplicaciones Python directamente en IRIS.

Cómo usarla

Para instanciar una aplicación WSGI en IRIS, necesitáis configurarla en la sección Seguridad->Aplicaciones->Aplicaciones Web del Portal de Gestión de IRIS.

Ejemplo simple de Flask:

Archivo llamado app.py en el directorio /irisdev/app/community:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

Configuración de la Interfaz de Usuario

image

En esta sección, podéis configurar la aplicación WSGI introduciendo lo siguiente:

  • Nombre de Aplicación

  • esto corresponde al nombre del archivo de la aplicación WSGI.

  • Ejemplo: app.py pero sin la extensión .py: app.

  • Nombre de la función a llamar

    • la función invocable que será llamada por el servidor WSGI

    • por ejemplo, app corresponde a la variable app en el archivo app.py

      • app = Flask(__name__)
  • Directorio de aplicaciones WSGI

    • la ruta donde se encuentra la aplicación WSGI
    • ej: /irisdev/app/community
  • Tipo de protocolo Python

    • puede ser wsgi o asgi
      • wsgi es el valor por defecto y el utilizado en este ejemplo
      • asgi es para aplicaciones asíncronas
        • por ahora soportamos asgi de forma sincronizada con el adaptador a2wsgi
  • DEBUG

    • si está marcada, la aplicación WSGI se ejecutará en modo depuración
      • esto es útil para fines de desarrollo, ya que cualquier cambio en la aplicación WSGI se recargará automáticamente

Fusión fichero CPF

También podéis configurar la aplicación WSGI utilizando el fichero de configuración CPF de IRIS. He aquí un ejemplo de configuración:

[Actions]
CreateApplication:Name=/flask,NameSpace=IRISAPP,WSGIAppLocation=/irisdev/app/community/,WSGIAppName=app,WSGICallable=app,Type=2,DispatchClass=%SYS.Python.WSGI,MatchRoles=:%ALL,WSGIDebug=0,WSGIType=0

Archivos de registro

Los registros de la aplicación (logs) WSGI se almacenan en el archivo WSGI.log ubicado en el directorio mgr de la instancia.

Ejemplos

Aquí tenéis algunos ejemplos de aplicaciones WSGI que podéis ejecutar en IRIS, pretenden mostrar cómo ejecutar diferentes frameworks de Python en IRIS.

Básicamente, el caso de uso será el mismo para todos los frameworks:

Endpoints

  • /iris - Devuelve un objeto JSON con las 10 principales clases presentes en el espacio de nombres IRISAPP.
  • /interop - Un endpoint que hará de "ping" para probar el framework de interoperabilidad de IRIS.
  • /posts - Un endpoint CRUD sencillo para un objeto Post.
  • /comments - Un endpoint CRUD sencillo para un objeto Comment.

Modelo de objeto

Objeto Post:

  • id
  • title
  • content

Objeto Comment:

  • id
  • post_id (foreign key to Post)
  • content

Flask

Django

FastAPI

Limitaciones

  • El ASGI es soportado sincrónicamente por ahora con el adaptador a2wsgi.
  • Las aplicaciones tornado ( jupyter, streamlit, .. ) no están soportadas ya que no son compatibles con WSGI.
2
0 346
Artículo Alberto Fuentes · ene 27, 2025 5m read

Me alegra anunciar la nueva versión de IoP, que, por cierto, no es solo una línea de comandos. Lo digo porque el nuevo motor de búsqueda con IA aún cree que IoP es solo una línea de comandos. Pero no lo es. Es todo un framework para construir aplicaciones sobre las funcionalidades de interoperabilidad de IRIS con un enfoque Python.

La nueva versión de IoP: 3.2.0 viene con muchas características nuevas, pero la más importante es el soporte para DTL . 🥳

Tanto para mensajes de IoP como para jsonschema. 🎉

image

Soporte para DTL

A partir de la versión 3.2.0, IoP admite transformaciones DTL.

DTL es la Capa de Transformación de Datos (Data Transformation Layer) en interoperabilidad de IRIS.

Las transformaciones DTL se utilizan para convertir datos de un formato a otro mediante un editor gráfico.
También es compatible con estructuras de jsonschema.

Cómo usar DTL con Mensajes

Primero, tenéis que registrar vuestra clase de mensaje en un archivo settings.py.

Para hacerlo, debéis añadir la siguiente línea en el archivo settings.py:

settings.py

from msg import MyMessage

SCHEMAS = [MyMessage]

Luego, podéis usar el comando de migración de IoP para generar los archivos de esquema para vuestras clases de mensaje.

iop --migrate /path/to/your/project/settings.py

Ejemplo

msg.py

from iop import Message
from dataclasses import dataclass

@dataclass
class MyMessage(Message):
    name: str = None
    age: int = None

settings.py

from msg import MyMessage

SCHEMAS = [MyMessage]

Migrad los archivos de esquema

iop --migrate /path/to/your/project/settings.py

Construyendo una transformación DTL

Para construir una transformación DTL, tenéis que crear una nueva clase de transformación DTL.

Id al Portal de Gestión de Interoperabilidad de IRIS y cread una nueva transformación DTL.

image

Luego, seleccionad las clases de mensaje de origen y destino.

image

Y su esquema.

image

Luego, podéis comenzar a construir vuestra transformación.

image

Incluso podéis probar vuestra transformación.

image

Ejemplo de payload para probar como mensaje de origen:

<test>
  <Message>
    <json><![CDATA[
{
"list_str":["toto","titi"],
"post":{"Title":"foo","Selftext":"baz"},
"list_post":[{"Title":"bar","Selftext":"baz"},{"Title":"foo","Selftext":"foo"}]
}
]]></json>
  </Message>
</test>

Soporte para JsonSchema

A partir de la versión 3.2.0, IoP admite estructuras jsonschema para las transformaciones DTL.

Al igual que con las clases de mensaje, necesitáis registrar vuestro jsonschema.

Para hacerlo, debéis invocar este comando de Iris:

zw ##class(IOP.Message.JSONSchema).ImportFromFile("/irisdev/app/random_jsonschema.json","Demo","Demo")

Donde el primer argumento es la ruta al archivo jsonschema, el segundo argumento es el nombre del paquete y el tercer argumento es el nombre del esquema.

Luego, podéis usarlo en vuestra transformación DTL.

El esquema estará disponible con el nombre de Demo.

Ejemplo de archivo jsonschema:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "title": "PostMessage",
    "properties": {
        "post": {
            "allOf": [
                {
                    "$ref": "#/$defs/PostClass"
                }
            ]
        },
        "to_email_address": {
            "type": "string",
            "default": null
        },
        "my_list": {
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "found": {
            "type": "string",
            "default": null
        },
        "list_of_post": {
            "type": "array",
            "items": {
                "allOf": [
                    {
                        "$ref": "#/$defs/PostClass"
                    }
                ]
            }
        }
    },
    "$defs": {
        "PostClass": {
            "type": "object",
            "title": "PostClass",
            "properties": {
                "title": {
                    "type": "string"
                },
                "selftext": {
                    "type": "string"
                },
                "author": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                },
                "created_utc": {
                    "type": "number"
                },
                "original_json": {
                    "type": "string",
                    "default": null
                }
            },
            "required": [
                "title",
                "selftext",
                "author",
                "url",
                "created_utc"
            ]
        }
    }
}

Ejemplo de Transformación DTL con JsonSchema o Clase de Mensaje

Muchos se pueden encontrar en el paquete UnitTest en el directorio ./src/tests/cls.

Class UnitTest.ComplexTransform Extends Ens.DataTransformDTL [ DependsOn = IOP.Message ]
{

Parameter IGNOREMISSINGSOURCE = 1;

Parameter REPORTERRORS = 1;

Parameter TREATEMPTYREPEATINGFIELDASNULL = 0;

XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl" ]
{
<transform sourceClass='IOP.Message' targetClass='IOP.Message' sourceDocType='registerFilesIop.message.ComplexMessage' targetDocType='registerFilesIop.message.ComplexMessage' create='new' language='objectscript' >
<assign value='source.{post}' property='target.{post}' action='set' />
<foreach property='source.{list_str()}' key='k1' >
<assign value='source.{list_str(k1)}_"foo"' property='target.{list_str()}' action='append' />
</foreach>
<foreach property='source.{list_post()}' key='k2' >
<assign value='source.{list_post().Title}' property='target.{list_post(k2).Title}' action='append' />
</foreach>
</transform>
}

}

Nueva documentación

IoP viene con una nueva documentación, la cual está disponible en https://grongierisc.github.io/interoperability-embedded-python/.

Allí encontraréis toda la información que necesitáis para comenzar a usar IoP.

image

Espero que disfrutéis esta nueva versión de IoP. 🎉

0
0 71
Artículo Joel Espinoza · ene 9, 2025 2m read

Hola colegas,

Hace unos días me surgio el requerimiento de cargar un archivo parquet, tal y como viene a una tabla en IRIS, así que desarrolló este classmethod que gatilla la carga solo indicando la ruta y nombre del archivo y el nombre de la tabla destino (en el mismo espacio de nombres). Para esto, escribí un pequeño código en Python que requiere las librerías: Pandas, pyarrow y fastparquet, las cuales deben instalar en el directorio de instalación de IRIS según su instalación, en mi caso el comando es:

instalación de pip3 --target /usr/irissys/mgr/python/ pandas pyarrow fastparquet

Para cargar los datos se deberán entregar los siguientes parametros

archivo: ruta completa al archivo parquet a cargar /var/data/archivo.parquet tabla: tabla en la cual se cargaran los datos

desde el terminal: do ##class(testing.python).cargaParquet("/var/data/files/testing-ndoc.parquet","testing.ndoc")

Espero les sea de utilidad.

Class testing.python Extends %RegisteredObject
{

ClassMethod cargaParquet(parquet, tabla) [ Language = python ]
{
    try:
        import pandas as pd
        import iris

        parquet_file= parquet
        df = pd.read_parquet(parquet_file)
        i=0
        linea=""
        valores=""
        sql=""
        max_index = len(df)       
        for x in df.columns:
                linea = linea + x + ","

        linea = linea.replace("_","")[:-1]
        sql="insert into "+ tabla +" (" + linea  + ")"
        while i<max_index:
            for x in df.columns:
                    valores = valores + "'" + str(df.at[i,x]) + "',"
            valores = " values("+ valores[:-1] +")"
            tsc = iris.sql.exec(sql + valores)
            valores=""
            i+=1
    except:
        print("error de carga de registro " +i)
}
}
1
0 105
Artículo Ricardo Paiva · ene 8, 2025 4m read

Link de Git: https://github.com/ecelg/InterSystems-IRIS-as-a-Spotify-REST-client

Recientemente se me ocurrió una idea: ¿cómo puedo poner mi lista de reproducción en IRIS? 🧐

Al mismo tiempo, me dijeron que debía pagar mi suscripción de Spotify 💸💸... oooh... ¿y si obtengo algunos datos de la API de Spotify? Así que empecé a investigar sobre eso.

Como en la mayoría de los desarrollos, comencemos con la documentación de la API: https://developer.spotify.com/documentation/web-api

Para obtener los datos, se requiere solicitar un token de acceso en la URL del endpoint de token. 🧐

0
0 89
Artículo Rolano Rebelo · dic 5, 2024 4m read

🌍 Inclusión e Innovación en la Educación 🌍
Nuestro proyecto reimagina el aprendizaje para todos los estudiantes, con un enfoque en la accesibilidad y experiencias interactivas. Diseñado con el objetivo de hacer que la educación sea atractiva e inclusiva, esta herramienta está creada para apoyar a estudiantes de todas las habilidades en el aprendizaje de material complejo de forma intuitiva.

💡 Lo que hace
Esta aplicación educativa transforma presentaciones de lecciones en sesiones de estudio interactivas:

0
0 62
InterSystems Official Jose-Tomas Salvador · dic 2, 2024

Hoy os anuncio la incorporación de un nuevo curso en nuestro catálogo: Desarrollo de asistentes virtuales con RAG. ¡Recien salido del horno  y con un precio promocional muy reducido! (plazas limitadas).

Se trata de un curso abierto a todo el que quiera iniciarse en el desarrollo de asistentes virtualesutilizando IA generativa y el patrón común RAG (Retrieval Augmented Generation). Utilizaréis Python, Jupyter Notebooks y Streamlit para el desarrollo de la solución que servirá de ejemplo y que iréis implementando paso a paso.

1
0 108
Artículo Kate Lau · nov 7, 2024 8m read

Escribo esto para compartir algo que me pasó anoche: ¡¡¡IRIS 2024.3 ya no viene con Python por defecto!!!

¡Eso significa que hay que instalarlo uno mismo como usuario! 😅 La ventaja es que puedo elegir mi versión de Python 😁🤭💃 Pero el problema es que, al principio... no sabía qué hacer 😥. Al revisar la comunidad (me gusta mucho más que la documentación oficial, lo siento equipo de documentación de InterSystems 😓), encontré el siguiente enlace:

https://docs.intersystems.com/iris20242/csp/docbook/DocBook.UI.Page.cls…

0
0 64
Artículo Ricardo Paiva · sep 12, 2024 3m read

Samba es el estándar para la interoperabilidad de servicios de archivos entre Linux, Unix, DOS, Windows, OS/2 y otros sistemas operativos. Desde 1992, Samba ha proporcionado servicios de archivos seguros, estables y rápidos para todos los clientes (sistemas operativos y programas) utilizando el protocolo SMB/CIFS. Los administradores de red han utilizado SAMBA para crear carpetas de red compartidas que permiten a los empleados de la empresa crear, editar y visualizar archivos corporativos como si estuvieran en sus ordenadores localmente, aunque estos archivos se encuentren físicamente en un

0
0 205
Artículo Jose-Tomas Salvador · sep 11, 2024 4m read

El desarrollo frontend puede ser una tarea desalentadora, incluso una pesadilla, para los desarrolladores centrados en el backend. Al principio de mi carrera, la línea entre frontend y backend era difusa, y se esperaba que todos manejaran ambos. El CSS, en particular, siempre fue una lucha constante; sentía que era una misión imposible.

Aunque disfruto trabajando en el frontend, el CSS sigue siendo un desafío complejo para mí, especialmente porque lo aprendí a base de prueba y error. El meme de Peter Griffin luchando por abrir persianas captura perfectamente mi experiencia aprendiendo CSS.

Peter Griffin CSS

Pero hoy, todo cambia. Herramientas como Streamlit han revolucionado el juego para desarrolladores como yo, que prefieren la comodidad de la pantalla negra del terminal. Se acabaron los días de pelear con líneas de código que parecen mensajes crípticos de extraterrestres (¡me refiero a ti, CSS!).
Como siempre dice el Doctor Károly Zsolnai-Fehér de Two Minute Papers, "¡Qué tiempo para estar vivos!"

Con Streamlit, podéis construir una aplicación web completa usando solo código en Python.
¿Queréis verlo en acción? Abrochaos el cinturón, porque estoy a punto de compartir mi intento de crear el frontend para SQLZilla usando esta increíble herramienta.

Para instalarlo, simplemente abrid vuestro terminal y lanzad este hechizo:

pip install streamlit

(O también podéis añadirlo a vuestro archivo requirements.txt.)

Cread un archivo llamado app.py y añadid este fragmento de código para mostrar un título de "SQLZilla":

import streamlit as st

st.title("SQLZilla")

¡Ejecutad la demostración!

Volved a abrir vuestro terminal y escribid este comando para activar vuestra creación:

streamlit run app.py

¡Voilà! Vuestra aplicación de Streamlit debería aparecer en vuestro navegador web, mostrando con orgullo el título "SQLZilla".

Para mostrar una imagen, carguemos un fichero (small_logo.png) con la imagen que queráis, en el mismo directorio en que se encuentra el programa app.py.

Para mostrar esa imagen basta entonces con usar el método image. Para centrarla, simplemente creé 3 columnas y la añadí al centro (¡creativo que estoy!).

   st.title("SQLZilla")

   left_co, cent_co, last_co = st.columns(3)
   with cent_co:
       st.image("small_logo.png", use_column_width=True)

Para gestionar configuraciones y resultados de consultas, podéis usar el estado de la sesión. Aquí tenéis cómo guardar valores de configuración y almacenar resultados de consultas:

if 'hostname' not in st.session_state:
    st.session_state.hostname = 'sqlzilla-iris-1'
if 'user' not in st.session_state:
    st.session_state.user = '_system'
if 'pwd' not in st.session_state:
    st.session_state.pwd = 'SYS'
# Add other session states as needed

Para conectar SQLZilla a una base de datos InterSystems IRIS, podéis usar SQLAlchemy. Primero, instalad SQLAlchemy con:

pip install sqlalchemy
pip install sqlalchemy-iris

El paquete sqlalchemy-iris se lo debemos al trabajo realizado por @Dimitry Maslennikov y tenéis más información en SQLAlchemy-iris en OpenExchange. Basicamente nos permitirá usar el dialecto "iris" cuando nos conectemos con sqlalchemy a un IRIS.

Luego, configurad la conexión en vuestro archivo app.py:

from sqlalchemy import create_engine
import pandas as pd

# Replace with your own connection details
engine = create_engine(f"iris://{user}:{password}@{host}:{port}/{namespace}")

def run_query(query):
    with engine.connect() as connection:
        result = pd.read_sql(query, connection)
        return result

Una vez que os hayáis conectado a la base de datos, podéis usar Pandas y Streamlit para mostrar los resultados de vuestras consultas. Aquí tenéis un ejemplo de cómo mostrar un DataFrame en vuestra aplicación de Streamlit:

if 'query' in st.session_state:
    query = st.session_state.query
    df = run_query(query)
    st.dataframe(df)

Para hacer que vuestra aplicación sea más interactiva, podéis usar st.rerun() para actualizar la aplicación cada vez que cambie la consulta:

if 'query' in st.session_state and st.button('Run Query'):
    df = run_query(st.session_state.query)
    st.dataframe(df)
    st.rerun()

Podéis encontrar varios componentes de Streamlit para usar. En SQLZilla, añadí una versión del editor de código ACE llamada streamlit-code-editor:

from code_editor import code_editor

editor_dict = code_editor(st.session_state.code_text, lang="sql", height=[10, 100], shortcuts="vscode")

if len(editor_dict['text']) != 0:
    st.session_state.code_text = editor_dict['text']

Dado que el asistente de SQLZilla está escrito en Python, simplemente llamé a la clase:

from sqlzilla import SQLZilla

def assistant_interaction(sqlzilla, prompt):
    response = sqlzilla.prompt(prompt)
    st.session_state.chat_history.append({"role": "user", "content": prompt})
    st.session_state.chat_history.append({"role": "assistant", "content": response})

    if "SELECT" in response.upper():
        st.session_state.query = response

    return response

¡Felicidades! Habéis creado vuestro propio SQLZilla. Seguid explorando Streamlit y mejorad vuestra aplicación con más características. Y si os gusta SQLZilla, ¡votad por este increíble asistente que convierte texto en consultas!

0
0 222
Artículo Ricardo Paiva · sep 6, 2024 5m read

En la sección anterior, exploramos el proceso de instalación y comenzamos a escribir el IRIS en Python nativo. Ahora procederemos a examinar el recorrido global y a interactuar con los objetos de la clase IRIS.

get: esta función se utiliza para obtener valores del nodo de recorrido

deftraversal_firstlevel_subscript():"""
    ^mygbl(235)="test66,62" and ^mygbl(912)="test118,78"
    """for  i in irispy.node('^mygbl'):
        print(i, gbl_node.get(i,''))
0
0 80
Artículo Ricardo Paiva · sep 2, 2024 4m read

Hola Comunidad

Anteriormente he experimentado con Python embebido en IRIS; sin embargo, aún no he tenido la oportunidad de implementar IRIS usando Python nativo. En este artículo, mi objetivo es esbozar los pasos que tomé para comenzar a aprender e implementar IRIS dentro de la fuente de Python. También me gustaría agradecer a @Guillaume Rongier y @Luis Angel Pérez Ramos su ayuda para resolver los problemas que encontré durante mi reciente instalación PIP de IRIS en Python, lo que finalmente permitió que funcionara correctamente.

Empecemos a escribir IRIS en python.

0
0 107
Artículo Alberto Fuentes · ago 8, 2024 5m read

Hacía mucho tiempo que no escribía un post de actualización en IoP (Interoperabilidad en Python).

image

¿Qué hay de nuevo desde el lanzamiento de la interfaz de línea de comandos de IoP?

Dos nuevas grandes características se han añadido a IoP:

  • Rebranding: el módulo grongier.pex fue renombrado a iop para reflejar el nuevo nombre del proyecto.
  • Soporte asíncrono**: IoP ahora soporta funciones asíncronas y corrutinas.

Rebranding

El módulo grongier.pex ha sido renombrado a iop para reflejar el nuevo nombre del proyecto.

El módulo grongier.pex sigue disponible para compatibilidad con versiones anteriores, pero se eliminará en el futuro.

Soporte Async

IoP soporta llamadas asíncronas desde hace mucho tiempo, pero no era posible utilizar funciones asíncronas y corrutinas directamente en IoP.

Antes de saltar a esta nueva característica, voy a explicar cómo funcionan las llamadas async en InterSystems IRIS y presentar dos ejemplos de cómo utilizar las llamadas async IoP.

Llamadas asíncronas heredadas

Veamos cómo funcionan las llamadas async legacy:

from iop import BusinessProcess
from msg import MyMessage


class MyBP(BusinessProcess):

    def on_message(self, request):
        msg_one = MyMessage(message="Message1")
        msg_two = MyMessage(message="Message2")

        self.send_request_async("Python.MyBO", msg_one,completion_key="1")
        self.send_request_async("Python.MyBO", msg_two,completion_key="2")

    def on_response(self, request, response, call_request, call_response, completion_key):
        if completion_key == "1":
            self.response_one = call_response
        elif completion_key == "2":
            self.response_two = call_response

    def on_complete(self, request, response):
        self.log_info(f"Received response one: {self.response_one.message}")
        self.log_info(f"Received response two: {self.response_two.message}")

Básicamente funcionan de la misma manera que las llamadas asíncronas en IRIS. El método send_request_async envía una petición a una Operación de Negocio y el método on_response es llamado cuando se recibe la respuesta.

Podéis distinguir las respuestas por el parámetro completion_key.

Enviar múltiples peticiones de sincronización

No es exactamente una nueva característica, pero vale la pena mencionar que se pueden enviar múltiples solicitudes de sincronización en paralelo:

from iop import BusinessProcess
from msg import MyMessage


class MyMultiBP(BusinessProcess):

    def on_message(self, request):
        msg_one = MyMessage(message="Message1")
        msg_two = MyMessage(message="Message2")

        tuple_responses = self.send_multi_request_sync([("Python.MyMultiBO", msg_one),
                                                        ("Python.MyMultiBO", msg_two)])

        self.log_info("All requests have been processed")
        for target,request,response,status in tuple_responses:
            self.log_info(f"Received response: {response.message}")

Aquí estamos enviando dos peticiones a la misma Operación de Negocio en paralelo.

La respuesta es una tupla con el objetivo, la petición, la respuesta y el estado de cada llamada.

Es realmente útil cuando necesitáis enviar múltiples peticiones y no te importa el orden de las respuestas.

Funciones asíncronas y corrutinas

Ahora vamos a ver cómo utilizar funciones asíncronas y corrutinas en IoP:

import asyncio

from iop import BusinessProcess
from msg import MyMessage


class MyAsyncNGBP(BusinessProcess):

    def on_message(self, request):

        results = asyncio.run(self.await_response(request))

        for result in results:
            print(f"Received response: {result.message}")

    async def await_response(self, request):
        msg_one = MyMessage(message="Message1")
        msg_two = MyMessage(message="Message2")

        # use asyncio.gather to send multiple requests asynchronously
        # using the send_request_async_ng method
        tasks = [self.send_request_async_ng("Python.MyAsyncNGBO", msg_one),
                 self.send_request_async_ng("Python.MyAsyncNGBO", msg_two)]

        return await asyncio.gather(*tasks)

En este ejemplo, estamos enviando múltiples peticiones a la misma Operación de Negocio en paralelo usando el método send_request_async_ng.

Si habéis leído atentamente este post hasta este punto, comentad «Boomerang». Esto puede ser un simple detalle para vosotros, pero para mi significa mucho. Gracias.

El método await_response es una corrutina que envía múltiples peticiones y espera a recibir todas las respuestas. Gracias a la función asyncio.gather, podemos esperar a que se reciban todas las respuestas en paralelo.

Las ventajas de utilizar funciones asíncronas y corrutinas son:

  • Mejor rendimiento: podéis enviar múltiples peticiones en paralelo.
  • Más fácil de leer y mantener: podéis usar la palabra clave await para esperar las respuestas.
  • Más flexibilidad: podéis utilizar el módulo asyncio para crear flujos de trabajo complejos.
  • Más control: podéis utilizar el módulo asyncio para manejar excepciones y tiempos de espera.

Conclusión

¿Cuáles son las diferencias entre send_request_async, send_multi_request_sync y send_request_async_ng?

  • send_request_async: envía una petición a una Operación de Negocio y espera la respuesta **si** se implementa el método on_responsey se utiliza el parámetrocompletion_key`.
    • ventaja: podéis utilizar las llamadas async de la forma habitual.
    • desventaja: puede ser difícil de mantener si necesitáis enviar múltiples peticiones en paralelo.
  • send_multi_request_sync`: envía múltiples peticiones a la misma Operación de Negocio en paralelo y espera a que se reciban todas las respuestas.
    • ventaja: es fácil de usar.
    • desventaja: se puede controlar el orden de las respuestas (es decir, la lista de respuestas no está ordenada).
  • send_request_async_ng`: envía múltiples peticiones a la misma Operación de Negocio en paralelo y espera a que se reciban todas las respuestas.
    • ventaja: se puede controlar el orden de las respuestas.
    • desventaja: es necesario utilizar funciones asíncronas y coroutines.

¡Feliz multithreading!

0
0 71