187 lecturas

Revisiting LangChain4J 6 meses después

por Nicolas Fränkel11m2025/05/01
Read on Terminal Reader

Demasiado Largo; Para Leer

El foco principal de este post es la integración de un servidor MCP en una aplicación LangChain4J. Si bien la configuración es sencilla gracias a la documentación, hay algunas advertencias.
featured image - Revisiting LangChain4J 6 meses después
Nicolas Fränkel HackerNoon profile picture

El año pasado,Empezamos a digerir un pocoalrededorConexión 4JEs un proyecto de rápido crecimiento, y quería familiarizarme con las actualizaciones.También quería comprobar cómo integrar un servidor de Protocolo de Contexto Modelo en LangChain4J.

Versión Beta 1

He escrito mi último post en noviembre de 2024 y usé la última versión disponible en ese momento, v0.35. LangChain4J comenzó su viaje hacia 1.0 el pasado diciembre.

Date

Release

September 25th, 2024

0.35.0

December 22th, 2024

1.0.0-alpha1

February 10th, 2025

1.0.0-beta1

March 13th, 2025

1.0.0-beta2

April 12th, 2025

1.0.0-beta3

25 de septiembre de 2024

0.35 0 0

22 de diciembre de 2024

1.0.0-Alfa1 y más

10 de febrero de 2025

1.0.0-beta 1

13 de marzo de 2025

1.0.0-Beta2 de la actualidad

12 de abril de 2025

1.0.0-Beta3 de la actualidad


Siguiente LangChain4JSemejanteLos mantenedores aprovecharon la ocasión para introducir cambios de breakout. En mi caso, tuve que actualizar mi código para compensar los cambios de API de breakout.

v0.35

v1.0.0-beta3

val s = Sinks.many()
.unicast()
.onBackpressureBuffer<String>()
chatBot.talk(m.sessionId, m.text)
.onNext(s::tryEmitNext)
.onError(s::tryEmitError)
.onComplete {
s.tryEmitComplete()
}.start()
return ServerResponse.ok().bodyAndAwait(
s.asFlux().asFlow()
)

val s = Sinks.many()
.unicast()
.onBackpressureBuffer<String>()
chatBot.talk(m.sessionId, m.text)
.onPartialResponse(s::tryEmitNext)
.onError(s::tryEmitError)
.onCompleteResponse {
s.tryEmitComplete()
}.start()
return ServerResponse.ok().bodyAndAwait(
s.asFlux().asFlow()
)

