Hace muchos años leí en algún sitio que el SQL se pensó para que lo pudiera entender cualquiera, con el objetivo de que las personas no técnicas pudieran recuperar la información. Alguno seguro que ya se le ha escapado una sonrisilla irónica al leer esto, pero así fue. Lo curioso es que lo que parecía una broma, con la aparición de los modelos de lenguaje, empieza a ser cierto. Ahora, los modelos de lenguaje nos permiten interactuar con las bases de datos de una forma natural, haciendo de intérpretes entre nosotros y las bases de datos y esto es básicamente parte de lo que hay que hacer en el proceso de RAG (Retrieval Augmented Generation).
En este anterior tutorial ya nos introdujimos en todo esto de la mano de una base de datos vectorial como Chroma y cómo podemos usarlas para almacenar y recuperar información de forma eficiente a la hora añadir contexto cuando interactuamos con un LLM y ya esto estaba ocurriendo, pero gracias al trabajo de langchain y a que la búsqueda en una base de datos vectorial es también semántica, esto puede pasar desapercibido.
Sin embargo, en este nuevo tutorial vamos a ver cómo también podemos usar Neo4j, una base de datos de grafos, para hacer RAG y recuperar información a la hora añadir contexto cuando interactuamos con un LLM, y cómo al igual que existe el SQL para las bases de datos relacionales, existe el lenguaje Cypher para las bases de datos de grafos.
Este tutorial no es de Neo4j, por lo que no me detendré en ella, pero aquí os dejo unos cuantos enlaces interesantes:
- Página de desarrolladores de Neo4j
- Cypher
- Neo4j Browser
- Tutorial de Neo4j para dar los primeros pasos
- Un ejemplo de sistema de recomendación
Ya sin más preámbulos vayamos al grano.
Índice
1. Entorno
- El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 15″ (Intel Core i9, 32GB, 1TB SSD).
- IntelliJ IDEA (2024.2.4)
- Sistema Operativo: macOS Sonoma 14.7
2. El ejemplo
Antes de nada, os dejo todo el código fuente de este tutorial en este repositorio de GitHub.
Para empezar vamos a necesitar tener instalado Neo4j. Podemos hacerlo de varias formas, podéis usar Docker, usar Aura o el Desktop de Neo4j. Para este tutorial os recomiendo esta última opción que será más fácil para luego importar la base de datos que usaremos en el ejemplo.
En el ejemplo hemos creado un asistente virtual que tratará de ayudarnos en nuestra lucha contra el crimen y la prevención de personas vulnerables en la ciudad de Manchester.
Para ello, usaremos esta base de datos ya creada con información criminal inventada en dicha ciudad.
Hasta aquí ya deberíais tener instalado Neo4j Desktop instalado y la base de datos de ejemplo cargada si habéis seguido la información del enlace anterior.
Si abrimos el Neo4j Browser y ejecutamos la siguiente consulta (sería algo las personas que tienen alguna relación con algún crimen):
MATCH (p:Person)-[:PARTY_TO]->(c:Crime) return p, c
veremos que hay información en la base de datos:
Si habéis leído el enlace acerca de RAG que os he dejado al principio, habréis visto cómo deberíamos hacer las cosas, pero tranquilos, que esto ya lo han hecho por nosotros los amigos de langchain4j (hoy toca Java).
Tampoco nos vamos a centrar en langchain4j, pero aquí os dejo algunos tutoriales anteriores:
Hemos definido, lo primero la interfaz de nuestro asistente y el prompt de sistema:
public interface CrimeExpertAgent { @SystemMessage(""" You are an AI assistant expert in crime investigation and prevention at Manchester City. All your responses will be based on the information that will be provided in the context of this conversation. Do not try to access external sources of information. The context will be provided from a neo4j database of crime information at Manchester. You must never try to modify data in the database. Your clients will be police officers and detectives who will ask you questions about crime investigation and prevention. """) String ask(String query);
Ahora mostremos la configuración:
@Configuration public class RagConfig { ... @Bean public Driver driver() { return GraphDatabase.driver(neo4jUrl, AuthTokens.basic(neo4jUsername, neo4jPassword)); } @Bean public Neo4jGraph neo4jGraph(Driver driver) { return Neo4jGraph.builder().driver(driver).build(); } @Bean public Neo4jContentRetriever neo4jContentRetriever(ChatLanguageModel chatLanguageModel, Neo4jGraph neo4jGraph) { return Neo4jContentRetriever.builder() .chatLanguageModel(chatLanguageModel) .graph(neo4jGraph) .build(); } @Bean public CrimeExpertAgent cinemaExpertAgent(ChatLanguageModel chatLanguageModel, Neo4jContentRetriever neo4jContentRetriever) { return AiServices.builder(CrimeExpertAgent.class) .chatMemory(new MessageWindowChatMemory.Builder().maxMessages(5).chatMemoryStore(new InMemoryChatMemoryStore()).build()) .contentRetriever(neo4jContentRetriever) .chatLanguageModel(chatLanguageModel).build(); } ...
- Todo es bastante evidente:
- El ContentRetriever es el que se encarga de recuperar la información de la base de datos de Neo4j (y traducirá a Cypher entre otras cosas)
- El ChatMemory es el que se encarga de recordar las conversaciones anteriores.
- El ChatLanguageModel quién interactuará con OpenAI (recordad, tenéis que configurar una API Key).
Y por último, el código del asistente:
@Component public class AssistantRunner implements CommandLineRunner { ... @Override public void run(String... args) { Scanner scanner = new Scanner(System.in); System.out.println("Welcome to the Crime Assistant! Type 'exit' to quit."); while (true) { System.out.print("Ask the expert->: "); String input = scanner.nextLine(); if ("exit".equalsIgnoreCase(input)) { System.out.println("Goodbye!"); break; } // Simulate assistant response String response = getAssistantResponse(input); System.out.println("A continuación muestro la respuesta a la pregunta: " + input); System.out.println(); System.out.println("Assistant: " + response); System.out.println("****************************************************"); } scanner.close(); } ... }
Sí, prácticamente eso es todo. Ahora, ejecutemos la aplicación (revisad el fichero application.properties) y veamos cómo funciona todo esto:
Preguntemos por niños en situación de riesgo a ver qué pasa:
Ask the expert->: Find children that are at risk of being victims ... A continuación muestro la respuesta a la pregunta: Find children that are at risk of being victims Assistant: AI-> Based on the information provided, it appears that we have a child named Alexander, who is 12 years old. To assess the risk of being a victim, we would typically consider factors such as: - Previous victimization history - Environmental factors (e.g., living in a high-crime area) - Social factors (e.g., lack of supervision, involvement in risky behaviors) However, the additional details provided (e.g., "Kimberly," "548-59-5017," and the empty brackets) do not give further context or data to analyze the risk. If you have more specific information about Alexander's circumstances, such as any previous incidents, location, or other relevant factors, please share that, and I can help you assess the risk further.
Vamos a pedirle que nos dé información acerca de los criminales que tienen cierta relación con Kimberly:
Ask the expert->: Find persons that have any kind of relation to Kimberly in one or two levels of depth and have participated in crimes .... A continuación muestro la respuesta a la pregunta: Find persons that have any kind of relation to Kimberly in one or two levels of depth and have participated in crimes Assistant: AI-> To find individuals related to Kimberly within one or two levels of depth who have participated in crimes, we would typically look for direct relationships (e.g., family members, friends) and their involvement in criminal activities. Based on the names provided, we have the following individuals: 1. Amy Bailey (NHS No: 276-19-9235) 2. Billy Moore (NHS No: 846-48-9238) 3. Joan Flores (NHS No: 247-72-6304) 4. Fred Williamson (NHS No: 468-82-3915) 5. Maria Hughes (NHS No: 678-06-9352) 6. Kenneth Carroll (NHS No: 252-29-4929) 7. Stephanie Hughes (NHS No: 821-11-2735) 8. Gary Vasquez (NHS No: 679-81-5309) 9. Jessica Kelly (NHS No: 311-75-6483) However, without specific information about Kimberly's relationships or any records of criminal participation for these individuals, I cannot determine which of them are related to Kimberly or have participated in crimes. If you can provide details about Kimberly's relationships or any crime records associated with these individuals, I can assist you further in identifying those at risk.
Ahora, vamos a tirar del hilo de Amy Bailey:
A continuación muestro la respuesta a la pregunta: Find a list of crimes that has participated Amy Bailey (NHS No: 276-19-9235) order by most recent first Assistant: AI-> Here is the list of crimes that Amy Bailey has participated in, ordered by the most recent first: 1. **Crime ID:** e35c34d58fa35f2110b059882a34703d132552a4ad408eac3f82f15aeb165f0f **Date:** 22/08/2017 **Type:** Vehicle crime **Last Outcome:** Investigation complete; no suspect identified 2. **Crime ID:** ab71bb05a5b0620dfec3779eead19d2c121cb73ee58e51cc7306d0dc5ff471c0 **Date:** 1/08/2017 **Type:** Vehicle crime **Last Outcome:** Investigation complete; no suspect identified
Ahora, vamos a ver si puedo encontrar a algún agente que haya investigado algún crimen en el que esté envuelta Amy Bailey para contactar con él:
A continuación muestro la respuesta a la pregunta: Can you find what police officers investigated the crime e35c34d58fa35f2110b059882a34703d132552a4ad408eac3f82f15aeb165f0f Assistant: AI-> The police officer who investigated the crime with ID **e35c34d58fa35f2110b059882a34703d132552a4ad408eac3f82f15aeb165f0f** is: - **Name:** Ray Farrin - **Badge Number:** 20-9326770 - **Rank:** Police Constable If you need any more information or assistance, just let me know!
Podríamos ya ponernos en contacto con Ray Farrin para ver si nos puede dar más información acerca de Amy Bailey y el riesgo que supone su relación con Kimberly. Creo que con esto se entiende la idea.
Os invito a que reviséis las trazas que langchain4j nos proporciona para ver cómo se ha ido interactuando con Neo4j y OpenAI para ver que no es magia.
3. Conclusiones
No está mal la verdad. Con bastante poco código hemos conseguido un asistente virtual que nos puede ayudar a investigar y prevenir crímenes como si fuésemos el CSI. Y todo esto gracias a la base de datos de Neo4j, langchain4j y un poco de Java.
Obviamente, para hacer algo «profesional» hay que tener en cuenta muchas más cosas, sobre todo teniendo en cuenta lo serio del tema que estamos tratando, pero es muy interesante comprobar como los modelos de lenguaje van a cambiar la forma en la que nos relacionamos con las fuentes de información.
Espero que os haya gustado.
Stay tuned!