AI 에이전트의 핵심 개념
AI 에이전트는 단순한 텍스트 생성을 넘어 스스로 판단하고 작업을 수행하는 자율형 시스템입니다. 자바 생태계에서 이러한 에이전트를 구축하기 위해 LangChain4j 프레임워크를 활용하면 LLM(거대언어모델)과의 상호작용, 도구 호출, 메모리 관리 등을 선언적이고 깔끔한 코드로 구현할 수 있습니다. 주요 핵심 기능은 다음과 같습니다.
- 의도 파악: 사용자의 자연어 입력을 분석하여 목적을 이해합니다.
- 작업 계획: 복잡한 요청을 처리 가능한 하위 단계로 분해합니다.
- 도구 호출: 필요한 정보를 얻거나 작업을 수행하기 위해 외부 API 및 로컬 함수를 실행합니다.
- 맥락 기억: 이전 대화 내역을 유지하여 다중 턴(Multi-turn) 상호작용을 지원합니다.
프로젝트 의존성 설정
Maven을 사용하는 경우, pom.xml에 LangChain4j 및 OpenAI 연동을 위한 종속성을 추가합니다.
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.35.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>0.35.0</version>
</dependency>
</dependencies>
기본 대화형 에이전트 구현
인터페이스를 정의하고 AiServices를 통해 프록시 객체를 생성하면, 복잡한 프롬프트 엔지니어링 없이도 메서드 호출만으로 LLM과 상호작용할 수 있습니다.
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;
// 에이전트 인터페이스 정의
public interface BasicAssistant {
@UserMessage("사용자 질문: {{it}}")
String interact(String userQuery);
}
// LLM 모델 초기화
ChatLanguageModel llm = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.temperature(0.7)
.build();
// 에이전트 인스턴스 생성
BasicAssistant assistant = AiServices.create(BasicAssistant.class, llm);
// 실행 및 결과 출력
String reply = assistant.interact("자바의 가비지 컬렉션 작동 원리를 설명해줘.");
System.out.println(reply);
외부 도구(Tool) 연동
에이전트가 실시간 정보나 특정 비즈니스 로직에 접근할 수 있도록 @Tool 어노테이션을 사용하여 기능을 주입할 수 있습니다. LLM은 사용자의 질문을 분석하여 자동으로 적절한 도구를 선택하고 호출합니다.
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import java.time.LocalDate;
public interface ToolEnabledAssistant {
@UserMessage("{{it}}")
String process(@MemoryId String userId, String query);
}
// 외부 도구 클래스 정의
public class FinanceTools {
@Tool("특정 통화의 현재 환율을 조회합니다.")
public double getExchangeRate(String baseCurrency, String targetCurrency) {
// 외부 환율 API 호출 로직 대체
return 1350.50;
}
@Tool("오늘의 날짜를 YYYY-MM-DD 형식으로 반환합니다.")
public String fetchTodayDate() {
return LocalDate.now().toString();
}
}
// 도구가 포함된 에이전트 빌드
ToolEnabledAssistant financeBot = AiServices.builder(ToolEnabledAssistant.class)
.chatLanguageModel(llm)
.tools(new FinanceTools())
.chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(15))
.build();
// 에이전트가 자동으로 도구 호출 여부 판단
String result = financeBot.process("user-101", "오늘 날짜가 뭐야? 그리고 달러 대비 원화 환율은 어때?");
대화 맥락 유지를 위한 메모리 관리
단순한 단발성 질문을 넘어, 이전 대화의 맥락을 이해하려면 ChatMemory를 설정해야 합니다. 세션별로 독립적인 메모리 공간을 할당하여 다중 사용자 환경을 지원할 수 있습니다.
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
// 최근 N개의 메시지를 유지하는 윈도우 방식 메모리 설정
MessageWindowChatMemory conversationMemory = MessageWindowChatMemory.withMaxMessages(30);
BasicAssistant memoryBot = AiServices.builder(BasicAssistant.class)
.chatLanguageModel(llm)
.chatMemory(conversationMemory)
.build();
// 다중 턴 대화에서 맥락 유지
memoryBot.interact("내 이름은 김철수야."); // 정보 저장
memoryBot.interact("내 이름이 뭐라고 했지?"); // 이전 맥락을 바탕으로 "김철수"라고 답변
실시간 스트리밍 응답 처리
사용자 경험(UX)을 향상시키기 위해 응답이 생성되는 대로 실시간으로 출력하는 스트리밍 방식을 적용할 수 있습니다.
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.model.StreamingResponseHandler;
import dev.langchain4j.model.output.Response;
StreamingChatLanguageModel streamLlm = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.build();
// 토큰 단위로 실시간 출력
streamLlm.generate("자바 스트림 API의 장점에 대해 짧게 써줘.", new StreamingResponseHandler<String>() {
@Override
public void onNext(String token) {
System.out.print(token); // 생성되는 즉시 콘솔에 출력
}
@Override
public void onComplete(Response<String> response) {
System.out.println("\n[스트리밍 종료]");
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
Spring Boot 환경에서의 통합
실제 서비스 환경에서는 Spring Boot의 의존성 주입(DI)과 REST 컨트롤러를 결합하여 에이전트를 API 형태로 노출하는 것이 일반적입니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
@Configuration
public class AgentConfig {
@Bean
public ChatLanguageModel chatModel() {
return OpenAiChatModel.builder()
.apiKey("${openai.api.key}")
.modelName("gpt-4o")
.build();
}
@Bean
public EnterpriseAssistant enterpriseAssistant(ChatLanguageModel chatModel) {
return AiServices.builder(EnterpriseAssistant.class)
.chatLanguageModel(chatModel)
.tools(new InternalDbTool(), new JiraIntegrationTool())
.chatMemoryProvider(sessionId -> MessageWindowChatMemory.withMaxMessages(20))
.build();
}
}
@RestController
@RequestMapping("/api/v1/agent")
public class AgentController {
private final EnterpriseAssistant assistant;
public AgentController(EnterpriseAssistant assistant) {
this.assistant = assistant;
}
@PostMapping("/execute")
public ResponseEntity<String> executeTask(@RequestBody AgentRequest req) {
String response = assistant.process(req.getSessionId(), req.getTaskDescription());
return ResponseEntity.ok(response);
}
}