val s = sinks.many()
.unicast()
.onBackpressureBuffer<String>()
chatBot.talk(m.sessionId, m.text)
.onSiguiente(s::tryEmitSiguiente)
.onError(s::trueEmitError)
.oncompletado
Página de inicio(
Inicio →
En caso de que se produzca una pérdida de peso, se procederá a la devolución del impuesto (
s.asFlux().asFlow()
)

val s = sinks.many()
.unicast()
.onBackpressureBuffer<String>()
chatBot.talk(m.sessionId, m.text)
.onPartialRespuesta(s::tryEmitSiguiente)
.onError(s::trueEmitError)
.onCompletaRespuesta {
Página de inicio(
Inicio →
En caso de que se produzca una pérdida de peso, se procederá a la devolución del impuesto (
s.asFlux().asFlow()
)

Proyecto de integración de reactores

LangChain4J ofrece una integración de Proyecto Reactor; lo perdí en mis primeras ediciones.a lot.

Estoy utilizandoAiServices, así que previamente he definido una interfaz para LangChain4J para implementar en el tiempo de ejecución:


interface ChatBot {
    fun talk(@MemoryId sessionId: String, @UserMessage message: String): TokenStream
}


Hay que añadir la siguiente dependencia:


<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-reactor</artifactId>
    <version>1.0.0-beta3</version>
</dependency>


Ahora podemos cambiar el tipo de devolución de unFlux<String>Dos aTokenStreamAquí está la firma actualizada:


interface ChatBot {
    fun talk(@MemoryId sessionId: String, @UserMessage message: String): Flux<String>
}


Es la creación de lasinkA continuación, podemos simplificar el código de la siguiente manera:


val flux = chatBot.talk(m.sessionId, m.text)
ServerResponse.ok().bodyAndAwait(flux.asFlow())


Recuerde que dos días de debugging pueden fácilmente ahorrarle dos horas de leer la documentación!

Integración de un modelo de protocolo de servidor

En esta sección, quiero integrar un <abbr title="Model Context Protocol">MCP</abbr> en mi aplicación LangChain4J.

Generación Retrieval aumentada

Uno necesita muchos y muchos recursos para entrenar un <abbr title="Large Language Model">LLM</abbr>: se traduce directamente en tiempo y dinero. Por esta razón, las empresas limitan la formación de nuevas versiones del modelo. La relevancia de un modelo disminuye con el tiempo a medida que la información se acumula y cambia, mientras que la base de datos del LLM es inmutable. Además, los LLM son entrenados en datos públicos-por naturaleza, mientras que la mayoría de las empresas quieren consultar sus datos privados también.


Retrieval-Augmented Generation es un proceso de dos pasos. En el primer paso, la herramienta analiza los datos, los vectoriza de acuerdo con el LLM, y los almacena en una base de datos vectorial; en el segundo, la herramienta utiliza la base de datos como datos adicionales cuando consulta el LLM.

Modelo de Protocolo Contextual

La forma más reciente de manejar la naturaleza estática de los LLM es MCP.


MCP es un protocolo abierto que estandariza cómo las aplicaciones proporcionan contexto a los LLM. Piense en MCP como un puerto USB-C para aplicaciones de IA. Al igual que USB-C proporciona una forma estandarizada de conectar sus dispositivos a diversos periféricos y accesorios, MCP proporciona una forma estandarizada de conectar modelos de IA a diferentes fuentes de datos y herramientas.


Comience con el Protocolo de Contexto Modelo

MCP es un protocolo abierto que estandariza cómo las aplicaciones proporcionan contexto a los LLM. Piense en MCP como un puerto USB-C para aplicaciones de IA. Al igual que USB-C proporciona una forma estandarizada de conectar sus dispositivos a diversos periféricos y accesorios, MCP proporciona una forma estandarizada de conectar modelos de IA a diferentes fuentes de datos y herramientas.


Comience con el Protocolo de Contexto Modelo


MCP tiene dos ventajas sobre RAG:


  • Data processed by a RAG is tailored for a model. If one wants to use a new model, one must re-execute the parsing phase. MCP standardizes the interactions between a client and a server, making them technology-independent.


  • RAG allows the reading of data. MCP allows any API call to either access data dynamically or execute actions!


MCP DefiniciónDos alternativas de transportePara las comunicaciones cliente-servidor:


  • stdio: El cliente lanza un subproceso, y la comunicación ocurre sobre estándar en y estándar fuera
  • HTTP con eventos de envío de servidor

Arquitectura de la solución

Después de la teoría anterior, ahora estamos listos para la parte práctica. comienza con la elección de un servidor MCP.AquíEs un buen punto de partida, pero eligióServicio GitHub MCPporque la documentación LangChain4J lo menciona.


El servidor MCP de GitHub ofrece laEstadioEs rápido en comparación con el transporte HTTP, pero dado el tiempo total que comprende la llamada HTTP al modelo y el tiempo de cálculo en su lado, es irrelevante. Desde un punto de vista arquitectónico, preferiría un componente dedicado con su proceso.


Después de varias investigaciones, encontramos elProyecto ProxyProyecto. Permite cambiar de estadio a HTTP o de HTTP a estadio. También está disponible como una imagen Docker. Podemos combinar tanto el servidor como el proxy con lo siguiente:Dockerfile:


FROM ghcr.io/sparfenyuk/mcp-proxy:latest

ENV VERSION=0.2.0
ENV ARCHIVE_NAME=github-mcp-server_Linux_x86_64.tar.gz

RUN wget https://github.com/github/github-mcp-server/releases/download/v$VERSION/$ARCHIVE_NAME -O /tmp/$ARCHIVE_NAME \ #1
    && tar -xzvf /tmp/$ARCHIVE_NAME -C /opt \                      #2
    && rm /tmp/$ARCHIVE_NAME                                       #3

RUN chmod +x /opt/github-mcp-server                                #4
  1. Descargar el archivo
  2. Extraerlo
  3. Eliminar el archivo
  4. Hacer que el binario sea ejecutable


Tenga en cuenta que no podemos definir elCMDcomo el binario sólo permite configurar el puerto y el host con parámetros. Por esta razón, debemos aplazar el comando en el tiempo de ejecución, o en mi caso, en eldocker-compose.yaml:


services:
  mcp-server:
    build:
      context: github-mcp-server
    env_file:
      - .env                                                       #1
    command:
      - --pass-environment                                         #2
      - --sse-port=8080                                            #3
      - --sse-host=0.0.0.0                                         #4
      - --                                                         #5
      - /opt/github-mcp-server                                     #6
      - --toolsets
      - all
      - stdio
  1. Necesitamos una variable de entorno GITHUB_PERSONAL_ACCESS_TOKEN con un token válido para autenticarse en GitHub
  2. Pasar todas las variables ambientales al subproceso
  3. Configurar el puerto de escucha
  4. Conexión a cualquier IP
  5. El proxy "se conecta" al servidor MCP del estadio después del dash
  6. Executa el servidor con todas las opciones habilitadas


La imagen proporcionará el/sseen el puerto 8080.

Codificar la solución

La parte de codificación es la más fácil.Documentación LangChain4J sobre MCPy sigue adelante. En el proyecto, se traduce de la siguiente manera:


bean {
    val transport = HttpMcpTransport.Builder()
        .sseUrl(ref<ApplicationProperties>().mcp.url)              //1
        .logRequests(true)                                         //2
        .logResponses(true)                                        //2
        .build()
    val mcpClient = DefaultMcpClient.Builder()
        .transport(transport)
        .build()
    mcpClient.listTools().forEach { println(it) }                  //3
    McpToolProvider.builder()
        .mcpClients(listOf(mcpClient))
        .build()
}
bean {
    coRouter {
        val chatBot = AiServices
            .builder(ChatBot::class.java)
            .streamingChatLanguageModel(ref<StreamingChatLanguageModel>())
            .chatMemoryProvider { MessageWindowChatMemory.withMaxMessages(40) }
            .contentRetriever(EmbeddingStoreContentRetriever.from(ref<EmbeddingStore<TextSegment>>()))
            .toolProvider(ref<McpToolProvider>())                  //4
            .build()
        POST("/")(PromptHandler(chatBot)::handle)
    }
}
  1. He añadido una clase de ConfigurationProperty para parametrizar la URL de SSE
  2. El protocolo MCP proporciona una manera de enviar los registros de vuelta al cliente
  3. No es necesario, pero me ayudó a asegurarme de que el cliente se conectara al servidor y pudiera listar las herramientas proporcionadas
  4. Plug en el proveedor de herramientas de MCP creado anteriormente en el AiServices


En este punto, el modelo debe reenviar una solicitud que coincida con cualquiera de las herramientas registradas al servidor MCP.


curl -N -H 'Content-Type: application/json' localhost:8080 -d '{ "sessionId": "1", "text": "What are my top three most popular GitHub repos?" }'


He intentado varias veces, y he recibido respuestas en estas líneas:


Unfortunately, the provided text does not contain any information about your top three most popular GitHub repositories. The text appears to be a blog post or a personal website, and it mentions some of your projects and experiences with GitHub, but it does not provide any metrics or statistics on the popularity of your repositories.

If you want to know more about the popularity of your GitHub repositories, I would recommend checking out GitHub's own analytics tools, such as GitHub Insights or the Repository Insights API. These tools can provide information about the number of followers, stars, and forks for each repository, as well as other metrics like engagement and activity.


El modelo simplemente ignoró las herramientas a pesar de la documentación que afirma lo contrario.

Fixar la solución

He leído la documentación de LangChain4J un par de veces, pero sin provecho. He intentado usar OpenAI y un puñado de otras herramientas de IA sin éxito. La mayoría de las respuestas confirmaron que debería funcionar fuera de la caja. Algunos mencionaron llamar la herramienta directamente, lo que derrota el propósito; uno mencionó que Ollama no soporta las herramientas.


La arquitectura desconectada introduce más piezas en movimiento. sospeché que algo podría estar mal en toda la cadena de llamadas.github-mcp-serverdirectamente a la imagen de la aplicación, y cambió el código de HTTP a stdio.


Estaba a punto de abandonar cuando decidí volver a las raíces.Ejemplo de la documentación¡Sólo funcionó!Fue mi ha-ha momento.


La muestra utiliza OpenAI, mientras que yo estaba usando Ollama. He probado MCP con OpenAI, Mistral AI y Ollama. Sólo el modelo OpenAI funciona con MCP. He enviado la misma solicitud como anteriormente:


curl -N -H 'Content-Type: application/json' localhost:8080 -d '{ "sessionId": "1", "text": "What are my top three most popular GitHub repos?" }'


Ahora, OpenAI mapea correctamente la solicitud a la herramienta correcta y devuelve la respuesta que esperaba:


Here are my findings regarding your top three most popular GitHub repositories:

1. **[opentelemetry-tracing](https://github.com/nfrankel/opentelemetry-tracing)**  
   - **Description**: Demo for end-to-end tracing via OpenTelemetry.  
   - **Stars**: 68  
   - **Forks**: 25  
   - **Open Issues**: 10  

2. **[kaadin](https://github.com/nfrankel/kaadin)**  
   - **Description**: Kotlin DSL for Vaadin.  
   - **Stars**: 44  
   - **Forks**: 12  
   - **Open Issues**: 3  

3. **[jvm-controller](https://github.com/nfrankel/jvm-controller)**  
   - **Description**: Example on how to write a Kubernetes controller in Java.  
   - **Stars**: 33  
   - **Forks**: 10  
   - **Open Issues**: 0  

These repositories demonstrate a range of your interests and contributions in the areas of observability, Kotlin development, and Kubernetes.%    


Debido a que pasamos un token de autenticación al servidor MCP, que lo pasa a la API de GitHub, este último sabe qué usuario hace la llamada.my reposparte de la consulta anterior. admito que es un caso de uso inusual para aplicaciones web regulares que atienden a múltiples usuarios, pero usan un único token de autenticación cada uno. sin embargo, se adapta perfectamente al caso de uso de una aplicación de escritorio.


Otras preguntas frecuentes,El E.G., encontrar los repositorios más populares en GitHub, son relevantes para las aplicaciones web, ya que no tienen contexto implícito-el usuario.

Conclusión

El foco principal de este post es la integración de un servidor MCP en una aplicación LangChain4J. Si bien la configuración es sencilla gracias a la documentación, hay algunas advertencias.


En primer lugar, la forma en que el servidor MCP se ajusta a su arquitectura aún depende de usted.Tuve que ser creativo para hacerla desconectada, utilizando la excelentemcp-proxyEntonces, LangChain4J parece ser una abstracción de fugas. hace todo lo posible para proporcionarle una capa de abstracción fuerte, pero las implementaciones debajo de ella no son iguales.


Aprendí sobre MCP en el mundo real, y abrió unas pocas puertas para ideas de proyecto.


El código fuente completo de este artículo se puede encontrar enGitHub.


To go further:


  • Comience con el Protocolo de Contexto Modelo
  • Encontrar excelentes servidores y clientes de MCP
  • LangChain4J - Protocolo de Contexto Modelo (MCP)



Publicado originalmente en A Java Geek el 27 de abril de 2025

El nuevo Java Geek

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks