poniedziałek, 1 września 2025

Agent AI w Javie: Spring AI + Docker Compose (krok po kroku)

Czy wiesz że w mniej niż godzinę zbudujesz i uruchomisz agenta AI? Agenta opartego o Spring AI, z wystawionym endpointem HTTP, gotowym do konteneryzacji i docker compose up. Pokażę Ci jak podłączyć narzędzia (Tools) w dwóch wariantach: własne metody oznaczone @Tool oraz serwer MCP (np. Brave/DuckDuckGo) w Compose.



Co zbudujemy

Wymagania

  • Java 21, Maven
  • Docker + Docker Compose
  • (Opcjonalnie) Klucz API do dostawcy modelu (np. OpenAI) – trzymamy w sekrecie

1) Inicjalizacja projektu

Najprościej zacząć od Spring Initializr – wybierz Spring Boot 3.4+, dodaj „Web” oraz startery Spring AI. Ręcznie w pom.xml możesz dodać:

<dependencies>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <version>1.0.1</version>
  </dependency>

  <!-- MCP klient, żeby podpiąć zewnętrzne serwery narzędzi (np. Brave/DuckDuckGo) -->
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
    <version>1.0.1</version>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

Konfiguracja minimalna w src/main/resources/application.yml:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY:} # NIE trzymaj klucza w repo; wstrzykniemy go z Dockera
      chat:
        options:
          # Możesz wskazać model, np.:
          # model: gpt-4o-mini
          # temperature: 0.2

# (Opcjonalnie) MCP – rejestrujemy serwer zewnętrznych narzędzi
  # ai:
  #   mcp:
  #     clients:
  #       search:
  #         transport: http-sse
  #         endpoint: http://mcp-brave:8080/  # nazwa usługi z Compose
  #         auto-initialize: true

2) Minimalny agent: ChatClient + @Tool

Spring AI pozwala wystawić metody jako tools, po które może sięgnąć model w trakcie rozmowy (Function/Tool Calling). Na start zróbmy prosty tool zwracający „plan dnia” na podstawie promptu.

package dev.softwareveteran.agent;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

record ChatRequest(String message) {}
record ChatResponse(String content) {}

@Component
class PlanningTools {

  @Tool(name = "plan_day", description = "Tworzy krótki plan dnia na podstawie preferencji użytkownika.")
  public String planDay(String preferences) {
    // Tu mógłbyś podpiąć swoje API/kalendarz. Na razie demo.
    return """
      - 08:00 poranna kawa ☕
      - 09:00 deep work nad najważniejszym zadaniem
      - 12:30 szybki lunch
      - 14:00 spotkania
      - 17:30 sport/relaks
    """;
  }
}

@RestController
@RequestMapping("/chat")
class ChatController {

  private final ChatClient chat;

  ChatController(ChatClient.Builder builder, PlanningTools tools) {
    this.chat = builder
      .defaultSystem("Jesteś pomocnym asystentem architekta oprogramowania. " +
                     "Jeśli użytkownik pyta o plan dnia, skorzystaj z narzędzia 'plan_day'.")
      .tools(tools)   // <- .build="" chat="" chatrequest="" chatresponse="" code="" content="" equestbody="" new="" ool="" ostmapping="" public="" rejestrujemy="" req="" return="" var="">

Test lokalny:

./mvnw spring-boot:run
curl -X POST http://localhost:8080/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"Zaproponuj plan dnia dla pracy koncepcyjnej i sportu wieczorem"}'

3) (Opcjonalnie) Narzędzia przez MCP (np. web search)

Gdy potrzebujesz „prawdziwych” akcji – wyszukiwania w sieci, plików, pogody – nie musisz wszystkiego programować sam. MCP (Model Context Protocol) udostępnia gotowe serwery narzędzi, które Twój agent może wywoływać jak zwykłe tools. Wystarczy dodać starter klienta MCP i wskazać endpoint MCP w konfiguracji (sekcja powyżej), a następnie dorzucić poradnikowo prosty advisor w budowie klienta (Spring AI zrobi większość za Ciebie). W Compose dołożymy kontener z serwerem MCP, np. Brave Search lub DuckDuckGo.

4) Dockerfile – produkcyjny build Javy

# --- build stage ---
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -q -DskipTests dependency:go-offline
COPY src ./src
RUN mvn -q -DskipTests package

# --- runtime stage ---
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
# Zadbaj o sensowne limity pamięci w runtime:
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75"
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]

5) Docker Compose – jeden plik, cały stos

Przykładowy compose.yaml uruchamia aplikację oraz (opcjonalnie) serwer MCP Brave Search. Klucz API do modelu przekazujemy jako secret – bezpiecznie i poza obrazem.

name: spring-ai-agent

