יצירת אפליקציות AI אקטיבי ב-Java באמצעות LangChain4j ו-Google GenAI

1. 1. לפני שמתחילים

סיכום של ה-Codelab בצורת סקיצה

שלום! ב-Codelab הזה נסביר איך ליצור אפליקציות AI אקטיבי ב-Java באמצעות ה-framework הפופולרי LangChain4j והמודול החדש שלו Google GenAI.

‫AI אקטיבי מתייחס למערכות שבהן LLM לא רק מגיבים להנחיות, אלא מצוידים בכלים, בזיכרון וביכולות תכנון כדי להשיג באופן אוטונומי מטרות מורכבות שכוללות כמה שלבים.

תתחילו בהגדרות פשוטות, תמשיכו ליצירת סוכנים עם כלים מקומיים ותפוקות מובנות, ולבסוף תלמדו על דפוסי תזמור מתקדמים של סוכנים מרובים, עד שתגיעו למערכת מבוססת-סוכנים של תכנון פעולות ממוקד-מטרה (GOAP).

הפעולות שתבצעו:

  • מגדירים את GoogleGenAiChatModel החדש כדי להתחבר ל-Gemini.
  • כדאי להפעיל רישום ביומן של בקשות ותגובות כדי שיהיה קל יותר לנפות באגים.
  • לתת ל-Gemini גישה לקוד Java מקומי באמצעות כלים.
  • אכיפה של פורמט פלט מובנה (POJO) מ-Gemini.
  • הגדרת סוכנים למטרה יחידה ויצירתם באמצעות הערות @Agent.
  • תזמור תהליכי עבודה מרובי-סוכנים עוקבים ומקבילים.
  • ליצור מערכת לתכנון פעולות ממוקד-מטרה (GOAP) שתתכנן באופן דינמי את הביצוע של הסוכן.

הדרישות

  • ‫Java Development Kit‏ (JDK) 17 ואילך.
  • גרסה Maven 3.5 ואילך מותקנת.
  • מפתח Gemini API מ-Google AI Studio.

2. 2. הגדרה: פרויקט ומפתח API

כדי להתחיל, צריך ליצור פרויקט Maven חדש ולהגדיר את מפתח Gemini API.

יצירת פרויקט Maven

יוצרים ספרייה חדשה לפרויקט ומפעילים אותה באמצעות קובץ pom.xml.

מוסיפים את יחסי התלות הבאים לקובץ pom.xml. הערה: אנחנו משתמשים בגרסה האחרונה 1.16.1-beta26 של מודולי LangChain4j Google GenAI ו-Agentic:

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

קבלת מפתח Gemini API

  1. עוברים אל Google AI Studio.
  2. לוחצים על Get API Key (קבלת מפתח API).
  3. יוצרים מפתח חדש (או בוחרים מפתח קיים).
  4. מגדירים אותו כמשתנה סביבה במסוף:
export GEMINI_API_KEY="your-api-key-here"

3. 3. הגדרת מודל Gemini Chat

עכשיו ניצור אפליקציית Hello World פשוטה שיוצרת מופע של GoogleGenAiChatModel ושולחת הנחיית בדיקה.

הגרסה החדשה של GoogleGenAiChatModel משתמשת ב-Google GenAI SDK המאוחד, שמציע גישה מאוחדת למודלים של Gemini.

יוצרים כיתה בשם HelloWorld.java ב-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);
    }
}

מריצים את המחלקה הזו. מכיוון שהאפשרות logRequestsAndResponses(true) מופעלת, יומני SLF4J מפורטים יוצגו עם מטען הייעודי (payload) של הבקשה המדויקת שנשלחה ל-API של Google, ותגובת ה-JSON הגולמית שהתקבלה, ואחריהם הפלט המודפס:

--- 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. הגדרת כלי Java מקומיים

מודלים מסוג LLM הם עוצמתיים, אבל הם מוגבלים על ידי נתוני האימון שלהם. אנחנו יכולים להרחיב את היכולות שלהם באמצעות כלים (שנקראים גם קריאה לפונקציות).

