W zeszłym roku,Zaczęłam się odrobinęWokółŁożysko4JJest to szybko rozwijający się projekt i chciałem zapoznać się z aktualizacjami. Chciałem również sprawdzić, jak zintegrować serwer Protokołu Modelowego w LangChain4J.
Wersja 1 beta
Napisałem mój ostatni post w listopadzie 2024 roku i użyłem najnowszej wersji dostępnej w tym czasie, 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 września 2024
0,35 zł
22 grudnia 2024
1.0.0-Alfa1 zbiórka
10 lutego 2025
Wersja 1.0-beta1
13 marca 2025
Wersja 1.0-beta2
12 kwietnia 2025
Wersja 1.0-Beta3
LangChain4J na bieżącoSemerówW moim przypadku musiałem zaktualizować mój kod, aby uwzględnić zmiany API.
v0.35 |
v1.0.0-beta3 |
---|---|
val s = Sinks.many() |
val s = Sinks.many() |
Val s = Sinks.many()
. jednorazowe()
.onBackpressureBuffer<String>()
chatBot.talk(m.sessionId, m.tekst)
.onNastępne(s::tryEmitNext)
.onError(s::tryEmitError) - błąd
. niepełna {
s.wykorzystywanieWykorzystywanie(
Na początek (
Wpisy z tagiem (wpisy z tagiem)
s.asFlux().asFlow()
)
Val s = Sinks.many()
. jednorazowe()
.onBackpressureBuffer<String>()
chatBot.talk(m.sessionId, m.tekst)
.onRzeczpospolitaOdpowiedź(s::tryEmitNext)
.onError(s::tryEmitError) - błąd
.onkompletneodpowiedź {
s.wykorzystywanieWykorzystywanie(
Na początek (
Wpisy z tagiem (wpisy z tagiem)
s.asFlux().asFlow()
)
Projekt integracji reaktorów
LangChain4J oferuje integrację z Project Reactor; przegapiłem ją w moich poprzednich rozmowach.a lot.
Ja używamAiServices
, więc wcześniej zdefiniowałem interfejs dla LangChain4J do wdrożenia w czasie biegu:
interface ChatBot {
fun talk(@MemoryId sessionId: String, @UserMessage message: String): TokenStream
}
Należy dodać następującą zależność:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.0.0-beta3</version>
</dependency>
Możemy teraz zmienić typ zwrotu zFlux<String>
Dwa aTokenStream
Oto zaktualizowany podpis:
interface ChatBot {
fun talk(@MemoryId sessionId: String, @UserMessage message: String): Flux<String>
}
Powoduje to powstaniesink
Powyżej niepotrzebne. Możemy uprościć kod w następujący sposób:
val flux = chatBot.talk(m.sessionId, m.text)
ServerResponse.ok().bodyAndAwait(flux.asFlow())
Pamiętaj, że dwa dni debugowania mogą łatwo zaoszczędzić dwie godziny czytania dokumentacji!
Integracja z serwerem protokołu kontekstowego
W tej sekcji chcę zintegrować <abbr title="Model Context Protocol">MCP</abbr> w mojej aplikacji LangChain4J.
Generacja Retrieval-Augmented
Potrzeba dużo i wiele zasobów, aby szkolić <abbr title="Large Language Model">LLM</abbr>: bezpośrednio przekłada się na czas i pieniądze. Z tego powodu firmy ograniczają szkolenie nowych wersji modelu. Znaczenie modelu zmniejsza się z biegiem czasu, gdy informacje gromadzą się i zmieniają, podczas gdy baza danych LLM jest niezmienna.
Retrieval-Augmented Generation to dwustopniowy proces. W pierwszym kroku narzędzie analizuje dane, wektoryzuje je zgodnie z LLM i przechowuje je w wektorowej bazie danych; w drugim narzędzie wykorzystuje bazę danych jako dodatkowe dane podczas kwerendowania LLM.
Model protokołu kontekstowego
Najnowszym sposobem radzenia sobie ze statycznym charakterem LLM jest MCP.
MCP jest otwartym protokołem, który standaryzuje sposób, w jaki aplikacje zapewniają kontekst LLM. Pomyśl o MCP jak o porcie USB-C dla aplikacji AI. Tak jak USB-C zapewnia standaryzowany sposób podłączenia urządzeń do różnych urządzeń peryferyjnych i akcesoriów, MCP zapewnia standaryzowany sposób podłączenia modeli AI do różnych źródeł danych i narzędzi.
Zacznij od protokołu Model Context Protocol
MCP jest otwartym protokołem, który standaryzuje sposób, w jaki aplikacje zapewniają kontekst LLM. Pomyśl o MCP jak o porcie USB-C dla aplikacji AI. Tak jak USB-C zapewnia standaryzowany sposób podłączenia urządzeń do różnych urządzeń peryferyjnych i akcesoriów, MCP zapewnia standaryzowany sposób podłączenia modeli AI do różnych źródeł danych i narzędzi.
– –Zacznij od protokołu modelu kontekstu
MCP ma dwie zalety w stosunku do 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 definiujeDwie alternatywy transportuW przypadku komunikacji klient-serwer:
- stdio: klient uruchamia podproces, a komunikacja odbywa się w standardzie i standardowo
- HTTP z wydarzeniami wysyłanymi przez serwer
Architektura rozwiązania
Po powyższej teorii jesteśmy teraz gotowi do praktycznej części. Zaczyna się od wyboru serwera MCP.tutajJest to świetny punkt wyjścia, jednak zdecydowałem się naOficjalny serwer GitHub MCPPonieważ w dokumentacji LangChain4J wspomniano o tym.
Serwer GitHub MCP oferujeStudioOznacza to, że powinniśmy uzyskać binarny i rozpocząć go z aplikacji. Jest szybki w porównaniu z transportem HTTP, ale biorąc pod uwagę ogólny czas składający się na połączenie HTTP do modelu i czas obliczeniowy po jego stronie, jest to nieistotne.
Po kilku badaniach odkryłem, żePliki mcp-proxyProjekt. Umożliwia przełączanie się między stdio na HTTP lub z HTTP na stdio. Jest również dostępny jako obraz Docker. Możemy połączyć zarówno serwer, jak i proxy z następującymi funkcjami: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
- Pobierz Archiwum
- Wyodrębnij go
- Usuń archiwum
- Uczyń binarny wykonalnym
Należy zauważyć, że nie można zdefiniowaćCMD
ponieważ binarne pozwala tylko na skonfigurowanie portu i hosta z parametrami. z tego powodu musimy opóźnić polecenie w czasie biegu, lub w moim przypadku, wdocker-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
- Potrzebujemy zmiennej środowiska GITHUB_PERSONAL_ACCESS_TOKEN z ważnym tokenem do uwierzytelniania na GitHub
- Przesyłanie wszystkich zmiennych środowiska do podprocesu
- Ustaw port słuchania
- Podłączanie do dowolnego IP
- Proxy "powiązuje się" z serwerem stdio MCP po
- Uruchom serwer ze wszystkimi opcjami
Obraz ten zapewni/sse
Wejście do portu 8080.
Kodowanie rozwiązania
Część kodowania jest najłatwiejsza. Przejdź doDokumentacja LangChain4J na temat MCPW programie, to tłumaczy się w następujący sposób:
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)
}
}
- Dodałem klasę ConfigurationProperty do parametryzacji adresu URL SSE
- Protokół MCP umożliwia przesyłanie logów z powrotem do klienta
- Nie jest to konieczne, ale pomogło mi to upewnić się, że klient jest podłączony do serwera i mógł wymienić dostarczone narzędzia
- Włączenie dostawcy narzędzi MCP utworzonego powyżej w AiServices
W tym momencie model powinien przesłać żądanie, które pasuje do któregokolwiek z zarejestrowanych narzędzi do serwera MCP.
curl -N -H 'Content-Type: application/json' localhost:8080 -d '{ "sessionId": "1", "text": "What are my top three most popular GitHub repos?" }'
Próbowałem wiele razy i dostałem odpowiedzi w tych liniach:
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 po prostu zignorował narzędzia, mimo że dokumentacja twierdzi, że jest odwrotnie.
Naprawienie rozwiązania
Przeczytałem dokumentację LangChain4J kilka razy, ale bezskutecznie. Próbowałem użyć OpenAI i kilku innych narzędzi AI bez powodzenia. Większość odpowiedzi potwierdziła, że powinno to działać z pudełka. Niektórzy wspominają o bezpośrednim nawoływaniu narzędzia, co pokonało cel; jeden wspomniał, że Ollama nie obsługuje narzędzi. Sprawdziłem blog Ollama: ogłosił on wsparcie narzędzi w 2024.
Odłączona architektura wprowadza więcej ruchomych elementów. podejrzewałem, że coś może być nie tak w całym łańcuchu połączeń. usunąłem proxy MCP, dodałemgithub-mcp-server
bezpośrednio do obrazu aplikacji i zmienił kod z HTTP na stdio.
Byłem bliski rezygnacji, gdy postanowiłem wrócić do korzeni.Wzór z dokumentacjiTo był mój ha-ha moment.
Wzór wykorzystuje OpenAI, podczas gdy ja korzystałem z Ollama. Próbowałem MCP z OpenAI, Mistral AI i Ollama. Tylko model OpenAI działa z MCP.
curl -N -H 'Content-Type: application/json' localhost:8080 -d '{ "sessionId": "1", "text": "What are my top three most popular GitHub repos?" }'
Teraz OpenAI poprawnie mapuje żądanie do właściwego narzędzia i zwraca odpowiedź, której się spodziewałem:
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.%
Ponieważ przekazujemy token uwierzytelniania do serwera MCP, który przekazuje go do API GitHub, ten ostatni wie, który użytkownik wykonuje połączenie.my reposPrzyznaję, że jest to nietypowy przypadek użycia dla zwykłych aplikacji internetowych, które obsługują wielu użytkowników, ale używają jednego tokena uwierzytelniania.
inne regularne pytania,wg ., znajdź najpopularniejsze repozytorium na GitHub, są istotne dla aplikacji internetowych, ponieważ nie mają implikowanego kontekstu – użytkownika.
konkluzji
Głównym celem tego postu jest integracja serwera MCP w aplikacji LangChain4J. Podczas gdy konfiguracja jest prosta dzięki dokumentacji, istnieje kilka ostrzeżeń.
Po pierwsze, to, jak serwer MCP pasuje do Twojej architektury, zależy od Ciebie.mcp-proxy
Wtedy LangChain4J wydaje się być wyciekłą abstrakcją. sprawia, że wszystko jest możliwe, aby zapewnić ci silną warstwę abstrakcji, ale implementacje pod nią chronią cię przed nie są równe.
Dowiedziałem się o MCP w świecie rzeczywistym, a to otworzyło całkiem sporo drzwi dla pomysłów na projekty.
Pełny kod źródłowy tego artykułu można znaleźć naGitHub.
To go further:
- Zacznij od protokołu modelu kontekstu
- Znajdź wspaniałe serwery i klienci MCP
- LangChain4J - Protokół kontekstowy modelu (MCP)
Pierwotnie opublikowano w A Java Geek 27 kwietnia, 2025
Zespół Java Geek