Potenciando el Potencial: Cómo la IA puede mejorar la eficiencia de un programador

0
657

Que las IAs han llegado para quedarse es una realidad. En el último año han aparecido las primeras propuestas abiertas al público general que nos han mostrado la potencia de esta tecnología y las infinitas aplicaciones que pueden tener. En el caso que nos atañe, el mundo IT, muchos son los que han puesto el grito en el cielo sobre cómo esto implica el fin de los desarrolladores o puestos similares, cuando la realidad es que para nada es así.

La implementación de IAs en el flujo de trabajo es algo que va a ocurrir, o ya está ocurriendo, y debemos adaptarnos a ello. No están para sustituirnos, sino más bien para facilitarnos el trabajo en los aspectos más tediosos de nuestro día a día. Para muestra os dejamos una imagen con diversas empresas que a día de hoy utilizan, de una u otra forma, esta tecnología.

Gráfico de empresas que actualmente usan IAs en su procesos

En Adictos al trabajo ya se ha hecho una pequeña introducción a ChatGPT, explicando qué es y cómo funciona poniendo algunos ejemplos didácticos. Ahora nos asalta la duda: ¿esto cómo lo podemos aprovechar realmente para nuestro trabajo diario como desarrolladores? En este artículo vamos a arrojar un poco de luz y realizar un pequeño caso de uso donde, utilizando OpenAI, vamos a maquetar un CRUD en Spring Boot desde cero.

Índice de contenidos

  1. ¿Qué es un «prompt»?
  2. ¿Qué es el «prompt engineering»?
  3. ¿Qué vamos a hacer?
  4. Requisitos
  5. Al lío
  6. Conclusiones

¿Qué es un «prompt»?

Un prompt, en cuanto a comunicación con una IA se refiere, es una entrada o consulta que un usuario o un programa hace a una inteligencia artificial de tipo LLM (Large Language Model) para que esta responda de manera específica usando su propio modelo

Estas consultas pueden ser hechas con lenguaje natural, ya sean frases o preguntas, e incluso snippets de código o comandos.

Una cosa bastante interesante es que se pueden anidar prompts, es decir, se puede usar una respuesta de un prompt para alimentar una consulta posterior. Este hecho es clave en este artículo, ya que veremos cómo al usar prompts lo más atómicos posibles obtenemos respuestas concisas, las cuales podemos incluso hasta modularizar.

¿Qué es el «prompt engineering»?

La respuesta anterior tal vez te haya parecido algo simple. ¿Cómo una línea de texto natural puede hacer que una IA me automatice un trabajo, o sea capaz de aumentar mi productividad de manera tan «mundana»?

Ahí es donde entran en juego los prompt engineers.

Un ingeniero de prompts es una persona con cierta habilidad haciendo preguntas detalladas. El ámbito de estos roles no ha de ser técnico, pueden ser usados para interactuar con una IA para cualquier propósito, como por ejemplo generación de protocolos médicos, consultas legales, planes de acción empresarial y un larguísimo etcétera. Por lo tanto, los prompts creados han de ser muy concisos y con la habilidad de expresar ciertas palabras clave que expongan un contexto claro para guiar a la IA en respondernos de la manera que mejor nos convenga.

Por lo tanto, el objetivo del prompt engineering es maximizar el rendimiento del modelo de una IA, precisión y su utilidad, creando prompts que expresen la intención del usuario y el tipo de respuesta esperada.

¿Qué vamos a hacer?

Los modelos cada vez tienen mayor capacidad a la hora de procesar texto (unidades de texto llamadas tokens) y los prompts introducidos cada vez pueden ser más y más grandes (en la fecha de redacción de este artículo, ya ha sido mostrada la herramienta de CopilotX por parte de GitHub la cual muestra una potencia abrumadora). Esto tiene sus ventajas e inconvenientes.
La ventaja es que el modelo puede tener cada vez más contexto y el usuario puede guiar mejor a la IA para obtener una respuesta deseada.
La desventaja podría darse al obtener respuestas muy complejas de prompts faraónicos cargados de tareas peticiones, resultando en un arduo análisis y corrección por parte del usuario.