ב-LangChain4j, כלים הם מחלקות Java פשוטות שבהן שיטות מסומנות ב-@Tool.

נכתוב כלי פשוט שמחשב את מספר הימים בין שני תאריכים. יצירת DateTools.java ב-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);
    }
}

כדי להשתמש בכלי הזה, נגדיר שירות AI. ב-framework LangChain4j Agentic, אפשר לבנות אותו באמצעות AgenticServices.agentBuilder().

יצירת ToolDemo.java ב-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);
    }
}

כשמריצים את הקוד הזה, Gemini מנתח את ההנחיה, מבין שהוא צריך להפעיל את daysBetween, יוצר בקשה להפעלת כלי, LangChain4j מפעיל את שיטת Java המקומית, שולח את התוצאה בחזרה ל-Gemini, ו-Gemini מעצב את התשובה הסופית בשפה טבעית.

הפלט אמור להיראות כך:

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

5. 5. הגדרת פלט מובנה

לעתים קרובות, רוצים שה-LLM יחזיר נתונים בפורמט מובנה ספציפי (למשל, JSON שתואם לסכימה) ולא בטקסט פשוט.

‫LangChain4j מטפל בזה באופן אוטומטי כשמציינים POJO או Java record כסוג ההחזרה של שיטת הסוכן או השירות.

נניח שרוצים להגדיר Java record שמייצג חילוץ של ספר:

יצירת Book.java ב-src/main/java/com/example/Book.java:

package com.example;

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

עכשיו תיצור ממשק סוכן שמחזיר Book. יצירת StructuredOutputDemo.java ב-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 תומך בפלט של סכימת JSON מדויקת. ‫LangChain4j יוצרת את סכימת ה-JSON מרשומה של Book, נותנת ל-Gemini הוראה לאכלס אותה ומבצעת דה-סריאליזציה של תגובת ה-JSON בחזרה לרשומה של Book.

הפלט אמור להיראות כך:

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

6. 6. יצירת הסוכן הראשון

במסגרת langchain4j-agentic, סוכן מוגדר באמצעות ההערה @Agent בשיטה של הממשק. ההערה הזו מספקת שם או תיאור שאפשר להשתמש בהם במערכות ניהול (כמו מתכננים).

בואו ניצור סוכן פשוט שמקבל נושא וכותב טיוטה של סיפור קצר מאוד עליו.

יצירת CreativeWriter.java ב-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);
}

נבדוק את הסוכן הזה בנפרד. יצירת AgentDemo.java ב-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);
    }
}

הפלט אמור להיראות כך:

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.

אחרי שמוסיפים את ההערה @Agent, אפשר להשתמש במחלקה הזו בתהליכי עבודה מורכבים יותר, כמו שרשור רציף.

7. 7. תזמור תהליכי עבודה מרובי-סוכנים: רציף

דפוס נפוץ של סוכנים הוא Sequential Workflow (תהליך עבודה רציף), שבו הסוכנים פועלים בסדר מוגדר מראש. הפלט של סוכן אחד מועבר כקלט לסוכן הבא.

בואו נגדיר סוכן שני, StyleEditor, שיערוך את הסיפור שנכתב על ידי CreativeWriter כך שיתאים לסגנון ספציפי (למשל: קומי, דרמטי).

יצירת StyleEditor.java ב-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);
}

עכשיו נחבר את CreativeWriter ו-StyleEditor ברצף באמצעות AgenticServices.sequenceBuilder().

נשתמש ב-UntypedAgent שמייצג את הרצף, כדי להעביר קלט ולאסוף פלט באמצעות מפה משותפת.

יצירת SequentialWorkflowDemo.java ב-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);
    }
}

הפלט אמור להיראות כך:

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.

חדש: AgenticScope

במהלך ההרצה, LangChain4j מנהל AgenticScope.

  1. מפת הקלט {"topic": "...", "style": "..."} נכתבת להיקף.
  2. CreativeWriter מופעל. היא דורשת topic, שהיא קוראת מההיקף. הפלט הוא story, שנשמר בחזרה בהיקף.
  3. StyleEditor מופעל. נדרשים story (שנוצר על ידי הכותב) וstyle (מתוך הקלט הראשוני). הפלט של הפונקציה הוא edited_story בטווח.
  4. תהליך העבודה מסתיים ומחזיר את הערך של edited_story.

8. 8. תזמור תהליכי עבודה מרובי סוכנים: מקבילי

בתרחישים מסוימים, סוכנים משנה יכולים לעבוד באופן עצמאי על אותו קלט, והמשימות שלהם יכולות להתבצע במקביל.

לדוגמה, ניצור יועץ נסיעות שיבקש ממומחה לסרטים וממומחה למסעדות המלצות לגבי עיר מסוימת, ואז ישלב ביניהן.

יצירת MovieExpert.java ב-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);
}

יצירת DiningExpert.java ב-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);
}

עכשיו נבנה את המערכת המקבילה מבוססת-הסוכנים. נגדיר ממשק לתיאום ברמה העליונה: TravelAdvisorAgent.

יצירת TravelAdvisorAgent.java ב-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);
}

כדי לקשר בין הדברים, יוצרים ParallelWorkflowDemo.java ב-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);
    }
}

הפלט אמור להיראות כך:

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; }
}

יצירת Sign.java ב-src/main/java/com/example/Sign.java:

package com.example;

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

2. הגדרת סוכני המשנה

יצירת HoroscopeGenerator.java ב-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);
}

יצירת PersonExtractor.java ב-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);
}

יצירת SignExtractor.java ב-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);
}

יצירת AmusingWriter.java ב-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. הגדרת הכלי לאיתור סיפורים

ניצור מחלקה פשוטה של כלי חיפוש שמחזירה סיפור מדומה. יצירת MockSearchTool.java ב-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!";
    }
}

יצירת סוכן StoryFinder.java ב-src/main/java/com/example/StoryFinder.java שמשתמש בכלי הזה:

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. פיתוח והרצה של מערכת סוכנית GOAP

יצירת GoapDemo.java ב-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);
    }
}

איך GOAP פתר את הבעיה:

כשמפעילים את הפונקציה:

  1. המערכת מזהה שהמצב ההתחלתי מכיל רק prompt.
  2. היעד הרצוי הוא writeup.
  3. הוא יוצר תרשים תלות ומחשב את הנתיב:
    • prompt -> PersonExtractor -> person
    • prompt -> SignExtractor -> sign
    • person + sign -> HoroscopeGenerator -> horoscope
    • sign -> StoryFinder -> story
    • person + horoscope + story -> AmusingWriter -> writeup
  4. סדר הביצוע המחושב הוא: [PersonExtractor, SignExtractor, HoroscopeGenerator, StoryFinder, AmusingWriter].
  5. הוא מפעיל כל סוכן משנה לפי הסדר ומחזיר את התוצאה הסופית.

הפלט אמור להציג את נתיב ההרצה ואת הסיכום הסופי, כמו בדוגמה הבאה:

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. הסרת המשאבים

כדי למחוק משאבים:

  • אם אתם כבר לא משתמשים במפתחות API, הקפידו למחוק אותם מ-Google AI Studio.
  • מבטלים את ההגדרה של משתנה הסביבה בטרמינל:
unset GEMINI_API_KEY

10. 11. מזל טוב

מעולה! הצלחתם לבנות סדרה של אפליקציות AI אקטיבי באמצעות LangChain4j ומודול ה-AI גנרטיבי החדש של Google.

כדי להרחיב את הידע, מומלץ לעיין במסמכי התיעוד הרשמיים של מודול LangChain4j Google GenAI ולקרוא מידע נוסף על יצירת סוכנים באמצעות LangChain4j.

מה למדתם

  • איך מגדירים את GoogleGenAiChatModel.
  • איך משתמשים בכלים מקומיים של Java ואיך רושמים ביומן את הבקשות והתגובות.
  • איך להפיק נתוני POJO מובְנים.
  • איך יוצרים סוכנים למטרה יחידה באמצעות @Agent.
  • איך לתזמן תהליכי עבודה של סוכנים עוקבים, מקבילים ומבוססי-תכנון פעולות להשגת יעדים (GOAP).

מידע נוסף