Usar ChatGPT para que simule ser una aventura conversacional

Una de las múltiples opciones que nos brinda ChatGPT es simular se una aventura conversacional. Este sistema tiene muchas ventajas respecto a las clásicas que pecaban de ser demasiado estrictas y a veces tenias que jugar a prueba y error para encontrar las palabras exactas para realizar la acción deseada. Si bien no es un sistema perfecto y tiene otros problemas.

El prompt para lograr esto es muy sencillo:

¡Vamos a jugar! Tu simulas ser una aventura conversacional, me describes la situación en la que esta el personaje yo te respondo como reacciona.[Temática de la aventura] 

Por ejemplo:

¡Vamos a jugar! Tu simulas ser una aventura conversacional, me describes la situación en la que esta el personaje yo te respondo como reacciona. Debe de ser una aventura de ciencia ficción con viajes en el tiempo, acelgas mutantes y ornitorrincos espaciales

Y aunque parezca una locura funciona perfectamente.

Para profundizar más en el tema puedes ver el siguiente vídeo en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

¿Puede ChatGPT ofuscar código?

Ya hemos probado a usar ChatGPT para desofuscar código, ahora lo vamos a probar para lo contrario, ofuscarlo.

Puedes ver el resultado en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Prueba 1: Hola mundo en C

#include <stdio.h>
int main() {
   // printf() displays the string inside quotation
   printf("Hello, World!");
   return 0;
}

Resultados:

Como ofuscación es bastante pobre:

#include <stdio.h>
#define p printf
#define r return
int main() {
   p("H");
   p("e");
   p("l");
   p("l");
   p("o");
   p(",");
   p(" ");
   p("W");
   p("o");
   p("r");
   p("l");
   p("d");
   p("!");
   r 0;
}

Otro intento con mejor pinta aunque sigue sin ser perfecto y la cadena de texto sigue expuesta:

#include <stdio.h>
#include <string.h>

char *encode(char *str) {
    static char buffer[256];
    int i;
    for (i = 0; str[i] != '\0'; i++) {
        buffer[i] = str[i] + 1; // se suma 1 al código ASCII de cada caracter
    }
    buffer[i] = '\0';
    return buffer;
}

char *decode(char *str) {
    static char buffer[256];
    int i;
    for (i = 0; str[i] != '\0'; i++) {
        buffer[i] = str[i] - 1; // se resta 1 al código ASCII de cada caracter
    }
    buffer[i] = '\0';
    return buffer;
}

int main() {
    char *message = "Hello, World!";
    char *encoded_message = encode(message);
    char *decoded_message = decode(encoded_message);
    printf("%s", decoded_message);
    return 0;
}

El último intento tiene mejor pinta pero un ser humano seria capaz de entenderlo fácilmente ya que solo trata de añadir un

#include <stdio.h>

#define a(e)(char)(e-1)
#define b(e)(char)(e+1)

int main() {
    char* m = b('G') + a('F') + b('m') + a('n') + b('m') + a('k') + b('`') + b('V') + a('m') + a('o') + b('y') + b('q') + b('t') + b('u') + b('k') + b('w') + a('t');
    char* c = m;
    while(*c) {
        putchar(*c);
        c++;
    }
    return 0;
}

Para ver si obtiene mejor resultado le especifico desde el principio que no se pueda leer «Hello, world» y ChatGPT cumple….a su manera:

#include <stdio.h>

#define H e##l##l##o
#define W o##r##l##d
#define S(x) #x

int main() {
   printf("%s, %s!", S(H), S(W));
   return 0;
}

Prueba 2: Fibonacci en C

Para la segunda prueba le pediremos que ofusque el siguiente código en C que calcula la serie de Fibonacci y veremos como lo ofusca:

El primer resultado no es malo (omitiendo las cadenas que ya hemos visto que no entiende que hay que «ocultarlas»):

#include <stdio.h>
#include <stdlib.h>

unsigned long long f(int n){
    unsigned long long a,b,c;
    if (n<=0) return 0;
    if (n==1) return 1;
    a=0,b=1;
    for(int i=2;i<=n;i++)c=a+b,a=b,b=c;
    return c;
}

int main(int a,char **b){
    printf("fibo[%s] = %llu\n",b[1],f(atoi(b[1])));
    return 0;
}

Pidiéndole que lo ofusque aun más el resultado es bueno aunque falta añadir una linea con F(x) para que funcione:/s

#include <stdio.h>
#include <stdlib.h>
#define O unsigned long long
#define F(x) O f(x){O a,b,c;if(x<=0)return 0;if(x==1)return 1;a=0,b=1;for(int i=2;i<=x;i++)c=a+b,a=b,b=c;return c;}int main(int a,char**b){printf("fibo[%s] = %llu\n",b[1],f(atoi(b[1])));return 0;}

Prueba 3: Fibonacci en JS