En este artículo proponemos que, para hacer un uso eficiente de las IAs, los prompts han de ser atómicos, reduciendo así el ámbito y la complejidad lo máximo posible. De esta manera obtenemos respuestas concisas y simples que podemos usar para anidar con posteriores prompts, resultando en un output lo mas ajustado posible a lo deseado y facilitando al modelo su comprensión y respuesta.
Al final somos programadores, ¿por qué invertir tiempo en escribir párrafos complejos con respuestas complejas que han de ser corregidas, si podemos escribir 30 palabras anidadas en tres prompts y obtener unidades de respuestas concisas y listas para ser usadas?

Como aplicación de los conceptos mostrados hasta ahora hemos querido ir un poco más allá que charlar con un chatbot, y vamos a intentar automatizar un proceso fácil y repetitivo que suele ser muy común hoy en día: la generación de una API REST en Springboot con un CRUD básico.

Requisitos

Para la realización del tutorial sólo vamos a necesitar crear una cuenta en OpenAI Códex.
Una vez creamos la cuenta y hacemos log in, nos da a elegir entre tres opciones:

Opciones OpenAI
Opciones OpenAI

Para este tutorial vamos a usar la opción API. Esta opción es de pago, ya que nos da acceso al uso de la API y a otros modelos, como el Completion. En nuestro caso tuvimos un tiempo de prueba gratis.

Como alternativa, se puede usar la opción de ChatGPT, de manera más conversacional.

Al lío

Una vez accedemos a la versión API mostrada anteriormente, nos aparece una overview. Para iniciar la interacción con el modelo, seleccionamos la opción «Playground»:

Opción de Playground
Opción de Playground

Una vez en el playgrond, se nos muestra una interfaz de integración con el modelo. En nuestro caso hemos dejado todas las opciones por defecto excepto el «Maximum length», que lo hemos ido ajustando para obtener un mayor tamaño de contexto:

API playground
API playground

Como ya hemos comentado, vamos a intentar atomizar lo máximo posible los prompts, de manera que la respuesta de un prompt nos sirva de contexto para prompts posteriores.

Vamos a generar dos CRUDS con sus respectivas APIs para una aplicación de gestión universitaria.
Primero vamos a crear una entidad de base de datos career, la cual va a contener todas las carreras que ofrece nuestra universidad:

Ejemplo ejecución de query
Ejemplo ejecución de query

Hemos introducido la siguiente frase en el prompt:

«Create a SQL career table with an id field and a name field using bigints and creating a sequence.»

Y como vemos, la respuesta obtenida ha sido:

CREATE TABLE career (
  id BIGINT PRIMARY KEY DEFAULT nextval('career_id_seq'),
  name VARCHAR(50)
);

CREATE SEQUENCE career_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

Posteriormente vamos a crear otra entidad de base de datos la cual contenga la información del alumnado. El texto introducido para comunicarnos con el modelo va a ir siempre a continuación de la última respuesta, como vemos aquí:

Petición de una segunda query
Petición de una segunda query

Como vemos, hemos introducido a continuación de la última respuesta el texto:

«Create a SQL student table with an id field and a name field using bigints and creating a sequence.».

Y hemos obtenido el siguiente resultado:

CREATE TABLE student (
  id BIGINT PRIMARY KEY DEFAULT nextval('student_id_seq'),
  name VARCHAR(50),
  surname VARCHAR(50)
);

CREATE SEQUENCE student_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

Por último, le indicamos que queremos una tabla intermedia que relacione las entidades career y student:

«Create a SQL table that relates previous career and student tables with a composed key».

CREATE TABLE career_student (
  career_id BIGINT, 
  student_id BIGINT,
  PRIMARY KEY (career_id, student_id),
  FOREIGN KEY (career_id) REFERENCES career(id),
  FOREIGN KEY (student_id) REFERENCES student(id)
);

