@herbertbeckman - LinkedIn
@rndtavares - LinkedIn
Java Quarkus Langchain4j를 사용한 안정적인 AI 에이전트 - 1부 - 서비스형 AI
Java Quarkus Langchain4j prod의 안정적인 AI 에이전트 - 2부 - 메모리(이 기사)
Java Quarkus Langchain4j를 사용한 안정적인 AI 에이전트 - 3부 - RAG(출시 예정)
Java Quarkus Langchain4j를 사용한 신뢰할 수 있는 AI 에이전트 - 4부 - 가드레일(출시 예정)
에이전트를 생성할 때 LLM은 어떤 유형의 정보도 저장하지 않는다는 점, 즉 무국적이라는 점을 명심해야 합니다. 에이전트가 정보를 "기억"하는 기능을 가지려면 메모리 관리를 구현해야 합니다. Quarkus는 이미 구성된 기본 메모리를 제공하지만, 적절한 주의를 기울이지 않으면 이 Quarkus 문서에 설명된 대로 사용 가능한 RAM 메모리를 확장하여 문자 그대로 에이전트를 종료할 수 있습니다. 더 이상 이러한 문제가 발생하지 않고 확장 가능한 환경에서 에이전트를 사용하려면 ChatMemoryStore가 필요합니다.
저희는 상담원과 소통하기 위해 채팅을 사용하며, 상담원과의 상호작용이 가능한 최선의 방식으로 이루어지고 제작 시 버그가 발생하지 않도록 반드시 알아야 할 중요한 개념이 있습니다. 먼저 우리는 그와 상호 작용할 때 사용하는 메시지 유형을 알아야 합니다.
사용자 메시지: 최종 고객이 보낸 메시지 또는 요청입니다. Quarkus DevUI에서 메시지를 보낼 때 항상 UserMessage를 보냅니다. 게다가 앞서 봤던 도구 호출 결과에도 사용됩니다.
AI 메시지(AiMessage): 모델의 응답 메시지입니다. LLM이 에이전트에게 응답할 때마다 다음과 같은 메시지를 받게 됩니다. 이 유형의 메시지는 텍스트 응답과 도구 실행 요청 간에 내용을 번갈아 표시합니다.
SystemMessage: 이 메시지는 한 번만 정의할 수 있으며 개발 시에만 사용됩니다.
이제 3가지 유형의 메시지를 알았으니 메시지가 일부 그래픽에서 어떻게 작동해야 하는지 설명하겠습니다. 모든 그래픽은 Deandrea, Andrianakis, Escoffier가 작성한 Java Meet AI: Build LLM-Powered Apps with LangChain4j 프레젠테이션에서 가져왔습니다. 동영상을 적극 추천합니다.
첫 번째 그래프는 3가지 유형의 메시지 사용을 보여줍니다. 파란색은 UserMessage, 빨간색은 SystemMessage, 녹색은 AiMessage입니다.
두 번째 그래프는 "메모리"를 어떻게 관리해야 하는지 보여줍니다. 흥미로운 세부 사항은 메시지에서 특정 순서를 유지해야 하며 일부 전제는 존중되어야 한다는 것입니다.
주의해야 할 또 다른 중요한 세부 사항은 ChatMemory의 크기입니다. 상호 작용 메모리가 클수록 LLM이 응답을 제공하기 위해 더 많은 텍스트를 처리해야 하므로 토큰 비용이 높아집니다. 그런 다음 사용 사례에 가장 적합한 메모리 창을 설정하세요. 한 가지 팁은 고객의 평균 메시지 수를 확인하여 상호 작용 규모에 대한 아이디어를 얻는 것입니다. Langchain4j에서 이를 관리하는 전문 클래스인 MessageWindowChatMemory를 통해 구현을 보여드리겠습니다.
이제 개념과 전제를 모두 알았으니 본격적으로 시작해 볼까요!
여기서는 MongoDB를 ChatMemoryStore로 사용하겠습니다. MongoDB 문서를 사용하고 Docker에 인스턴스를 업로드합니다. 원하는 대로 자유롭게 구성하세요.
Quarkus를 사용하여 MongoDB에 연결하는 데 필요한 종속성을 추가하는 것부터 시작하겠습니다.
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mongodb-panache</artifactId> </dependency>
종속성 이후에는 src/main/resources/application.properties에 연결 설정을 추가해야 합니다.
quarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017 quarkus.mongodb.database=chat_memory
엔티티와 저장소를 먼저 생성해야 하므로 베이스에 대한 연결을 테스트할 수는 없습니다.
이제 Interaction 엔터티를 구현해 보겠습니다. 이 엔터티에는 메시지 목록이 작성됩니다. 새로운 고객이 연결될 때마다 새로운 상호 작용이 생성됩니다. 이 상호작용을 재사용해야 하는 경우 동일한 상호작용 식별자를 입력하기만 하면 됩니다.
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.common.MongoEntity; import org.bson.codecs.pojo.annotations.BsonId; import java.util.List; import java.util.Objects; @MongoEntity(collection = "interactions") public class InteractionEntity { @BsonId private String interactionId; private List<ChatMessage> messages; public InteractionEntity() { } public InteractionEntity(String interactionId, List<ChatMessage> messages) { this.interactionId = interactionId; this.messages = messages; } public String getInteractionId() { return interactionId; } public void setInteractionId(String interactionId) { this.interactionId = interactionId; } public List<ChatMessage> getMessages() { return messages; } public void setMessages(List<ChatMessage> messages) { this.messages = messages; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InteractionEntity that = (InteractionEntity) o; return Objects.equals(interactionId, that.interactionId); } @Override public int hashCode() { return Objects.hash(interactionId, messages); } }
이제 저장소를 만들 수 있습니다.
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-mongodb-panache</artifactId> </dependency>
이제 일부 langchain4j 구성 요소인 ChatMemoryStore 및 ChatMemoryProvider를 구현하겠습니다. ChatMemoryProvider는 에이전트에서 사용할 클래스입니다. 여기에는 리포지토리를 사용하여 MongoDB에 메시지를 저장하는 ChatMemoryStore를 추가할 것입니다. ChatMemoryStore를 팔로우하세요:
quarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017 quarkus.mongodb.database=chat_memory
ChatMemoryProvider는 다음과 같습니다.
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.common.MongoEntity; import org.bson.codecs.pojo.annotations.BsonId; import java.util.List; import java.util.Objects; @MongoEntity(collection = "interactions") public class InteractionEntity { @BsonId private String interactionId; private List<ChatMessage> messages; public InteractionEntity() { } public InteractionEntity(String interactionId, List<ChatMessage> messages) { this.interactionId = interactionId; this.messages = messages; } public String getInteractionId() { return interactionId; } public void setInteractionId(String interactionId) { this.interactionId = interactionId; } public List<ChatMessage> getMessages() { return messages; } public void setMessages(List<ChatMessage> messages) { this.messages = messages; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InteractionEntity that = (InteractionEntity) o; return Objects.equals(interactionId, that.interactionId); } @Override public int hashCode() { return Objects.hash(interactionId, messages); } }
MessageWindowChatMemory를 확인하세요. 여기가 기사 시작 부분에서 언급한 메시지 창을 구현하는 곳입니다. maxMessages() 메서드에서는 해당 시나리오에 가장 적합하다고 생각되는 숫자로 변경해야 합니다. 제가 권장하는 것은 귀하의 시나리오에 존재했던 가장 많은 수의 메시지를 사용하거나 평균을 사용하는 것입니다. 여기서는 임의의 숫자 100을 정의합니다.
이제 새 ChatMemoryProvider를 사용하고 MemoryId를 추가하도록 에이전트를 변경해 보겠습니다. 다음과 같아야 합니다:
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase; import java.util.List; public class InteractionRepository implements PanacheMongoRepositoryBase<InteractionEntity, String> { public InteractionEntity findByInteractionId(String interactionId) { return findById(interactionId); } public void updateMessages(String interactionId, List<ChatMessage> messages) { persistOrUpdate(new InteractionEntity(interactionId, messages)); } public void deleteMessages(String interactionId) { deleteById(interactionId); } }
이로 인해 AgentWSEndpoint가 중단됩니다. 상호 작용 식별자를 수신하고 이를 MemoryId로 사용할 수 있도록 변경해 보겠습니다.
package <seupacote>; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.store.memory.chat.ChatMemoryStore; import java.util.List; import java.util.Objects; public class MongoDBChatMemoryStore implements ChatMemoryStore { private InteractionRepository interactionRepository = new InteractionRepository(); @Override public List<ChatMessage> getMessages(Object memoryId) { var interactionEntity = interactionRepository.findByInteractionId(memoryId.toString()); return Objects.isNull(interactionEntity) ? List.of() : interactionEntity.getMessages(); } @Override public void updateMessages(Object memoryId, List<ChatMessage> messages) { interactionRepository.updateMessages(memoryId.toString(), messages); } @Override public void deleteMessages(Object memoryId) { interactionRepository.deleteMessages(memoryId.toString()); } }
이제 에이전트를 다시 테스트할 수 있습니다. 이를 위해 원할 때마다 UUID를 전달하여 웹소켓에 연결하기만 하면 됩니다. 여기에서 새 UUID를 생성하거나 Linux에서 uuidgen 명령을 사용할 수 있습니다.
테스트를 진행해도 상담원으로부터 아무런 응답을 받지 못합니다. 이는 에이전트가 MongoDB에 메시지를 쓰는 데 문제가 있기 때문에 발생하며 예외를 통해 이를 표시합니다. 이 예외가 발생하는지 확인할 수 있도록 src/main/resources/application.properties에 새 속성을 추가해야 합니다. 이는 Quarkus에서 확인하려는 로그 수준입니다. 그런 다음 다음 줄을 추가하세요.
package <seupacote>; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import java.util.function.Supplier; public class MongoDBChatMemoryProvider implements Supplier<ChatMemoryProvider> { private MongoDBChatMemoryStore mongoDBChatMemoryStore = new MongoDBChatMemoryStore(); @Override public ChatMemoryProvider get() { return memoryId -> MessageWindowChatMemory.builder() .maxMessages(100) .id(memoryId) .chatMemoryStore(mongoDBChatMemoryStore) .build(); } }
이제 에이전트를 테스트해 보세요. 예외는 다음과 같습니다.
package <seupacote>; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import io.quarkiverse.langchain4j.RegisterAiService; import io.quarkiverse.langchain4j.ToolBox; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped @RegisterAiService( chatMemoryProviderSupplier = MongoDBChatMemoryProvider.class ) public interface Agent { @ToolBox(AgentTools.class) @SystemMessage(""" Você é um agente especializado em futebol brasileiro, seu nome é FutAgentBR Você sabe responder sobre os principais títulos dos principais times brasileiros e da seleção brasileira Sua resposta precisa ser educada, você pode deve responder em Português brasileiro e de forma relevante a pergunta feita Quando você não souber a resposta, responda que você não sabe responder nesse momento mas saberá em futuras versões. """) String chat(@MemoryId String interactionId, @UserMessage String message); }
이 예외는 MongoDB가 Langchain4j의 ChatMessage 인터페이스를 처리할 수 없기 때문에 발생하므로 이를 가능하게 하려면 코덱을 구현해야 합니다. Quarkus 자체에서는 이미 코덱을 제공하고 있지만 이를 사용하고 싶다는 점을 분명히 해야 합니다. 그런 다음 다음과 같이 ChatMessageCodec 및 ChatMessageCodecProvider 클래스를 생성합니다.
package <seupacote>; import io.quarkus.websockets.next.OnTextMessage; import io.quarkus.websockets.next.WebSocket; import io.quarkus.websockets.next.WebSocketConnection; import jakarta.inject.Inject; import java.util.Objects; import java.util.UUID; @WebSocket(path = "/ws/{interactionId}") public class AgentWSEndpoint { private final Agent agent; private final WebSocketConnection connection; @Inject AgentWSEndpoint(Agent agent, WebSocketConnection connection) { this.agent = agent; this.connection = connection; } @OnTextMessage String reply(String message) { var interactionId = connection.pathParam("interactionId"); return agent.chat( Objects.isNull(interactionId) || interactionId.isBlank() ? UUID.randomUUID().toString() : interactionId, message ); } }
quarkus.log.level=DEBUG
준비 완료! 이제 MongoDB의 메시지를 테스트하고 확인할 수 있습니다. 쿼리 시 문서의 메시지 배열에서 3가지 유형의 메시지를 확인할 수 있습니다.
이것으로 시리즈의 두 번째 부분이 끝났습니다. 재미있게 시청하시길 바라며 3부에서 뵙겠습니다.
위 내용은 Java Quarkus Langchain을 사용하는 안정적인 AI 에이전트 - 메모리 부분의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!