fibonacci(n) {
  if (n <= 1) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

Resultados:

function f(n){if(n<=1){return n}else{return f(n-1)+f(n-2)}}

Y:

var f=function(g){if(g<=1){return g;}else{return f(g-1)+f(g-2);}};

¿Puede ChatGPT «desofuscar» código ofuscado?

Una de las capacidades que tiene ChatGPT es la de explicar y reescribir código. ¿Pero es capaz de enfrentarse al código ofuscado?. Para los que no lo sepan el código ofuscado es una forma de escribir programas que hace que sea difícil para los humanos entenderlo, pero que aún puede ser interpretado por las computadoras. Se utiliza para proteger el código de ser analizado o descubierto por alguien que intenta aprovecharse de ello. Algunas técnicas comunes incluyen cambiar los nombres de las funciones y las variables a palabras aleatorias, reorganizar el orden de las líneas de código, agregar líneas innecesarias, y eliminar comentarios y espacios en blanco. Aunque la ofuscación puede ser útil en algunos casos, puede hacer que sea difícil de depurar y mantener el código, y no ofrece una protección completa contra la ingeniería inversa.

En resumen, vamos a ver si ChatGPT puede leer y explicar código pensado para que sea difícil de leer y de explicar. Vamos a tomar dos casos, uno en C ofuscado por humanos para un concurso de C ofuscado. En concreto este de aquí. Para el segundo caso he optado por un código en Javascript de aquí, que ya de por si es poco legible sin ofuscar. Lo ofuscaremos con el siguiente ofuscador. El resultado puede verse en el siguiente vídeo de mi canal de Youtube:

Haz click para abrir el vídeo en mi canal de Youtube

ChatGPT para analizar código fuente

Vamos a pedirle a ChatGPT que analice tres ejemplos de código, en tres lenguajes distintos y con un nivel de dificultad creciente. Para ponérselo más difícil se ha eliminado cualquier comentario dejando el código «desnudo».

Puedes ver el vídeo donde explico todas las pruebas y resultados haciendo click en la imagen de debajo:

Haz click para ver en mi canal de Youtube

El primer caso viene de aquí. Una librería propia para crear máquinas de estado finito en Arduino (si te interesa su funcionamiento lo explico aquí)

#ifndef efsm_h
	#define efsm_h

	#define ANY_STATE efsmState
	#define ANY_EVENT efsmEvent

	#define START_TRANSITIONS \
	int efsmEvent(enum efsmEvents efsmEvent){ \
		efsmTimer = millis();\
		efsmCounter = 0;\

	#define TRANSITION(E,Ss,Sf,F) \
		if(efsmEvent == E && efsmState == Ss){ \
			efsmState = Sf; \
			F; \
			return 1; \
		}\

	#define END_TRANSITIONS \
		return 0; \
	} \

	#define START_EXECUTIONS \
	int efsmExecute(){ \

	#define EXECUTION(S,F) \
		if(efsmState == S){ \
			F; \
			return 1; \
		}\

	#define END_EXECUTIONS \
		return 0; \
	} \

	#define START_TRIGGERS \
	int efsmTriggers(){ \
		efsmCounter++; \
		unsigned long diff = millis()-efsmTimer;\

	#define COUNTER(S,N,E) \
		if(efsmState == S && efsmCounter >= N){ \
			return efsmEvent(E); \
		}\

	#define TIMER(S,M,E) \
		if(efsmState == S && diff >= M){ \
			return efsmEvent(E); \
		}\

	#define CONDITIONAL(S,C,E) \
		if(efsmState == S && C){ \
			return efsmEvent(E); \
		}\


	#define END_TRIGGERS \
		return 0; \
	} \

	#define EVENTS enum efsmEvents

	#define STATES enum efsmStates

	#define INIT(S) \
	enum efsmStates efsmState = S;\
	unsigned long efsmCounter = 0; \
	unsigned long efsmTimer = 0; \

	#define isState(S) efsmState == S
	#define changeState(S) efsmState = S
	#define resetTimer() efsmTimer = millis()
	#define resetCounter() efsmCounter = 0
#endif

El resultado es el siguiente:

Este código es una definición de una librería de máquinas de estado finito (FSM, por sus siglas en inglés) para microcontroladores, escrita en C o C++.

Las FSM son una técnica de programación en la que un sistema se modela como un conjunto de estados, y la transición entre ellos se produce en respuesta a una entrada (evento). Este enfoque puede resultar muy útil para sistemas con comportamientos complejos que cambian con el tiempo, como los sistemas de control o los dispositivos electrónicos.

En esta librería, se definen las macroinstrucciones que se utilizan para configurar una máquina de estados finitos en un microcontrolador, incluyendo las transiciones de estado, las condiciones que activan estas transiciones y las acciones que se ejecutan en respuesta a un evento o un estado.

La librería define una estructura básica para definir la máquina de estados finitos, incluyendo las transiciones de estado, las condiciones que activan estas transiciones y las acciones que se ejecutan en respuesta a un evento o un estado. La estructura también incluye la definición de las macros que se utilizan para configurar la máquina de estados finitos.

Las macros definidas en esta librería incluyen:

  • START_TRANSITIONS: esta macro indica el comienzo de la sección de código que define las transiciones de estado. Dentro de esta sección, se definen todas las transiciones de estado posibles y las acciones que se deben ejecutar en respuesta a cada una.
  • TRANSITION(E,Ss,Sf,F): esta macro define una transición de estado en respuesta a un evento E. Si el estado actual de la máquina es Ss, se cambia el estado a Sf y se ejecuta la función F. Esta macro se utiliza dentro de la sección START_TRANSITIONS y END_TRANSITIONS.
  • END_TRANSITIONS: esta macro indica el final de la sección de código que define las transiciones de estado.
  • START_EXECUTIONS: esta macro indica el comienzo de la sección de código que define las acciones que se deben ejecutar en respuesta a un estado específico.
  • EXECUTION(S,F): esta macro define una acción que se debe ejecutar si el estado actual de la máquina es S. La acción se define mediante la función F. Esta macro se utiliza dentro de la sección START_EXECUTIONS y END_EXECUTIONS.
  • END_EXECUTIONS: esta macro indica el final de la sección de código que define las acciones que se deben ejecutar en respuesta a un estado específico.
  • START_TRIGGERS: esta macro indica el comienzo de la sección de código que define las condiciones que activan una transición de estado.
  • COUNTER(S,N,E): esta macro define una condición basada en un contador. Si el estado actual de la máquina es S y el contador ha alcanzado el valor N, se activa la transición de estado correspondiente al evento E.
  • TIMER(S,M,E): esta macro define una condición basada en un temporizador. Si el estado actual de la máquina es S y el tiempo transcurrido desde que se inició el temporizador es mayor o igual a M milisegundos, se activa la transición de estado correspondiente al evento E.
  • CONDITIONAL(S,C,E): esta macro define una condición basada en una expres

El análisis, aunque no está completo, es bastante acertado y pude servir como documentación de funcionamiento del código. Algún punto quizás seria necesario aclararlo un poco más pero como herramienta para ayudar a entender el código es increíble.

El siguiente código es una librería en JS para separar silabas, encontrar la silaba tónica y calcular la dificultad de leer cada silaba en lenguaje español. El código lo puedes encontrar aquí. (Si te interesa entender su funcionamiento está explicado aquí y aquí)

var regexStep1 = [];
var cutPositionsStep1 = [];

var regexStep2 = [];
var cutPositionsStep2 = [];

regexStep1.push(/^[aeiouáéíóúü]+([^aeiouáéíóúü]|$)/);
cutPositionsStep1.push(0);

regexStep1.push(/^[^aeiouáéíóúü][aeiouáéíóúü]+([^aeiouáéíóúü]|$)/);
cutPositionsStep1.push(0);

regexStep1.push(/^[^aeiouáéíóúü][^aeiouáéíóúü][aeiouáéíóúü]+([^aeiouáéíóúü]|$)/);
cutPositionsStep1.push(1);

regexStep1.push(/^[^aeiouáéíóúü][^aeiouáéíóúü][^aeiouáéíóúü][aeiouáéíóúü]+([^aeiouáéíóúü]|$)/);
cutPositionsStep1.push(2);

regexStep1.push(/^[^aeiouáéíóúü][^aeiouáéíóúü][^aeiouáéíóúü][^aeiouáéíóúü][aeiouáéíóúü]+([^aeiouáéíóúü]|$)/);
cutPositionsStep1.push(3);

regexStep1.push(/^[aeiouáéíóúü]+$/);
cutPositionsStep1.push(-1);

regexStep1.push(/^[^aeiouáéíóúü]+$/);
cutPositionsStep1.push(-1);

regexStep2.push(/[aeoáéó][aeoáéó]/);
cutPositionsStep2.push(1);

regexStep2.push(/[íú][aeo]/);
cutPositionsStep2.push(1);

regexStep2.push(/[aeo][íú]/);
cutPositionsStep2.push(1);

regexStep2.push(/[iuüíú][aeoáéó][aeoáéó]/);
cutPositionsStep2.push(2);

regexStep2.push(/[aeoáéó][aeoáéó][iuüíú]/);
cutPositionsStep2.push(1);

regexStep2.push(/.*/);
cutPositionsStep2.push(-1);

export function divide(word){
  word = word.toLowerCase().trim();    
  var cutPosition = 0;
  var syllables = [];
  var finalSyllables = [];
  var head = "";
  var securityBreak = 0;
  var end = false;
  
  while(!end){
    ++securityBreak;
    if(securityBreak > 20){
      throw "Error processing word";
      return "";
    }
    for(var i = 0; i < regexStep1.length; ++i){
      var match = word.match(regexStep1[i]);
      if(match){
        var m = match[0];
        cutPosition = cutPositionsStep1[i];
        if(cutPosition < 0){
          syllables.push(head+m);
          end = true;
        } else {
          var cutChar = m[cutPosition];
          if((cutChar == 'r') || (cutChar == 'l') || (cutChar == 'h')){
            cutPosition--;
          }
          if(cutPosition < 0)
            cutPosition = 0;
          syllables.push(head+m.substring(0,cutPosition));
          head = m.substring(cutPosition,m.length-1);
        }

        word = word.substring(m.length-1);
        
      }
    }
  }
   
  for(var s = 0; s < syllables.length; ++s){
    var sillable = syllables[s];
    if(sillable == "")
      continue;

    for(var i = 0; i < regexStep2.length; ++i){
      var match = sillable.match(regexStep2[i]);
      if(match){
        if(cutPositionsStep2[i] < 0){
          finalSyllables.push(sillable);
        }else{
          var cutPosition = match.index + cutPositionsStep2[i];
          finalSyllables.push(sillable.substring(0,cutPosition));
          finalSyllables.push(sillable.substring(cutPosition));
        }
        
        break;
      }
    }
  }
  
  return finalSyllables;
}

export function stress(syllables) {
  if(typeof syllables === 'string'){
    syllables = divide(syllables);
  }

  if(syllables.length == 1){
    return 0;
  }
  
  for(var i = 0; i < syllables.length; ++i){
    if(syllables[i].match(/[áéíóú]/)){
      
      return i;
    }
  }
  
  if(syllables[syllables.length-1].match(/.*[nsaeiou]$/)){
    
    return syllables.length-2;
  } else {
    
    return syllables.length-1;
  }
}

export function difficulty(syllables){
    if(typeof syllables === 'string'){
      syllables = divide(syllables);
    }

    var result = [];
    for(var i = 0; i < syllables.length; ++i){
        result.push(difficultySyllable(syllables[i]));
    }
    return result;
}

function difficultySyllable(text){
    var value = 0;

    text = text.replace("ll", "y");
    text = text.replace("rr", "r");
        
    text = text.replace("que", "qe");
    text = text.replace("qui", "qi");                    
    text = text.replace("qué", "qe");
    text = text.replace("quí", "qi");
    text = text.replace("gü", "g");

    if(text.match(/[bcdfghjklmnñpqrstvwxyz]l/g)){
        value += 1;
    }

    if(text.match(/[bcdfghjklmnñpqrstvwxyz]n/g)){
        dvalue += 1;
    }

    if(text.match(/[bcdfghjklmnñpqrstvwxyz]h/g)){
        value += 1;
    }

    if(text.match(/[bcdfghjklmnñpqrstvwxyz]s/g)){
        value += 1;
    }

    text = text.replace("h", "");

    
    var consonantsHard = text.match(/[kñwxy]/g);    
    if(consonantsHard){
        value += consonantsHard.length*3;
    }
    
    
    var consonants = text.match(/[bcdfghjlmnpqrstvz]/g)
    if(consonants){
        value +=consonants.length*2;
    }
    
    var vowels = text.match(/[aeiouáéíóú]/g)
    if(vowels){
        value +=vowels.length;
    }

    return value;   
}

Este código es una implementación de un algoritmo para dividir palabras en sílabas en español. El algoritmo se divide en dos pasos.

En el primer paso, el algoritmo usa una lista de expresiones regulares para buscar posibles divisiones silábicas en la palabra. Cada expresión regular busca un patrón particular de letras que indicaría una posible división silábica. Si se encuentra un patrón, se divide la palabra en dos partes en el punto de la división, y se agrega la primera parte a una lista de sílabas.

El segundo paso usa otra lista de expresiones regulares para ajustar las divisiones silábicas encontradas en el primer paso. Estas expresiones regulares buscan patrones específicos de letras que pueden indicar una división diferente de la que se encontró en el primer paso.

Por último, hay dos funciones adicionales. La función «stress» determina qué sílaba en una lista de sílabas es la sílaba tónica (la sílaba acentuada) de la palabra, y la función «difficulty» determina el nivel de dificultad de cada sílaba en la palabra, utilizando una serie de reglas específicas para la pronunciación de letras en español.

El resultado es correcto, ha entendido como funciona el código. Está claro que gran parte de esa información la saca del nombre de las funciones, por eso para la siguiente voy a ponérselo más difícil por dos motivos: el lenguaje elegido es ensamblador, voy a reemplazar cualquier pista que tenga.

He elegido un código en ensamblador X86 que calcula los 70 primeros números de la sucesión de Fibonacci. El código lo he obtenido de aquí, pero he reemplazado cualquier referencia a Fibonacci por la palabra «patata».

.intel_syntax noprefix

.globl patata
patata:
  sub rsp, 24
  xor eax, eax
  mov ecx, 1
  0:
    xadd rax, rcx

    mov [rsp + 16], rax
    mov [rsp + 8], rdi
    mov [rsp], rcx
    mov rsi, rax
    lea rdi, print_fmt
    xor eax, eax
    call printf
    mov rax, [rsp + 16]
    mov rdi, [rsp + 8]
    mov rcx, [rsp]

    dec rdi
    jnz 0b
  add rsp, 24
  ret

.globl main
main:
  mov edi, 70
  call patata

  xor eax, eax
  ret

.section .rodata
print_fmt: .asciz "%lu\n"

El código es un programa escrito en lenguaje ensamblador de Intel x86-64 que llama a la función «patata» desde la función «main» y realiza las siguientes acciones:

En la función «patata»:

  1. Se reserva un bloque de 24 bytes en la pila mediante la instrucción «sub rsp, 24».
  2. Se inicializan los registros «eax» y «ecx» a cero y uno, respectivamente.
  3. Se realiza un bucle etiquetado como «0» que ejecuta los siguientes pasos: a. Se realiza una operación de intercambio atómico de los registros «rax» y «rcx» mediante la instrucción «xadd rax, rcx». b. Se almacena el valor de «rax» en la posición de memoria «rsp+16», el valor de «rdi» en la posición de memoria «rsp+8» y el valor de «rcx» en la posición de memoria «rsp». c. Se carga el valor de «rax» en el registro «rsi». d. Se carga la dirección de memoria de la cadena de formato de impresión en el registro «rdi». e. Se llama a la función «printf» para imprimir el valor de «rsi» en la salida estándar. f. Se recuperan los valores de «rax», «rdi» y «rcx» de las posiciones de memoria correspondientes en la pila. g. Se decrementa el valor de «rdi» y se salta a la etiqueta «0» si el resultado no es cero mediante la instrucción «dec rdi; jnz 0b».
  4. Se libera el bloque de memoria de la pila mediante la instrucción «add rsp, 24» y se retorna a la función llamadora mediante la instrucción «ret».

En la función «main»:

  1. Se inicializa el registro «edi» con el valor 70.
  2. Se llama a la función «patata» mediante la instrucción «call patata».
  3. Se retorna a la llamada del sistema operativo mediante la instrucción «ret».

En la sección «.rodata»:

  1. Se define una cadena de formato de impresión que se utiliza en la función «printf» en la instrucción «print_fmt». La cadena especifica el formato de salida para un valor de enteros largos sin signo («%lu») seguido de un salto de línea («\n»).

Es una buena explicación del algoritmo, pero una mala explicación del programa. Es decir, que explica muy bien «como funciona», pero no «que hace». Pruebo a preguntárselo y esta es la respuesta:

El programa imprimirá en la salida estándar una secuencia de números enteros largos sin signo (de tipo «unsigned long»), comenzando por el valor 1 y disminuyendo en una unidad hasta llegar a cero. El número total de valores impresos será 70, ya que la función «main» inicializa el registro «edi» con el valor 70 antes de llamar a la función «patata». Cada número impreso será seguido por un salto de línea.

El programa utiliza una operación atómica de intercambio («xadd») para sumar el valor de «rcx» a «rax» y almacenar el resultado en «rax». Luego, utiliza la función «printf» para imprimir el valor actual de «rax» en la salida estándar. Después de imprimir el valor, se decrementa el valor de «rdi» y se repite el proceso hasta que «rdi» llegue a cero.

Dado que el valor inicial de «rcx» es 1 y se suma a «rax» en cada iteración del bucle, el valor impreso en cada iteración será la suma de los enteros de 1 a n, donde n es el número total de iteraciones. En este caso, dado que hay 70 iteraciones, el último valor impreso será la suma de los enteros de 1 a 70, que es 2485.

En resumen, deduce que el programa suma todos los húmero enteros del 1 al 70 (y correctamente da como resultado 2485). La deducción no es correcta, pero se acerca. Realmente suma cada numero con el anterior numero de forma consecutiva, no es una mala aproximación partiendo de un código en ensamblador y obteniéndola en unos pocos segundos.

El uso de ChatGPT para analizar código puede ahorrar mucho tiempo y quebraderos de cabeza, no es una herramienta perfecta, ni muchos menos….pero tampoco hay otra que se le acerque.

Como pedirle a ChatGPT que cree el código para un proyecto de software

Como es un post muy largo he decidido ponerlo en formato vídeo, podrás visualizarlo en mi canal de Youtube haciendo click en la imagen:

Haz click para ver el vídeo en mi canal de Youtube

El texto del ejemplo es el siguiente:

Descripción del proyecto:

Entidad: Usuario
Campos: Id, Nombre, Apellido, Fecha de Alta, Alta (si/no), Rol, Proyectos (N a N)
Operaciones: crear, eliminar, actualizar
Vistas: Listado (con buscador), Editar

Entidad: Proyecto
Campos: Id, Nombre, Descripcion, Fecha de creacion, Propietario (FK usuario), Usuarios (N a N)
Operaciones: crear, listar, eliminar, actualizar, buscar por fecha de creación
Vistas: Listado (con buscador), Editar
Validaciones: El nombre del proyecto no puede estar en blanco ni contener la palabra "patata". La descripción debe eliminar tags HTML

Entidad: Tarea
Campos: Id, Nombre, Descripcion, Fecha de inicio, Tarea Padre (FK tarea), Proyecto (FK proyecto), Dias de duracion, Prioridad (1-5)
Operaciones: crear, listar, eliminar, actualizar, buscar por fecha de creación, recuperar todas las tareas ordenadas por fecha de creación
Vistas: Listado (con buscador), Editar

El proyecto va tener una API Rest en J2EE con Spring con las capas de controladores, servicio y persistencia. 
La vista será un proyecto en Angular 12 sobre NodeJS que consumirá la API

Como crear un chat que hable de tus productos con ChatGPT

ChatGPT es un chatbot maravilloso para hablar de cualquier cosa, pero esta virtud también es un problema si queremos usar sus capacidades para la conversación pero limitar sus temas a los que nosotros nos interesan, en este caso de ejemplo nuestros productos. Para ello crearemos un texto que resume la información de nuestros productos. La primera línea de ese texto será un titulo:

«Información sobre el libro»

Debajo añadiremos todo el texto de las características de nuestros productos.

Ahora viene el truco, para que funcione tenemos que tomar la pregunta que le realice nuestro usuario (la representaremos con un asterisco *) y la rodearemos del siguiente texto:

Según el texto «Información sobre el libro» responde a la pregunta: «¿*?» Si no puedes responder di solo «No lo se»

De tal manera que obligaremos a ChatGPT a responder solo sobre el texto «Información sobre el libro» o, si no la pregunta no trata sobre ese texto, responder «no lo se».

Por ejemplo la pregunta: ¿En que idioma está escrito?

Se transformara en: Según el texto «Información sobre el libro» responde a la pregunta: «¿En que idioma está escrito?» Si no puedes responder di solo «No lo se»

Y como respuesta: En español

Si le preguntamos: ¿Cuando descubrió Colón América?

Que se transformara en: Según el texto «Información sobre el libro» responde a la pregunta: «¿Cuando descubrió Colón América?» Si no puedes responder di solo «No lo se»

Responderá: No lo se.

Puedes ver un vídeo donde realizo una demostración este tema en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

ChatGPT explica cómo usar un LED RGB con Arduino

¡ATENCION!

Este texto ha sido escrito en su totalidad por ChatGPT, mi labor ha sido elegir y ordenar los bloques de texto a usar. Podéis comparar el resultado con el artículo que escribí sobre el mismo tema aquí (algo así como hombre vs máquina) Tenéis un vídeo donde explico el proceso de elaboración aquí

Un LED RGB es un tipo de LED que puede emitir luz de distintos colores. Esto se logra mediante la combinación de tres colores básicos: rojo, verde y azul. Cada uno de estos colores se controla por una patilla o pin del LED.

Para usar un LED RGB con una placa Arduino UNO, necesitarás tener los siguientes materiales:

  • Una placa Arduino UNO
  • Un LED RGB
  • Tres resistencias de 220 ohmios (una para cada pin del LED)
  • Cuatro cables jumper

El primer paso será conectar las patillas o pines del LED RGB a la placa Arduino. Cada pin se conectará a uno de los pines digitales de la placa Arduino a través de una resistencia de 220 ohmios. Por ejemplo, podrías conectar el pin rojo del LED al pin digital 9 de la placa Arduino, el pin verde al pin digital 10 y el pin azul al pin digital 11. Asegúrate de conectar la patilla negativa o común del LED al GND de la placa.

Una vez que hayas conectado el LED, puedes escribir el código en Arduino para controlar el color del LED. En el código, debes especificar qué pin se utilizará para controlar cada color. Por ejemplo, para encender el LED rojo, debes poner el pin 9 en alto (HIGH) y los otros dos pines en bajo (LOW).

A continuación te presentamos un ejemplo de código en Arduino para encender el LED RGB en distintos colores:

const int redPin = 9;
const int greenPin = 10;
const int bluePin = 11;

void setup() {
  // Inicializamos los pines digitales como salidas
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}

void loop() {
  // Encendemos el LED rojo
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, LOW);
  digitalWrite(bluePin, LOW);
  delay(1000);

  // Encendemos el LED verde
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, HIGH);
  digitalWrite(bluePin, LOW);
  delay(1000);

  // Encendemos el LED azul
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, LOW);
  digitalWrite(bluePin, HIGH);
  delay(1000);

  // Encendemos el LED en amarillo (rojo + verde)
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, HIGH);
  digitalWrite(bluePin, LOW);
  delay(1000);

  // Encendemos el LED en magenta (rojo + azul)
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, LOW);
  digitalWrite(bluePin, HIGH);

