187 čtení

Revisiting LangChain4J 6 Months Later

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

Příliš dlouho; Číst

The main focus of this post is the integration of an MCP server in a LangChain4J app. While the configuration is straightforward thanks to the documentation, there are a few caveats.
featured image - Revisiting LangChain4J 6 Months Later
Nicolas Fränkel HackerNoon profile picture

Last year, Začal jsem trochu hýčkatkolemZávěsná 4JJedná se o rychle rostoucí projekt a chtěl jsem se seznámit s aktualizacemi.Chtěl jsem také zkontrolovat, jak integrovat server Model Context Protocol v LangChain4J.

Verze 1 beta

Napsal jsem svůj poslední příspěvek v listopadu 2024 a použil jsem nejnovější verzi dostupnou v té době, v0.35.

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. září 2024

0,35 Kč

22. prosince 2024

1.0.0-alfa1 a více

10. února 2025

Číslo 1.0-beta1

13. března 2025

Číslo 1.0-beta2

12. dubna 2025

Číslo 1.0-beta3


LangChain4J přicházíSemerováUdržovatelé využili příležitosti, aby zavedli změny, které přetrvávají.V mém případě jsem musel aktualizovat svůj kód, abych vypočítal změny API, které přetrvávají.

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()
Jednotlivé (
.onBackpressureBuffer<String>()
chatBot.talk (m.sessionId, m.text)
.onNásledující(s::tryEmitNásledující)
.onError(s::třebaEmitError)
neúplné
s.přesměrováníDokonalé()
Na začátek ( )
Připravte se na to, abyste se dostali na místo, kde se nacházíte. (
s.asFlux().asFlow()
)

Val s = Sinks.many()
Jednotlivé (
.onBackpressureBuffer<String>()
chatBot.talk (m.sessionId, m.text)
.onPoznávací odpověď(s::tryEmitNext)
.onError(s::třebaEmitError)
Kompletní odpověď {
s.přesměrováníDokonalé()
Na začátek ( )
Připravte se na to, abyste se dostali na místo, kde se nacházíte. (
s.asFlux().asFlow()
)

Projektová integrace reaktorů

LangChain4J nabízí integraci s projektovým reaktorem; chyběl mi v mých předchozích rozhovorech.a lot.

Já používámAiServices, takže jsem dříve definoval rozhraní pro LangChain4J k implementaci v běhu:


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


Měli bychom přidat následující závislost:


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


Nyní můžeme změnit typ vrácení zFlux<String>Dvě ATokenStreamZde je aktualizovaný podpis:


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


Tím se vytvářísinkZjednodušit můžeme kód následovně:


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


Nezapomeňte, že dva dny debugování vám mohou snadno ušetřit dvě hodiny čtení dokumentace!

Integrace serveru s kontextovým protokolem

V této sekci chci do své aplikace LangChain4J integrovat <abbr title="Model Context Protocol">MCP</abbr>.

Retrieval rozšířené generace

Člověk potřebuje spoustu zdrojů, aby trénoval <abbr title="Large Language Model">LLM</abbr>: přímo se překládá do času a peněz. Z tohoto důvodu společnosti omezují školení nových verzí modelu. Relevance modelu se časem snižuje, protože informace se hromadí a mění, zatímco databáze LLM je neměnná.


Retrieval-Augmented Generation je dvoustupňový proces.V prvním kroku nástroj analyzuje data, vektorizuje je podle LLM a ukládá je do vektorové databáze; ve druhém, nástroj používá databázi jako další data při dotazování LLM.

Kontextový protokol

Nejnovějším způsobem, jak řešit statickou povahu LLM je MCP.


MCP je otevřený protokol, který standardizuje, jak aplikace poskytují kontext LLM. Přemýšlejte o MCP jako o portu USB-C pro aplikace AI. Stejně jako USB-C poskytuje standardizovaný způsob připojení vašich zařízení k různým periferiím a příslušenstvím, MCP poskytuje standardizovaný způsob připojení modelů AI k různým zdrojům dat a nástrojům.


Začněte s modelovým kontextovým protokolem

MCP je otevřený protokol, který standardizuje, jak aplikace poskytují kontext LLM. Přemýšlejte o MCP jako o portu USB-C pro aplikace AI. Stejně jako USB-C poskytuje standardizovaný způsob připojení vašich zařízení k různým periferiím a příslušenstvím, MCP poskytuje standardizovaný způsob připojení modelů AI k různým zdrojům dat a nástrojům.


Začněte s modelovým kontextovým protokolem


MCP má dvě výhody oproti 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 definujeDvě alternativy dopravypro komunikaci klient-server:


  • stdio: Klient spouští podproces a komunikace probíhá nad standardem a standardem
  • HTTP se serverovými událostmi

Architektura řešení

Po výše uvedené teorii jsme nyní připraveni na praktickou část. Začíná výběrem serveru MCP.zdeJe to dobrý začátek, ale já jsem si vybralOficiální server GitHub MCPProtože v dokumentaci LangChain4J se o tom zmiňuje.


GitHub MCP server nabízíStadiónTo znamená, že bychom měli získat binární a spustit ji z aplikace. Je to rychlé ve srovnání s HTTP transportem, ale vzhledem k celkovému času, který zahrnuje HTTP volání do modelu a výpočetní čas na jeho straně, je to irelevantní.


Po několika výzkumech jsem zjistil, žePříslušenství pro proxyProjekt. Umožňuje přepínat mezi buď ze stdio na HTTP nebo z HTTP na stdio. Je také k dispozici jako obrázek Docker. Můžeme kombinovat server i proxy s následujícími funkcemi: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. Stáhněte si archiv
  2. extrahovat to
  3. Odstranit archiv
  4. Udělejte binární exekutivní


Všimněte si, že nelze definovatCMDjako binární umožňuje pouze konfigurovat port a hostitel s parametry. z tohoto důvodu musíme odložit příkaz při běhu, nebo v mém případě vdocker-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. Potřebujeme změnu prostředí GITHUB_PERSONAL_ACCESS_TOKEN s platným tokenem pro ověření na GitHub
  2. Přeneste všechny environmentální proměnné do podprocesu
  3. Nastavení portu pro poslech
  4. Připojte se k jakémukoliv IP
  5. Proxy "připojí" se k stdio MCP serveru po
  6. Spusťte server se všemi možnostmi zapnutými


Obrázek poskytne/ssePřipojení na port 8080.

Kódování řešení

Kódování je nejjednodušší část. Hlavu dolů naDokumentace LangChain4J na MCPa pokračujte dále.V projektu se překládá následovně:


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. Přidal jsem třídu ConfigurationProperty pro parametrizování URL SSE
  2. Protokol MCP poskytuje způsob, jak posílat protokoly zpět do klienta
  3. Není to nutné, ale pomohlo mi to zajistit, aby se klient připojil k serveru a mohl seznam nástrojů poskytnutých
  4. Plug v poskytovateli nástrojů MCP vytvořeném výše v AiServices


V tomto okamžiku by měl model předat žádost, která odpovídá některému z registrovaných nástrojů serveru MCP.


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


Zkoušela jsem to několikrát a dostala jsem odpovědi na tyto otázky:


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.


Model prostě ignoroval nástroje navzdory dokumentaci, která tvrdí opak.

Fixování řešení

Párkrát jsem četl dokumentaci LangChain4J, ale k ničemu. Snažil jsem se používat OpenAI a hrstku dalších nástrojů AI bez úspěchu. Většina odpovědí potvrdila, že by mělo fungovat mimo krabici. Někteří zmínili volání nástroje přímo, což porazí účel; jeden zmínil, že Ollama nepodporuje nástroje. Zkontroloval jsem blog Ollama: oznámil podporu nástrojů v roce 2024.


Rozpojená architektura zavádí více pohyblivých kusů. Podezřel jsem se, že v celém řetězci hovorů může být něco špatně. Odstranil jsem proxy MCP, přidalgithub-mcp-serverpřímo na obraz aplikace a změnil kód z HTTP na stdio.


Byl jsem blízko k tomu, abych se vzdal, když jsem se rozhodl vrátit se ke kořenům.Vzorek z dokumentaceByl to můj ha-ha moment.


Vzorek používá OpenAI, zatímco já používám Ollama. Zkoušel jsem MCP s OpenAI, Mistral AI a Ollama. Pouze model OpenAI funguje s MCP. Poslal jsem stejnou žádost jako výše:


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


Nyní OpenAI správně mapuje požadavek na správný nástroj a vrací odpověď, kterou jsem očekával:


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.%    


Protože předáváme token autentizace serveru MCP, který jej předává API GitHub, ten ví, který uživatel volá.my reposPřiznávám, že se jedná o neobvyklý případ použití pro běžné webové aplikace, které slouží více uživatelům, ale používají jeden identifikační token každý.


Další časté otázky,Jde o g., najít nejoblíbenější úložiště na GitHub, jsou relevantní pro webové aplikace, protože nemají implicitní kontext – uživatele.

Závěr

Hlavním zaměřením tohoto příspěvku je integrace serveru MCP do aplikace LangChain4J. Zatímco konfigurace je díky dokumentaci jednoduchá, existuje několik varování.


Za prvé, jak se server MCP vejde do vaší architektury, je stále na vás. Musel jsem být kreativní, abych ho odpojil, pomocí vynikajícímcp-proxyPak se zdá, že LangChain4J je úniková abstrakce. dělá vše možné, aby vám poskytla silnou abstrakční vrstvu, ale implementace pod ní vás chrání před nejsou stejné.


Dozvěděl jsem se o MCP v reálném světě, a to otevřelo docela několik dveří pro projektové nápady.


Kompletní zdrojový kód tohoto článku naleznete naGitHub.


To go further:


  • Začněte s modelovým kontextovým protokolem
  • Najít skvělé MCP servery a klienty
  • LangChain4J - Modelový kontextový protokol (MCP)



Původně publikováno v A Java Geek dne 27. dubna 2025

Jak na Java Geek
L O A D I N G
. . . comments & more!

About Author

Nicolas Fränkel HackerNoon profile picture
Nicolas Fränkel@nfrankel
Dev Advocate | Developer & architect | Love learning and passing on what I learned!

ZAVĚŠIT ZNAČKY

TENTO ČLÁNEK BYL PŘEDSTAVEN V...

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks