LangChain4j 및 Google 생성형 AI를 사용하여 Java로 에이전트 AI 애플리케이션 빌드

1. 1. 시작하기 전에

Codelab의 스케치 노트 요약

환영합니다! 이 Codelab에서는 널리 사용되는 LangChain4j 프레임워크와 새로운 Google GenAI 모듈을 사용하여 Java로 에이전트형 AI 애플리케이션을 빌드하는 방법을 알아봅니다.

에이전트 AI는 LLM이 프롬프트에 응답하는 데 그치지 않고 도구, 메모리, 계획 기능을 갖춰 복잡한 다단계 목표를 자율적으로 달성하는 시스템을 말합니다.

간단한 구성으로 시작하여 로컬 도구와 구조화된 출력을 사용하여 에이전트를 빌드하고, 마지막으로 고급 멀티 에이전트 오케스트레이션 패턴을 살펴보고 목표 지향적 행동 계획 (GOAP) 에이전트 시스템으로 마무리합니다.

실습할 내용

  • Gemini에 연결하도록 새 GoogleGenAiChatModel를 구성합니다.
  • 손쉬운 디버깅을 위해 요청/응답 로깅을 사용 설정합니다.
  • 도구를 사용하여 Gemini에 로컬 Java 코드에 대한 액세스 권한을 부여합니다.
  • Gemini에서 구조화된 출력 형식 (POJO)을 적용합니다.
  • @Agent 주석을 사용하여 단일 목적 에이전트를 정의하고 빌드합니다.
  • 순차적병렬 멀티 에이전트 워크플로를 조정합니다.
  • 에이전트 실행을 동적으로 계획하는 목표 지향적 행동 계획 (GOAP) 시스템을 빌드합니다.

필요한 항목

  • Java Development Kit (JDK) 17 이상
  • Maven 3.5 이상이 설치되어 있어야 합니다.
  • Google AI Studio의 Gemini API 키

2. 2. 설정: 프로젝트 및 API 키

시작하려면 새 Maven 프로젝트를 만들고 Gemini API 키를 구성해야 합니다.

Maven 프로젝트 만들기

프로젝트의 새 디렉터리를 만들고 pom.xml 파일로 초기화합니다.

pom.xml에 다음 종속 항목을 추가합니다. LangChain4j Google GenAI 및 Agentic 모듈의 최신 버전 1.16.1-beta26을 사용하고 있습니다.

<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. API 키 가져오기를 클릭합니다.
  3. 새 키를 만들거나 기존 키를 선택합니다.
  4. 터미널에서 환경 변수로 설정합니다.
export GEMINI_API_KEY="your-api-key-here"

3. 3. Gemini Chat 모델 구성

이제 GoogleGenAiChatModel를 인스턴스화하고 테스트 프롬프트를 전송하는 간단한 'Hello World' 애플리케이션을 만들어 보겠습니다.

새로운 GoogleGenAiChatModel는 Gemini 모델에 대한 통합 액세스를 제공하는 통합 Google 생성형 AI SDK를 사용합니다.

src/main/java/com/example/HelloWorld.javaHelloWorld.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)가 사용 설정되어 있으므로 Google API로 전송된 정확한 요청 페이로드와 수신된 원시 JSON 응답을 자세히 설명하는 SLF4J 로그가 표시되고 그 뒤에 출력 내용이 표시됩니다.

--- 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은 강력하지만 학습 데이터에 의해 제한됩니다. 도구 (함수 호출이라고도 함)를 제공하여 LLM의 기능을 확장할 수 있습니다.

LangChain4j에서 도구는 메서드가 @Tool로 주석이 지정된 간단한 Java 클래스입니다.

두 날짜 사이의 일수를 계산하는 간단한 도구를 작성해 보겠습니다. src/main/java/com/example/DateTools.java에서 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 서비스를 정의합니다. LangChain4j Agentic 프레임워크에서는 AgenticServices.agentBuilder()을 사용하여 빌드할 수 있습니다.

src/main/java/com/example/ToolDemo.java에서 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를 정의해 보겠습니다.

src/main/java/com/example/Book.java에서 Book.java를 만듭니다.

package com.example;

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

