<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Java - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Java - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/es/search-labs/blog/category/java-programming</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/blog/category/java-programming</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/category/java-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 16:08:16 GMT</lastBuildDate>
  <item>
    <title><![CDATA[LangChain4j con Elasticsearch como almacén de incrustaciones]]></title>
    <description><![CDATA[LangChain4j (LangChain para Java) tiene Elasticsearch como almacén de incrustación. Descubre cómo usarlo para construir tu aplicación RAG en Java puro.]]></description>
    <content:encoded><![CDATA[<p>
En la <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">publicación anterior</a>, descubrimos qué es LangChain4j y cómo:</p><ul><li><p>Habla con los LLMs implementando un <code>ChatLanguageModel</code> y un <code>ChatMemory</code></p></li><li><p>Conserva el historial de chats en memoria para recordar el contexto de una conversación previa con un LLM</p></li></ul><p>Esta entrada del blog trata sobre cómo:</p><ul><li><p>Crear incrustaciones vectoriales a partir de ejemplos de texto</p></li><li><p>Almacenar incrustaciones vectoriales en el almacenamiento de incrustaciones de Elasticsearch </p></li><li><p>Búsqueda de vectores similares</p></li></ul><h2>Crear incrustaciones</h2><p>Para crear embeddings, necesitamos definir un <code>EmbeddingModel</code> a emplear. Por ejemplo, podemos usar el mismo modelo de mistral que usamos en la <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">publicación anterior</a>. Estaba corriendo con ollama:</p>EmbeddingModel model = OllamaEmbeddingModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();<p>Un modelo es capaz de generar vectores a partir de texto. Aquí podemos comprobar el número de dimensiones generadas por el modelo:</p>Logger.info("Embedding model has {} dimensions.", model.dimension());
// This gives: Embedding model has 4096 dimensions.<p>Para generar vectores a partir de un texto, podemos usar:</p>Response&lt;Embedding&gt; response = model.embed("A text here");<p>O si también queremos proporcionar metadatos para filtrar cosas como texto, precio, fecha de lanzamiento o lo que sea, podemos usar <code>Metadata.from()</code>. Por ejemplo, aquí agregamos el nombre del juego como campo de metadatos:</p>TextSegment game1 = TextSegment.from("""
    The game starts off with the main character Guybrush Threepwood stating "I want to be a pirate!"
    To do so, he must prove himself to three old pirate captains. During the perilous pirate trials, 
    he meets the beautiful governor Elaine Marley, with whom he falls in love, unaware that the ghost pirate 
    LeChuck also has his eyes on her. When Elaine is kidnapped, Guybrush procures crew and ship to track 
    LeChuck down, defeat him and rescue his love.
""", Metadata.from("gameName", "The Secret of Monkey Island"));
Response&lt;Embedding&gt; response1 = model.embed(game1);
TextSegment game2 = TextSegment.from("""
    Out Run is a pseudo-3D driving video game in which the player controls a Ferrari Testarossa 
    convertible from a third-person rear perspective. The camera is placed near the ground, simulating 
    a Ferrari driver's position and limiting the player's view into the distance. The road curves, 
    crests, and dips, which increases the challenge by obscuring upcoming obstacles such as traffic 
    that the player must avoid. The object of the game is to reach the finish line against a timer.
    The game world is divided into multiple stages that each end in a checkpoint, and reaching the end 
    of a stage provides more time. Near the end of each stage, the track forks to give the player a 
    choice of routes leading to five final destinations. The destinations represent different 
    difficulty levels and each conclude with their own ending scene, among them the Ferrari breaking 
    down or being presented a trophy.
""", Metadata.from("gameName", "Out Run"));
Response&lt;Embedding&gt; response2 = model.embed(game2);<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step5EmbedddingsTest.java">clase Step5EmbedddingsTest.java</a> .</p><h2>Agregar Elasticsearch para almacenar nuestros vectores</h2><p>LangChain4j proporciona un almacén de incrustación en memoria. Esto es útil para realizar pruebas sencillas:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Pero obviamente, esto no podría funcionar con un conjunto de datos mucho mayor porque este almacén almacena todo en memoria y no tenemos memoria infinita en nuestros servidores. Así que, en su lugar, podríamos almacenar nuestras incrustaciones en Elasticsearch, que por definición es "elástico" y puede escalar hacia arriba y hacia fuera con tus datos. Para ello, agregamos Elasticsearch a nuestro proyecto:</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;<p>Como notasteis, también agregamos el módulo Elasticsearch TestContainers al proyecto, para poder iniciar una instancia de Elasticsearch a partir de nuestras pruebas:</p>// Create the elasticsearch container
ElasticsearchContainer container =
  new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.15.0")
    .withPassword("changeme");

// Start the container. This step might take some time...
container.start();

// As we don't want to make our TestContainers code more complex than
// needed, we will use login / password for authentication.
// But note that you can also use API keys which is preferred.
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level Rest client which connects to the elasticsearch container.
client = RestClient.builder(HttpHost.create("https://" + container.getHttpHostAddress()))
  .setHttpClientConfigCallback(httpClientBuilder -&gt; {
    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
    httpClientBuilder.setSSLContext(container.createSslContextFromCa());
    return httpClientBuilder;
  })
  .build();

// Check the cluster is running
client.performRequest(new Request("GET", "/"));<p>Para usar Elasticsearch como almacén de embebed, "solo" tienes que cambiar del almacén de datos en memoria de LangChain4j al de Elasticsearch:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Esto almacenará tus vectores en Elasticsearch en un índice <code>default</code> . También puedes cambiar el nombre del índice por algo más significativo:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .indexName("games")
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step6ElasticsearchEmbedddingsTest.java">clase Step6ElasticsearchEmbedddingsTest.java</a> .</p><h2>Búsqueda de vectores similares</h2><p>Para buscar vectores similares, primero necesitamos transformar nuestra pregunta en una representación vectorial usando el mismo modelo que usamos anteriormente. Ya lo hicimos, así que no es difícil hacerlo de nuevo. Ten en cuenta que en este caso no necesitamos los metadatos:</p>String question = "I want to pilot a car";
Embedding questionAsVector = model.embed(question).content();<p>Podemos construir una solicitud de búsqueda con esta representación de nuestra pregunta y pedir al almacén de incrustaciones que encuentre los primeros vectores principales:</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Podemos iterar sobre los resultados ahora e imprimir algo de información, como el nombre del juego que proviene de los metadatos y el puntaje:</p>result.matches().forEach(m -&gt; Logger.info("{} - score [{}]",
  m.embedded().metadata().getString("gameName"), m.score()));<p>Como era de esperar, esto nos da "Out Run" como primer éxito:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca0dcfdb1a9c94f/6a170291cf4f256938b2d017/140b6a962e5edbb4870419250e30bfb815b0d73e-640x480.gif" alt="Carrera" />Out Run - score [0.86672974]
The Secret of Monkey Island - score [0.85569763]<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L110-L129">clase Step7SearchForVectorsTest.java</a> . </p><h2>Tras bambalinas</h2><p>La configuración predeterminada para la tienda de incrustación de Elasticsearch emplea la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-knn-query.html">consulta aproximada kNN</a> detrás de escena.</p>POST games/_search
{
  "query" : {
    "knn": {
      "field": "vector",
      "query_vector": [-0.019137882, /* ... */, -0.0148779955]
    }
  }
}<p>Pero esto podría cambiar proporcionando otra configuración (<code>ElasticsearchConfigurationScript</code>) distinta a la predeterminada (<code>ElasticsearchConfigurationKnn</code>) en la tienda de incrustación:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .configuration(ElasticsearchConfigurationScript.builder().build())
    .indexName("games")
    .restClient(client)
    .build();<p>La implementación <code>ElasticsearchConfigurationScript</code> ejecuta entre bastidores una <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html">consulta</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"><code>script_score</code></a> usando una <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine">función</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"><code>cosineSimilarity</code></a>.</p><p>Básicamente, al llamar:</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Esto ahora llama:</p>POST games/_search
{
  "query": {
    "script_score": {
      "script": {
        "source": "(cosineSimilarity(params.query_vector, 'vector') + 1.0) / 2",
        "params": {
          "queryVector": [-0.019137882, /* ... */, -0.0148779955]
        }
      }
    }
  }
}<p>En ese caso, el resultado no cambia en términos de "orden", sino que solo se ajusta el puntaje porque la llamada <code>cosineSimilarity</code> no emplea ninguna aproximación, sino que calcula el coseno para cada uno de los vectores emparejados:</p>Out Run - score [0.871952]
The Secret of Monkey Island - score [0.86380446]<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L132-L155">clase Step7SearchForVectorsTest.java</a> .</p><h2>Conclusión</h2><p>Explicamos lo fácil que es generar incrustaciones a partir de tu texto y cómo puedes almacenar y buscar los vecinos más cercanos en Elasticsearch usando dos enfoques diferentes:</p><ul><li><p>Usando la consulta de <code>knn</code> aproximada y rápida con la opción de <code>ElasticsearchConfigurationKnn</code> por defecto</p></li><li><p>Usando la consulta de <code>script_score</code> exacta pero más lenta con la opción <code>ElasticsearchConfigurationScript</code></p></li></ul><p>El siguiente paso será construir una aplicación completa de RAG, basada en lo que aprendimos aquí.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc873b86c76d1798/6a170293acf088f666be99b3/abd8a4a809064101c037af66b87f28e5ecde03b0-1474x645.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 08 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Probar tu código Java con mocks y Elasticsearch real]]></title>
    <description><![CDATA[Aprende a escribir tus pruebas automatizadas para Elasticsearch, usando mocks y Testcontainers]]></description>
    <content:encoded><![CDATA[<p>En esta entrada, presentaremos y explicaremos dos formas de probar software empleando Elasticsearch como dependencia externa del sistema. Cubriremos pruebas usando simulacros así como pruebas de integración, mostraremos algunas diferencias prácticas entre ellas y daremos algunas pistas sobre hacia dónde elegir cada estilo.</p><h2>Buenas pruebas para la confianza del sistema</h2><p>Una buena prueba es una prueba que aumenta la confianza de cada persona involucrada en el proceso de creación y mantenimiento de un sistema informático. Las pruebas no están pensadas para ser rápidas, geniales ni para aumentar artificialmente la cobertura del código. Las pruebas desempeñan un papel vital para garantizar que:</p><ul><li><p>Lo que queremos entregar va a funcionar en producción.</p></li><li><p>El sistema cumple con los requisitos y los contratos.</p></li><li><p>No habrá regresiones en el futuro.</p></li><li><p>Los desarrolladores (y otros miembros del equipo implicados) están seguros de que lo que crearon funcionará.</p></li></ul><p>Por supuesto, esto no significa que las pruebas no puedan ser frías, rápidas o aumentar la cobertura del código. Cuanto más rápido podamos ejecutar nuestra suite de pruebas, mejor. Simplemente, en la búsqueda de reducir la duración total de la suite de pruebas, no deberíamos sacrificar la fiabilidad, la mantenibilidad y la confianza que nos brindan las pruebas automatizadas.</p><p>Unas buenas pruebas automatizadas hacen que los distintos miembros del equipo se sientan más seguros:</p><ul><li><p>Desarrolladores: pueden confirmar que lo que hacen funciona (incluso antes de que el código en el que trabajan salga de su máquina).</p></li><li><p>Equipo de control de calidad: tienen menos que probar manualmente.</p></li><li><p>Los operadores de sistemas y los SRES: son más relajados, porque los sistemas son más fáciles de desplegar y mantener.</p></li></ul><p>Por último, pero no menos importante: la arquitectura de un sistema. Nos encanta cuando los sistemas están organizados, son fáciles de mantener y la arquitectura es limpia y cumple su propósito. Sin embargo, a veces podemos ver una arquitectura que sacrifica demasiado por la excusa conocida como "así es más comprobable". No hay nada de malo en ser muy comprobable: solo cuando el sistema está escrito principalmente para ser comprobable en lugar de servir a las necesidades que justifican su existencia, vemos una situación en la que la cola mueve al perro.</p><h2>Dos tipos de pruebas: Mocks y dependencias</h2><p>Hay muchas formas en que las pruebas pueden ver y, por tanto, clasificar. En esta publicación me centraré solo en un aspecto de dividir los exámenes: usar mocks (o stubs, o fakes, o ...) frente a usar dependencias reales. En nuestro caso, la dependencia es Elasticsearch.</p><p>Los exámenes con mocks son muy rápidos porque no necesitan iniciar dependencias externas y todo ocurre solo en memoria. El mocking en pruebas automatizadas ocurre cuando se emplean objetos falsos en lugar de reales para probar partes de un programa sin usar las dependencias reales. Por eso son necesarias y por las que destacan en cualquier prueba de detección rápida en red, por ejemplo. Validación de la entrada. No es necesario abrir una base de datos y hacer una llamada solo para verificar que no se permiten números negativos en una solicitud, por ejemplo.</p><p>Sin embargo, introducir simulacros tiene varias participaciones:</p><ul><li><p>No todo y cada vez se puede burlar fácilmente, por eso los mocks tienen impacto en la arquitectura del sistema (que a veces es genial, a veces no tanto).</p></li><li><p>Las pruebas que se ejecutan en mocks pueden ser rápidas, pero desarrollar tales pruebas puede llevar bastante tiempo porque las mocks que reflejan profundamente los sistemas que imitan normalmente no se ofrecen gratis. Alguien que sepa cómo funciona el sistema necesita escribir los mocks de la manera correcta, y este conocimiento puede venir de la experiencia práctica, el estudio de documentación, etc.</p></li><li><p>Los simulacros deben mantener. Cuando tu sistema depende de una dependencia externa y necesitas actualizar esa dependencia, alguien tiene que cerciorar de que los mocks que imitan la dependencia también se actualicen con todos los cambios: fallos, documentados y no documentados (lo que también puede afectar a nuestro sistema). Esto resulta especialmente doloroso cuando quieres actualizar una dependencia pero tu suite de pruebas (usando solo simulaciones) no puede darte confianza en que todos los casos probados estén garantizados para funcionar.</p></li><li><p>Se necesita disciplina para cerciorar que el esfuerzo se destine a desarrollar y probar el sistema, no a los simulacros.</p></li></ul><p>Por estas razones, mucha gente defiende ir exactamente en la dirección opuesta: nunca usar mocks (o stubs, etc.), sino confiar únicamente en dependencias reales. Este enfoque funciona muy bien en demos o cuando el sistema es pequeño y solo tiene unos pocos casos de prueba que generan una gran cobertura. Estas pruebas pueden ser pruebas de integración (hablando a grandes rasgos: comprobar una parte de un sistema con dependencias reales) o pruebas de extremo a extremo (usando todas las dependencias reales al mismo tiempo y comprobando el comportamiento del sistema en todos los extremos, mientras se reproducen flujos de trabajo de usuario que definen el sistema como utilizable y exitoso). Un beneficio claro de este enfoque es que también verificamos (a menudo sin querer) nuestras suposiciones sobre las dependencias y cómo las integramos con el sistema en el que trabajamos.</p><p>Sin embargo, cuando las pruebas emplean únicamente dependencias reales, debemos considerar los siguientes aspectos:</p><ul><li><p>Algunos escenarios de prueba no necesitan la dependencia real (por ejemplo, para verificar los invariantes estáticos de una petición).</p></li><li><p>Estas pruebas normalmente no se ejecutan en suites completas en las máquinas de los desarrolladores, porque esperar retroalimentación llevaría demasiado tiempo.</p></li><li><p>Requieren más recursos en máquinas de CI, y puede que lleve más tiempo ajustar las cosas para no perder tiempo y recursos.</p></li><li><p>Puede que no sea trivial inicializar dependencias con datos de prueba.</p></li><li><p>Las pruebas con dependencias reales son ideales para cordonar el código antes de una refactorización importante, migración o actualización de dependencias.</p></li><li><p>Es más probable que sean pruebas opacas, es decir, que no sean detalladas sobre los componentes internos del sistema en prueba, sino que cuiden sus resultados.</p></li></ul><h2>El punto óptimo: usa ambas pruebas</h2><p>En lugar de probar tu sistema con un solo tipo de prueba, puedes confiar en ambos tipos donde tenga sentido e intentar mejorar tu uso de ambos.</p><ul><li><p>Ejecuta primero pruebas basadas en mocks porque son mucho más rápidas, y solo cuando todas tengan éxito, haz pruebas de dependencia más lentas solo después.</p></li><li><p>Elige mocks para escenarios donde realmente no se necesitan dependencias externas: cuando el mocking llevaría demasiado tiempo, el código debería modificar mucho solo para eso; depender de dependencias externas.</p></li><li><p>No hay nada de malo en probar un fragmento de código usando ambos enfoques, siempre que tenga sentido.</p></li></ul><h2>Ejemplo de SystemUnderTest</h2><p>Para las siguientes secciones vamos a usar un ejemplo que se puede encontrar <a href="https://github.com/pioorg/testing-elasticsearch">aquí</a>. Es una pequeña aplicación demo escrita en Java 21, que emplea Maven como herramienta de compilación, que se basa en el cliente Elasticsearch y emplea la última incorporación de Elasticsearch, usando <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a> (el nuevo lenguaje de consultas procedimentales de Elastic). Si Java no es tu lenguaje de programación, deberías poder entender los conceptos que vamos a discutir a continuación y traducirlos a tu stack. Simplemente usar un ejemplo real de código hace que ciertas cosas sean más fáciles de explicar.</p><p>El <code>BookSearcher</code> nos ayuda a gestionar la búsqueda y el análisis de datos, siendo en nuestro caso los libros (como se demostró en <a href="https://www.elastic.co/search-labs/blog/esql-queries-to-java-objects">una de las entradas anteriores</a>).</p><ul><li><p>Requiere Elasticsearch exactamente en la versión <code>8.15.x</code> como su única dependencia (ver <code>isCompatibleWithBackend()</code>), por ejemplo, porque no estamos seguros de si nuestro código es compatible hacia adelante, y estamos seguros de que no es compatible hacia atrás. Antes de actualizar Elasticsearch en producción a una versión más reciente, primero lo incluiremos en las pruebas para cerciorar que el comportamiento del Sistema Bajo Prueba se mantenga igual.</p></li><li><p>Podemos usarlo para buscar el número de libros publicados en un año determinado (ver <code>numberOfBooksPublishedInYear</code>).</p></li><li><p>También podríamos emplearlo cuando necesitemos analizar nuestro conjunto de datos y encontrar los 20 autores más publicados entre dos años determinados (ver <code>mostPublishedAuthorsInYears</code>).</p></li></ul>public class BookSearcher {

    private final ElasticsearchClient esClient;

    public BookSearcher(ElasticsearchClient esClient) {
        this.esClient = esClient;
        if (!isCompatibleWithBackend()) {
            throw new UnsupportedOperationException("This is not compatible with backend");
        }
    }

    private boolean isCompatibleWithBackend() {
        try (ResultSet rs = esClient.esql().query(ResultSetEsqlAdapter.INSTANCE, """
            show info
            | keep version
            | dissect version "%{major}.%{minor}.%{patch}"
            | keep major, minor
            | limit 1""")) {
            if (!rs.next()) {
                throw new RuntimeException("No version found");
            }
            return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
        } catch (SQLException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public int numberOfBooksPublishedInYear(int year) {
        try (ResultSet rs = esClient.esql().query(ResultSetEsqlAdapter.INSTANCE, """
            from books
            | where year == ?
            | stats published = count(*) by year
            | limit 1000""", year)) {

            if (rs.next()) {
                return rs.getInt("published");
            }
        } catch (SQLException | IOException e) {
            throw new RuntimeException(e);
        }
        return 0;
    }


    public List&lt;MostPublished&gt; mostPublishedAuthorsInYears(int minYear, int maxYear) {
        assert minYear &lt;= maxYear;
        String query = """
            from books
            | where year &gt;= ? and year &lt;= ?
            | stats first_published = min(year), last_published = max(year), times = count (*) by author
            | eval years_published = last_published - first_published
            | sort years_published desc
            | drop years_published
            | limit 20
            """;

        try {
            Iterable&lt;MostPublished&gt; published = esClient.esql().query(
                ObjectsEsqlAdapter.of(MostPublished.class),
                query,
                minYear,
                maxYear);

            List&lt;MostPublished&gt; mostPublishedAuthors = new ArrayList&lt;&gt;();
            for (MostPublished mostPublished : published) {
                mostPublishedAuthors.add(mostPublished);
            }
            return mostPublishedAuthors;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public record MostPublished(
        String author,
        @JsonProperty("first_published") int firstPublished,
        @JsonProperty("last_published") int lastPublished,
        int times
    ) {
        public MostPublished {
            assert author != null;
            assert firstPublished &lt;= lastPublished;
            assert times &gt; 0;
        }
    }
}
<h2>Prueba con simulacros para empezar</h2><p>Para crear los mocks usados en nuestros exámenes vamos a usar <a href="https://site.mockito.org/">Mockito</a>, una biblioteca de mocking muy popular en el ecosistema Java.</p><p>Podríamos empezar con lo siguiente, para que los simulacros se resetear antes de cada examen:</p>public class BookSearcherMockingTest {

    ResultSet mockResultSet;
    ElasticsearchClient esClient;
    ElasticsearchEsqlClient esql;

    @BeforeEach
    void setUpMocks() {
        mockResultSet = mock(ResultSet.class);
        esClient = mock(ElasticsearchClient.class);
        esql = mock(ElasticsearchEsqlClient.class);

    }
}
<p>Como dijimos antes, no todo se puede evaluar fácilmente usando simulacros. Pero hay cosas que sí podemos (y probablemente deberíamos). Intentemos verificar que la única versión soportada de Elasticsearch es <code>8.15.x</code> por ahora (en el futuro podríamos ampliar el rango una vez confirmemos que nuestro sistema es compatible con futuras versiones):</p>@Test
void canCreateSearcherWithES_8_15() throws SQLException, IOException{
    // when
    when(esClient.esql()).thenReturn(esql);
    when(esql.query(eq(ResultSetEsqlAdapter.INSTANCE), anyString())).thenReturn(mockResultSet);
    when(mockResultSet.next()).thenReturn(true).thenReturn(false);
    when(mockResultSet.getInt(1)).thenReturn(8);
    when(mockResultSet.getInt(2)).thenReturn(15);

    // then
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(esClient));
}
<p>Podemos verificar de forma similar (simplemente devolviendo una versión menor diferente) que nuestro <code>BookSearcher</code> aún no va a funcionar con <code>8.16.x</code> , porque no estamos seguros de si será compatible con él:</p>@Test
void cannotCreateSearcherWithoutES_8_15() throws SQLException, IOException {
    // when
    when(esClient.esql()).thenReturn(esql);
    when(esql.query(eq(ResultSetEsqlAdapter.INSTANCE), anyString())).thenReturn(mockResultSet);
    when(mockResultSet.next()).thenReturn(true).thenReturn(false);
    when(mockResultSet.getInt(1)).thenReturn(8);
    when(mockResultSet.getInt(2)).thenReturn(16);

    // then
    Assertions.assertThrows(UnsupportedOperationException.class, () -&gt; new BookSearcher(esClient));
}
<p>Ahora veamos cómo podemos lograr algo similar al probar contra un Elasticsearch real. Para esto vamos a usar <a href="https://java.testcontainers.org/modules/elasticsearch/">el módulo Elasticsearch de Testcontainers</a>, que solo tiene un requisito: necesita acceso a Docker, porque ejecuta contenedores Docker por ti. Desde cierto ángulo, los Testcontainers son simplemente una forma de operar contenedores Docker, pero en lugar de hacerlo en tu Docker Desktop (o similar), en tu LI o scripts, puedes expresar tus necesidades en el lenguaje de programación que conoces. Esto permite obtener imágenes, iniciar contenedores, recogerlas tras pruebas, copiar archivos de un lado a otro, ejecutar comandos, examinar registros, etc., directamente desde tu código de prueba.</p><p>El artículo puede ver así:</p>@Testcontainers
public class BookSearcherIntTest {

    static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
    static final JacksonJsonpMapper JSONP_MAPPER = new JacksonJsonpMapper();

    RestClientTransport transport;
    ElasticsearchClient client;

    @Container
    ElasticsearchContainer elasticsearch = new ElasticsearchContainer(ELASTICSEARCH_IMAGE);

    @BeforeEach
    void setupClient() {
        transport = // setup transport here
        client = new ElasticsearchClient(transport);
    }

    @AfterEach
    void closeClient() throws IOException {
        if (transport != null) {
            transport.close();
        }
    }

}
<p>En este ejemplo dependemos de <a href="https://java.testcontainers.org/test_framework_integration/junit_5/">la integración de JUnit de Testcontainers</a> con <code>@Testcontainers</code> y <code>@Container</code>, lo que significa que no tenemos que preocuparnos por iniciar Elasticsearch antes de nuestras pruebas y pararlo después. Lo único que tenemos que hacer es crear el cliente antes de cada prueba y cerrarlo luego de cada prueba (para evitar fugas de recursos, que podrían afectar a conjuntos de pruebas más grandes).</p><p>Anotar un campo no estático con <code>@Container</code> significa que se iniciará un nuevo contenedor para cada prueba, por lo que no tenemos que preocuparnos por datos obsoletos ni por resetear el estado del contenedor. Sin embargo, con muchas pruebas, este enfoque puede no funcionar bien, así que lo compararemos con alternativas en una de las próximas publicaciones.</p><p><strong>Nota:</strong></p>Al depender de <code>docker.elastic.co</code> (el repositorio oficial de imágenes Docker de Elastic), evitas agotar tus límites en el hub Docker.

También se recomienda usar la misma versión de tu dependencia en tus entornos de pruebas y producción, para garantizar la máxima compatibilidad. También recomendamos ser precisos al seleccionar la versión, por lo que no hay etiqueta <code>latest</code> para las imágenes de Elasticsearch.<h2>Conexión con Elasticsearch en pruebas</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html">El cliente Java de Elasticsearch</a> es capaz de conectarse a Elasticsearch ejecutar en un contenedor de prueba incluso con seguridad y SSL/TLS activados (que son los valores predeterminados de las versiones 8.x, por eso no tuvimos que especificar nada relacionado con la seguridad en la declaración del contenedor). Suponiendo que el Elasticsearch que usas en producción también tenga TLS y algo de seguridad activados, se recomienda optar por la configuración de pruebas de integración lo más parecida posible al escenario de producción, y por tanto no desactivarlos en las pruebas.</p><p>Cómo obtener los datos necesarios para la conexión, suponiendo que el contenedor esté asignado a campo o variable <code>elasticsearch</code>:</p><ul><li><p><code>elasticsearch.getHost()</code> Te dará el host en el que se ejecuta el contenedor (que la mayoría de las veces probablemente será <code>"localhost"</code>, pero por favor no lo codifiques de forma fija porque a veces, dependiendo de tu configuración, puede ser otro nombre, por lo que el host siempre debe obtener dinámicamente).</p></li><li><p><code>elasticsearch.getMappedPort(9200)</code> dará el puerto host que tienes que usar para conectarte a Elasticsearch que está dentro del contenedor (porque cada vez que inicias el contenedor, el puerto exterior cambia, así que también tiene que ser una llamada dinámica).</p></li><li><p>A menos que fueron sobreescribir por defecto, el nombre de usuario y la contraseña por defecto son <code>"elastic"</code> y <code>"changeme"</code> respectivamente.</p></li><li><p>Si no se especificó ningún certificado SSL/TLS durante la configuración del contenedor, y la conectividad segura no está desactivada (que es el comportamiento por defecto de las versiones 8.x), se genera un certificado autofirmado. Confiar en ella (por ejemplo, como <a href="https://curl.se/docs/manpage.html#--cacert">puede hacer cURL</a>) el certificado puede obtener usando <code>elasticsearch.caCertAsBytes()</code> (que devuelve <code>Optional&lt;byte[]&gt;</code>), u otra forma conveniente es obtener <code>SSLContext</code> usando <code>createSslContextFromCa()</code>.</p></li></ul><p>El resultado general podría ser el siguiente:</p>BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level rest client
RestClient restClient = RestClient.builder(new HttpHost(elasticsearch.getHost(), elasticsearch.getMappedPort(9200), "https"))
    .setHttpClientConfigCallback(httpClientBuilder -&gt;
        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
            .setSSLContext(elasticsearch.createSslContextFromCa())
    )
    .build();

// The RestClientTransport is mainly for serialization/deserialization
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());

// The official Java API Client for Elasticsearch
ElasticsearchClient client = new ElasticsearchClient(transport);
<p>Otro ejemplo de creación de una instancia de <code>ElasticsearchClient</code> se puede encontrar en el <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherIntTest.java#L61">proyecto demo</a>.</p><p><strong>Nota</strong>:</p>Para crear clientes en entornos de producción, consulte <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/connecting.html#_verifying_https_with_a_certificate_fingerprint">la documentación</a>.<h2>Primera prueba de integración</h2><p>Nuestra primera prueba, verificando que podemos crear <code>BookSearcher</code> usando Elasticsearch versión 8.15.x, podría ser así:</p>@Test
void canCreateClientWithContainerRunning_8_15() {
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(client));
}
<p>Como puedes ver, no necesitamos montar nada más. No necesitamos simular la versión devuelta por Elasticsearch, lo único que necesitamos es proporcionar <code>BookSearcher</code> le un cliente conectado a una instancia real de Elasticsearch, que fue iniciada para nosotros por Testcontainers.</p><h2>A las pruebas de integración les importan menos los componentes internos</h2><p>Hagamos un pequeño experimento: supongamos que tenemos que dejar de extraer datos del conjunto de resultados usando índices de columna, pero tenemos que confiar en los nombres de las columnas. Así que en el método <code>isCompatibleWithBackend</code> en lugar de</p>return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
<p>Vamos a tener:</p>return rs.getInt("major") == 8 &amp;&amp; rs.getInt("minor") == 15;
<p>Cuando volvamos a ejecutar ambas pruebas, notaremos que la prueba de integración con Elasticsearch real sigue pasando sin problemas. Sin embargo, los tests con mocks dejaron de funcionar, porque simulábamos llamadas como <code>rs.getInt(int)</code>, no <code>rs.getInt(String)</code>. Para que pasen, ahora tenemos que simularlos o simularlos a ambos, dependiendo de otros casos de uso que tengamos en nuestro conjunto de pruebas.</p><h2>Las pruebas de integración pueden ser un cañón para matar a una mosca</h2><p>Las pruebas de integración son capaces de verificar el comportamiento del sistema, incluso si no se necesitan dependencias externas. Sin embargo, usarlos de esta manera suele ser una pérdida de tiempo y recursos de ejecución. Veamos el método <code>mostPublishedAuthorsInYears(int minYear, int maxYear)</code>. Las dos primeras líneas son las siguientes:</p>assert minYear &lt;= maxYear;
String query = // here goes the query
<p>La primera afirmación es comprobar una condición, que no depende de Elasticsearch (ni de ninguna otra dependencia externa) de ninguna manera. Por lo tanto, no necesitamos iniciar ningún contenedor solo para verificar que, si el <code>minYear</code> es mayor que <code>maxYear</code>, se lanza una excepción.</p><p>Un simple examen de simulación, que además es rápido y no requiere muchos recursos, es más que suficiente para cerciorarlo. Luego de preparar los simulacros, simplemente podemos hacer lo siguiente:</p>BookSearcher systemUnderTest = new BookSearcher(esClient);

Assertions.assertThrows(
    AssertionError.class,
    () -&gt; systemUnderTest.mostPublishedAuthorsInYears(2012, 2000)
);
<p>Iniciar una dependencia, en lugar de burlar, sería un desperdicio en <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherMockingTest.java#L89">este caso de prueba</a> porque no hay posibilidad de tomar una decisión significativa para esta dependencia.</p><p>Sin embargo, para verificar el comportamiento que empieza por <code>String query = ...</code>, que la consulta está correctamente escrita, se obtiene los resultados esperados: la biblioteca cliente es capaz de enviar solicitudes y respuestas adecuadas, no hay cambios de sintaxis y por tanto es mucho más fácil usar una prueba de integración, por ejemplo:</p>@BeforeEach
void setupDataInContainer() {
    // here we initialise data in the Elasticsearch running in a container
}

@Test
void shouldGiveMostPublishedAuthorsInGivenYears() {
    var systemUnderTest = new BookSearcher(client);
    var list = systemUnderTest.mostPublishedAuthorsInYears(1800, 2010);
    Assertions.assertEquals("Beatrix Potter", list.get(12).author(), "Beatrix Potter was 13th most published author between 1800 and 2010");
}
<p>De este modo, podemos estar seguros de que cuando alimentemos nuestros datos a Elasticsearch (en esta o cualquier versión futura a la que elijamos migrar), nuestra consulta nos dará exactamente lo que esperábamos: el formato de datos no cambió, la consulta sigue siendo válida y todo el middleware (clientes, controladores, seguridad, etc.) seguirá funcionando. No tenemos que preocuparnos por mantener los mocks actualizados, el único cambio necesario para cerciorar la compatibilidad con, por ejemplo, <code>8.15</code> cambiaría esto:</p>static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
<p>Lo mismo ocurre si decides, por ejemplo, usar el buen y viejo QueryDSL en lugar de ES|QL: los resultados que recibes de la consulta (independientemente del lenguaje) deberían seguir siendo los mismos.</p><h2>Emplea ambos enfoques cuando sea necesario</h2><p>El caso del método <code>mostPublishedAuthorsInYears</code> ilustra que un solo método puede probar usando ambos métodos. Y quizá incluso debería estarlo.</p><ul><li><p>Usar solo mocks significa que tenemos que mantener el mock y no tener ninguna confianza al actualizar nuestro sistema.</p></li><li><p>Usar solo pruebas de integración significaría que estamos desperdiciando bastantes recursos, sin necesidad de ellos en absoluto.</p></li></ul><h2>Vamos a recapitular</h2><ul><li><p>Es posible usar tanto pruebas de simulación como de integración con Elasticsearch.</p></li><li><p>Emplea las pruebas de simulación como una red de detección rápida y solo si pasan con éxito, inicia pruebas con dependencias (por ejemplo, usando <code>./mvnw test '-Dtest=!TestInt*' &amp;&amp; ./mvnw test '-Dtest=TestInt*'</code> o <a href="https://maven.apache.org/surefire/maven-failsafe-plugin/">plugins Failsafe</a> y <a href="https://maven.apache.org/surefire/maven-surefire-plugin/">Surefire</a> ).</p></li><li><p>Usa mocks para probar el comportamiento de tu sistema ("líneas de código") donde la integración con dependencias externas realmente no importa (o incluso podría saltar).</p></li><li><p>Emplea pruebas de integración para verificar tus suposiciones e integración con sistemas externos.</p></li><li><p>No tengas miedo de probar usando ambos enfoques —si tiene sentido— según los puntos anteriores.</p></li></ul><p>Se podría observar que ser tan estricto con la versión (en nuestro caso <code>8.15.x</code>) es demasiado. Usar solo la etiqueta de versión podría serlo, pero ten en cuenta que en esta publicación sirve como representación de todas las demás características que puedan cambiar entre las versiones.</p><p>En la <a href="https://www.elastic.co/search-labs/blog/automated-integration-tests-faster-elasticsearch">próxima entrega del serial</a>, veremos formas de inicializar Elasticsearch ejecutar en un contenedor de prueba, con conjuntos de datos de prueba. Cuéntanos si creaste algo basado en este blog o si tienes preguntas en nuestros <a href="https://discuss.elastic.co/">foros de discusión</a> y <a href="https://communityinviter.com/apps/elasticstack/elastic-community">en el canal comunitario de Slack</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tests-with-mocks-and-real-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tests-with-mocks-and-real-elasticsearch</guid>
    <category><![CDATA[Java]]></category>
    <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e4d003f09dfbe50/6a1709aba929cf810aae0957/b6bb727815ebdb844aeb36d5c44cdf3657f0e4bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 03 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Presentamos LangChain4j para simplificar la integración de LLM en aplicaciones Java]]></title>
    <description><![CDATA[LangChain4j (LangChain para Java) es un conjunto de herramientas poderosa para construir tu aplicación RAG en Java puro.]]></description>
    <content:encoded><![CDATA[<p>El <a href="https://docs.langchain4j.dev/">marco LangChain4j</a> se creó en 2023 con <a href="https://github.com/langchain4j/langchain4j/blob/main/README.md#introduction">este objetivo</a>:</p>El objetivo de LangChain4j es simplificar la integración de LLMs en aplicaciones Java.<p>LangChain4j está proporcionando una forma estándar de:</p><ul><li><p>crear incrustaciones (vectores) a partir de un contenido dado, por ejemplo un texto</p></li><li><p>Almacenar incrustaciones en un almacén de incrustaciones</p></li><li><p>Busca vectores similares en la tienda de incrustación</p></li><li><p>discute con los LLMs</p></li><li><p>usar una memoria de chat para recordar el contexto de una conversación con un LLM</p></li></ul><p>Esta lista no es exhaustiva y la comunidad de LangChain4j siempre está implementando nuevas funcionalidades.</p><p>Esta entrada cubrirá las primeras partes principales del marco.</p><h2>Agregando LangChain4j OpenAI a nuestro proyecto</h2><p>Como en todos los proyectos Java, es solo cuestión de dependencias. Aquí usaremos Maven, pero lo mismo podría lograr con cualquier otro gestor de dependencias.</p><p>Como primer paso para el proyecto que queremos construir aquí, usaremos OpenAI, así que solo necesitamos agregar el <code>langchain4j-open-ai</code> artefacto:</p>&lt;properties&gt;
  &lt;langchain4j.version&gt;0.34.0&lt;/langchain4j.version&gt;
&lt;/properties&gt;

&lt;dependencies&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j-open-ai&lt;/artifactId&gt;
    &lt;version&gt;${langchain4j.version}&lt;/version&gt;
  &lt;/dependency&gt;
&lt;/dependencies&gt;
<p>Para el resto del código usaremos nuestra propia clave API, que puedes obtener registrándote en <a href="https://platform.openai.com/signup/">OpenAI</a>, o la que proporciona el proyecto LangChain4j solo para demostraciones:</p>static String getOpenAiApiKey() {
  String apiKey = System.getenv(API_KEY_ENV_NAME);
  if (apiKey == null || apiKey.isEmpty()) {
    Logger.warn("Please provide your own key instead using [{}] env variable", API_KEY_ENV_NAME);
    return "demo";
  }
  return apiKey;
}
<p>Ahora podemos crear una instancia de nuestro ChatLanguageModel:</p>ChatLanguageModel model = OpenAiChatModel.withApiKey(getOpenAiApiKey());
<p>Y finalmente podemos hacer una pregunta sencilla y obtener la respuesta:</p>String answer = model.generate("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);
<p>La respuesta dada podría ser algo así:</p>Thomas Pesquet is a French aerospace engineer, pilot, and European Space Agency astronaut.
He was selected as a member of the European Astronaut Corps in 2009 and has since completed 
two space missions to the International Space Station, including serving as a flight engineer 
for Expedition 50/51 in 2016-2017. Pesquet is known for his contributions to scientific 
research and outreach activities during his time in space.
<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step1AiChatTest.java">clase Step1AiChatTest.java</a> .</p><h2>Aportando más contexto con langchain4j</h2><p>Vamos a agregar el artefacto <code>langchain4j</code> :</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;
<p>Este está proporcionando un conjunto de herramientas que puede ayudarnos a construir una integración LLM más avanzada para construir nuestro asistente. Aquí simplemente crearemos una interfaz <code>Assistant</code> que proporcione el método <code>chat</code> que llamará automáticamente a la <code>ChatLanguageModel</code> que definimos antes:</p>interface Assistant {
  String chat(String userMessage);
}
<p>Solo tenemos que pedirle a LangChain4j <code>AiServices</code> clase que construya una instancia para nosotros:</p>Assistant assistant = AiServices.create(Assistant.class, model);
<p>Y luego llama al método <code>chat(String)</code> :</p>String answer = assistant.chat("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);
<p>Esto es tener el mismo comportamiento que antes. ¿Entonces por qué cambiamos el código? En primer lugar, es más elegante, pero más que eso, ahora puedes dar algunas instrucciones al LLM usando anotaciones sencillas:</p>interface Assistant {
  @SystemMessage("Please answer in a funny way.")
  String chat(String userMessage);
}
<p>Esto ahora es dar:</p>Ah, Thomas Pesquet is actually a super secret spy disguised as an astronaut! 
He's out there in space fighting aliens and saving the world one spacewalk at a time. 
Or maybe he's just a really cool French astronaut who has been to the International 
Space Station. But my spy theory is much more exciting, don't you think?
<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step2AssistantTest.java">clase Step2AssistantTest.java</a> .</p><h2>Cambiar a otro LLM: langchain4j-ollama</h2><p>Podemos aprovechar el <a href="https://ollama.com/">gran proyecto Ollama</a>. Ayuda tener un LLM localmente en tu máquina.</p><p>Vamos a agregar el artefacto <code>langchain4j-ollama</code> :</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-ollama&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;
<p>Como ejecutamos el código de ejemplo usando pruebas, vamos a agregar <a href="https://java.testcontainers.org/">Testcontainers</a> a nuestro proyecto:</p>&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;ollama&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
<p>Ahora podemos iniciar/detener contenedores Docker:</p>static String MODEL_NAME = "mistral";
static String DOCKER_IMAGE_NAME = "langchain4j/ollama-" + MODEL_NAME + ":latest";

static OllamaContainer ollama = new OllamaContainer(
  DockerImageName.parse(DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("ollama/ollama"));

@BeforeAll
public static void setup() {
  ollama.start();
}

@AfterAll
public static void teardown() {
  ollama.stop();
}
<p>Simplemente tenemos que cambiar el objeto <code>model</code> para que se convierta en un <code>OllamaChatModel</code> en lugar del <code>OpenAiChatModel</code> que usamos antes:</p>OllamaChatModel model = OllamaChatModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();
<p>Ten en cuenta que puede tardar un tiempo en extraer la imagen con su modelo, pero luego de un rato, podrías obtener la respuesta:</p>Oh, Thomas Pesquet, the man who single-handedly keeps the French space program running 
while sipping on his crisp rosé and munching on a baguette! He's our beloved astronaut 
with an irresistible accent that makes us all want to learn French just so we can 
understand him better. When he's not floating in space, he's probably practicing his 
best "je ne sais quoi" face for the next family photo. Vive le Thomas Pesquet! 
🚀🌍🇫🇷 #FrenchSpaceHero
<h2>Mejor con la memoria</h2><p>Si hacemos varias preguntas, por defecto el sistema no recordará las preguntas frecuentes anteriores. Así que si preguntamos luego de la primera pregunta "¿Cuándo nació?", Nuestra solicitud responderá:</p>Oh, you're asking about this legendary figure from history, huh? Well, let me tell 
you a hilarious tale! He was actually born on Leap Year's Day, but only every 400 
years! So, do the math... if we count backwards from 2020 (which is also a leap year), 
then he was born in... *drumroll please* ...1600! Isn't that a hoot? But remember 
folks, this is just a joke, and historical records may vary.
<p>Lo cual es una tontería. En su lugar, deberíamos usar <a href="https://docs.langchain4j.dev/tutorials/chat-memory">Chat Memory</a>:</p>ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
Assistant assistant = AiServices.builder(Assistant.class)
  .chatLanguageModel(model)
  .chatMemory(chatMemory)
  .build();
<p>Hacer las mismas preguntas ahora da una respuesta significativa:</p>Oh, Thomas Pesquet, the man who was probably born before sliced bread but after dinosaurs! 
You know, around the time when people started putting wheels on suitcases and calling it 
a revolution. So, roughly speaking, he came into this world somewhere in the late 70s or 
early 80s, give or take a year or two - just enough time for him to grow up, become an 
astronaut, and make us all laugh with his space-aged antics! Isn't that a hoot? 
*laughs maniacally*
<h2>Conclusión</h2><p>En <a href="https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store">la próxima publicación</a>, descubriremos cómo podemos hacer preguntas a nuestro conjunto de datos privado usando Elasticsearch como almacén de incrustaciones. Eso nos dará una forma de transformar nuestra búsqueda de aplicaciones al siguiente nivel.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0435ed6d14579089/6a17e79ae8fbce7e433a192f/cf129b8b25fbe7204e2adca8fca5fec04207f096-720x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 23 Sep 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>