services:
  app:
    build: .
    image: swv/spring-ai-agent:latest
    ports:
      - "8080:8080"
    environment:
      # Spring AI pobierze klucz z env – nie wklejaj go do application.yml
      OPENAI_API_KEY_FILE: /run/secrets/openai_api_key
      # Jeśli korzystasz z MCP (sekcja application.yml):
      # SPRING_AI_MCP_CLIENTS_SEARCH_ENDPOINT: http://mcp-brave:8080/
    secrets:
      - openai_api_key
    depends_on:
      - mcp-brave
    # Opcjonalnie healthcheck
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
      interval: 15s
      timeout: 3s
      retries: 10

  # (opcjonalnie) Serwer MCP Brave Search – wymaga BRAVE_API_KEY
  mcp-brave:
    image: shoofio/brave-search-mcp-sse:latest
    environment:
      BRAVE_API_KEY_FILE: /run/secrets/brave_api_key
      PORT: "8080"
    ports:
      - "18080:8080"
    secrets:
      - brave_api_key

secrets:
  openai_api_key:
    file: ./secret.openai-api-key
  brave_api_key:
    file: ./secret.brave-api-key

Uruchomienie:

# 1) Zapisz sekrety do plików (po jednym w linii)
echo "sk-...twoj-openai-key..." > secret.openai-api-key
echo "brv-...twoj-brave-key..." > secret.brave-api-key

# 2) Odpal całość
docker compose up --build

# 3) Test
curl -X POST http://localhost:8080/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"Znajdź najnowsze wieści o Spring AI i zrób 3-punktowe streszczenie"}'

Uwaga: jeśli nie chcesz MCP – usuń usługę mcp-brave i odpowiadające jej zmienne. Zamiast OpenAI możesz podłączyć lokalny model przez Docker Model Runner/Ollama – wtedy w konfiguracji Spring AI wybierz odpowiedniego providera i endpoint.

Co dalej?

  • Dodaj pamięć rozmowy i RAG (Vector Store) – Spring AI ma gotowe adaptery.
  • Dołóż drugi/zewnętrzny tool (np. pogodę/finanse) i pozwól modelowi decydować, którego użyć.
  • Wydziel agenta do osobnego mikroserwisu; dziel się nim przez HTTP/GRPC.

Przydatne linki

poniedziałek, 25 sierpnia 2025

AI Chatbot w Javie - RAG krok po kroku

Na bazie świetnego artykułu Baeldung o budowie chatbota w Javie z LangChain4j i MongoDB przygotowałem podobny, praktyczny przewodnik – ze wskazówkami, kodem i alternatywami. Oryginał znajdziesz tutaj: Building an AI Chatbot in Java With Langchain4j and MongoDB Atlas .



Dlaczego ten stack?

  • LangChain4j – wygodna warstwa do pracy z LLM w Javie (prompty, łańcuchy, pamięci, narzędzia).
  • MongoDB Atlas + Vector Search – trwałe przechowywanie embeddingów i szybkie zapytania semantyczne; idealne do RAG.

Efekt końcowy: prosty chatbot w stylu Q&A nad Twoimi dokumentami – z indeksem wektorowym w Atlasie, generacją embeddingów i łączeniem wyników z modelem językowym (RAG).

Wymagania wstępne

  • Java 17+
  • Konto w MongoDB Atlas i klaster (free tier wystarczy)
  • Klucz do dostawcy LLM/embeddingów (np. OpenAI) lub lokalny Ollama

Struktura projektu (Maven + Spring Boot)

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>5.1.0</version>
  </dependency>

  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>0.35.0</version>
  </dependency>

  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-openai</artifactId>
    <version>0.35.0</version>
  </dependency>

  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-mongodb</artifactId>
    <version>0.35.0</version>
  </dependency>
</dependencies>

Konfiguracja środowiska

server:
  port: 8080

app:
  mongo:
    uri: ${MONGODB_URI}
    db: ragdb
    collection: chunks
  openai:
    apiKey: ${OPENAI_API_KEY}
  rag:
    indexName: rag_vector_index
    dimensions: 1536

Definicja indeksu wektorowego

{
  "fields": [
    { "type": "vector", "path": "embedding", "numDimensions": 1536, "similarity": "cosine" },
    { "type": "string", "path": "source" },
    { "type": "string", "path": "text" }
  ]
}

Chunkowanie i embeddingi

Przykładowy kod Java generujący embeddingi i zapisujący do MongoDB:

float[] vec = embeddings.embed(chunk);
col.insertOne(new Document()
  .append("source", sourceName)
  .append("text", chunk)
  .append("embedding", toList(vec))
);

Retriever i RAG

Zapytanie semantyczne do Atlas Vector Search:

List<Bson> pipeline = List.of(
  Aggregates.vectorSearch("embedding", toList(qVec), indexName, 5, 200),
  Aggregates.project(Projections.include("text", "source"))
);

Łańcuch RAG z kontekstem i modelem czatu:

var messages = List.of(
  SystemMessage.from("Jesteś asystentem RAG..."),
  UserMessage.from("Kontekst:\n" + contextBlock + "\n\nPytanie: " + userQuestion)
);
return chat.generate(messages).content().text();

REST API

@PostMapping("/ingest")
public void ingest(@RequestBody IngestRequest req) {
  ing.ingest(req.source(), req.text());
}

@PostMapping
public ChatResponse chat(@RequestBody ChatRequest req) {
  return new ChatResponse(rag.ask(req.question()));
}

Alternatywy

Typowe pułapki

  1. Niedopasowany numDimensions w indeksie vs. model embeddingów.
  2. Brak overlapu przy chunkowaniu – tracony kontekst.
  3. Za duży kontekst – tnij do top-k i stosuj re-ranking.
  4. Sekrety w repo – używaj zmiennych środowiskowych.

Podsumowanie

W kilkudziesięciu linijkach kodu stworzyliśmy RAG chatbota – z MongoDB Atlas, LangChain4j i prostym REST API. Teraz możesz dodać streaming, cache embeddingów, czy personalizację dostępu.

Źródło, na którym bazowałem: Baeldung – Building an AI Chatbot in Java With Langchain4j and MongoDB Atlas

Dalsza lektura

poniedziałek, 18 sierpnia 2025

Nowe modele OpenAI gpt-oss – otwarte wagi i praktyczne wdrożenia AI

OpenAI wprowadza gpt-oss — serię modeli z otwartymi wagami, które możesz pobrać, uruchamiać lokalnie i dostosować do własnych potrzeb. To kolejny krok w kierunku większej kontroli nad sztuczną inteligencją, bez kompromisów w jakości rozumowania i elastyczności wdrożeń. Sprawdź, co oferują wersje 20b i 120b oraz jak szybko zacząć z nimi pracę.




1. O co chodzi z gpt‑oss?

gpt‑oss to rodzina modeli OpenAI z otwartymi wagami (open‑weight), które można pobierać, uruchamiać lokalnie i dostrajać pod własne przypadki użycia. Seria obejmuje dwa warianty: gpt‑oss‑120b (wyższa wydajność) oraz gpt‑oss‑20b (lżejszy, „edge/on‑device”).

  • Wydajność: 120b osiąga wyniki bliskie o4‑mini na kluczowych benchmarkach; 20b jest porównywalny do o3‑mini.
  • Wymagania sprzętowe: 120b działa na pojedynczej karcie 80 GB; 20b może działać przy ~16 GB pamięci.
  • Licencja: Apache 2.0 + zasady użycia gpt‑oss.
  • Przeznaczenie: rozumowanie, zadania agentyczne, elastyczne scenariusze developerskie.

2. Co dostajesz „w pudełku”

3. Jak to uruchomić w praktyce

Poniżej minimalny szkic — dopasuj do swojego środowiska:

# vLLM (przykład)
python -m vllm.entrypoints.openai.api_server \
  --model openai/gpt-oss-20b \
  --max-model-len 8192

# Zapytanie w stylu OpenAI Responses API (HTTP)
POST /v1/responses
{
  "model": "openai/gpt-oss-20b",
  "input": "Napisz krótkie streszczenie wpisu na blog."
}

Jeśli używasz Ollama, sprawdź gotowe pliki Modelfile i instrukcje z Cookbook/Guides. Do strumieniowania i pracy z narzędziami wykorzystaj format Harmony, który odwzorowuje zachowanie Responses API.

4. Zastosowania i wzorce

  • Na urządzeniu / edge: 20b do asystentów offline, automatyzacji IDE, podsumowań i ekstrakcji danych.
  • Serwerowo: 120b do pipeline’ów analitycznych, agentów wykonujących wieloetapowe zadania i klasycznych RAG.
  • Dostrajanie: klasyczne fine‑tuning + taktyki optymalizacji (LoRA/QLoRA, 4‑bit), gotowe „recipes”.

5. Bezpieczeństwo i zgodność

Modele gpt‑oss przeszły szkolenia i ewaluacje bezpieczeństwa (w tym testy zgodne z Preparedness Framework). Zadbano o redukcję ryzyk i zgodność z dobrymi praktykami — to ważne przy wdrożeniach komercyjnych.

6. Szybki start — checklista

  1. Pobierz wagi 20b lub 120b i sprawdź wymagania sprzętowe.
  2. Wybierz runtime (Ollama, vLLM, Transformers) i uruchom endpoint kompatybilny z OpenAI API.
  3. Włącz Harmony dla stabilnego formatowania odpowiedzi i integracji narzędzi.
  4. Skonfiguruj logowanie, limity tokenów, caching i monitorowanie.
  5. Jeśli potrzebujesz — wykonaj fine‑tuning na własnych danych zgodnie z „recipes”.

7. Podsumowanie

gpt‑oss to pragmatyczne otwarcie: realne wagi, solidne wyniki i komplet materiałów do wdrożeń. Dla developerów oznacza to większą kontrolę kosztów, szybszą iterację i możliwość działania lokalnie — bez rezygnacji z jakości rozumowania.