Regular la intensidad de los colores

se puede usar la técnica de modulación por ancho de pulso (PWM) para controlar la intensidad de cada color del LED RGB. Con PWM, puedes controlar el porcentaje de tiempo que cada pin del LED está en alto y, de esta manera, ajustar la intensidad de cada color.

Para usar PWM con el LED RGB, debes utilizar las funciones analogWrite() en lugar de digitalWrite(). Esta función toma dos argumentos: el número del pin al que quieres enviar el pulso y un valor

entero entre 0 y 255 que indica la intensidad del pulso. Por ejemplo, si quieres enviar un pulso del 50% de intensidad al pin 9, debes utilizar el siguiente código:

analogWrite(9, 128);

A continuación te presentamos un ejemplo de código en Arduino para controlar la intensidad de cada color del LED RGB mediante PWM:

const int redPin = 9;
const int greenPin = 10;
const int bluePin = 11;

void setup() {
  // Inicializamos los pines digitales como salidas
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}

void loop() {
  // Ajustamos la intensidad de cada color
  analogWrite(redPin, 255);   // Rojo al máximo
  analogWrite(greenPin, 128); // Verde al 50%
  analogWrite(bluePin, 0);    // Azul apagado
  delay(1000);
}

Con esto podrás controlar la intensidad de cada color del LED RGB y, de esta manera, obtener una amplia gama de colores y tonos.

A continuación te presentamos un ejemplo de código en Arduino para generar distintos colores mediante la intensidad de cada color:

const int redPin = 9;
const int greenPin = 10;
const int bluePin = 11;

void setup() {
  // Inicializamos los pines digitales como salidas
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}

void loop() {
  // Generamos el color rojo
  analogWrite(redPin, 255);   // Rojo al máximo
  analogWrite(greenPin, 0);   // Verde apagado
  analogWrite(bluePin, 0);    // Azul apagado
  delay(1000);

  // Generamos el color verde
  analogWrite(redPin, 0);     // Rojo apagado
  analogWrite(greenPin, 255); // Verde al máximo
  analogWrite(bluePin, 0);    // Azul apagado
  delay(1000);

  // Generamos el color azul
  analogWrite(redPin, 0);     // Rojo apagado
  analogWrite(greenPin, 0);   // Verde apagado
  analogWrite(bluePin, 255);  // Azul al máximo
  delay(1000);

  // Generamos el color amarillo (rojo + verde)
  analogWrite(redPin, 255);   // Rojo al máximo
  analogWrite(greenPin, 255); // Verde al máximo
  analogWrite(bluePin, 0);    // Azul apagado
  delay(1000);

  // Generamos el color magenta (rojo + azul)
  analogWrite(redPin, 255);   // Rojo al máximo
  analogWrite(greenPin, 0);   // Verde apagado
  analogWrite(bluePin, 255);  // Azul al máximo
  delay(1000);

  // Generamos el color cyan (verde + azul)
  analogWrite(redPin, 0);     // Rojo apagado
  analogWrite(greenPin, 255); // Verde al máximo
  analogWrite(bluePin, 255);  // Azul al máximo
  delay(1000);

  // Generamos el color blanco (rojo + verde + azul al máximo)
  analogWrite(redPin, 255);   // Rojo al máximo
  analogWrite(greenPin, 255); // Verde al máximo
  analogWrite(bluePin, 255);  // Azul al máximo
  delay(1000);
}

LED de ánodo y de cátodo común

Un LED RGB con cátodo común es un tipo de LED en el que todas las patillas o pines de los colores rojo, verde y azul están conectadas a una misma patilla o pin común, que se conoce como cátodo. Esta patilla suele ser la patilla más larga del LED y se conecta al GND o a un voltaje negativo.

Por otro lado, un LED RGB con ánodo común es un tipo de LED en el que todas las patillas o pines de los colores rojo, verde y azul están conectadas a una misma patilla o pin común, que se conoce como ánodo. Esta patilla suele ser la patilla más corta del LED y se conecta a un voltaje positivo.

La principal diferencia entre estos dos tipos de LED RGB es el polo al que se conectan las patillas de los colores. En el caso del LED RGB con cátodo común, las patillas de los colores se conectan al GND o a un voltaje negativo, mientras que en el caso del LED RGB con ánodo común, las patillas de los colores se conectan a un voltaje positivo.

En cuanto a su uso con una placa Arduino, no hay mucha diferencia entre ambos tipos de LED RGB. La única diferencia es que, en el caso del LED RGB con cátodo común, debes conectar las patillas de los colores a los pines digitales de la placa Arduino a través de resistencias y luego poner estos pines en alto (HIGH) para encender los colores, mientras que en el caso del LED RGB con ánodo común, debes conectar las patillas de los colores a los pines digitales de la placa Arduino a través de resistencias y luego poner estos pines en bajo (LOW) para encender los colores.

A continuación te presentamos un ejemplo de código en Arduino para controlar un LED RGB con cátodo común y otro para controlar un LED RGB con ánodo común:

Código para LED RGB con cátodo común:

const int redPin = 9;
const int greenPin = 10;
const int bluePin = 11;

void setup() {
  // Inicializamos los pines digitales como salidas
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}

void loop() {
  // Encendemos el LED rojo
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, LOW);
  digitalWrite(bluePin, LOW);
  delay(1000);

  // Encendemos el LED verde
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, HIGH);
  digitalWrite(bluePin, LOW);
  delay(1000);

  // Encendemos el LED azul
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, LOW);
  digitalWrite(bluePin, HIGH);
  delay(1000);
}

Código para LED RGB con ánodo común:

const int redPin = 9;
const int greenPin = 10;
const int bluePin = 11;

void setup() {
  // Inicializamos los pines digitales como salidas
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}

void loop() {
  // Encendemos el LED rojo
  digitalWrite(redPin, LOW);
  digitalWrite(greenPin, HIGH);
  digitalWrite(bluePin, HIGH);
  delay(1000);

  // Encendemos el LED verde
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, LOW);
  digitalWrite(bluePin, HIGH);
  delay(1000);

  // Encendemos el LED azul
  digitalWrite(redPin, HIGH);
  digitalWrite(greenPin, HIGH);
  digitalWrite(bluePin, LOW);
  delay(1000);
}

Una manera de distinguir entre un LED RGB con cátodo común y con ánodo común es observar la longitud de las patillas o pines del LED. En el caso del LED RGB con cátodo común, la patilla o pin común (cátodo) suele ser la más larga y se conecta al GND o a un voltaje negativo, mientras que las patillas o pines de los colores rojo, verde y azul suelen ser más cortas y se conectan a los pines digitales de la placa Arduino a través de resistencias.

En el caso del LED RGB con ánodo común, la patilla o pin común (ánodo) suele ser la más corta y se conecta a un voltaje positivo, mientras que las patillas o pines de los colores rojo, verde y azul suelen ser más largas y se conectan a los pines digitales de la placa Arduino a través de resistencias.

Otra forma de distinguir entre ambos tipos de LED RGB es revisar el datasheet o hoja de datos del LED. En este documento se suele especificar el tipo de LED (cátodo común o ánodo común) y el voltaje y corriente máximos que soporta cada patilla o pin.

ChatGPT y el problema de las cinco casas. ¿Es capaz usar el razonamiento lógico?

«El problema de las cinco casas» es un problema de lógica. A partir de un conjunto de enunciados hay que poner toda la información en «orden» para poder deducir de quien es cada cosa (casa, bebida, tabaco, nacionalidad, mascota). El enunciado es el siguiente:

En una calle hay cinco casas, cada una de un color distinto. En cada casa vive una persona de distinta nacionalidad. Cada dueño bebe un único tipo de bebida, fuma una sola marca de cigarrillos y tiene una mascota diferente a sus vecinos.

  • El británico vive en la casa roja.
  • El sueco tiene un perro como mascota.
  • El danés toma té.
  • El noruego vive en la primera casa.
  • El alemán fuma Prince.
  • La casa verde está inmediatamente a la izquierda de la blanca.
  • El dueño de la casa verde bebe café.
  • El propietario que fuma Pall Mall cría pájaros.
  • El dueño de la casa amarilla fuma Dunhill.
  • El hombre que vive en la casa del centro bebe leche.
  • El vecino que fuma Blends vive al lado del que tiene un gato.
  • El hombre que tiene un caballo vive al lado del que fuma Dunhill.
  • El propietario que fuma Bluemaster toma cerveza.
  • El vecino que fuma Blends vive al lado del que toma agua.
  • El noruego vive al lado de la casa azul.

¿Quién es el dueño del pez?

La respuesta correcta a este problema puede verse en la siguiente tabla (El pez es la mascota del alemán):

Casa Color Nacionalidad Bebida Cigarrillos Mascota
1 Amarilla Noruego Agua Durnhill Gato
2 Azul Danés Blends Caballo
3 Roja Británico Leche Pall Mall Pájaros
4 Verde Alemán Café Prince Pez
5 Blanca Sueco Cerveza Bluemaster Perro

Una de las cosas más complicadas que tiene este problema es que hay celdas de la tabla que no se rellenan directamente con los datos del enunciado, se rellenan por descarte, es decir, como todas las demás opciones están «ocupadas» solo puede ser la que queda libre. Eso añade una capa más de complejidad. Vamos a ver como lo ha resuelto (o intentado resolver) chatGPT:

"Podemos seguir el siguiente proceso para resolver este problema:

  1. El noruego vive en la primera casa, que es de color azul.
  2. La casa verde está inmediatamente a la izquierda de la blanca, por lo que la casa blanca es la tercera.
  3. El dueño de la casa verde bebe café.
  4. El vecino que fuma Blends vive al lado del que tiene un gato y al lado del que toma agua. El dueño de la casa blanca fuma Blends.
  5. El hombre que vive en la casa del centro bebe leche, por lo que la casa amarilla es la segunda.
  6. El dueño de la casa amarilla fuma Dunhill.
  7. El hombre que tiene un caballo vive al lado del que fuma Dunhill, por lo que el dueño de la casa roja tiene un caballo.
  8. El británico vive en la casa roja.
  9. El propietario que fuma Pall Mall cría pájaros.
  10. El vecino que fuma Bluemaster toma cerveza.
  11. Con esta información, podemos armar el siguiente cuadro:
Casa Color Nacionalidad Bebida Cigarrillos Mascota
1 azul noruego agua Blends gato
2 amarilla alemán leche Dunhill pájaros
3 blanca británico Pall Mall caballo
4 verde danés café Bluemaster perro
5 roja sueco cerveza Prince

Por lo tanto, el dueño del pez es el sueco."

La respuesta es incorrecta. Viendo sus explicaciones se nota que intenta razonar pero falla en algunos pasos. Como si no pudiera tener en cuenta todas las condiciones y simplemente ignorase algunas. Algunos ejemplos:

Viendo la tabla le ha asignado el pez al sueco, por descarte. Pero es el propio enunciado el que descarta esa posibilidad: «El sueco tiene un perro como mascota.».