이제 Book를 반환하는 에이전트 인터페이스를 만듭니다. src/main/java/com/example/StructuredOutputDemo.java에서 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는 Book 레코드에서 JSON 스키마를 생성하고, Gemini에게 스키마를 채우도록 지시하고, JSON 응답을 다시 Book 레코드로 역직렬화합니다.

다음과 비슷한 출력이 표시됩니다.

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

6. 6. 첫 번째 에이전트 만들기

langchain4j-agentic 프레임워크에서 에이전트는 인터페이스 메서드에 @Agent 주석을 사용하여 정의됩니다. 이 주석은 조정 시스템 (예: 플래너)에서 사용할 수 있는 이름/설명을 제공합니다.

주제를 입력받아 주제에 관한 아주 짧은 이야기를 작성하는 간단한 에이전트를 만들어 보겠습니다.

src/main/java/com/example/CreativeWriter.java에서 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);
}

이 에이전트를 개별적으로 테스트해 보겠습니다. src/main/java/com/example/AgentDemo.java에서 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. 멀티 에이전트 워크플로 조정: 순차적

일반적인 에이전트 패턴은 순차적 워크플로입니다. 에이전트가 사전 정의된 순서로 실행됩니다. 한 에이전트의 출력은 다음 에이전트의 입력으로 전달됩니다.

CreativeWriter이 작성한 이야기를 특정 스타일 (예: 코미디, 드라마)에 맞게 수정하는 두 번째 에이전트 StyleEditor를 정의해 보겠습니다.

src/main/java/com/example/StyleEditor.java에서 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);
}

이제 AgenticServices.sequenceBuilder()를 사용하여 CreativeWriterStyleEditor을 순차적으로 연결해 보겠습니다.

시퀀스를 나타내는 UntypedAgent를 사용하여 공유 지도를 통해 입력을 전달하고 출력을 수집할 수 있습니다.

src/main/java/com/example/SequentialWorkflowDemo.java에서 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. 멀티 에이전트 워크플로 조정: 병렬

일부 시나리오에서는 하위 에이전트가 동일한 입력에 대해 독립적으로 작업할 수 있으며 태스크를 병렬로 실행할 수 있습니다.

예를 들어 영화 전문가와 식사 전문가에게 도시 추천을 요청한 다음 이를 결합하는 여행 어드바이저를 만들어 보겠습니다.

src/main/java/com/example/MovieExpert.java에서 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);
}

src/main/java/com/example/DiningExpert.java에서 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)의 인터페이스를 정의합니다.

src/main/java/com/example/TravelAdvisorAgent.java에서 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);
}

src/main/java/com/example/ParallelWorkflowDemo.java에서 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; }
}

src/main/java/com/example/Sign.java에서 Sign.java를 만듭니다.

package com.example;

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

2. 하위 에이전트 정의

src/main/java/com/example/HoroscopeGenerator.java에서 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);
}

src/main/java/com/example/PersonExtractor.java에서 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);
}

src/main/java/com/example/SignExtractor.java에서 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);
}

src/main/java/com/example/AmusingWriter.java에서 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. 스토리 찾기 도구 정의

모의 스토리를 반환하는 간단한 검색 도구 클래스를 만듭니다. src/main/java/com/example/MockSearchTool.java에서 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!";
    }
}

이 도구를 사용하는 src/main/java/com/example/StoryFinder.java 에이전트에서 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 에이전트형 시스템 빌드 및 실행

src/main/java/com/example/GoapDemo.java에서 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. 마무리

축하합니다. LangChain4j와 새로운 Google GenAI 모듈을 사용하여 일련의 에이전트형 AI 애플리케이션을 성공적으로 빌드했습니다.

실력을 더 키우려면 공식 LangChain4j Google 생성형 AI 모듈 문서를 살펴보고 LangChain4j로 에이전트를 만드는 방법을 자세히 알아보세요.

학습한 내용

  • GoogleGenAiChatModel 구성 방법
  • 로컬 Java 도구 및 요청/응답 로깅을 사용하는 방법
  • 구조화된 POJO 데이터를 출력하는 방법
  • @Agent로 단일 목적 에이전트를 빌드하는 방법
  • 순차적, 병렬, 목표 지향적 행동 계획 (GOAP) 에이전트형 워크플로를 조정하는 방법

자세히 알아보기