¡A construir se ha dicho!
En la entrada anterior les conté sobre el diseño de Mercurio. En esta vamos a empezar a elegir algunas tecnologías y a construir los componentes.
¿Cómo interactuar con el agente?
Lo primero que vamos a hacer es decidir la interfaz gráfica, y tengo dos opciones.
La primera es construir una aplicación desde cero para interactuar con el agente, algo parecido a lo que ya hice en Light House. Pero, siendo sincero, mi idea es que este agente trabaje más en segundo plano y que yo solo intervenga cuando lo vea necesario. Por eso, construir una aplicación me trae más problemas que beneficios.
La segunda opción que consideré fue Telegram. Después de todo, ya permite crear y gestionar bots de manera muy sencilla, y así yo solo me concentraría en lo principal: el agente.
Ya he trabajado antes con Telegram y sus bots, así que la decisión está más que tomada: voy a usar Telegram.
Motor de búsqueda
Una de las partes principales de este proyecto es el motor de búsqueda: el que se encarga de buscar las ofertas, extraer la información y dejarla lista en la base de datos para que el agente la analice y la pondere.
Primera decisión: ¿qué portal de empleo voy a usar? En mi cabeza, LinkedIn era la solución fácil. Seguro tenía un API y, además, ya tiene mi perfil; me conoce lo suficiente como para hacer una primera curaduría de ofertas, y eso le facilitaría el trabajo al agente.
Todo maravilloso, hasta que, investigando, me di cuenta de que LinkedIn no tiene un API para buscar en su banco de ofertas. Tiene API para buscar gente, candidatos y otros enfoques, pero no para lo que necesito.
Cuando me encontré con esto, intenté romper la regla principal de este proyecto: “IA solo donde sea necesario”. Pensé: tal vez puedo buscar un MCP y dejar que un agente se encargue de navegar en LinkedIn como si fuera yo. Así que lo hice e implementé mcp-server-linkedin.
Después caí en cuenta de que el MCP es solo el protocolo que unifica el acceso; por detrás, igual tiene que haber algo que haga las consultas. Y efectivamente: el MCP usaba web scraping para obtener las ofertas. Ahí dejé de verle sentido a usar IA.
Lo que sí le vi valor fue a conservar el MCP, aunque ya no lo usara un agente. Ya tenía resueltas cosas como levantar una instancia de Chromium para navegar, manejar las sesiones, el scraping y demás. Podría quitarlo, pero tendría que gestionar por mi cuenta todo lo que el MCP ya resolvía. Así que lo único que hice fue quitar el modelo y dejar la búsqueda como un proceso determinista.
Revisando las políticas de LinkedIn, me topé con otra “pequeña” restricción: un script que abra LinkedIn y recorra ofertas automáticamente tiene un alto riesgo de bloqueo. Esto limitó un poco el funcionamiento del motor. Mi idea era desplegar el componente en la nube, en una Lambda o algo parecido, pero el cambio constante de IP entre ejecuciones podría levantar sospechas en LinkedIn y terminar con mi cuenta bloqueada. Obviamente, no quería eso.
Por eso, el motor de búsqueda se queda en local. Es decir, me toca tener un cron que lo ejecute de manera periódica. ¿Y qué pasa si el computador está suspendido? Bueno, el script tendría que encargarse de “despertarlo”. La ventaja es que pocas veces a la semana lo apago del todo, así que no debería ser un problema.
Últimos ajustes antes de dar por terminado el motor. Si bien LinkedIn ya ofrece una primera capa de curaduría, necesitaba filtrar todavía mejor. Así que el motor de búsqueda tiene acceso de lectura a la base de datos, donde consulta las preferencias de búsqueda que yo defina: la ubicación, si el trabajo debe ser remoto, híbrido o presencial, las empresas que me interesan especialmente y las que quiero excluir. Básicamente, dejé parametrizados en la base de datos los filtros que cualquier persona aplicaría a mano en LinkedIn.
Finalmente, una vez terminada la búsqueda, el motor guarda las ofertas en la base de datos y le avisa al siguiente componente: el agente.
En resumen, una corrida del motor se ve así:

El agente, por sí solo, tiene sus cosas que me gustaría destacar, así que lo dejaré para la siguiente publicación.