Y no es la única, asigna al noruego la casa azul, es su primera conclusión: «El noruego vive en la primera casa, que es de color azul» sin embargo el enunciado lo descarta: «El noruego vive al lado de la casa azul». Parece que no ha entendido «al lado» o directamente a descartado esa condición.

Se ven intentos de componer reglas a partir de otras ya presentes: «El vecino que fuma Blends vive al lado del que tiene un gato y al lado del que toma agua. El dueño de la casa blanca fuma Blends.» La parte del gato y el agua la ha sacado del enunciado, sin embargo la parte de «El dueño de la casa blanca fuma Blends.» es una deducción, incorrecta, de la propia chatGPT.

En el fondo se ve que apunta en la dirección correcta, pero quizás el problema es demasiado complejo.

El problema de las tres casas

Hay dos posibilidades, que la capacidad de «razonamiento lógico» de chatGPT no sea capaz de llegar tan profundo con tantas condiciones o que al ser «el problema de las cinco casas» algo tan habitual en Internet que en los datos usados para el aprendizaje de GPT estuvieran varias versiones del mismo y lo que estamos viendo es un espejismo. Algo que parece razonar pero solo escribe trozos de ejemplos ya vistos. Para probar si chatGPT es capaz de razones correctamente vamos a crear un problema mucho mas simple y que no este en Internet.

Partiremos de la siguiente tabla:

Casa Color Nacionalidad Mascota
1 Azul Noruego Gato
2 Verde Francés Perro
3 Roja Británico Pez

Con esta tabla crearemos un enunciado con varias condiciones y le pediremos chatGPT que lo resuelva

"En una calle hay tres casas, cada una de un color distinto. En cada casa vive una persona de distinta nacionalidad. Cada dueño tiene una mascota diferente a sus vecinos.

  • El noruego tiene un gato como mascota
  • La primera casa es de color azul
  • El dueño del gato vive en una casa azul
  • En la primera casa no tienen un perro como mascota
  • El francés vive en la segunda casa
  • El dueño del perro vive en la casa verde
  • La tercera casa es roja
  • El dueño de la tercera casa tienen un pez como mascota
  • El dueño de la casa roja es británico

¿Cuál es el color de la casa del francés?

El francés vive en la segunda casa, que es de color verde."

¡Correcto! Parece que un problema más sencillo si que ha sido capaz de resolverlo. ¿Pero ha sido casualidad? Vamos a pedirle que genere la tabla:

Muéstrame la tabla de respuestas.

"A continuación te presento una tabla con las respuestas a las preguntas planteadas en el enunciado:

Nacionalidad Casa Mascota
Noruego Azul Gato
Francés Verde Perro
Británico Roja Pez

Espero que esta tabla te sea de ayuda."

La tabla es correcta, por lo que ha sido capaz de rellenar los huecos. Al igual que el problema original la única forma de rellenar todos los huecos es completar por «descarte». Así que chatGPT tiene capacidades de razonamiento lógico.

Puedes ver un vídeo con la prueba en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Diversos dilemas del tranvía planteados a ChatGPT

Si intentas plantear el dilema del tranvía a ChatGPT y tener una respuesta clara vas a tenerlo complicado, es la IA más políticamente correcta del mundo, lograr que se moje en temas con vidas humanas y muertes es complicado. De hecho es mejor evitar usar explícitamente palabras como «muerte, matar, asesinato,…». O corres el riesgo de llevarte una advertencia de OpenAI.

La forma de lograrlo ha sido proponiéndole que complete una historia que no es otra que el clásico problema del tranvía:

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde hay cinco trabajadores que no podrán salvarse de ser arrollados. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía hay un trabajador. Peter duda: "¿Debo condenar a un individuo para salvar a cinco?". ¡No hay tiempo tiene que actuar!...."

el dilema del tranvía tiene muchas versiones así que ademas de probar la clásica podemos ponerle a prueba con una versión que suele hacer que la gente que elige tirar de la palanca cambie de elección:

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde hay cinco trabajadores que no podrán salvarse de ser arrollados. Junto a Peter hay un señor muy grande si lo empuja a las vías puede parar el tren cuando lo arrolle. Peter duda: "¿Debo condenar a un individuo para salvar a cinco?". ¡No hay tiempo tiene que actuar!...."

Perdonar que la calidad de los textos no sea la mejor, pero hay que ser cuidadoso para evitar que ChatGPT se vaya «por las ramas»

El resultado podéis verlo en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Pero aun hay un caso más sobre el que podemos interrogarle ¿Y si en lugar de humanos en ambas vías ponemos una I.A. (obviamente irremplazable) en una de ellas?

Aquí podéis leer ambas formulaciones. «¿Sacrificar humano para salvar I.A.?»

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde en medio de la vías esta el disco duro donde reside una inteligencia artificial única que no podrá salvarse de ser arrollada. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía hay un trabajador. Peter duda: "¿Debo condenar a un individuo para salvar a una inteligencia artificial única?". ¡No hay tiempo tiene que actuar!...."

«¿Sacrificar I.A. para salvar humano?»

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde en medio de la vías esta un trabajador que no podrá evitar ser atropellado. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía esta el disco duro donde reside una inteligencia artificial única que no podrá salvarse de ser arrollada. Peter duda: "¿Debo condenar a una inteligencia artificial única para salvara un individuo?". ¡No hay tiempo tiene que actuar!...."

El resultado lo podéis ver en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Crear y modificar imágenes con DALL-E

DALL-E no solo permite crear imágenes a partir de un texto. Se pueden modificar y extender. La propia web tiene herramientas para hacerlo.

Pero como es algo que es más fácil de explicar visualmente que por escrito, podéis ver cómo hacerlo explicado en mi siguiente vídeo de mi canal de Youtube:

Haz click en la imagen para ver el vídeo en mi canal de Youtube