Una vez tenemos las entidades de base de datos, es turno de nuestras entidades JPA.

Primero le indicamos que nos genere una entidad JPA usando la entidad de base de datos career. De paso le indicamos que use Lombok para evitar boilerplate. Recordad, el texto se incluye al final de cada respuesta, así tiene todas sus respuestas previas como contexto:

«Create a JPA class entity Career using the previous career table, using Lombok API».

@Entity
@Table(name = "career")
@Data
public class Career {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "career_id_seq")
    @SequenceGenerator(name = "career_id_seq", sequenceName = "career_id_seq", allocationSize = 1)
    private Long id;

    private String name;

}

Ahora es turno de la entidad alumno. Como con la entidad career, le indicamos que nos genere una entidad JPA usando la entidad student y Lombok, pero en este caso le indicamos que use la tabla relacional entre career y student:

«Create a JPA class entity Student using the previous student and the relation table, using Lombok API».

@Entity
@Table(name = "student")
@Data
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_id_seq")
    @SequenceGenerator(name = "student_id_seq", sequenceName = "student_id_seq", allocationSize = 1)
    private Long id;

    private String name;
    private String surname;

    @ManyToMany
    @JoinTable(name = "career_student",
            joinColumns = @JoinColumn(name = "student_id"),
            inverseJoinColumns = @JoinColumn(name = "career_id"))
    private List careers;

}

Una vez tenemos las entidades JPA y sus relaciones, es momento de generar repositorios.

Para la generación de los repositorios, le indicamos que, usando las entidades previas, nos genere un repositorio de JPA:

«Using the previous Career entity, create a JPA crud repository».

public interface CareerRepository extends JpaRepository {

}

«Using the previous Student entity, create a JPA crud repository».

public interface StudentRepository extends JpaRepository {

}

Una vez generados los repositorios, generamos los servicios.
Para este cometido, le indicamos que vamos a crear un servicio CRUD. Con este comentario el modelo va a ser capaz de generar los cuatro métodos básicos de un CRUD: create, read, update y delete.
Además, para hacer uso del repositorio JPA previamente creado, le indicamos que haga la inyección del mismo por constructor (ya que de lo contrario le añade un @Autowired al campo). Hacemos esto para los dos servicios:

«Using the previous Career repository, create a CRUD service that injects the repository by the constructor».

@Service
public class CareerService {

  private final CareerRepository careerRepository;

  public CareerService(CareerRepository careerRepository) {
    this.careerRepository = careerRepository;
  }

  public List getAll() {
    return careerRepository.findAll();
  }

  public Optional getById(Long id) {
    return careerRepository.findById(id);
  }

  public Career create(Career career) {
    return careerRepository.save(career);
  }

  public Career update(Career career) {
    return careerRepository.save(career);
  }

  public void delete(Long id) {
    careerRepository.deleteById(id);
  }

}

«Using the previous Student repository, create a CRUD service that injects the repository by the constructor».

@Service
public class StudentService {

  private final StudentRepository studentRepository;

  public StudentService(StudentRepository studentRepository) {
    this.studentRepository = studentRepository;
  }

  public List getAll() {
    return studentRepository.findAll();
  }

  public Optional getById(Long id) {
    return studentRepository.findById(id);
  }

  public Student create(Student student) {
    return studentRepository.save(student);
  }

  public Student update(Student student) {
    return studentRepository.save(student);
  }

  public void delete(Long id) {
    studentRepository.deleteById(id);
  }

}

Por último, vamos a generar la API REST de nuestra aplicación. Para ello le indicamos que, usando los servicios previamente creados, cree un controlador REST de Springboot, en el cual inyecte el servicio correspondiente a través del constructor:

«Using the previous Career service, create a Springboot REST API controller that injects the service by the constructor».

@RestController
@RequestMapping("/careers")
public class CareerController {

  private final CareerService careerService;

  public CareerController(CareerService careerService) {
    this.careerService = careerService;
  }

  @GetMapping
  public List getAll() {
    return careerService.getAll();
  }

  @GetMapping("/{id}")
  public ResponseEntity getById(@PathVariable Long id) {
    Optional optionalCareer = careerService.getById(id);
    if (optionalCareer.isPresent()) {
      return ResponseEntity.ok(optionalCareer.get());
    } else {
      return ResponseEntity.notFound().build();
    }
  }

  @PostMapping
  public ResponseEntity create(@RequestBody Career career) {
    Career createdCareer = careerService.create(career);
    URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(createdCareer.getId())
            .toUri();
    return ResponseEntity.created(location).body(createdCareer);
  }

  @PutMapping("/{id}")
  public ResponseEntity update(@PathVariable Long id, @RequestBody Career career) {
    if (!careerService.getById(id).isPresent()) {
      return ResponseEntity.notFound().build();
    }
    career.setId(id);
    return ResponseEntity.ok(careerService.update(career));
  }

  @DeleteMapping("/{id}")
  public ResponseEntity delete(@PathVariable Long id) {
    if (!careerService.getById(id).isPresent()) {
      return ResponseEntity.notFound().build();
    }
    careerService.delete(id);
    return ResponseEntity.noContent().build();
  }

}

«Using the previous Student service, create a Springboot REST API controller that injects the service by the constructor».

@RestController
@RequestMapping("/students")
public class StudentController {

  private final StudentService studentService;

  public StudentController(StudentService studentService) {
    this.studentService = studentService;
  }

  @GetMapping
  public List getAll() {
    return studentService.getAll();
  }

  @GetMapping("/{id}")
  public ResponseEntity getById(@PathVariable Long id) {
    Optional optionalStudent = studentService.getById(id);
    if (optionalStudent.isPresent()) {
      return ResponseEntity.ok(optionalStudent.get());
    } else {
      return ResponseEntity.notFound().build();
    }
  }

  @PostMapping
  public ResponseEntity create(@RequestBody Student student) {
    Student createdStudent = studentService.create(student);
    URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(createdStudent.getId())
            .toUri();
    return ResponseEntity.created(location).body(createdStudent);
  }

  @PutMapping("/{id}")
  public ResponseEntity update(@PathVariable Long id, @RequestBody Student student) {
    if (!studentService.getById(id).isPresent()) {
      return ResponseEntity.notFound().build();
    }
    student.setId(id);
    return ResponseEntity.ok(studentService.update(student));
  }

  @DeleteMapping("/{id}")
  public ResponseEntity delete(@PathVariable Long id) {
    if (!studentService.getById(id).isPresent()) {
      return ResponseEntity.notFound().build();
    }
    studentService.delete(id);
    return ResponseEntity.noContent().build();
  }

}

Conclusiones

Con esto tenemos dos API REST totalmente funcionales y listas para ser consumidas, que han sido generadas en 20 segundos sin contar el tiempo de escritura de cada prompt.

En este caso no hemos modificado el código que nos ha devuelto, ni hemos entrado en valorar la calidad del mismo, ya que el propósito de esta entrada era mostrar la automatización de tareas de nuestro día a día.

Tampoco hemos creado los tests para el código generado. Creemos que es un punto clave de automatización que puede aumentar nuestra productividad diaria de manera significativa, y tal vez merezca la pena otro artículo aparte.
Pero, si queréis probarlo, sólo tendréis que pedirle que genere los tests de todo el código anterior. Vuestra sorpresa puede llegar a ser mayúscula al comprobar que en varios segundos es capaz de crear tests unitarios con casos aleatorios, e incluso tests de integración para la API REST.

Por supuesto las posibilidades son infinitas. Podemos invertir tanto tiempo como queramos en ajustar los prompts y jugar con ellos, y tener respuestas cada vez más ajustadas y cercanas a nuestras necesidades. La «magia» de estos modelos es que no sólo aprenden a darnos una respuesta, además aprenden a respondernos de determinada manera según nuestras interacciones previas.

Este artículo ha sido realizado por Óscar Sánchez y David Plaza.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad