Tworzenie aplikacji opartych na agentach AI w języku Java za pomocą LangChain4j i generatywnej AI od Google

1. 1. Zanim zaczniesz

Podsumowanie ćwiczeń z programowania w formie notatki wizualnej

Witamy! Z tego ćwiczenia dowiesz się, jak tworzyć aplikacje oparte na agentach AI w języku Java za pomocą popularnej platformy LangChain4j i jej nowego modułu Google GenAI.

Sztuczna inteligencja agentowa to systemy, w których duże modele językowe nie tylko odpowiadają na prompty, ale są wyposażone w narzędzia, pamięć i możliwości planowania, dzięki czemu mogą samodzielnie realizować złożone, wieloetapowe cele.

Zaczniesz od prostych konfiguracji, przejdziesz do tworzenia agentów za pomocą lokalnych narzędzi i strukturalnych danych wyjściowych, a na koniec poznasz zaawansowane wzorce orkiestracji wielu agentów, które doprowadzą Cię do systemu agentów Goal-Oriented Action Planning (GOAP).

Jakie zadania wykonasz

  • Skonfiguruj nowe urządzenie GoogleGenAiChatModel, aby połączyć je z Gemini.
  • Włącz logowanie żądań i odpowiedzi, aby ułatwić debugowanie.
  • Przyznaj Gemini dostęp do lokalnego kodu Java za pomocą Narzędzi.
  • Wymuszanie formatu Structured Output (POJO) z Gemini.
  • Definiuj i twórz agenty o jednym przeznaczeniu za pomocą @Agent adnotacji.
  • Aranżuj sekwencyjnerównoległe przepływy pracy z wieloma agentami.
  • Stwórz system planowania działań zorientowanego na cel (GOAP), który dynamicznie planuje działania agenta.

Czego potrzebujesz

  • Java Development Kit (JDK) w wersji 17 lub nowszej.
  • Zainstalowany Maven w wersji 3.5 lub nowszej.
  • Klucz interfejsu Gemini API z Google AI Studio.

2. 2. Konfiguracja: projekt i klucz interfejsu API

Na początek musimy utworzyć nowy projekt Maven i skonfigurować klucz interfejsu Gemini API.

Tworzenie projektu Maven

Utwórz nowy katalog projektu i zainicjuj go plikiem pom.xml.

Dodaj do pliku pom.xml te zależności: Pamiętaj, że używamy najnowszej wersji 1.16.1-beta26 modułów LangChain4j Google generatywna AI i agentowych:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>gemini-agents-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <langchain4j.version>1.16.1-beta26</langchain4j.version>
    </properties>

    <dependencies>
        <!-- LangChain4j Google GenAI Integration -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-google-genai</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>

        <!-- LangChain4j Agentic Framework Core -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-agentic</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>

        <!-- LangChain4j Agentic Patterns (needed for GOAP) -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-agentic-patterns</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>

        <!-- SLF4J Logger for logging requests/responses -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.12</version>
        </dependency>
    </dependencies>
</project>

Uzyskiwanie klucza interfejsu Gemini API

  1. Otwórz Google AI Studio.
  2. Kliknij Get API Key (Pobierz klucz interfejsu API).
  3. Utwórz nowy klucz (lub wybierz istniejący).
  4. Ustaw ją jako zmienną środowiskową w terminalu:
export GEMINI_API_KEY="your-api-key-here"

3. 3. Konfigurowanie modelu Gemini Chat

Teraz utwórzmy prostą aplikację „Hello World”, która tworzy instancję GoogleGenAiChatModel i wysyła testowy prompt.

Nowa usługa GoogleGenAiChatModel korzysta z ujednoliconego pakietu Google GenAI SDK, który zapewnia ujednolicony dostęp do modeli Gemini.

Utwórz zajęcia o nazwie HelloWorld.java w src/main/java/com/example/HelloWorld.java:

package com.example;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.google.genai.GoogleGenAiChatModel;

public class HelloWorld {
    public static void main(String[] args) {
        // 1. Configure the Gemini model
        ChatModel model = GoogleGenAiChatModel.builder()
                .apiKey(System.getenv("GEMINI_API_KEY"))
                .modelName("gemini-3.5-flash")
                // Enable logging for both requests and responses
                .logRequestsAndResponses(true)
                .build();

        // 2. Invoke the model
        String response = model.chat("Hello Gemini! Explain 'Agentic AI' in one sentence.");

        // 3. Print response
        System.out.println("\n--- Gemini Response ---");
        System.out.println(response);
    }
}

Uruchom tę klasę. Ponieważ włączono logRequestsAndResponses(true), zobaczysz szczegółowe dzienniki SLF4J zawierające dokładny ładunek żądania wysłanego do interfejsu API Google i otrzymaną surową odpowiedź JSON, a następnie wydruk:

--- Gemini Response ---
**Agentic AI** refers to autonomous artificial intelligence systems that can proactively plan, make decisions, use tools, and execute multi-step tasks to achieve specific goals with minimal human supervision.

4. 4. Definiowanie lokalnych narzędzi Java

Modele LLM są zaawansowane, ale ich możliwości są ograniczone przez dane treningowe. Możemy rozszerzyć ich możliwości, udostępniając im narzędzia (znane też jako wywoływanie funkcji).

W LangChain4j narzędzia to proste klasy Java, w których metody są oznaczone adnotacją @Tool.

Napiszmy proste narzędzie, które oblicza liczbę dni między dwiema datami. Utwórz instancję DateTools.java w klastrze src/main/java/com/example/DateTools.java:

package com.example;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class DateTools {

    @Tool("Calculates the number of days between two dates")
    public long daysBetween(
            @P("The start date in ISO format (YYYY-MM-DD)") String startDate,
            @P("The end date in ISO format (YYYY-MM-DD)") String endDate) {
        LocalDate start = LocalDate.parse(startDate);
        LocalDate end = LocalDate.parse(endDate);
        return ChronoUnit.DAYS.between(start, end);
    }
}

Aby użyć tego narzędzia, zdefiniujemy usługę AI. W platformie LangChain4j Agentic możemy utworzyć go za pomocą AgenticServices.agentBuilder().

Utwórz instancję ToolDemo.java w klastrze src/main/java/com/example/ToolDemo.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.google.genai.GoogleGenAiChatModel;
import dev.langchain4j.service.V;

public class ToolDemo {

    public interface DateAssistant {
        @Agent
        String ask(@V("prompt") String prompt);
    }

    public static void main(String[] args) {
        ChatModel model = GoogleGenAiChatModel.builder()
                .apiKey(System.getenv("GEMINI_API_KEY"))
                .modelName("gemini-3.5-flash")
                .build();

        // Build the assistant and register our DateTools
        DateAssistant assistant = AiServices.builder(DateAssistant.class)
                .chatModel(model)
                .tools(new DateTools())
                .build();

        String response = assistant.ask("How many days are there between 2026-01-01 and 2026-06-30?");
        System.out.println("Response: " + response);
    }
}

Gdy uruchomisz ten kod, Gemini przeanalizuje prompta, stwierdzi, że musi wywołać funkcję daysBetween, wygeneruje żądanie wywołania narzędzia, LangChain4j wykona Twoją lokalną metodę Java, wyśle wynik z powrotem do Gemini, a Gemini sformatuje ostateczną odpowiedź w języku naturalnym.

Dane wyjściowe powinny być podobne do tych:

Response: There are 180 days between 2026-01-01 and 2026-06-30.

5. 5. Definiowanie uporządkowanych danych wyjściowych

Często chcesz, aby LLM zwracał dane w określonym formacie strukturalnym (np. JSON zgodny ze schematem), a nie w postaci zwykłego tekstu.

LangChain4j automatycznie obsługuje to, gdy określisz POJO lub interfejs Java record jako typ zwracany metody agenta lub usługi.

Zdefiniujmy klasę Java record reprezentującą wyodrębnianie książki:

Utwórz instancję Book.java w klastrze src/main/java/com/example/Book.java:

package com.example;

public record Book(String title, String author, int publicationYear) {}

Teraz utwórz interfejs agenta, który zwraca Book. Utwórz instancję StructuredOutputDemo.java w klastrze src/main/java/com/example/StructuredOutputDemo.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.google.genai.GoogleGenAiChatModel;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public class StructuredOutputDemo {

    public interface BookExtractor {
        @UserMessage("Extract book details from: {{text}}")
        Book extract(@V("text") String text);
    }

    public static void main(String[] args) {
        ChatModel model = GoogleGenAiChatModel.builder()
                .apiKey(System.getenv("GEMINI_API_KEY"))
                .modelName("gemini-3.5-flash")
                .build();

        BookExtractor extractor = AiServices.builder(BookExtractor.class)
                .chatModel(model)
                .build();

        String text = "I just finished reading 'Project Hail Mary' by Andy Weir, published in 2021. It was amazing!";
        Book book = extractor.extract(text);

        System.out.println("Extracted POJO: " + book);
    }
}

Gemini 3.5 Flash obsługuje ścisłe dane wyjściowe schematu JSON. LangChain4j generuje schemat JSON z Twojego rekordu Book, instruuje Gemini, aby go wypełnił, a następnie deserializuje odpowiedź JSON z powrotem do rekordu Book.

Dane wyjściowe powinny być podobne do tych:

Extracted POJO: Book[title=Project Hail Mary, author=Andy Weir, publicationYear=2021]

6. 6. Tworzenie pierwszego agenta

langchain4j-agentic platformie, Agent jest definiowany za pomocą adnotacji @Agent w metodzie interfejsu. Ta adnotacja zawiera nazwę lub opis, które mogą być używane przez systemy orkiestracji (np. plany).

Utwórzmy prostego agenta, który na podstawie tematu tworzy bardzo krótkie opowiadanie.

Utwórz instancję CreativeWriter.java w klastrze src/main/java/com/example/CreativeWriter.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface CreativeWriter {

    @SystemMessage("You are a creative writer. Generate a draft of a story no more than 3 sentences long.")
    @UserMessage("Write a story about {{topic}}.")
    @Agent(description = "Generates a story based on the given topic", outputKey = "story")
    String writeStory(@V("topic") String topic);
}

Przetestujmy tego agenta osobno. Utwórz instancję AgentDemo.java w klastrze src/main/java/com/example/AgentDemo.java:

package com.example;

import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.google.genai.GoogleGenAiChatModel;

public class AgentDemo {
    public static void main(String[] args) {
        ChatModel model = GoogleGenAiChatModel.builder()
                .apiKey(System.getenv("GEMINI_API_KEY"))
                .modelName("gemini-3.5-flash")
                .build();

        CreativeWriter writer = AgenticServices.agentBuilder(CreativeWriter.class)
                .chatModel(model)
                .build();

        String story = writer.writeStory("a lonely robot on Mars");
        System.out.println("Generated Story:\n" + story);
    }
}

Dane wyjściowe powinny być podobne do tych:

Generated Story:
For eighty years, the rusty little rover trundled across the crimson dunes, diligently collecting soil samples for a home world that had long since gone dark. Each evening, it pointed its high-gain antenna toward the fading blue speck of Earth and beamed its lonely coordinates into the silent cosmos. Receiving only static in return, the robot tucked its camera arm close against the freezing Martian wind and sang a quiet lullaby to the empty stars.

Dzięki dodaniu adnotacji @Agent ta klasa jest teraz przygotowana do udziału w bardziej złożonych przepływach pracy, takich jak sekwencyjne łączenie.

7. 7. Administrowanie przepływami pracy z wieloma agentami: sekwencyjnymi

Typowym wzorcem agentów jest sekwencyjny przepływ pracy, w którym agenci wykonują zadania w zdefiniowanej kolejności. Dane wyjściowe jednego agenta są przekazywane jako dane wejściowe do następnego.

Zdefiniujmy drugiego agenta, StyleEditor, który będzie edytował artykuł napisany przez agenta CreativeWriter, aby dopasować go do określonego stylu (np. komediowego lub dramatycznego).

Utwórz instancję StyleEditor.java w klastrze src/main/java/com/example/StyleEditor.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface StyleEditor {

    @SystemMessage("You are a professional editor. Analyze and rewrite the story to align with the style requested.")
    @UserMessage("Rewrite this story: '{{story}}' into a {{style}} style.")
    @Agent(description = "Edits a story to fit a specific style", outputKey = "edited_story")
    String editStory(@V("story") String story, @V("style") String style);
}

Teraz połączmy kolejno urządzenia CreativeWriterStyleEditor za pomocą AgenticServices.sequenceBuilder().

Użyjemy symbolu UntypedAgent reprezentującego sekwencję, co pozwoli nam przekazywać dane wejściowe i zbierać dane wyjściowe za pomocą wspólnej mapy.

Utwórz instancję SequentialWorkflowDemo.java w klastrze src/main/java/com/example/SequentialWorkflowDemo.java:

package com.example;

import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.UntypedAgent;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.google.genai.GoogleGenAiChatModel;
import java.util.Map;

public class SequentialWorkflowDemo {
    public static void main(String[] args) {
        ChatModel model = GoogleGenAiChatModel.builder()
                .apiKey(System.getenv("GEMINI_API_KEY"))
                .modelName("gemini-3.5-flash")
                .build();

        // 1. Build the sub-agents
        CreativeWriter writer = AgenticServices.agentBuilder(CreativeWriter.class)
                .chatModel(model)
                .outputKey("story") // Output goes into AgenticScope as "story"
                .build();

        StyleEditor editor = AgenticServices.agentBuilder(StyleEditor.class)
                .chatModel(model)
                .outputKey("edited_story") // Output goes into AgenticScope as "edited_story"
                .build();

        // 2. Build the sequence
        UntypedAgent workflow = AgenticServices.sequenceBuilder()
                .subAgents(writer, editor)
                .outputKey("edited_story") // Final output of workflow
                .build();

        // 3. Run the workflow with inputs
        Map<String, Object> inputs = Map.of(
                "topic", "a cat learning to fly",
                "style", "Shakespearean"
        );

        String result = (String) workflow.invoke(inputs);
        System.out.println("Final Edited Story:\n" + result);
    }
}

Dane wyjściowe powinny być podobne do tych:

Final Edited Story:
For years had Barnaby with envy watched
The soaring sparrows with a green-eyed spite,
Convinced that heavy gravity was but
An idle law he deigned to tolerate.
But lo! Upon the rare and azure moon,
He scaled the summit of the ancient oak,
Closed fast his eyes, and leapt with blind belief.
While the dread squirrel shrieked in sheer dismay,
No downward plunge befell the daring beast;
For summer's zephyr bore his belly up,
And through the vault of night's celestial sphere,
He rowed his velvet paws among the stars.

Przedstawiamy AgenticScope

Podczas wykonywania LangChain4j zarządza AgenticScope.

  1. Mapa wejściowa {"topic": "...", "style": "..."} jest zapisywana w zakresie.
  2. CreativeWriter wykonuje działanie. Wymaga topic, które odczytuje z zakresu. Zwraca story, które jest zapisywane z powrotem w zakresie.
  3. StyleEditor wykonuje działanie. Wymaga story (utworzonego przez pisarza) i style (z danych wejściowych). Zwraca edited_story do zakresu.
  4. Proces się zakończy i zwróci wartość edited_story.

8. 8. Administrowanie przepływami pracy z wieloma agentami: równoległe

W niektórych przypadkach subagenci mogą pracować niezależnie nad tym samym wejściem, a ich zadania mogą być wykonywane równolegle.

Utwórzmy na przykład doradcę podróżniczego, który poprosi eksperta od filmów i eksperta od restauracji o rekomendacje dotyczące danego miasta, a następnie je połączy.

Utwórz instancję MovieExpert.java w klastrze src/main/java/com/example/MovieExpert.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import java.util.List;

public interface MovieExpert {
    @UserMessage("Suggest 2 movies filmed in {{city}}.")
    @Agent(description = "Suggests movies filmed in a city")
    List<String> findMovies(@V("city") String city);
}

Utwórz instancję DiningExpert.java w klastrze src/main/java/com/example/DiningExpert.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import java.util.List;

public interface DiningExpert {
    @UserMessage("Suggest 2 local dishes to try in {{city}}.")
    @Agent(description = "Suggests local dishes to try in a city")
    List<String> findDishes(@V("city") String city);
}

Teraz zbudujmy równoległy system oparty na agentach. Zdefiniujemy interfejs koordynatora najwyższego poziomu: TravelAdvisorAgent.

Utwórz instancję TravelAdvisorAgent.java w klastrze src/main/java/com/example/TravelAdvisorAgent.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.V;
import java.util.Map;

public interface TravelAdvisorAgent {
    @Agent
    Map<String, Object> planTrip(@V("city") String city);
}

Aby to połączyć, utwórz ParallelWorkflowDemo.java w src/main/java/com/example/ParallelWorkflowDemo.java:

package com.example;

import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.google.genai.GoogleGenAiChatModel;
import java.util.List;
import java.util.Map;

public class ParallelWorkflowDemo {
    public static void main(String[] args) {
        ChatModel model = GoogleGenAiChatModel.builder()
                .apiKey(System.getenv("GEMINI_API_KEY"))
                .modelName("gemini-3.5-flash")
                .build();

        // 1. Build independent agents
        MovieExpert movieExpert = AgenticServices.agentBuilder(MovieExpert.class)
                .chatModel(model)
                .outputKey("movies")
                .build();

        DiningExpert diningExpert = AgenticServices.agentBuilder(DiningExpert.class)
                .chatModel(model)
                .outputKey("dishes")
                .build();

        // 2. Build the Parallel agent
        TravelAdvisorAgent travelAdvisor = AgenticServices.parallelBuilder(TravelAdvisorAgent.class)
                .subAgents(movieExpert, diningExpert)
                .output(scope -> {
                    // Combine the independent outputs in the AgenticScope
                    List<String> movies = scope.readState("movies", List.of());
                    List<String> dishes = scope.readState("dishes", List.of());
                    return Map.of("movies", movies, "dishes", dishes);
                })
                .build();

        // 3. Invoke
        Map<String, Object> plan = travelAdvisor.planTrip("Tokyo");
        System.out.println("Tokyo Trip Plan:\n" + plan);
    }
}

Dane wyjściowe powinny być podobne do tych:

Tokyo Trip Plan:
{movies=[Lost in Translation, Tokyo Story], dishes=[Monjayaki, Edomae-zushi]}
In this parallel workflow, `movieExpert` and `diningExpert` execute concurrently. The `output(...)` configuration defines how to gather their respective results from the `AgenticScope` and merge them into the final result.

## 9. Build Goal-Oriented Agents (GOAP)
Duration: 07:00

Sequential and Parallel workflows are structured and predictable, but rigid. What if we want the system to figure out the path to the goal autonomously, but in a **deterministic, algorithmic way** rather than trusting an LLM to loop (like ReAct)?

This is where **Goal-Oriented Action Planning (GOAP)** shines. 

By looking at the declared inputs and outputs of each agent, the `GoalOrientedPlanner` builds a dependency graph. When you invoke the system with a goal, it calculates the shortest path from the current state (available variables) to that goal.

Let's build a Horoscope & News combined writer. It requires 4 agents:
1. `PersonExtractor`: extracts person details from prompt.
2. `SignExtractor`: extracts zodiac sign from prompt.
3. `HoroscopeGenerator`: generates horoscope given person and sign.
4. `AmusingWriter`: writes a funny story combining a horoscope and a news story.

We will also define a `StoryFinder` that uses a mock search tool.

### 1. Define Model Classes
Create `Person.java` in `src/main/java/com/example/Person.java`:
```java
package com.example;

public record Person(String name) {
    @Override public String toString() { return name; }
}

Utwórz instancję Sign.java w klastrze src/main/java/com/example/Sign.java:

package com.example;

public record Sign(String signName) {
    @Override public String toString() { return signName; }
}

2. Określanie sub-agentów

Utwórz instancję HoroscopeGenerator.java w klastrze src/main/java/com/example/HoroscopeGenerator.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface HoroscopeGenerator {
    @UserMessage("Generate a funny horoscope for {{person}} who is a {{sign}}.")
    @Agent(description = "Generates horoscopes based on name and zodiac sign")
    String horoscope(@V("person") Person person, @V("sign") Sign sign);
}

Utwórz instancję PersonExtractor.java w klastrze src/main/java/com/example/PersonExtractor.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface PersonExtractor {
    @UserMessage("Extract the person name from: {{prompt}}")
    @Agent(description = "Extracts a person from user's prompt")
    Person extractPerson(@V("prompt") String prompt);
}

Utwórz instancję SignExtractor.java w klastrze src/main/java/com/example/SignExtractor.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface SignExtractor {
    @UserMessage("Extract the zodiac sign from: {{prompt}}")
    @Agent(description = "Extracts a zodiac sign from user's prompt")
    Sign extractSign(@V("prompt") String prompt);
}

Utwórz instancję AmusingWriter.java w klastrze src/main/java/com/example/AmusingWriter.java:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface AmusingWriter {
    @UserMessage("Create an amusing writeup for {{person}} based on their horoscope: {{horoscope}} and current news story: {{story}}.")
    @Agent(description = "Create an amusing writeup combining horoscope and news")
    String write(@V("person") Person person, @V("horoscope") String horoscope, @V("story") String story);
}

3. Określanie narzędzia do wyszukiwania historii

Utworzymy prostą klasę narzędzia do wyszukiwania, która zwraca przykładową historię. Utwórz instancję MockSearchTool.java w klastrze src/main/java/com/example/MockSearchTool.java:

package com.example;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;

public class MockSearchTool {
    @Tool("Searches the web for news stories related to a zodiac sign")
    public String searchNews(@P("The zodiac sign") String sign) {
        return "Breaking news: A massive cheese festival was announced for " + sign + " natives today!";
    }
}

Utwórz agenta StoryFinder.javasrc/main/java/com/example/StoryFinder.java, który korzysta z tego narzędzia:

package com.example;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

public interface StoryFinder {
    @UserMessage("Find a funny story for zodiac sign {{sign}} using search tool.")
    @Agent(description = "Finds a news story on the internet about a zodiac sign")
    String findStory(@V("sign") Sign sign);
}

4. Kompilowanie i uruchamianie systemu agentów GOAP

Utwórz instancję GoapDemo.java w klastrze src/main/java/com/example/GoapDemo.java:

package com.example;

import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.UntypedAgent;
import dev.langchain4j.agentic.patterns.goap.GoalOrientedPlanner;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.google.genai.GoogleGenAiChatModel;
import java.util.Map;

public class GoapDemo {
    public static void main(String[] args) {
        ChatModel model = GoogleGenAiChatModel.builder()
                .apiKey(System.getenv("GEMINI_API_KEY"))
                .modelName("gemini-3.5-flash")
                .build();

        // Instantiate sub-agents with output keys matching the inputs of other agents
        HoroscopeGenerator horoscopeGen = AgenticServices.agentBuilder(HoroscopeGenerator.class)
                .chatModel(model).outputKey("horoscope").build();

        PersonExtractor personExt = AgenticServices.agentBuilder(PersonExtractor.class)
                .chatModel(model).outputKey("person").build();

        SignExtractor signExt = AgenticServices.agentBuilder(SignExtractor.class)
                .chatModel(model).outputKey("sign").build();

        StoryFinder storyFinder = AgenticServices.agentBuilder(StoryFinder.class)
                .chatModel(model).tools(new MockSearchTool()).outputKey("story").build();

        AmusingWriter writer = AgenticServices.agentBuilder(AmusingWriter.class)
                .chatModel(model).outputKey("writeup").build();

        // Build the GOAP Planner agentic system
        UntypedAgent horoscopeNewsAgent = AgenticServices.plannerBuilder()
                .subAgents(horoscopeGen, personExt, signExt, storyFinder, writer)
                .outputKey("writeup") // The Goal we want to achieve
                .planner(GoalOrientedPlanner::new) // Register the GOAP Planner
                .build();

        // Input provides only "prompt"
        Map<String, Object> inputs = Map.of(
                "prompt", "My name is Alice and my zodiac sign is Leo"
        );

        System.out.println("Invoking GOAP Agentic System...");
        String result = (String) horoscopeNewsAgent.invoke(inputs);

        System.out.println("\n--- Final Writeup ---");
        System.out.println(result);
    }
}

Jak GOAP rozwiązał ten problem:

Gdy jest wywoływana:

  1. System wykrywa, że stan początkowy zawiera tylko prompt.
  2. Oczekiwany cel to writeup.
  3. Tworzy wykres zależności i oblicza ścieżkę:
    • prompt -> PersonExtractor -> person
    • prompt -> SignExtractor -> sign
    • person + sign -> HoroscopeGenerator -> horoscope
    • sign -> StoryFinder -> story
    • person + horoscope + story -> AmusingWriter -> writeup
  4. Obliczona kolejność wykonywania to: [PersonExtractor, SignExtractor, HoroscopeGenerator, StoryFinder, AmusingWriter].
  5. Wykonuje poszczególne sub-agenty w odpowiedniej kolejności i zwraca ostateczny wynik.

Powinny się wyświetlić dane wyjściowe pokazujące ścieżkę wykonania i końcowy opis, podobne do tych:

Invoking GOAP Agentic System...
[com.example.GoapDemo.main()] INFO dev.langchain4j.agentic.patterns.goap.GoalOrientedSearchGraph - Agents path sequence: [extractPerson, extractSign, findStory, horoscope, write]

--- Final Writeup ---
**The Cosmic Registry's Weekly Forecast & Special Event Guide for: Alice, the Leo Lioness**

Alice, the universe looked at your astrological chart this week and frankly, it's asking for your autograph. Your main-character energy is currently so potent that secondary characters are practically fading into the background.

But the biggest cosmic news of the week? The universe has finally recognized your royal status with the announcement of a **Massive Cheese Festival**...

9. 10. Czyszczenie danych

Aby zwolnić miejsce na zasoby:

  • Jeśli nie używasz już kluczy interfejsu API, usuń je z Google AI Studio.
  • Usuń zmienną środowiskową w terminalu:
unset GEMINI_API_KEY

10. 11. Gratulacje

Gratulacje! Udało Ci się utworzyć serię aplikacji opartych na agentach AI przy użyciu LangChain4j i nowego modułu Google GenAI.

Aby poszerzyć swoją wiedzę, zapoznaj się z oficjalną dokumentacją modułu LangChain4j Google GenAI i dowiedz się więcej o tworzeniu agentów za pomocą LangChain4j.

Czego się dowiedziałeś(-aś)

  • Jak skonfigurować GoogleGenAiChatModel
  • Jak korzystać z lokalnych narzędzi Java i logować żądania oraz odpowiedzi.
  • Jak generować uporządkowane dane POJO.
  • Jak tworzyć agenty do konkretnych zadań za pomocą @Agent.
  • Jak aranżować sekwencyjne, równoległe i zorientowane na cel przepływy pracy agenta.

Więcej informacji