Autores
Partes do artigo
caso não se tome os devidos cuidados. Pra não termos mais esse problema e também para que seja possível utilizarmos nosso agente em um ambiente de escalabilidade, precisamos de um
ChatMemoryStore.
Conceitos
Utilizamos um chat para interagir com o nosso agente e há conceitos importantes que devemos conhecer para que a nossa interação com ele possa ocorrer da melhor forma possível e não ocasione bugs em produção. Primeiramente precisamos conhecer os tipos de mensagens que utilizamos na hora de interagir com ele, são eles:
Mensagens do usuário (
UserMessage): A mensagem ou solicitação enviada pelo cliente final. Quando enviamos a mensagem no DevUI do quarkus, sempre estamos enviando umaUserMessage. Além disso, ela também é utilizada nos resultados de chamadas das ferramentas (tools) que vimos antes.Mensagens da IA (
AiMessage): A mensagem de resposta do modelo. Sempre que o LLM responder pro nosso agente, ele receberá uma mensagem desse tipo. Este tipo de mensagem fica alternando o seu conteúdo entre uma resposta textual e solicitações de execução de ferramentas (tools).Mensagem do Sistema (
SystemMessage): Esta mensagem pode ser definida somente 1 vez e é somente em tempo de desenvolvimento.
Agora que você conhece os 3 tipos de mensagens que temos, vamos explicar como elas devem se comportar com alguns gráficos. Todos os gráficos foram tirados da apresentação
Neste segundo gráfico, demonstra-se como que a "memória" deve ser gerenciada. Um detalhe interessante é que devemos manter uma certa ordem nas mensagens e algumas premissas devem ser respeitadas.
e subimos uma instância no docker. Sinta-se a vontade pra configurar ele como bem desejar.
Adicionando nossa conexão com o MongoDB
Vamos iniciar adicionando a dependência necessária para termos uma conexão com o MongoDB utilizando o Quarkus.
CODE<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-panache</artifactId>
</dependency>
Após as dependências, precisamos adicionar as configurações de conexão no nosso
src/main/resources/application.properties.
CODEquarkus.mongodb.connection-string=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@localhost:27017
quarkus.mongodb.database=chat_memory
Ainda não iremos conseguir testar a nossa conexão com a base, pois antes precisamos criar nossas entidades e repositórios.
Criando nossa entidade e nosso repositório
Agora vamos implementar nossa entidade
Interaction. Essa entidade terá a nossa lista de mensagens realizadas. Sempre que um cliente novo se conectar, será gerada uma novaInteraction. Se precisarmos reaproveitar essaInteraction, basta informamos o mesmo identificador daInteraction.
CODEpackage <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);
}
}
Podemos agora criar o nosso repositório.
CODEpackage <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);
}
}
Agora iremos implementar alguns componentes do langchain4j, o
ChatMemoryStoree oChatMemoryProvider. OChatMemoryProvideré a classe que utilizaremos no nossoAgent. Nele iremos adicionar umaChatMemoryStoreque irá utilizar nosso repositório para armazenar as mensagens no nosso MongoDB. Segue oChatMemoryStore:
CODEpackage <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());
}
}
O
ChatMemoryProviderficará desse jeito:
CODEpackage <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();
}
}
Repare no
MessageWindowChatMemory. É nele que implementamos a janela de mensagens que mencionamos no começo do artigo. No métodomaxMessages(), você deve alterar pro número que achar melhor pro seu cenário. O que recomendo é utilizar o maior número de mensagens que já existiu no seu cenário, ou utilizar a média. Aqui definimos o número arbitrário100.
Vamos agora alterar o nosso agente para utilizar o nosso
ChatMemoryProvidernovo e adicionarMemoryId. Ele deve ficar assim:
CODEpackage <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);
}
Isso deve quebrar o nosso
AgentWSEndpoint. Vamos alterá-lo para que ele receba o identificador daInteractione possamos utilizar como nossoMemoryId:
CODEpackage <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
);
}
}
Já podemos testar o nosso agente novamente. Para isso basta conectar-mos no websocket passando um UUID sempre que quisermos. Você pode gerar um novo UUID
Isso encerra a segunda parte da nossa série. Esperamos que tenham gostado e até a parte 3.
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
SOCIAL SHARE CARD GENERATOR